0001: package net.sourceforge.squirrel_sql.plugins.syntax.oster;
0002:
0003: /*
0004: * Copyright (C) 2003 Colin Bell
0005: * colbell@users.sourceforge.net
0006: *
0007: * This is based on the text editor demonstration class that comes with
0008: * the Ostermiller Syntax Highlighter Copyright (C) 2001 Stephen Ostermiller
0009: * http://ostermiller.org/contact.pl?regarding=Syntax+Highlighting
0010: *
0011: * This program is free software; you can redistribute it and/or
0012: * modify it under the terms of the GNU General Public License
0013: * as published by the Free Software Foundation; either version 2
0014: * of the License, or any later version.
0015: *
0016: * This program is distributed in the hope that it will be useful,
0017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0019: * GNU General Public License for more details.
0020: *
0021: * You should have received a copy of the GNU General Public License
0022: * along with this program; if not, write to the Free Software
0023: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0024: */
0025: import com.Ostermiller.Syntax.Lexer.Lexer;
0026: import com.Ostermiller.Syntax.Lexer.SQLLexer;
0027: import com.Ostermiller.Syntax.Lexer.Token;
0028: import net.sourceforge.squirrel_sql.client.session.ExtendedColumnInfo;
0029: import net.sourceforge.squirrel_sql.client.session.ISession;
0030: import net.sourceforge.squirrel_sql.client.session.SQLTokenListener;
0031: import net.sourceforge.squirrel_sql.client.session.schemainfo.SchemaInfo;
0032: import net.sourceforge.squirrel_sql.client.session.parser.ParserEventsAdapter;
0033: import net.sourceforge.squirrel_sql.client.session.parser.kernel.ErrorInfo;
0034: import net.sourceforge.squirrel_sql.fw.gui.FontInfo;
0035: import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
0036: import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;
0037: import net.sourceforge.squirrel_sql.fw.id.IIdentifier;
0038: import net.sourceforge.squirrel_sql.plugins.syntax.IConstants;
0039: import net.sourceforge.squirrel_sql.plugins.syntax.SyntaxPreferences;
0040: import net.sourceforge.squirrel_sql.plugins.syntax.SyntaxStyle;
0041: import net.sourceforge.squirrel_sql.plugins.syntax.KeyManager;
0042:
0043: import javax.swing.*;
0044: import javax.swing.plaf.ComponentUI;
0045: import javax.swing.text.*;
0046: import java.awt.*;
0047: import java.awt.event.MouseEvent;
0048: import java.io.IOException;
0049: import java.io.Reader;
0050: import java.util.*;
0051:
0052: public class OsterTextControl extends JTextPane {
0053: private static final long serialVersionUID = 1L;
0054:
0055: /** Logger for this class. */
0056: private static final ILogger s_log = LoggerController
0057: .createLogger(OsterTextControl.class);
0058:
0059: /** Current session. */
0060: private final ISession _session;
0061:
0062: /**
0063: * A lock for modifying the document, or for
0064: * actions that depend on the document not being
0065: * modified.
0066: */
0067: private Object doclock = new Object();
0068:
0069: /**
0070: * The styled document that is the model for
0071: * the textPane.
0072: */
0073: private HighLightedDocument document;
0074:
0075: /**
0076: * A reader wrapped around the document
0077: * so that the document can be fed into
0078: * the lexer.
0079: */
0080: private DocumentReader documentReader;
0081:
0082: /**
0083: * The lexer that tells us what colors different
0084: * words should be.
0085: */
0086: private Lexer syntaxLexer;
0087:
0088: /**
0089: * A thread that handles the actual coloring.
0090: */
0091: private Colorer colorer;
0092:
0093: /**
0094: * A hash table containing the text styles.
0095: * Simple attribute sets are hashed by name (String)
0096: */
0097: private Hashtable<String, SimpleAttributeSet> styles = new Hashtable<String, SimpleAttributeSet>();
0098:
0099: /** Preferences for this plugin. */
0100: private final SyntaxPreferences _syntaxPrefs;
0101:
0102: private Vector<SQLTokenListener> _sqlTokenListeners = new Vector<SQLTokenListener>();
0103: private Vector<ErrorInfo> _currentErrorInfos = new Vector<ErrorInfo>();
0104: private Vector<ErrorInfo> _oldErrorInfos = new Vector<ErrorInfo>();
0105:
0106: OsterTextControl(ISession session, SyntaxPreferences prefs,
0107: final IIdentifier sqlEntryPanelIdentifier) {
0108: super ();
0109: _session = session;
0110: _syntaxPrefs = prefs;
0111:
0112: document = new HighLightedDocument();
0113: setDocument(document);
0114:
0115: // Start the thread that does the coloring
0116: colorer = new Colorer();
0117: colorer.start();
0118:
0119: // Set up the hash table that contains the styles.
0120: initStyles();
0121:
0122: // create the new document.
0123: documentReader = new DocumentReader(document);
0124:
0125: // Put the initial text into the text pane and
0126: // set it's initial coloring style.
0127: initDocument();
0128:
0129: setToolTipText("Just to make getToolTiptext() to be called");
0130:
0131: updateFromPreferences();
0132:
0133: SwingUtilities.invokeLater(new Runnable() {
0134: public void run() {
0135: initParser(_session, sqlEntryPanelIdentifier);
0136: }
0137: });
0138:
0139: new KeyManager(this );
0140: }
0141:
0142: private void initParser(ISession session,
0143: IIdentifier sqlEntryPanelIdentifier) {
0144: session.getParserEventsProcessor(sqlEntryPanelIdentifier)
0145: .addParserEventsListener(new ParserEventsAdapter() {
0146: public void errorsFound(ErrorInfo[] errorInfos) {
0147: onErrorsFound(errorInfos);
0148: }
0149: });
0150: }
0151:
0152: private void onErrorsFound(ErrorInfo[] errorInfos) {
0153: boolean errorsChanged = false;
0154: if (_currentErrorInfos.size() == errorInfos.length) {
0155: for (int i = 0; i < errorInfos.length; i++) {
0156: if (false == errorInfos[i].equals(_currentErrorInfos
0157: .get(i))) {
0158: errorsChanged = true;
0159: break;
0160: }
0161: }
0162: } else {
0163: errorsChanged = true;
0164: }
0165:
0166: if (errorsChanged) {
0167: _oldErrorInfos.clear();
0168: _oldErrorInfos.addAll(_currentErrorInfos);
0169:
0170: _currentErrorInfos.clear();
0171: _currentErrorInfos.addAll(Arrays.asList(errorInfos));
0172:
0173: int heuristicDist = 20;
0174:
0175: for (int i = 0; i < errorInfos.length; i++) {
0176: int colBegin = Math.max(errorInfos[i].beginPos
0177: - heuristicDist, 0);
0178: int colLen = Math.min(errorInfos[i].endPos
0179: - errorInfos[i].beginPos + 2 * heuristicDist,
0180: getDocument().getLength() - colBegin);
0181: color(colBegin, colLen, false, null);
0182: }
0183:
0184: for (int i = 0; i < _oldErrorInfos.size(); i++) {
0185: ErrorInfo errorInfo = _oldErrorInfos.elementAt(i);
0186: int colBegin = Math.max(errorInfo.beginPos
0187: - heuristicDist, 0);
0188: int colLen = Math.min(errorInfo.endPos
0189: - errorInfo.beginPos + 2 * heuristicDist,
0190: getDocument().getLength() - colBegin);
0191: color(colBegin, colLen, false, null);
0192: }
0193: }
0194: }
0195:
0196: public void endColorerThread() {
0197: colorer.endThread();
0198: }
0199:
0200: // This stops the text control from line wrapping.
0201: public boolean getScrollableTracksViewportWidth() {
0202: final Component parent = getParent();
0203: final ComponentUI ui = getUI();
0204:
0205: if (parent != null) {
0206: return (ui.getPreferredSize(this ).width <= parent.getSize().width);
0207: }
0208: return true;
0209: }
0210:
0211: void updateFromPreferences() {
0212: synchronized (doclock) {
0213: final FontInfo fi = _session.getProperties().getFontInfo();
0214: SyntaxStyle style;
0215: SimpleAttributeSet attribs;
0216:
0217: style = _syntaxPrefs.getColumnStyle();
0218: attribs = getMyStyle(IConstants.IStyleNames.COLUMN);
0219: applyStyle(attribs, style, fi);
0220:
0221: style = _syntaxPrefs.getCommentStyle();
0222: attribs = getMyStyle(IConstants.IStyleNames.COMMENT);
0223: applyStyle(attribs, style, fi);
0224:
0225: style = _syntaxPrefs.getDataTypeStyle();
0226: attribs = getMyStyle(IConstants.IStyleNames.DATA_TYPE);
0227: applyStyle(attribs, style, fi);
0228:
0229: style = _syntaxPrefs.getErrorStyle();
0230: attribs = getMyStyle(IConstants.IStyleNames.ERROR);
0231: applyStyle(attribs, style, fi);
0232:
0233: style = _syntaxPrefs.getFunctionStyle();
0234: attribs = getMyStyle(IConstants.IStyleNames.FUNCTION);
0235: applyStyle(attribs, style, fi);
0236:
0237: style = _syntaxPrefs.getIdentifierStyle();
0238: attribs = getMyStyle(IConstants.IStyleNames.IDENTIFIER);
0239: applyStyle(attribs, style, fi);
0240:
0241: style = _syntaxPrefs.getLiteralStyle();
0242: attribs = getMyStyle(IConstants.IStyleNames.LITERAL);
0243: applyStyle(attribs, style, fi);
0244:
0245: style = _syntaxPrefs.getOperatorStyle();
0246: attribs = getMyStyle(IConstants.IStyleNames.OPERATOR);
0247: applyStyle(attribs, style, fi);
0248:
0249: style = _syntaxPrefs.getReservedWordStyle();
0250: attribs = getMyStyle(IConstants.IStyleNames.RESERVED_WORD);
0251: applyStyle(attribs, style, fi);
0252:
0253: style = _syntaxPrefs.getSeparatorStyle();
0254: attribs = getMyStyle(IConstants.IStyleNames.SEPARATOR);
0255: applyStyle(attribs, style, fi);
0256:
0257: style = _syntaxPrefs.getTableStyle();
0258: attribs = getMyStyle(IConstants.IStyleNames.TABLE);
0259: applyStyle(attribs, style, fi);
0260:
0261: style = _syntaxPrefs.getWhiteSpaceStyle();
0262: attribs = getMyStyle(IConstants.IStyleNames.WHITESPACE);
0263: applyStyle(attribs, style, fi);
0264:
0265: colorAll();
0266: }
0267: }
0268:
0269: /**
0270: * Color or recolor the entire document
0271: */
0272: public void colorAll() {
0273: color(0, document.getLength(), false, null);
0274: }
0275:
0276: /**
0277: * Color a section of the document.
0278: * The actual coloring will start somewhere before
0279: * the requested position and continue as long
0280: * as needed.
0281: *
0282: * @param position the starting point for the coloring.
0283: * @param adjustment amount of text inserted or removed
0284: * at the starting point.
0285: */
0286: public void color(int position, int adjustment,
0287: boolean fireTableOrViewFoundEvent, String change) {
0288: colorer.color(position, adjustment, fireTableOrViewFoundEvent,
0289: change);
0290: }
0291:
0292: /**
0293: * retrieve the style for the given type of text.
0294: *
0295: * @param styleName the label for the type of text ("tag" for example)
0296: * or null if the styleName is not known.
0297: * @return the style
0298: */
0299: private SimpleAttributeSet getMyStyle(String styleName) {
0300: return styles.get(styleName);
0301: }
0302:
0303: private Token getNextToken() throws IOException {
0304: return syntaxLexer.getNextToken();
0305: }
0306:
0307: private void applyStyle(SimpleAttributeSet attribs,
0308: SyntaxStyle style, FontInfo fi) {
0309: StyleConstants.setFontFamily(attribs, fi.getFamily());
0310: StyleConstants.setFontSize(attribs, fi.getSize());
0311: StyleConstants.setBackground(attribs, new Color(style
0312: .getBackgroundRGB()));
0313: StyleConstants.setForeground(attribs, new Color(style
0314: .getTextRGB()));
0315: StyleConstants.setBold(attribs, style.isBold());
0316: StyleConstants.setItalic(attribs, style.isItalic());
0317: }
0318:
0319: /**
0320: * Set the initial type of syntax highlighting.
0321: */
0322: private void initDocument() {
0323: syntaxLexer = new SQLLexer(documentReader);
0324: try {
0325: document.insertString(document.getLength(), "",
0326: getMyStyle("text"));
0327: } catch (BadLocationException ex) {
0328: s_log.error("Error setting initial document style", ex);
0329: }
0330: }
0331:
0332: /**
0333: * Create the styles and place them in the hash table.
0334: */
0335: private void initStyles() {
0336: final FontInfo fi = _session.getProperties().getFontInfo();
0337:
0338: SyntaxStyle style;
0339: SimpleAttributeSet attribs;
0340:
0341: // TODO: Do we need this one. */
0342: attribs = new SimpleAttributeSet();
0343: StyleConstants.setFontFamily(attribs, fi.getFamily());
0344: StyleConstants.setFontSize(attribs, fi.getSize());
0345: StyleConstants.setBackground(attribs, Color.white);
0346: StyleConstants.setForeground(attribs, Color.black);
0347: StyleConstants.setBold(attribs, false);
0348: StyleConstants.setItalic(attribs, false);
0349: styles.put("body", attribs);
0350:
0351: // TODO: Do we need this one. */
0352: attribs = new SimpleAttributeSet();
0353: StyleConstants.setFontFamily(attribs, fi.getFamily());
0354: StyleConstants.setFontSize(attribs, fi.getSize());
0355: StyleConstants.setBackground(attribs, Color.white);
0356: StyleConstants.setForeground(attribs, Color.blue);
0357: StyleConstants.setBold(attribs, true);
0358: StyleConstants.setItalic(attribs, false);
0359: styles.put("tag", attribs);
0360:
0361: // TODO: Do we need this one. */
0362: attribs = new SimpleAttributeSet();
0363: StyleConstants.setFontFamily(attribs, fi.getFamily());
0364: StyleConstants.setFontSize(attribs, fi.getSize());
0365: StyleConstants.setBackground(attribs, Color.white);
0366: StyleConstants.setForeground(attribs, Color.blue);
0367: StyleConstants.setBold(attribs, false);
0368: StyleConstants.setItalic(attribs, false);
0369: styles.put("endtag", attribs);
0370:
0371: // TODO: Do we need this one. */
0372: attribs = new SimpleAttributeSet();
0373: StyleConstants.setFontFamily(attribs, fi.getFamily());
0374: StyleConstants.setFontSize(attribs, fi.getSize());
0375: StyleConstants.setBackground(attribs, Color.white);
0376: StyleConstants.setForeground(attribs, Color.black);
0377: StyleConstants.setBold(attribs, false);
0378: StyleConstants.setItalic(attribs, false);
0379: styles.put("reference", attribs);
0380:
0381: // TODO: Do we need this one. */
0382: attribs = new SimpleAttributeSet();
0383: StyleConstants.setFontFamily(attribs, fi.getFamily());
0384: StyleConstants.setFontSize(attribs, fi.getSize());
0385: StyleConstants.setBackground(attribs, Color.white);
0386: StyleConstants
0387: .setForeground(attribs, new Color(0xB03060)/*Color.maroon*/);
0388: StyleConstants.setBold(attribs, true);
0389: StyleConstants.setItalic(attribs, false);
0390: styles.put("name", attribs);
0391:
0392: // TODO: Do we need this one. */
0393: attribs = new SimpleAttributeSet();
0394: StyleConstants.setFontFamily(attribs, fi.getFamily());
0395: StyleConstants.setFontSize(attribs, fi.getSize());
0396: StyleConstants.setBackground(attribs, Color.white);
0397: StyleConstants
0398: .setForeground(attribs, new Color(0xB03060)/*Color.maroon*/);
0399: StyleConstants.setBold(attribs, false);
0400: StyleConstants.setItalic(attribs, true);
0401: styles.put("value", attribs);
0402:
0403: // TODO: Do we need this one. */
0404: attribs = new SimpleAttributeSet();
0405: StyleConstants.setFontFamily(attribs, fi.getFamily());
0406: StyleConstants.setFontSize(attribs, fi.getSize());
0407: StyleConstants.setBackground(attribs, Color.white);
0408: StyleConstants.setForeground(attribs, Color.black);
0409: StyleConstants.setBold(attribs, true);
0410: StyleConstants.setItalic(attribs, false);
0411: styles.put("text", attribs);
0412:
0413: style = _syntaxPrefs.getColumnStyle();
0414: attribs = new SimpleAttributeSet();
0415: applyStyle(attribs, style, fi);
0416: styles.put(IConstants.IStyleNames.COLUMN, attribs);
0417:
0418: style = _syntaxPrefs.getCommentStyle();
0419: attribs = new SimpleAttributeSet();
0420: applyStyle(attribs, style, fi);
0421: styles.put(IConstants.IStyleNames.COMMENT, attribs);
0422:
0423: style = _syntaxPrefs.getDataTypeStyle();
0424: attribs = new SimpleAttributeSet();
0425: applyStyle(attribs, style, fi);
0426: styles.put(IConstants.IStyleNames.DATA_TYPE, attribs);
0427:
0428: style = _syntaxPrefs.getErrorStyle();
0429: attribs = new SimpleAttributeSet();
0430: applyStyle(attribs, style, fi);
0431: styles.put(IConstants.IStyleNames.ERROR, attribs);
0432:
0433: style = _syntaxPrefs.getFunctionStyle();
0434: attribs = new SimpleAttributeSet();
0435: applyStyle(attribs, style, fi);
0436: styles.put(IConstants.IStyleNames.FUNCTION, attribs);
0437:
0438: style = _syntaxPrefs.getIdentifierStyle();
0439: attribs = new SimpleAttributeSet();
0440: applyStyle(attribs, style, fi);
0441: styles.put(IConstants.IStyleNames.IDENTIFIER, attribs);
0442:
0443: style = _syntaxPrefs.getLiteralStyle();
0444: attribs = new SimpleAttributeSet();
0445: applyStyle(attribs, style, fi);
0446: styles.put(IConstants.IStyleNames.LITERAL, attribs);
0447:
0448: style = _syntaxPrefs.getOperatorStyle();
0449: attribs = new SimpleAttributeSet();
0450: applyStyle(attribs, style, fi);
0451: styles.put(IConstants.IStyleNames.OPERATOR, attribs);
0452:
0453: style = _syntaxPrefs.getReservedWordStyle();
0454: attribs = new SimpleAttributeSet();
0455: applyStyle(attribs, style, fi);
0456: styles.put(IConstants.IStyleNames.RESERVED_WORD, attribs);
0457:
0458: style = _syntaxPrefs.getSeparatorStyle();
0459: attribs = new SimpleAttributeSet();
0460: applyStyle(attribs, style, fi);
0461: styles.put(IConstants.IStyleNames.SEPARATOR, attribs);
0462:
0463: style = _syntaxPrefs.getTableStyle();
0464: attribs = new SimpleAttributeSet();
0465: applyStyle(attribs, style, fi);
0466: styles.put(IConstants.IStyleNames.TABLE, attribs);
0467:
0468: style = _syntaxPrefs.getWhiteSpaceStyle();
0469: attribs = new SimpleAttributeSet();
0470: applyStyle(attribs, style, fi);
0471: styles.put(IConstants.IStyleNames.WHITESPACE, attribs);
0472:
0473: // TODO: Do we need this one. */
0474: attribs = new SimpleAttributeSet();
0475: StyleConstants.setFontFamily(attribs, fi.getFamily());
0476: StyleConstants.setFontSize(attribs, fi.getSize());
0477: StyleConstants.setBackground(attribs, Color.white);
0478: StyleConstants.setForeground(attribs, new Color(0xA020F0)
0479: .darker());
0480: StyleConstants.setBold(attribs, false);
0481: StyleConstants.setItalic(attribs, false);
0482: styles.put("preprocessor", attribs);
0483:
0484: // TODO: Do we need this one. */
0485: attribs = new SimpleAttributeSet();
0486: StyleConstants.setFontFamily(attribs, fi.getFamily());
0487: StyleConstants.setFontSize(attribs, fi.getSize());
0488: StyleConstants.setBackground(attribs, Color.white);
0489: StyleConstants.setForeground(attribs, Color.orange);
0490: StyleConstants.setBold(attribs, false);
0491: StyleConstants.setItalic(attribs, false);
0492: styles.put("unknown", attribs);
0493: }
0494:
0495: private class Colorer extends Thread {
0496:
0497: private boolean _endThread;
0498: private Vector<int[]> _currentLiteralAndCommentIntervals = new Vector<int[]>();
0499: private Hashtable<String, String> _knownTables = new Hashtable<String, String>();
0500:
0501: /**
0502: * A simple wrapper representing something that needs to be colored.
0503: * Placed into an object so that it can be stored in a Vector.
0504: */
0505: private class RecolorEvent {
0506: private int position;
0507: private int adjustment;
0508: private boolean fireTableOrViewFoundEvent;
0509: private String change;
0510:
0511: public RecolorEvent(int position, int adjustment,
0512: boolean fireTableOrViewFoundEvent, String change) {
0513: this .position = position;
0514: this .adjustment = adjustment;
0515: this .fireTableOrViewFoundEvent = fireTableOrViewFoundEvent;
0516: this .change = change;
0517: }
0518: }
0519:
0520: /**
0521: * Vector that stores the communication between the two threads.
0522: */
0523: private volatile Vector<RecolorEvent> recolorEventQueue = new Vector<RecolorEvent>();
0524:
0525: /**
0526: * The last position colored
0527: */
0528:
0529: private volatile boolean asleep = false;
0530:
0531: /**
0532: * When accessing the vector, we need to create a critical section.
0533: * we will synchronize on this object to ensure that we don't get
0534: * unsafe thread behavior.
0535: */
0536: private Object lock = new Object();
0537:
0538: /**
0539: * Tell the Syntax Highlighting thread to take another look at this
0540: * section of the document. It will process this as a FIFO.
0541: * This method should be done inside a doclock.
0542: */
0543: public void color(int position, int adjustment,
0544: boolean fireTableOrViewFoundEvent, String change) {
0545: synchronized (lock) {
0546: recolorEventQueue.add(new RecolorEvent(position,
0547: adjustment, fireTableOrViewFoundEvent, change));
0548: if (asleep) {
0549: this .interrupt();
0550: }
0551: }
0552: }
0553:
0554: public void endThread() {
0555: _endThread = true;
0556: if (asleep) {
0557: this .interrupt();
0558: }
0559: }
0560:
0561: /**
0562: * The colorer runs forever and may sleep for long
0563: * periods of time. It should be interrupted every
0564: * time there is something for it to do.
0565: */
0566: public void run() {
0567: int colorStartPos = -1;
0568: int colorLen = 0;
0569: boolean fireTableOrViewFoundEvent = true;
0570: // if we just finish, we can't go to sleep until we
0571: // ensure there is nothing else for us to do.
0572: // use try again to keep track of this.
0573: boolean tryAgain = false;
0574: for (;;) { // forever
0575: synchronized (lock) {
0576: if (recolorEventQueue.size() > 0) {
0577: RecolorEvent re = recolorEventQueue
0578: .elementAt(0);
0579: recolorEventQueue.removeElementAt(0);
0580:
0581: if (null != re.change) {
0582: // Only if the text did really changed (null != re.change) Intervals
0583: // must be adopted.
0584: // If the text did not change there is nothing to adopt.
0585: adoptIntervalsToAdjustment(re);
0586: }
0587:
0588: colorStartPos = getColorStartPos(re);
0589: colorLen = getColorLen(colorStartPos, re);
0590:
0591: if (null != re.change) {
0592: if (-1 != re.change.indexOf('\'')
0593: || -1 != re.change.indexOf('/')
0594: || -1 != re.change.indexOf('*')
0595: || -1 != re.change.indexOf('-')
0596: || null != getInvolvedLiteralOrCommentInterval(re.position)) {
0597: reinitLiteralAndCommentIntervals();
0598:
0599: int colorStartPos2 = getColorStartPos(re);
0600: int colorLen2 = getColorLen(
0601: colorStartPos, re);
0602:
0603: int newStartPos = Math.min(
0604: colorStartPos, colorStartPos2);
0605: int newEndPos = Math.max(colorStartPos2
0606: + colorLen2, colorStartPos
0607: + colorLen);
0608:
0609: colorStartPos = newStartPos;
0610: colorLen = newEndPos - newStartPos;
0611: }
0612: }
0613:
0614: fireTableOrViewFoundEvent = re.fireTableOrViewFoundEvent;
0615: } else {
0616: tryAgain = false;
0617: colorStartPos = -1;
0618: colorLen = 0;
0619: fireTableOrViewFoundEvent = false;
0620:
0621: }
0622: }
0623: if (colorStartPos != -1) {
0624: try {
0625: final SchemaInfo si = _session.getSchemaInfo();
0626: Token t;
0627: synchronized (doclock) {
0628: // we are playing some games with the lexer for efficiency.
0629: // we could just create a new lexer each time here, but instead,
0630: // we will just reset it so that it thinks it is starting at the
0631: // beginning of the document but reporting a funny start colorStartPos.
0632: // Reseting the lexer causes the close() method on the reader
0633: // to be called but because the close() method has no effect on the
0634: // DocumentReader, we can do this.
0635: syntaxLexer.reset(documentReader, 0,
0636: colorStartPos, 0);
0637: // After the lexer has been set up, scroll the reader so that it
0638: // is in the correct spot as well.
0639: documentReader.seek(colorStartPos);
0640: // we will highlight tokens until we reach a good stopping place.
0641: // the first obvious stopping place is the end of the document.
0642: // the lexer will return null at the end of the document and wee
0643: // need to stop there.
0644: t = getNextToken();
0645: }
0646: SimpleAttributeSet errStyle = getMyStyle(IConstants.IStyleNames.ERROR);
0647: ErrorInfo[] errInfoClone = _currentErrorInfos
0648: .toArray(new ErrorInfo[0]);
0649: while (t != null
0650: && t.getCharEnd() <= colorStartPos
0651: + colorLen + 1) {
0652: // this is the actual command that colors the stuff.
0653: // Color stuff with the description of the style matched
0654: // to the hash table that has been set up ahead of time.
0655: synchronized (doclock) {
0656: if (t.getCharEnd() <= document
0657: .getLength()) {
0658: String type = t.getDescription();
0659: if (type
0660: .equals(IConstants.IStyleNames.IDENTIFIER)) {
0661: final String data = t
0662: .getContents();
0663: if (si.isTable(data)) {
0664: type = IConstants.IStyleNames.TABLE;
0665: if (fireTableOrViewFoundEvent) {
0666: fireTableOrViewFound(t
0667: .getContents());
0668: }
0669:
0670: String upperCaseTableName = data
0671: .toUpperCase();
0672: if (false == _knownTables
0673: .contains(upperCaseTableName)) {
0674: _knownTables
0675: .put(
0676: upperCaseTableName,
0677: upperCaseTableName);
0678: recolorColumns(upperCaseTableName);
0679: }
0680:
0681: } else if (si.isColumn(data)) {
0682: type = IConstants.IStyleNames.COLUMN;
0683: } else if (si.isDataType(data)) {
0684: type = IConstants.IStyleNames.DATA_TYPE;
0685: } else if (si.isKeyword(data)) {
0686: type = IConstants.IStyleNames.RESERVED_WORD;
0687: }
0688: }
0689:
0690: int begin = t.getCharBegin();
0691: int len = t.getCharEnd()
0692: - t.getCharBegin();
0693:
0694: SimpleAttributeSet myStyle = null;
0695: for (int i = 0; i < errInfoClone.length; i++) {
0696: if (isBetween(
0697: errInfoClone[i].beginPos,
0698: errInfoClone[i].endPos,
0699: begin)
0700: && isBetween(
0701: errInfoClone[i].beginPos,
0702: errInfoClone[i].endPos,
0703: (begin + len - 1))) {
0704: myStyle = errStyle;
0705: }
0706: }
0707:
0708: if (null == myStyle) {
0709: myStyle = getMyStyle(type);
0710: }
0711:
0712: setCharacterAttributes(begin, len,
0713: myStyle, true);
0714: // record the colorStartPos of the last bit of text that we colored
0715: }
0716: }
0717: synchronized (doclock) {
0718: t = getNextToken();
0719: }
0720: }
0721:
0722: } catch (IOException x) {
0723: }
0724: // since we did something, we should check that there is
0725: // nothing else to do before going back to sleep.
0726: tryAgain = true;
0727: }
0728:
0729: if (_endThread) {
0730: break;
0731: }
0732:
0733: asleep = true;
0734: if (!tryAgain) {
0735: try {
0736: sleep(0xffffff);
0737: } catch (InterruptedException x) {
0738: }
0739:
0740: }
0741: if (_endThread) {
0742: break;
0743: }
0744:
0745: asleep = false;
0746: }
0747: }
0748:
0749: private void adoptIntervalsToAdjustment(RecolorEvent re) {
0750: for (int i = 0; i < _currentLiteralAndCommentIntervals
0751: .size(); i++) {
0752: int[] interval = _currentLiteralAndCommentIntervals
0753: .elementAt(i);
0754:
0755: if (re.position < interval[0]) {
0756: interval[0] += re.adjustment;
0757: }
0758: if (re.position < interval[1]) {
0759: interval[1] += re.adjustment;
0760: }
0761: }
0762: }
0763:
0764: private void recolorColumns(String tableName) {
0765: String text = getText().toUpperCase();
0766:
0767: ExtendedColumnInfo[] cols = _session.getSchemaInfo()
0768: .getExtendedColumnInfos(tableName);
0769:
0770: for (int i = 0; i < cols.length; i++) {
0771: String upperCaseColName = cols[i].getColumnName()
0772: .toUpperCase();
0773:
0774: int fromIndex = 0;
0775: for (;;) {
0776: fromIndex = text.indexOf(upperCaseColName,
0777: fromIndex);
0778:
0779: if (-1 == fromIndex) {
0780: break;
0781: }
0782:
0783: color(fromIndex, upperCaseColName.length(), false,
0784: null);
0785: ++fromIndex;
0786:
0787: }
0788: }
0789: }
0790:
0791: private void reinitLiteralAndCommentIntervals() {
0792: try {
0793: _currentLiteralAndCommentIntervals.clear();
0794:
0795: Document doc = getDocument();
0796: int docLen = doc.getLength();
0797:
0798: int[] curInterval = null;
0799: boolean inMultiLineComment = false;
0800: boolean inSinglLineComment = false;
0801: boolean inLiteral = false;
0802: for (int i = 0; i < docLen; ++i) {
0803: if (i < docLen + 1
0804: && "/*".equals(doc.getText(i, 2))
0805: && false == inMultiLineComment
0806: && false == inSinglLineComment
0807: && false == inLiteral) {
0808: curInterval = new int[2];
0809: curInterval[0] = i;
0810: curInterval[1] = docLen;
0811: inMultiLineComment = true;
0812: ++i;
0813: continue;
0814: }
0815:
0816: if (i < docLen + 1
0817: && "--".equals(doc.getText(i, 2))
0818: && false == inMultiLineComment
0819: && false == inSinglLineComment
0820: && false == inLiteral) {
0821: curInterval = new int[2];
0822: curInterval[0] = i;
0823: curInterval[1] = docLen;
0824: inSinglLineComment = true;
0825: ++i;
0826: continue;
0827: }
0828:
0829: if ('\'' == doc.getText(i, 1).charAt(0)
0830: && false == inMultiLineComment
0831: && false == inSinglLineComment
0832: && false == inLiteral) {
0833: curInterval = new int[2];
0834: curInterval[0] = i;
0835: curInterval[1] = docLen;
0836: inLiteral = true;
0837: continue;
0838: }
0839:
0840: if (i < docLen + 1
0841: && "*/".equals(doc.getText(i, 2))
0842: && inMultiLineComment) {
0843: curInterval[1] = i + 1;
0844: _currentLiteralAndCommentIntervals
0845: .add(curInterval);
0846: curInterval = null;
0847: inMultiLineComment = false;
0848: ++i;
0849: }
0850:
0851: if ('\n' == doc.getText(i, 1).charAt(0)
0852: && inSinglLineComment) {
0853: curInterval[1] = i;
0854: _currentLiteralAndCommentIntervals
0855: .add(curInterval);
0856: curInterval = null;
0857: inSinglLineComment = false;
0858: }
0859:
0860: if ('\'' == doc.getText(i, 1).charAt(0)
0861: && inLiteral) {
0862: if (i < docLen + 1
0863: && '\'' == doc.getText(i + 1, 1)
0864: .charAt(0)) {
0865: ++i;
0866: } else {
0867: curInterval[1] = i;
0868: _currentLiteralAndCommentIntervals
0869: .add(curInterval);
0870: curInterval = null;
0871: inLiteral = false;
0872: }
0873:
0874: }
0875: }
0876:
0877: if (null != curInterval) {
0878: _currentLiteralAndCommentIntervals.add(curInterval);
0879: }
0880: } catch (BadLocationException e) {
0881: throw new RuntimeException(e);
0882: }
0883: }
0884:
0885: private int getColorLen(int begin, RecolorEvent re) {
0886: try {
0887: int reBegin = Math.min(re.position, re.position
0888: + re.adjustment);
0889: reBegin = Math.max(0, reBegin);
0890:
0891: int end = begin + (reBegin - begin)
0892: + Math.max(0, re.adjustment);
0893:
0894: int docLen = getDocument().getLength();
0895:
0896: if (end > docLen - 1) {
0897: return docLen - begin;
0898: }
0899:
0900: for (; end < docLen - 1; ++end) {
0901: if (Character.isWhitespace(getDocument().getText(
0902: end, 1).charAt(0))) {
0903: break;
0904: }
0905: }
0906:
0907: int[] interval = getInvolvedLiteralOrCommentInterval(end);
0908:
0909: if (null != interval) {
0910: return Math.max(end - begin, interval[1] - begin);
0911: } else {
0912: return end - begin;
0913: }
0914: } catch (BadLocationException e) {
0915: throw new RuntimeException(e);
0916: }
0917: }
0918:
0919: private int getColorStartPos(RecolorEvent re) {
0920: try {
0921: int startPos = Math.min(re.position, re.position
0922: + re.adjustment);
0923:
0924: if (0 > startPos) {
0925: return 0;
0926: }
0927:
0928: for (; startPos > 0; --startPos) {
0929: if (Character.isWhitespace(getDocument().getText(
0930: startPos - 1, 1).charAt(0))) {
0931: break;
0932: }
0933: }
0934:
0935: int[] interval = getInvolvedLiteralOrCommentInterval(startPos);
0936:
0937: if (null != interval) {
0938: return Math.min(startPos, interval[0]);
0939: } else {
0940: return startPos;
0941: }
0942:
0943: } catch (BadLocationException e) {
0944: throw new RuntimeException(e);
0945: }
0946: }
0947:
0948: private int[] getInvolvedLiteralOrCommentInterval(int pos) {
0949: for (int i = 0; i < _currentLiteralAndCommentIntervals
0950: .size(); i++) {
0951: int[] interval = _currentLiteralAndCommentIntervals
0952: .elementAt(i);
0953:
0954: if (interval[0] - 1 <= pos && pos <= interval[1] + 1) {
0955: // The Interval is involved even if pos lied one point before
0956: // or after the interval.
0957: // This way for example we get
0958: // -- Select ...
0959: // out of comment coloring when the first "-" is removed.
0960:
0961: return interval;
0962: }
0963: }
0964: return null;
0965: }
0966:
0967: private boolean isBetween(int beg, int end, int p) {
0968: return beg <= p && p <= end;
0969: }
0970:
0971: private void setCharacterAttributes(final int offset,
0972: final int length, final AttributeSet s,
0973: final boolean replace) {
0974: SwingUtilities.invokeLater(new Runnable() {
0975: public void run() {
0976: // Though in API-Doc they say setCharacterAttributes() is thread save we
0977: // received observed java.lang.Errors from Swing as well as dead locks.
0978: // That's why we do changes synchron now.
0979: document.setCharacterAttributes(offset, length, s,
0980: replace);
0981: }
0982: });
0983: }
0984:
0985: }
0986:
0987: public void addSQLTokenListener(SQLTokenListener l) {
0988: _sqlTokenListeners.add(l);
0989: }
0990:
0991: public void removeSQLTokenListener(SQLTokenListener l) {
0992: _sqlTokenListeners.remove(l);
0993: }
0994:
0995: private void fireTableOrViewFound(String name) {
0996: Vector<SQLTokenListener> buf;
0997: synchronized (_sqlTokenListeners) {
0998: buf = new Vector<SQLTokenListener>(_sqlTokenListeners);
0999: }
1000:
1001: for (int i = 0; i < buf.size(); ++i) {
1002: buf.get(i).tableOrViewFound(name);
1003: }
1004: }
1005:
1006: public String getToolTipText(MouseEvent event) {
1007: int pos = viewToModel(event.getPoint());
1008:
1009: for (int i = 0; i < _currentErrorInfos.size(); i++) {
1010: ErrorInfo errInfo = _currentErrorInfos.elementAt(i);
1011:
1012: if (errInfo.beginPos - 1 <= pos && pos <= errInfo.endPos) {
1013: return errInfo.message;
1014: }
1015: }
1016:
1017: return null;
1018: }
1019:
1020: /**
1021: * Just like a DefaultStyledDocument but intercepts inserts and
1022: * removes to color them.
1023: */
1024: private class HighLightedDocument extends DefaultStyledDocument {
1025: private static final long serialVersionUID = 1L;
1026:
1027: public HighLightedDocument() {
1028: super ();
1029: putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
1030: }
1031:
1032: public void insertString(int offs, String str, AttributeSet a)
1033: throws BadLocationException {
1034: // synchronized (doclock)
1035: // {
1036: super .insertString(offs, str, a);
1037: color(offs, str.length(), true, str);
1038: documentReader.update(offs, str.length());
1039: // }
1040: }
1041:
1042: public void remove(int offs, int len)
1043: throws BadLocationException {
1044: // synchronized (doclock)
1045: // {
1046: String change = getText(offs, len);
1047: super .remove(offs, len);
1048: color(offs, -len, true, change);
1049: documentReader.update(offs, -len);
1050: // }
1051: }
1052: }
1053:
1054: class DocumentReader extends Reader {
1055:
1056: /**
1057: * Modifying the document while the reader is working is like
1058: * pulling the rug out from under the reader. Alerting the
1059: * reader with this method (in a nice thread safe way, this
1060: * should not be called at the same time as a read) allows
1061: * the reader to compensate.
1062: */
1063: public void update(int position, int adjustment) {
1064: if (position < this .position) {
1065: if (this .position < position - adjustment) {
1066: this .position = position;
1067: } else {
1068: this .position += adjustment;
1069: }
1070: }
1071: }
1072:
1073: /**
1074: * Current position in the document. Incremented
1075: * whenever a character is read.
1076: */
1077: private long position = 0;
1078:
1079: /**
1080: * Saved position used in the mark and reset methods.
1081: */
1082: private long mark = -1;
1083:
1084: /**
1085: * The document that we are working with.
1086: */
1087: private AbstractDocument document;
1088:
1089: /**
1090: * Construct a reader on the given document.
1091: *
1092: * @param document the document to be read.
1093: */
1094: public DocumentReader(AbstractDocument document) {
1095: this .document = document;
1096: }
1097:
1098: /**
1099: * Has no effect. This reader can be used even after
1100: * it has been closed.
1101: */
1102: public void close() {
1103: }
1104:
1105: /**
1106: * Save a position for reset.
1107: *
1108: * @param readAheadLimit ignored.
1109: */
1110: public void mark(int readAheadLimit) {
1111: mark = position;
1112: }
1113:
1114: /**
1115: * This reader support mark and reset.
1116: *
1117: * @return true
1118: */
1119: public boolean markSupported() {
1120: return true;
1121: }
1122:
1123: /**
1124: * Read a single character.
1125: *
1126: * @return the character or -1 if the end of the document has been reached.
1127: */
1128: public int read() {
1129: if (position < document.getLength()) {
1130: try {
1131: char c = document.getText((int) position, 1)
1132: .charAt(0);
1133: position++;
1134: return c;
1135: } catch (BadLocationException x) {
1136: return -1;
1137: }
1138: } else {
1139: return -1;
1140: }
1141: }
1142:
1143: /**
1144: * Read and fill the buffer.
1145: * This method will always fill the buffer unless the end of the document is reached.
1146: *
1147: * @param cbuf the buffer to fill.
1148: * @return the number of characters read or -1 if no more characters are available in the document.
1149: */
1150: public int read(char[] cbuf) {
1151: return read(cbuf, 0, cbuf.length);
1152: }
1153:
1154: /**
1155: * Read and fill the buffer.
1156: * This method will always fill the buffer unless the end of the document is reached.
1157: *
1158: * @param cbuf the buffer to fill.
1159: * @param off offset into the buffer to begin the fill.
1160: * @param len maximum number of characters to put in the buffer.
1161: * @return the number of characters read or -1 if no more characters are available in the document.
1162: */
1163: public int read(char[] cbuf, int off, int len) {
1164: if (position < document.getLength()) {
1165: int length = len;
1166: if (position + length >= document.getLength()) {
1167: length = document.getLength() - (int) position;
1168: }
1169: if (off + length >= cbuf.length) {
1170: length = cbuf.length - off;
1171: }
1172: try {
1173: String s = document.getText((int) position, length);
1174: position += length;
1175: for (int i = 0; i < length; i++) {
1176: // The Ostermiller SQLLexer crashes with an ArrayIndexOutOfBoundsException
1177: // if the char is greater then 255. So we prevent the char from being greater.
1178: // This is surely not a proper Unicode treatment but it doesn't seem
1179: // to do no harm and it keeps the SQLLexer working.
1180: cbuf[off + i] = (char) ((s.charAt(i)) % 256);
1181: }
1182: return length;
1183: } catch (BadLocationException x) {
1184: return -1;
1185: }
1186: } else {
1187: return -1;
1188: }
1189: }
1190:
1191: /**
1192: * @return true
1193: */
1194: public boolean ready() {
1195: return true;
1196: }
1197:
1198: /**
1199: * Reset this reader to the last mark, or the beginning of the document if a mark has not been set.
1200: */
1201: public void reset() {
1202: if (mark == -1) {
1203: position = 0;
1204: } else {
1205: position = mark;
1206: }
1207: mark = -1;
1208: }
1209:
1210: /**
1211: * Skip characters of input.
1212: * This method will always skip the maximum number of characters unless
1213: * the end of the file is reached.
1214: *
1215: * @param n number of characters to skip.
1216: * @return the actual number of characters skipped.
1217: */
1218: public long skip(long n) {
1219: if (position + n <= document.getLength()) {
1220: position += n;
1221: return n;
1222: } else {
1223: long oldPos = position;
1224: position = document.getLength();
1225: return (document.getLength() - oldPos);
1226: }
1227: }
1228:
1229: /**
1230: * Seek to the given position in the document.
1231: *
1232: * @param n the offset to which to seek.
1233: */
1234: public void seek(long n) {
1235: if (n <= document.getLength()) {
1236: position = n;
1237: } else {
1238: position = document.getLength();
1239: }
1240: }
1241: }
1242:
1243: }
|