001: /*
002: * Copyright (C) 2002 Christian Sell
003: * csell@users.sourceforge.net
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *
019: * created by cse, 07.10.2002 11:57:54
020: *
021: * @version $Id: ParserThread.java,v 1.9 2007/08/19 20:35:17 manningr Exp $
022: */
023: package net.sourceforge.squirrel_sql.client.session.parser.kernel;
024:
025: import net.sourceforge.squirrel_sql.client.session.parser.kernel.completions.ErrorListener;
026: import net.sourceforge.squirrel_sql.client.session.parser.kernel.completions.SQLSelectStatementListener;
027: import net.sourceforge.squirrel_sql.client.session.parser.kernel.completions.SQLStatement;
028: import net.sourceforge.squirrel_sql.fw.util.StringManager;
029: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
030:
031: import java.text.CharacterIterator;
032: import java.text.StringCharacterIterator;
033: import java.util.Vector;
034:
035: /**
036: * a thread subclass which drives the SQL parser. The thread reads from a _workingBuffer
037: * which always blocks until data is made available. It can thus be run in the
038: * background, parsing the input from the text UI as it arrives.
039: *
040: * <em>Unfortunately, it depends on the generated parser/scanner and therefore
041: * cannot be generalized, unless the generated classes are made to implement public
042: * interfaces</em>
043: */
044: public final class ParserThread extends Thread {
045: private static final StringManager s_stringMgr = StringManagerFactory
046: .getStringManager(ParserThread.class);
047:
048: public static final String PARSER_THREAD_NM = "SQLParserThread";
049:
050: private String _pendingString;
051: private Errors _errors;
052: private SQLSchema _schema;
053: private SQLStatement _curSQLSelectStat;
054:
055: private Vector<TableAliasInfo> _workingTableAliasInfos = new Vector<TableAliasInfo>();
056: private TableAliasInfo[] _lastRunTableAliasInfos = new TableAliasInfo[0];
057: private Vector<ErrorInfo> _workingErrorInfos = new Vector<ErrorInfo>();
058: private ErrorInfo[] _lastRunErrorInfos = new ErrorInfo[0];
059:
060: private boolean _exitThread;
061: private ParsingFinishedListener _parsingFinishedListener;
062:
063: private int _lastParserRunOffset;
064: private int _lastErrEnd = -1;
065: private int _nextStatBegin = -1;
066:
067: private String _workingString;
068: private IncrementalBuffer _workingBuffer;
069: private boolean _errorDetected;
070:
071: public ParserThread(SQLSchema schema) {
072: super (PARSER_THREAD_NM);
073: this ._schema = schema;
074:
075: ErrorListener errListener = new ErrorListener() {
076: public void errorDetected(String message, int line,
077: int column) {
078: onErrorDetected(message, line, column);
079: }
080: };
081:
082: this ._errors = new Errors(errListener);
083:
084: setPriority(Thread.MIN_PRIORITY);
085:
086: start();
087: }
088:
089: private void onErrorDetected(String message, int line, int column) {
090: _errorDetected = true;
091: int errPos = getPos(line, column);
092: _lastErrEnd = getTokenEnd(errPos);
093: _nextStatBegin = predictNextStatementBegin(errPos);
094:
095: if (_lastErrEnd > _nextStatBegin) {
096: return;
097: }
098:
099: int beginPos = _lastParserRunOffset + errPos;
100: int endPos = _lastParserRunOffset + _lastErrEnd;
101:
102: if (beginPos < endPos) {
103: _workingErrorInfos.add(new ErrorInfo(message,
104: _lastParserRunOffset + errPos, _lastParserRunOffset
105: + _lastErrEnd - 1));
106: }
107: }
108:
109: private int predictNextStatementBegin(int errPos) {
110: int commentIntervals[][] = calculateCommentIntervals();
111:
112: // for (int i = 0; i < commentIntervals.length; i++)
113: // {
114: // System.out.println("###################");
115: // System.out.println(_workingString.substring(commentIntervals[i][0], commentIntervals[i][1]));
116: // System.out.println("###################");
117: // }
118:
119: int ret = errPos;
120: while (_workingString.length() > ret
121: && (false == startsWithBeginKeyWord(ret) || isInComment(
122: ret, commentIntervals))) {
123: ++ret;
124: }
125:
126: // if(_workingString.length() > ret)
127: // {
128: // System.out.println("*****************************BEGIN startsWithBeginKeyWord(ret) " + startsWithBeginKeyWord(ret) + " isInComment(ret, commentIntervals)" + isInComment(ret, commentIntervals));
129: // System.out.println(_workingString.substring(ret));
130: // }
131:
132: return ret;
133: }
134:
135: private int[][] calculateCommentIntervals() {
136: Vector<int[]> ret = new Vector<int[]>();
137: boolean inMultiLineComment = false;
138: boolean inLineComment = false;
139: boolean isaSlash = false;
140: boolean isaStar = false;
141: boolean isaMinus = false;
142:
143: int[] curComment = null;
144:
145: for (int i = 0; i < _workingString.length(); ++i) {
146: if ('*' == _workingString.charAt(i) && isaSlash
147: && false == inMultiLineComment
148: && false == inLineComment) {
149: inMultiLineComment = true;
150: curComment = new int[] { i - 1, -1 };
151: } else if ('/' == _workingString.charAt(i) && isaStar
152: && false == inLineComment && inMultiLineComment) {
153: inMultiLineComment = false;
154: curComment[1] = i;
155: ret.add(curComment);
156: curComment = null;
157:
158: } else if ('-' == _workingString.charAt(i) && isaMinus
159: && false == inMultiLineComment
160: && false == inLineComment) {
161: inLineComment = true;
162: curComment = new int[] { i - 1, -1 };
163: } else if ('\n' == _workingString.charAt(i)
164: && false == inMultiLineComment && inLineComment) {
165: inLineComment = false;
166: curComment[1] = i;
167: ret.add(curComment);
168: curComment = null;
169: }
170:
171: if ('/' == _workingString.charAt(i)) {
172: isaSlash = true;
173: } else if ('*' == _workingString.charAt(i)) {
174: isaStar = true;
175: } else if ('-' == _workingString.charAt(i)) {
176: isaMinus = true;
177: } else {
178: isaSlash = false;
179: isaStar = false;
180: isaMinus = false;
181: }
182: }
183:
184: if (null != curComment) {
185: curComment[1] = _workingString.length();
186: }
187:
188: return ret.toArray(new int[ret.size()][]);
189:
190: }
191:
192: private boolean isInComment(int ret, int commentIntervals[][]) {
193: for (int i = 0; i < commentIntervals.length; ++i) {
194: if (commentIntervals[i][0] <= ret
195: && ret <= commentIntervals[i][1]) {
196: return true;
197: }
198: }
199:
200: return false;
201: }
202:
203: private boolean startsWithBeginKeyWord(int ret) {
204: return startsWithIgnoreCase(ret, "SELECT")
205: || startsWithIgnoreCase(ret, "UPDATE")
206: || startsWithIgnoreCase(ret, "DELETE")
207: || startsWithIgnoreCase(ret, "INSERT")
208: || startsWithIgnoreCase(ret, "ALTER")
209: || startsWithIgnoreCase(ret, "CREATE")
210: || startsWithIgnoreCase(ret, "DROP");
211: }
212:
213: private boolean startsWithIgnoreCase(int ret, String keyWord) {
214: int beginPos = ret;
215: int endPos;
216:
217: if (ret == 0) {
218: // Either are at teh beginning ...
219: beginPos = 0;
220: } else if (Character.isWhitespace(_workingString
221: .charAt(ret - 1))) {
222: // or a white space must be in front of the keyword.
223: beginPos = ret;
224: } else {
225: return false;
226: }
227:
228: if (_workingString.length() == beginPos + keyWord.length()) {
229: endPos = beginPos + keyWord.length();
230: } else if (_workingString.length() > beginPos
231: + keyWord.length()
232: && Character.isWhitespace(_workingString
233: .charAt(beginPos + keyWord.length()))) {
234: endPos = beginPos + keyWord.length();
235: } else {
236: return false;
237: }
238:
239: return keyWord.equalsIgnoreCase(_workingString.substring(
240: beginPos, endPos));
241: }
242:
243: private int getTokenEnd(int errPos) {
244: int ret = errPos;
245: while (_workingString.length() > ret
246: && false == Character.isWhitespace(_workingString
247: .charAt(ret))) {
248: ++ret;
249: }
250: return ret;
251: }
252:
253: private int getPos(int line, int column) {
254: int ix = 0;
255:
256: for (int i = 0; i < line - 1; i++) {
257: ix = _workingString.indexOf('\n', ix) + 1;
258: }
259: ix += column;
260:
261: return ix - 1; // -1 because column starts with 1 put pos with 0
262: }
263:
264: public void notifyParser(String sqlText) {
265: synchronized (this ) {
266: _pendingString = sqlText;
267: this .notify();
268: }
269: }
270:
271: public void exitThread() {
272: _exitThread = true;
273: synchronized (this ) {
274: this .notify();
275: }
276: }
277:
278: public void setParsingFinishedListener(
279: ParsingFinishedListener parsingFinishedListener) {
280: _parsingFinishedListener = parsingFinishedListener;
281: }
282:
283: public void run() {
284: try {
285: while (true) {
286: synchronized (this ) {
287: this .wait();
288: _workingString = _pendingString;
289: _workingBuffer = new IncrementalBuffer(
290: new StringCharacterIterator(_workingString));
291: }
292:
293: if (_exitThread) {
294: break;
295: }
296:
297: //////////////////////////////////////////////////////////////
298: // On Errors we restart the parser behind the error
299: _errorDetected = false;
300: runParser();
301: while (_errorDetected) {
302: if (_workingString.length() > _nextStatBegin) {
303: _workingString = _workingString
304: .substring(_nextStatBegin,
305: _workingString.length());
306: if ("".equals(_workingString.trim())) {
307: break;
308: }
309: } else {
310: break;
311: }
312:
313: _lastParserRunOffset += _nextStatBegin;
314: _workingBuffer = new IncrementalBuffer(
315: new StringCharacterIterator(_workingString));
316:
317: _errorDetected = false;
318: runParser();
319: }
320:
321: //
322: ////////////////////////////////////////////////////////////
323:
324: ///////////////////////////////////////////////////////////
325: // We are through with parsing. Now we store the outcome
326: // in _lastRun... and tell the listeners.
327: _lastRunTableAliasInfos = _workingTableAliasInfos
328: .toArray(new TableAliasInfo[_workingTableAliasInfos
329: .size()]);
330: _lastRunErrorInfos = _workingErrorInfos
331: .toArray(new ErrorInfo[_workingErrorInfos
332: .size()]);
333: _workingTableAliasInfos.clear();
334: _workingErrorInfos.clear();
335: _lastParserRunOffset = 0;
336: if (null != _parsingFinishedListener) {
337: _parsingFinishedListener.parsingFinished();
338: }
339: //
340: /////////////////////////////////////////////////////////////
341:
342: if (_exitThread) {
343: break;
344: }
345: }
346: } catch (Exception e) {
347: if (null != _parsingFinishedListener) {
348: _parsingFinishedListener.parserExitedOnException(e);
349: }
350: e.printStackTrace();
351: }
352: }
353:
354: private void runParser() {
355: _errors.reset();
356: Scanner scanner = new Scanner(_workingBuffer, _errors);
357:
358: Parser parser = new Parser(scanner);
359: parser.rootSchema = _schema;
360:
361: parser.addParserListener(new ParserListener() {
362: public void statementAdded(SQLStatement statement) {
363: onStatementAdded(statement);
364: }
365: });
366:
367: parser
368: .addSQLSelectStatementListener(new SQLSelectStatementListener() {
369: public void aliasDefined(String tableName,
370: String aliasName) {
371: onAliasDefined(tableName, aliasName);
372: }
373: });
374:
375: parser.parse();
376: }
377:
378: private void onStatementAdded(SQLStatement statement) {
379: _curSQLSelectStat = statement;
380: }
381:
382: private void onAliasDefined(String tableName, String aliasName) {
383: _workingTableAliasInfos.add(new TableAliasInfo(aliasName,
384: tableName, _curSQLSelectStat.getStart()
385: + _lastParserRunOffset));
386: }
387:
388: public TableAliasInfo[] getTableAliasInfos() {
389: return _lastRunTableAliasInfos;
390: }
391:
392: public ErrorInfo[] getErrorInfos() {
393: return _lastRunErrorInfos;
394: }
395:
396: /**
397: * reset the parser, starting a new parse on the characters given by the iterator
398: * @param chars the characters to start parsing from
399: */
400: public void reset(CharacterIterator chars) {
401: IncrementalBuffer oldBuffer = this ._workingBuffer;
402: this ._workingBuffer = new IncrementalBuffer(chars);
403: oldBuffer.eof();
404: }
405:
406: /**
407: * terminate the parser
408: */
409: public void end() {
410: IncrementalBuffer oldBuffer = this ._workingBuffer;
411: this ._workingBuffer = null;
412: oldBuffer.eof();
413: }
414:
415: /**
416: * accept the next character sequence to be parsed
417: * @param chars
418: */
419: public void accept(CharacterIterator chars) {
420: _workingBuffer.waitChars(); //wait for pending chars to be processed
421: _workingBuffer.accept(chars); //post new characters
422: }
423:
424: /**
425: * This is a Scanner.Buffer implementation which blocks until character data is
426: * available. The {@link #read} method is invoked from the background parsing thread.
427: * The parsing thread can be terimated by calling the {@link #eof} method on this object
428: */
429: private static class IncrementalBuffer extends Scanner.Buffer {
430: private CharacterIterator chars;
431: private char current;
432: private boolean atEnd;
433:
434: IncrementalBuffer(CharacterIterator chars) {
435: this .atEnd = false;
436: this .chars = chars;
437: this .current = chars != null ? chars.first()
438: : CharacterIterator.DONE;
439: }
440:
441: /**
442: * read the next character. This method is invoked from the parser thread
443: * @return the next available character
444: */
445: protected synchronized char read() {
446: if (atEnd) {
447: return eof;
448: } else {
449: if (current == CharacterIterator.DONE) {
450: if (chars != null) {
451: synchronized (chars) {
452: chars.notify(); //tell the UI that this _workingBuffer is through
453: }
454: }
455: // try
456: // {
457: // wait();
458: // }
459: // catch (InterruptedException e)
460: // {
461: // }
462: }
463: if (atEnd) {
464: current = eof;
465: return eof;
466: } else {
467: char prev = current;
468: //System.out.print(prev);
469: current = chars.next();
470: return prev;
471: }
472: }
473: }
474:
475: synchronized void eof() {
476: atEnd = true;
477: notify();
478: }
479:
480: /**
481: * Post a character sequence to be read. Notify the parser thread accordingly. Invoking
482: * this method should always be followed by a call to {@link #waitChars} to ensure that
483: * the character sequence is not overwritten before it has been fully processed.
484: * @param chars the chracters to be read
485: */
486: synchronized void accept(CharacterIterator chars) {
487: this .chars = chars;
488: this .current = chars != null ? chars.first()
489: : CharacterIterator.DONE;
490: notify();
491: }
492:
493: /**
494: * block the current thread until all characters from the current iterator have
495: * been processed
496: */
497: void waitChars() {
498: if (chars != null && current != CharacterIterator.DONE) {
499: synchronized (chars) {
500: try {
501: chars.wait();
502: } catch (InterruptedException e) {
503: }
504: }
505: }
506: }
507:
508: int getBeginIndex() {
509: return chars != null ? chars.getBeginIndex() : 0;
510: }
511:
512: protected void setIndex(int position) {
513: this .current = chars.setIndex(position);
514: }
515: }
516:
517: /**
518: * error stream which simply saves the error codes and line info
519: * circularily in an array of fixed size, and notifies a listener
520: * if requested
521: */
522: private static class Errors extends ErrorStream {
523: private int[][] errorStore;
524: private int count;
525: private ErrorListener listener;
526:
527: public Errors(ErrorListener listener) {
528: this .listener = listener;
529: errorStore = new int[5][3];
530: }
531:
532: protected void ParsErr(int n, int line, int col) {
533: errorStore[count][0] = n;
534: errorStore[count][1] = line;
535: errorStore[count][2] = col;
536: count = (count + 1) % 5;
537: if (listener != null)
538: super .ParsErr(n, line, col);
539: }
540:
541: protected void SemErr(int n, int line, int col) {
542: errorStore[count][0] = n;
543: errorStore[count][1] = line;
544: errorStore[count][2] = col;
545: count = (count + 1) % 5;
546: if (listener != null) {
547: switch (n) {
548: case ParsingConstants.KW_MINUS:
549: //i18n[parserthread.undefinedTable=undefined table]
550: StoreError(n, line, col, s_stringMgr
551: .getString("parserthread.undefinedTable"));
552: break;
553: default:
554: super .SemErr(n, line, col);
555: }
556: }
557: }
558:
559: protected void StoreError(int n, int line, int col, String s) {
560: if (listener != null)
561: listener.errorDetected(s, line, col);
562: }
563:
564: public void reset() {
565: errorStore = new int[5][3];
566: }
567: }
568: }
|