001package gudusoft.gsqlparser;
002
003import java.util.concurrent.*;
004import java.util.concurrent.atomic.AtomicInteger;
005import java.util.Map;
006
007/**
008 * Thread-safe parser pool implementation to replace the singleton pattern.
009 * This pool dramatically improves performance in multi-threaded environments
010 * by eliminating synchronized method bottlenecks.
011 * 
012 * Each database vendor has its own pool of parser instances that can be
013 * borrowed and returned for reuse.
014 */
015public class TParserPool {
016    
017    // Pool configuration
018    private static final int DEFAULT_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;
019    private static final int MAX_POOL_SIZE = 64;
020    private static final long BORROW_TIMEOUT_MS = 5000;
021    
022    // Pools for each database vendor
023    private final ConcurrentHashMap<EDbVendor, BlockingQueue<TGSqlParser>> parserPools;
024    private final ConcurrentHashMap<EDbVendor, AtomicInteger> poolSizes;
025    private final ConcurrentHashMap<EDbVendor, Semaphore> poolSemaphores;
026    
027    // Pool statistics for monitoring
028    private final ConcurrentHashMap<EDbVendor, AtomicInteger> borrowCount;
029    private final ConcurrentHashMap<EDbVendor, AtomicInteger> returnCount;
030    private final ConcurrentHashMap<EDbVendor, AtomicInteger> createCount;
031    
032    private final int poolSize;
033    private volatile boolean shutdown = false;
034    
035    /**
036     * Creates a parser pool with default size
037     */
038    public TParserPool() {
039        this(DEFAULT_POOL_SIZE);
040    }
041    
042    /**
043     * Creates a parser pool with specified size
044     * @param poolSize Size of the pool for each vendor
045     */
046    public TParserPool(int poolSize) {
047        this.poolSize = Math.min(poolSize, MAX_POOL_SIZE);
048        this.parserPools = new ConcurrentHashMap<>();
049        this.poolSizes = new ConcurrentHashMap<>();
050        this.poolSemaphores = new ConcurrentHashMap<>();
051        this.borrowCount = new ConcurrentHashMap<>();
052        this.returnCount = new ConcurrentHashMap<>();
053        this.createCount = new ConcurrentHashMap<>();
054    }
055    
056    /**
057     * Borrows a parser from the pool for the specified vendor.
058     * If no parser is available, creates a new one up to the pool limit.
059     * 
060     * @param vendor Database vendor
061     * @return Parser instance
062     * @throws InterruptedException if interrupted while waiting
063     */
064    public TGSqlParser borrowParser(EDbVendor vendor) throws InterruptedException {
065        if (shutdown) {
066            throw new IllegalStateException("Parser pool has been shut down");
067        }
068        
069        // Get or create the semaphore for this vendor
070        Semaphore semaphore = poolSemaphores.computeIfAbsent(vendor, 
071            v -> new Semaphore(poolSize, true));
072        
073        // Try to acquire a permit with timeout
074        if (!semaphore.tryAcquire(BORROW_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
075            throw new TimeoutException("Timeout waiting for available parser");
076        }
077        
078        try {
079            // Get or create the pool for this vendor
080            BlockingQueue<TGSqlParser> pool = parserPools.computeIfAbsent(vendor, 
081                v -> new LinkedBlockingQueue<>());
082            
083            // Update borrow statistics
084            borrowCount.computeIfAbsent(vendor, v -> new AtomicInteger(0)).incrementAndGet();
085            
086            // Try to get an existing parser from the pool
087            TGSqlParser parser = pool.poll();
088            
089            if (parser == null) {
090                // No parser available, create a new one
091                parser = createParser(vendor);
092                AtomicInteger currentSize = poolSizes.computeIfAbsent(vendor, 
093                    v -> new AtomicInteger(0));
094                currentSize.incrementAndGet();
095                createCount.computeIfAbsent(vendor, v -> new AtomicInteger(0)).incrementAndGet();
096            }
097            
098            // Reset parser state before returning
099            resetParser(parser);
100            return parser;
101            
102        } catch (Exception e) {
103            // Release permit if we fail to get a parser
104            semaphore.release();
105            throw new RuntimeException("Failed to borrow parser", e);
106        }
107    }
108    
109    /**
110     * Returns a parser to the pool for reuse.
111     * 
112     * @param vendor Database vendor
113     * @param parser Parser instance to return
114     */
115    public void returnParser(EDbVendor vendor, TGSqlParser parser) {
116        if (parser == null) {
117            return;
118        }
119        
120        if (shutdown) {
121            // Don't return to pool if shutting down
122            return;
123        }
124        
125        BlockingQueue<TGSqlParser> pool = parserPools.get(vendor);
126        if (pool != null) {
127            // Reset parser state before returning to pool
128            resetParser(parser);
129            
130            // Try to return parser to pool
131            if (pool.offer(parser)) {
132                returnCount.computeIfAbsent(vendor, v -> new AtomicInteger(0)).incrementAndGet();
133            }
134        }
135        
136        // Release the semaphore permit
137        Semaphore semaphore = poolSemaphores.get(vendor);
138        if (semaphore != null) {
139            semaphore.release();
140        }
141    }
142    
143    /**
144     * Executes a function with a borrowed parser and automatically returns it.
145     * This is the recommended way to use the pool.
146     * 
147     * @param vendor Database vendor
148     * @param function Function to execute with the parser
149     * @return Result of the function
150     */
151    public <T> T withParser(EDbVendor vendor, ParserFunction<T> function) throws Exception {
152        TGSqlParser parser = null;
153        try {
154            parser = borrowParser(vendor);
155            return function.apply(parser);
156        } finally {
157            if (parser != null) {
158                returnParser(vendor, parser);
159            }
160        }
161    }
162    
163    /**
164     * Creates a new parser instance for the specified vendor.
165     */
166    private TGSqlParser createParser(EDbVendor vendor) {
167        return new TGSqlParser(vendor);
168    }
169    
170    /**
171     * Resets parser state for reuse.
172     */
173    private void resetParser(TGSqlParser parser) {
174        if (parser != null) {
175            // Clear previous SQL text and results
176            parser.sqltext = null;
177            parser.sqlfilename = null;
178            // And the stream: a pooled parser that kept an exhausted stream from
179            // an earlier borrow served the NEXT borrower zero statements, because
180            // an input stream outranks sqltext when the source is chosen.
181            parser.setSqlInputStream(null);
182            if (parser.sourcetokenlist != null) {
183                parser.sourcetokenlist.clear();
184            }
185            if (parser.sqlstatements != null) {
186                parser.sqlstatements.clear();
187            }
188            // Prevent opt-in SQL Server syntax from leaking to the next borrower.
189            parser.setEnableMssqlColonBindVariables(false);
190            // Note: Some internal parser state cannot be reset from outside
191            // The parser will handle this internally on next parse
192        }
193    }
194    
195    /**
196     * Gets pool statistics for monitoring.
197     */
198    public PoolStatistics getStatistics(EDbVendor vendor) {
199        return new PoolStatistics(
200            poolSizes.getOrDefault(vendor, new AtomicInteger(0)).get(),
201            borrowCount.getOrDefault(vendor, new AtomicInteger(0)).get(),
202            returnCount.getOrDefault(vendor, new AtomicInteger(0)).get(),
203            createCount.getOrDefault(vendor, new AtomicInteger(0)).get(),
204            parserPools.containsKey(vendor) ? parserPools.get(vendor).size() : 0
205        );
206    }
207    
208    /**
209     * Pre-warms the pool by creating parsers in advance.
210     * This avoids the initialization cost during actual usage.
211     * 
212     * @param vendor Database vendor
213     * @param count Number of parsers to pre-create (up to poolSize)
214     */
215    public void prewarm(EDbVendor vendor, int count) {
216        if (shutdown) {
217            throw new IllegalStateException("Parser pool has been shut down");
218        }
219        
220        int toCreate = Math.min(count, poolSize);
221        BlockingQueue<TGSqlParser> pool = parserPools.computeIfAbsent(vendor, 
222            v -> new LinkedBlockingQueue<>());
223        
224        for (int i = 0; i < toCreate; i++) {
225            TGSqlParser parser = createParser(vendor);
226            resetParser(parser);
227            pool.offer(parser);
228            poolSizes.computeIfAbsent(vendor, v -> new AtomicInteger(0)).incrementAndGet();
229            createCount.computeIfAbsent(vendor, v -> new AtomicInteger(0)).incrementAndGet();
230        }
231    }
232    
233    /**
234     * Shuts down the pool and clears all parsers.
235     */
236    public void shutdown() {
237        shutdown = true;
238        parserPools.clear();
239        poolSizes.clear();
240        poolSemaphores.clear();
241    }
242    
243    /**
244     * Functional interface for parser operations.
245     */
246    @FunctionalInterface
247    public interface ParserFunction<T> {
248        T apply(TGSqlParser parser) throws Exception;
249    }
250    
251    /**
252     * Pool statistics for monitoring.
253     */
254    public static class PoolStatistics {
255        public final int totalParsers;
256        public final int borrowCount;
257        public final int returnCount;
258        public final int createCount;
259        public final int availableParsers;
260        
261        public PoolStatistics(int totalParsers, int borrowCount, int returnCount, 
262                             int createCount, int availableParsers) {
263            this.totalParsers = totalParsers;
264            this.borrowCount = borrowCount;
265            this.returnCount = returnCount;
266            this.createCount = createCount;
267            this.availableParsers = availableParsers;
268        }
269        
270        @Override
271        public String toString() {
272            return String.format("PoolStats[total=%d, available=%d, borrows=%d, returns=%d, creates=%d]",
273                totalParsers, availableParsers, borrowCount, returnCount, createCount);
274        }
275    }
276    
277    /**
278     * Custom exception for timeout scenarios.
279     */
280    public static class TimeoutException extends RuntimeException {
281        public TimeoutException(String message) {
282            super(message);
283        }
284    }
285}