001: /*
002: * JavaTagger.java
003: *
004: * Copyright (C) 1998-2002 Peter Graves
005: * $Id: JavaTagger.java,v 1.6 2003/12/30 19:24:02 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import java.util.Stack;
025: import java.util.ArrayList;
026:
027: public class JavaTagger extends Tagger implements Constants {
028: // States.
029: private static final int NEUTRAL = 0;
030: private static final int INTERFACE_NAME = 1;
031: private static final int CLASS_NAME = 2;
032: private static final int CLASS_PROLOG = 3; // After name, before opening brace.
033: private static final int EXTENDS = 4;
034: private static final int IMPLEMENTS = 5;
035: private static final int METHOD_NAME = 6;
036: private static final int METHOD_PROLOG = 7;
037: private static final int STATIC_INITIALIZER = 8;
038: private static final int NEW = 9;
039: private static final int FIELD_INITIALIZER = 10; // After '=' in a variable declaration.
040:
041: protected Position pos;
042: protected String token;
043: protected Position tokenStart;
044:
045: private ArrayList tags;
046: private JavaClass currentClass;
047: private int visibility; // TAG_PUBLIC, TAG_PRIVATE, TAG_PROTECTED
048:
049: public JavaTagger(SystemBuffer buffer) {
050: super (buffer);
051: }
052:
053: public synchronized void run() {
054: pos = new Position(buffer.getFirstLine(), 0);
055: token = null;
056: tokenStart = null;
057: tags = new ArrayList();
058: currentClass = null;
059: visibility = 0;
060: final boolean beanShell = buffer.getModeId() == BEANSHELL_MODE;
061: final boolean javaScript = buffer.getModeId() == JAVASCRIPT_MODE;
062: final Stack stack = new Stack();
063: int state = NEUTRAL;
064: while (!pos.atEnd()) {
065: char c = pos.getChar();
066: if (Character.isWhitespace(c)) {
067: pos.skipWhitespace();
068: continue;
069: }
070: if (c == '\'' || c == '"') {
071: pos.skipQuote();
072: continue;
073: }
074: if (pos.lookingAt("/*")) {
075: skipComment(pos);
076: continue;
077: }
078: if (pos.lookingAt("//")) {
079: skipSingleLineComment(pos);
080: continue;
081: }
082: if (state == STATIC_INITIALIZER) {
083: if (c == '{') {
084: skipBrace();
085: state = NEUTRAL;
086: continue;
087: }
088: // Anything else...
089: state = NEUTRAL;
090: // Fall through...
091: }
092: if (state == METHOD_NAME) {
093: if (c == '{') {
094: addTag(TAG_METHOD);
095: visibility = 0;
096: skipBrace();
097: state = NEUTRAL;
098: continue;
099: }
100: if (c == ';') {
101: if (beanShell) {
102: ; // It's just a function call.
103: } else {
104: // Abstract or native method.
105: addTag(TAG_METHOD);
106: visibility = 0;
107: }
108: state = NEUTRAL;
109: pos.next();
110: continue;
111: }
112: if (pos.lookingAt("throws")) {
113: addTag(TAG_METHOD);
114: // Set token to null here so we don't try to add this method again.
115: token = null;
116: visibility = 0;
117: state = METHOD_PROLOG;
118: pos.skip(6); // Skip over "throws".
119: continue;
120: }
121: state = NEUTRAL;
122: // Fall through...
123: }
124: if (state == CLASS_PROLOG) {
125: if (pos.lookingAt("extends")) {
126: state = EXTENDS;
127: pos.skip(7); // Skip over "extends".
128: continue;
129: }
130: if (pos.lookingAt("implements")) {
131: state = IMPLEMENTS;
132: pos.skip(10);
133: continue;
134: }
135: if (c == '{')
136: state = NEUTRAL;
137: pos.next();
138: continue;
139: }
140: if (state == IMPLEMENTS) {
141: if (c == '{') {
142: state = NEUTRAL;
143: pos.next();
144: continue;
145: }
146: // else fall through...
147: }
148: if (state == METHOD_PROLOG) {
149: // Wait for a semicolon or the opening brace of the method body.
150: if (c == '{') {
151: skipBrace();
152: state = NEUTRAL;
153: } else if (c == ';') {
154: // Abstract or native method. If token is null, we've
155: // added it already.
156: if (token != null) {
157: addTag(TAG_METHOD);
158: visibility = 0;
159: }
160: state = NEUTRAL;
161: pos.next();
162: } else
163: pos.next();
164: continue;
165: }
166: if (state == NEW) {
167: if (c == '(') {
168: skipParen();
169: continue;
170: } else if (c == '{') {
171: skipBrace();
172: continue;
173: } else if (c == ';')
174: state = NEUTRAL;
175: pos.next();
176: continue;
177: }
178: if (state == FIELD_INITIALIZER) {
179: if (c == '(') {
180: skipParen();
181: continue;
182: }
183: if (c == '{') {
184: skipBrace();
185: if (javaScript)
186: state = NEUTRAL;
187: continue;
188: }
189: if (c == ',')
190: state = NEUTRAL;
191: else if (c == ';') {
192: state = NEUTRAL;
193: visibility = 0;
194: }
195: pos.next();
196: continue;
197: }
198: if (state == IMPLEMENTS) {
199: if (c == ',') {
200: pos.next();
201: continue;
202: }
203: }
204: if (c == '}') {
205: if (!stack.empty())
206: currentClass = (JavaClass) stack.pop();
207: else
208: currentClass = null;
209: pos.next();
210: continue;
211: }
212: if (Character.isJavaIdentifierStart(c)) {
213: // If we're in an "extends" or "implements" clause, the token
214: // may be a canonical class name (like "java.lang.String").
215: // Otherwise the token is a simple identifier.
216: gatherToken(state == EXTENDS || state == IMPLEMENTS);
217: if (state == INTERFACE_NAME) {
218: if (currentClass != null)
219: stack.push(currentClass);
220: final JavaClass parentClass = currentClass;
221: currentClass = new JavaClass(token, TAG_INTERFACE);
222: state = CLASS_PROLOG;
223: // Add a tag for the class itself.
224: tags.add(new JavaTag("interface ".concat(token),
225: tokenStart, TAG_INTERFACE, visibility,
226: parentClass));
227: visibility = 0;
228: } else if (state == CLASS_NAME) {
229: if (currentClass != null)
230: stack.push(currentClass);
231: final JavaClass parentClass = currentClass;
232: currentClass = new JavaClass(token, TAG_CLASS);
233: state = CLASS_PROLOG;
234: // Add a tag for the class itself.
235: tags.add(new JavaTag("class ".concat(token),
236: tokenStart, TAG_CLASS, visibility,
237: parentClass));
238: visibility = 0;
239: } else if (state == EXTENDS) {
240: tags.add(new JavaTag(token, tokenStart,
241: TAG_EXTENDS, visibility, currentClass));
242: state = CLASS_PROLOG;
243: } else if (state == IMPLEMENTS)
244: tags.add(new JavaTag(token, tokenStart,
245: TAG_IMPLEMENTS, visibility, currentClass));
246: else if (token.equals("package")
247: || token.equals("import"))
248: skipSemi();
249: else if (token.equals("interface"))
250: state = INTERFACE_NAME;
251: else if (token.equals("class"))
252: state = CLASS_NAME;
253: else if (token.equals("static"))
254: state = STATIC_INITIALIZER;
255: else if (token.equals("new"))
256: // Don't be confused by lines like "Runnable r = new Runnable() { ... };"
257: state = NEW;
258: else if (token.equals("public"))
259: visibility |= TAG_PUBLIC;
260: else if (token.equals("protected"))
261: visibility |= TAG_PROTECTED;
262: else if (token.equals("private"))
263: visibility |= TAG_PRIVATE;
264: continue;
265: }
266: if (c == '(') {
267: skipParen();
268: state = METHOD_NAME;
269: continue;
270: }
271: if (c == '=') {
272: addTag(TAG_FIELD);
273: state = FIELD_INITIALIZER;
274: pos.next();
275: continue;
276: }
277: // "int x, y;"
278: if (c == ';' || c == ',') {
279: if (token != null)
280: addTag(TAG_FIELD);
281: // Don't reset the visibility until we see the semicolon.
282: if (c == ';')
283: visibility = 0;
284: }
285: pos.next();
286: }
287: buffer.setTags(tags);
288: }
289:
290: private void addTag(int type) {
291: if (currentClass != null) {
292: FastStringBuffer sb = new FastStringBuffer(currentClass
293: .getName());
294: sb.append('.');
295: sb.append(token);
296: tags.add(new JavaTag(sb.toString(), tokenStart, type,
297: visibility, currentClass));
298: } else
299: tags.add(new JavaTag(token, tokenStart, type, visibility));
300: }
301:
302: protected static final void skipComment(Position pos) {
303: while (true) {
304: if (pos.lookingAt("*/")) {
305: pos.skip(2);
306: return;
307: }
308: if (!pos.next())
309: return;
310: }
311: }
312:
313: protected final void skipSingleLineComment(Position pos) {
314: checkForExplicitTag(pos);
315: Line next = pos.getNextLine();
316: if (next != null)
317: pos.moveTo(next, 0);
318: else
319: pos.setOffset(pos.getLineLength());
320: }
321:
322: private final void checkForExplicitTag(Position pos) {
323: if (tags == null)
324: return; // Only supported for Java and JavaScript for now.
325: final String explicitTag = Editor.preferences()
326: .getStringProperty(Property.EXPLICIT_TAG);
327: if (explicitTag != null && explicitTag.length() > 0) {
328: pos = pos.copy();
329: String s = pos.getString(); // Substring to end of line.
330: int index = s.indexOf(explicitTag);
331: if (index >= 0) {
332: pos.skip(index + explicitTag.length());
333: pos.skipWhitespace();
334: // Now we're looking at the first character of the tag.
335: FastStringBuffer sb = new FastStringBuffer();
336: char c = pos.getChar();
337: if (c == '"') {
338: while (pos.next()) {
339: c = pos.getChar();
340: if (c == '"')
341: break;
342: sb.append(c);
343: }
344: } else {
345: // By default, explicit tags are whitespace-delimited, so
346: // they can contain characters that are not legal in Java
347: // identifiers ("symbol-value").
348: sb.append(c);
349: while (pos.next()) {
350: c = pos.getChar();
351: if (Character.isWhitespace(c))
352: break;
353: sb.append(c);
354: }
355: }
356: final String tag = sb.toString();
357: // Exact location of tag is beginning of text on line
358: // containing tag.
359: pos.setOffset(0);
360: pos.skipWhitespace();
361: tags.add(new JavaTag(tag, pos, TAG_EXPLICIT, 0,
362: currentClass));
363: }
364: }
365: }
366:
367: // If canonical is true, strings like "java.lang.String" are considered to
368: // be a single token. Used for "extends" and "implements" clauses.
369: private void gatherToken(boolean canonical) {
370: tokenStart = new Position(pos);
371: FastStringBuffer sb = new FastStringBuffer();
372: char c;
373: if (canonical) {
374: while (Character.isJavaIdentifierPart(c = pos.getChar())
375: || c == '.') {
376: sb.append(c);
377: if (!pos.next())
378: break;
379: }
380: } else {
381: while (Character.isJavaIdentifierPart(c = pos.getChar())) {
382: sb.append(c);
383: if (!pos.next())
384: break;
385: }
386: }
387: token = sb.toString();
388: }
389:
390: protected void skipParen() {
391: if (pos.next()) {
392: int count = 1;
393: while (true) {
394: if (pos.lookingAt("/*")) {
395: skipComment(pos);
396: continue;
397: }
398: if (pos.lookingAt("//")) {
399: skipSingleLineComment(pos);
400: continue;
401: }
402: char c = pos.getChar();
403: if (c == '"' || c == '\'') {
404: pos.skipQuote();
405: continue;
406: }
407: if (c == '(')
408: ++count;
409: else if (c == ')')
410: --count;
411: if (!pos.next())
412: break;
413: if (count == 0)
414: break;
415: }
416: }
417: }
418:
419: protected void skipBrace() {
420: if (pos.next()) {
421: int count = 1;
422: while (true) {
423: if (pos.lookingAt("/*")) {
424: skipComment(pos);
425: continue;
426: }
427: if (pos.lookingAt("//")) {
428: skipSingleLineComment(pos);
429: continue;
430: }
431: char c = pos.getChar();
432: if (c == '"' || c == '\'') {
433: pos.skipQuote();
434: continue;
435: }
436: if (c == '\\') {
437: // Escape. Ignore this char and the next.
438: if (!pos.next())
439: break;
440: if (!pos.next())
441: break;
442: continue;
443: }
444: if (c == '{')
445: ++count;
446: else if (c == '}')
447: --count;
448: if (!pos.next())
449: break;
450: if (count == 0)
451: break;
452: }
453: }
454: }
455:
456: private void skipSemi() {
457: while (!pos.atEnd()) {
458: if (pos.lookingAt("/*")) {
459: skipComment(pos);
460: continue;
461: }
462: if (pos.lookingAt("//")) {
463: skipSingleLineComment(pos);
464: continue;
465: }
466: char c = pos.getChar();
467: if (c == '"' || c == '\'') {
468: pos.skipQuote();
469: continue;
470: } else if (c == ';') {
471: pos.next();
472: break;
473: } else
474: pos.next();
475: }
476: }
477:
478: // Used by the C, C++ and Objective C taggers.
479: protected static final void skipPreprocessor(Position pos) {
480: while (true) {
481: Line line = pos.getLine();
482: Line nextLine = line.next();
483: if (nextLine == null) {
484: pos.setOffset(line.length());
485: return;
486: }
487: pos.moveTo(nextLine, 0);
488: if (line.length() == 0
489: || line.charAt(line.length() - 1) != '\\')
490: return;
491: }
492: }
493: }
|