001: package org.jsqltool.gui.graphics;
002:
003: import javax.swing.*;
004: import javax.swing.event.*;
005: import java.awt.*;
006: import java.awt.event.*;
007: import java.util.*;
008: import javax.swing.text.*;
009:
010: /**
011: * <p>Title: JSqlTool Project</p>
012: * <p>Description: Scroll pane that contains a SQL text pane.
013: * </p>
014: * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
015: *
016: * <p> This file is part of JSqlTool project.
017: * This library is free software; you can redistribute it and/or
018: * modify it under the terms of the (LGPL) Lesser General Public
019: * License as published by the Free Software Foundation;
020: *
021: * GNU LESSER GENERAL PUBLIC LICENSE
022: * Version 2.1, February 1999
023: *
024: * This library is distributed in the hope that it will be useful,
025: * but WITHOUT ANY WARRANTY; without even the implied warranty of
026: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
027: * Library General Public License for more details.
028: *
029: * You should have received a copy of the GNU Library General Public
030: * License along with this library; if not, write to the Free
031: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
032: *
033: * The author may be contacted at:
034: * maurocarniel@tin.it</p>
035: *
036: * @author Mauro Carniel
037: * @version 1.0
038: */
039:
040: public class SQLTextArea extends JScrollPane implements
041: DocumentListener {
042:
043: /** contains SQL text */
044: private JTextPane editor = new JTextPane();
045:
046: /** thread used to color the text according to the SQL syntax */
047: Thread t = new ColoredThread();
048:
049: /** lock variable used by the colored thread */
050: Object lock = new Object();
051:
052: /** flag used inside the colored thread */
053: private boolean isAlive = false;
054:
055: /** hash table containing the text styles. Simple attribute sets are hashed by name (String) */
056: private Hashtable styles = new Hashtable();
057:
058: /** char pos to use to start text analysys */
059: private int startpos = 0;
060:
061: public SQLTextArea() {
062: try {
063: this .getViewport().add(editor, null);
064: initStyles();
065: t.start();
066:
067: editor.getDocument().addDocumentListener(this );
068: Document doc = editor.getDocument();
069: doc.insertString(startpos, "", getStyle("text"));
070: } catch (Exception ex) {
071: ex.printStackTrace();
072: }
073: }
074:
075: public final void setText(String text) {
076: editor.setText(text);
077: }
078:
079: public final String getText() {
080: return editor.getText();
081: }
082:
083: public void requestFocus() {
084: editor.requestFocus();
085: }
086:
087: public final void setCaretPosition(int caretPosition) {
088: editor.setCaretPosition(caretPosition);
089: }
090:
091: public final int getCaretPosition() {
092: return editor.getCaretPosition();
093: }
094:
095: public final String getSelectedText() {
096: return editor.getSelectedText();
097: }
098:
099: public final Rectangle modelToView(int pos)
100: throws BadLocationException {
101: return editor.modelToView(pos);
102: }
103:
104: /**
105: * @param pos char index currently added/deleted
106: * @return char pos to use to start analysys
107: */
108: private int getStartPos(int pos) {
109: try {
110: for (int i = pos - 1; i > 0; i--) {
111: if (i < 0)
112: return 0;
113: else if (editor.getText().charAt(i) == ' '
114: || editor.getText().charAt(i) == '\t'
115: || editor.getText().charAt(i) == '\n') {
116: return i;
117: }
118: }
119: } catch (Exception ex) {
120: return 0;
121: }
122: return 0;
123: }
124:
125: /**
126: * Gives notification that there was an insert into the document. The
127: * range given by the DocumentEvent bounds the freshly inserted region.
128: *
129: * @param e the document event
130: */
131: public void insertUpdate(DocumentEvent e) {
132: try {
133: synchronized (lock) {
134: if (isAlive)
135: return;
136: }
137: startpos = getStartPos(e.getOffset());
138: t.interrupt();
139:
140: } catch (Exception ex) {
141: ex.printStackTrace();
142: }
143: }
144:
145: /**
146: * Gives notification that a portion of the document has been
147: * removed. The range is given in terms of what the view last
148: * saw (that is, before updating sticky positions).
149: *
150: * @param e the document event
151: */
152: public void removeUpdate(DocumentEvent e) {
153: try {
154: synchronized (lock) {
155: if (isAlive)
156: return;
157: }
158: startpos = getStartPos(e.getOffset());
159: t.interrupt();
160: } catch (Exception ex) {
161: ex.printStackTrace();
162: }
163: }
164:
165: /**
166: * Gives notification that an attribute or set of attributes changed.
167: *
168: * @param e the document event
169: */
170: public void changedUpdate(DocumentEvent e) {
171:
172: }
173:
174: /**
175: * retrieve the style for the given type of text.
176: *
177: * @param styleName the label for the type of text ("tag" for example)
178: * or null if the styleName is not known.
179: * @return the style
180: */
181: private SimpleAttributeSet getStyle(String styleName) {
182: return ((SimpleAttributeSet) styles.get(styleName));
183: }
184:
185: /**
186: * Create the styles and place them in the hash table.
187: */
188: private void initStyles() {
189: SimpleAttributeSet style;
190:
191: style = new SimpleAttributeSet();
192: StyleConstants.setFontFamily(style, "Monospaced");
193: StyleConstants.setFontSize(style, 12);
194: StyleConstants.setBackground(style, Color.white);
195: StyleConstants.setForeground(style, Color.black);
196: StyleConstants.setBold(style, false);
197: StyleConstants.setItalic(style, false);
198: styles.put("text", style);
199:
200: style = new SimpleAttributeSet();
201: StyleConstants.setFontFamily(style, "Monospaced");
202: StyleConstants.setFontSize(style, 12);
203: StyleConstants.setBackground(style, Color.white);
204: StyleConstants.setForeground(style, Color.blue);
205: StyleConstants.setBold(style, false);
206: StyleConstants.setItalic(style, false);
207: styles.put("reservedWord", style);
208:
209: style = new SimpleAttributeSet();
210: StyleConstants.setFontFamily(style, "Monospaced");
211: StyleConstants.setFontSize(style, 12);
212: StyleConstants.setBackground(style, Color.white);
213: StyleConstants.setForeground(style, Color.red);
214: StyleConstants.setBold(style, false);
215: StyleConstants.setItalic(style, false);
216: styles.put("literal", style);
217:
218: style = new SimpleAttributeSet();
219: StyleConstants.setFontFamily(style, "Monospaced");
220: StyleConstants.setFontSize(style, 12);
221: StyleConstants.setBackground(style, Color.white);
222: StyleConstants.setForeground(style, Color.red);
223: StyleConstants.setBold(style, false);
224: StyleConstants.setItalic(style, false);
225: styles.put("type", style);
226:
227: style = new SimpleAttributeSet();
228: StyleConstants.setFontFamily(style, "Monospaced");
229: StyleConstants.setFontSize(style, 12);
230: StyleConstants.setBackground(style, Color.white);
231: StyleConstants.setForeground(style, Color.blue);
232: StyleConstants.setBold(style, false);
233: StyleConstants.setItalic(style, false);
234: styles.put("operator", style);
235:
236: style = new SimpleAttributeSet();
237: StyleConstants.setFontFamily(style, "Monospaced");
238: StyleConstants.setFontSize(style, 12);
239: StyleConstants.setBackground(style, Color.white);
240: StyleConstants.setForeground(style, Color.green.darker());
241: StyleConstants.setBold(style, false);
242: StyleConstants.setItalic(style, false);
243: styles.put("comment", style);
244:
245: }
246:
247: /**
248: * Method called by document listener to update text color, according to SQL syntax.
249: */
250: private synchronized void highlight() {
251: // wait for lock and change isAlive flag state...
252: synchronized (lock) {
253: isAlive = true;
254: }
255: // now document listener cannot fires events...
256:
257: try {
258: Document doc = editor.getDocument();
259: String text = doc.getText(startpos, doc.getLength()
260: - startpos);
261: doc.remove(startpos, text.length());
262: doc.insertString(startpos, text, getStyle("text"));
263: text = doc.getText(0, doc.getLength()).toUpperCase();
264: int pos;
265: int endpos;
266:
267: // search for patterns...
268: String pattern = null;
269: String[] patterns = new String[] { "SELECT", "FROM",
270: "WHERE", "ORDER BY", "GROUP BY", "HAVING", "IS",
271: "NOT", "NULL", "CREATE", "ALTER", "DROP", "ADD",
272: "TABLE", "INDEX", "FOREIGN", "KEY", "REFERENCES",
273: "CONSTRAINT", "PRIMARY", "ON", "INTO", "UNIQUE" };
274: for (int i = 0; i < patterns.length; i++) {
275: pos = 0;
276: pattern = patterns[i];
277: while ((pos = text.indexOf(pattern, pos)) >= 0) {
278: if ((pos == 0 || pos > 0
279: && (text.charAt(pos - 1) == ' '
280: || text.charAt(pos - 1) == '\t'
281: || text.charAt(pos - 1) == '\n' || text
282: .charAt(pos - 1) == '('))
283: && (pos + pattern.length() == text.length() || pos
284: + pattern.length() < text.length()
285: && (text.charAt(pos
286: + pattern.length()) == ' '
287: || text.charAt(pos
288: + pattern.length()) == '\t'
289: || text.charAt(pos
290: + pattern.length()) == '\n' || text
291: .charAt(pos
292: + pattern.length()) == '('))) {
293: editor.getDocument().remove(pos,
294: pattern.length());
295: editor.getDocument().insertString(pos, pattern,
296: getStyle("reservedWord"));
297: }
298: pos += pattern.length();
299: }
300: }
301:
302: // search for types...
303: patterns = new String[] { "NUMBER", "INTEGER", "INT",
304: "DOUBLE", "DECIMAL", "NUMERIC", "VARCHAR",
305: "VARCHAR2", "CHAR", "DATE", "TIMESTAMP",
306: "DATETIME", "BOOLEAN", "LONG", "BLOB", "CLOB",
307: "LONGRAW", "REAL", "FLOAT", "LONGVARCHAR",
308: "SMALLINT", "LONGVARBINARY", "BIGINT" };
309: for (int i = 0; i < patterns.length; i++) {
310: pos = 0;
311: pattern = patterns[i];
312: while ((pos = text.indexOf(pattern, pos)) >= 0) {
313: if ((pos == 0 || pos > 0
314: && (text.charAt(pos - 1) == ' '
315: || text.charAt(pos - 1) == '\t'
316: || text.charAt(pos - 1) == '\n' || text
317: .charAt(pos - 1) == '('))
318: && (pos + pattern.length() == text.length() || pos
319: + pattern.length() < text.length()
320: && (text.charAt(pos
321: + pattern.length()) == ' '
322: || text.charAt(pos
323: + pattern.length()) == '\t'
324: || text.charAt(pos
325: + pattern.length()) == '\n' || text
326: .charAt(pos
327: + pattern.length()) == '('))) {
328: editor.getDocument().remove(pos,
329: pattern.length());
330: editor.getDocument().insertString(pos, pattern,
331: getStyle("type"));
332: }
333: pos += pattern.length();
334: }
335: }
336:
337: // search for operators...
338: patterns = new String[] { "||", "+", "-", "*", "/", "(+)" };
339: for (int i = 0; i < patterns.length; i++) {
340: pos = startpos;
341: pattern = patterns[i];
342: while ((pos = text.indexOf(pattern, pos)) >= 0) {
343: editor.getDocument().remove(pos, pattern.length());
344: editor.getDocument().insertString(pos, pattern,
345: getStyle("operator"));
346: pos += pattern.length();
347: }
348: }
349:
350: // find out literals...
351: pos = startpos;
352: while ((pos = text.indexOf("'", pos)) >= 0) {
353: endpos = text.indexOf("'", pos + 1);
354: if (endpos == -1)
355: endpos = text.length() - 1;
356: editor.getDocument().remove(pos, endpos - pos + 1);
357: editor.getDocument().insertString(pos,
358: text.substring(pos, endpos + 1),
359: getStyle("literal"));
360: pos = endpos + 1;
361: }
362:
363: // find out comments...
364: pos = startpos;
365: while ((pos = text.indexOf("--", pos)) >= 0) {
366: endpos = text.indexOf("\n", pos);
367: if (endpos == -1)
368: endpos = text.length() - 1;
369: editor.getDocument().remove(pos, endpos - pos + 1);
370: editor.getDocument().insertString(pos,
371: text.substring(pos, endpos + 1),
372: getStyle("comment"));
373: pos = endpos + 1;
374: }
375:
376: pos = startpos;
377: while ((pos = text.indexOf("/*", pos)) >= 0) {
378: endpos = text.indexOf("*/", pos);
379: if (endpos == -1)
380: endpos = text.length() - 2;
381: editor.getDocument().remove(pos, endpos - pos + 2);
382: editor.getDocument().insertString(pos,
383: text.substring(pos, endpos + 2),
384: getStyle("comment"));
385: pos += endpos + 1;
386: }
387:
388: } catch (Exception e) {
389: e.printStackTrace();
390: }
391:
392: // wait for lock and change isAlive flag state...
393: synchronized (lock) {
394: isAlive = false;
395: }
396: // now document listener can fires events...
397:
398: }
399:
400: /**
401: * <p>Description: Thread used to color the text.</p>
402: */
403: class ColoredThread extends Thread {
404:
405: public void run() {
406: // the thread never ends: it suspends until document listener interrupt it...
407: while (true) {
408: // analyze the content...
409: highlight();
410: try {
411: sleep(0xffffff);
412: } catch (InterruptedException x) {
413: }
414: }
415: }
416:
417: }
418:
419: public final void setEditable(boolean editable) {
420: editor.setEditable(editable);
421: }
422:
423: public void addFocusListener(FocusListener listener) {
424: if (editor != null)
425: editor.addFocusListener(listener);
426: }
427:
428: public void addMouseListener(MouseListener listener) {
429: if (editor != null)
430: editor.addMouseListener(listener);
431: }
432:
433: public void addKeyListener(KeyListener listener) {
434: if (editor != null)
435: editor.addKeyListener(listener);
436: }
437:
438: }
|