001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext.java;
015:
016: import java.util.HashMap;
017: import java.util.List;
018: import java.util.Map;
019:
020: import javax.swing.event.DocumentEvent;
021: import javax.swing.text.BadLocationException;
022: import javax.swing.text.JTextComponent;
023:
024: import org.netbeans.editor.Analyzer;
025: import org.netbeans.editor.BaseDocument;
026: import org.netbeans.editor.FinderFactory;
027: import org.netbeans.editor.TextBatchProcessor;
028: import org.netbeans.editor.TokenContextPath;
029: import org.netbeans.editor.TokenID;
030: import org.netbeans.editor.ext.ExtSyntaxSupport;
031:
032: /**
033: * Support methods for syntax analyzes
034: *
035: * @author Miloslav Metelka
036: * @version 1.00
037: */
038:
039: public class JavaSyntaxSupport extends ExtSyntaxSupport {
040:
041: // Internal java declaration token processor states
042: static final int INIT = 0;
043: static final int AFTER_TYPE = 1;
044: static final int AFTER_VARIABLE = 2;
045: static final int AFTER_COMMA = 3;
046: static final int AFTER_DOT = 4;
047: static final int AFTER_TYPE_LSB = 5;
048: static final int AFTER_MATCHING_VARIABLE_LSB = 6;
049: static final int AFTER_MATCHING_VARIABLE = 7;
050: static final int AFTER_EQUAL = 8; // in decl after "var ="
051:
052: private static final TokenID[] COMMENT_TOKENS = new TokenID[] {
053: JavaTokenContext.LINE_COMMENT,
054: JavaTokenContext.BLOCK_COMMENT };
055:
056: private static final TokenID[] BRACKET_SKIP_TOKENS = new TokenID[] {
057: JavaTokenContext.LINE_COMMENT,
058: JavaTokenContext.BLOCK_COMMENT,
059: JavaTokenContext.CHAR_LITERAL,
060: JavaTokenContext.STRING_LITERAL };
061:
062: private static final char[] COMMAND_SEPARATOR_CHARS = new char[] {
063: ';', '{', '}' };
064:
065: private JavaImport javaImport;
066:
067: public JavaSyntaxSupport(BaseDocument doc) {
068: super (doc);
069:
070: tokenNumericIDsValid = true;
071: javaImport = new JavaImport();
072: }
073:
074: protected void documentModified(DocumentEvent evt) {
075: super .documentModified(evt);
076: javaImport.documentModifiedAtPosition(evt.getOffset());
077: }
078:
079: public TokenID[] getCommentTokens() {
080: return COMMENT_TOKENS;
081: }
082:
083: public TokenID[] getBracketSkipTokens() {
084: return BRACKET_SKIP_TOKENS;
085: }
086:
087: /**
088: * Return the position of the last command separator before the given
089: * position.
090: */
091: public int getLastCommandSeparator(int pos)
092: throws BadLocationException {
093: TextBatchProcessor tbp = new TextBatchProcessor() {
094: public int processTextBatch(BaseDocument doc, int startPos,
095: int endPos, boolean lastBatch) {
096: try {
097: int[] blks = getCommentBlocks(endPos, startPos);
098: FinderFactory.CharArrayBwdFinder cmdFinder = new FinderFactory.CharArrayBwdFinder(
099: COMMAND_SEPARATOR_CHARS);
100: return findOutsideBlocks(cmdFinder, startPos,
101: endPos, blks);
102: } catch (BadLocationException e) {
103: e.printStackTrace();
104: return -1;
105: }
106: }
107: };
108: return getDocument().processText(tbp, pos, 0);
109: }
110:
111: /**
112: * Get the class from name. The import sections are consulted to find the
113: * proper package for the name. If the search in import sections fails the
114: * method can ask the finder to search just by the given name.
115: *
116: * @param className
117: * name to resolve. It can be either the full name or just the
118: * name without the package.
119: * @param searchByName
120: * if true and the resolving through the import sections fails
121: * the finder is asked to find the class just by the given name
122: */
123: public JCClass getClassFromName(String className,
124: boolean searchByName) {
125: refreshJavaImport();
126: JCClass ret = JavaCompletion.getPrimitiveClass(className);
127: if (ret == null) {
128:
129: ret = javaImport.getClazz(className);
130: }
131: if (ret == null && searchByName) {
132: if (isUnknownImport(className))
133: return null;
134: List clsList = JavaCompletion.getFinder().findClasses(null,
135: className, true);
136: if (clsList != null && clsList.size() > 0) {
137: if (clsList.size() > 0) { // more matching classes
138: ret = (JCClass) clsList.get(0); // get the first one
139: }
140: }
141:
142: }
143: return ret;
144: }
145:
146: protected boolean isUnknownImport(String className) {
147: return javaImport.isUnknownImport(className);
148: }
149:
150: /** Returns all imports that aren't in parser DB yet */
151: protected List getUnknownImports() {
152: return javaImport.getUnknownImports();
153: }
154:
155: /**
156: * Returns true if the given class is in the import statement directly or
157: * indirectly (package.name.*)
158: */
159: public boolean isImported(JCClass cls) {
160: return javaImport.isImported(cls);
161: }
162:
163: public void refreshJavaImport() {
164: javaImport.update(getDocument());
165: }
166:
167: protected void refreshClassInfo() {
168: }
169:
170: /** Get the class that belongs to the given position */
171: public JCClass getClass(int pos) {
172: return null;
173: }
174:
175: public boolean isStaticBlock(int pos) {
176: return false;
177: }
178:
179: protected DeclarationTokenProcessor createDeclarationTokenProcessor(
180: String varName, int startPos, int endPos) {
181: return new JavaDeclarationTokenProcessor(this , varName);
182: }
183:
184: protected VariableMapTokenProcessor createVariableMapTokenProcessor(
185: int startPos, int endPos) {
186: return new JavaDeclarationTokenProcessor(this , null);
187: }
188:
189: /** Checks, whether caret is inside method */
190: private boolean insideMethod(JTextComponent textComp, int startPos) {
191: try {
192: int level = 0;
193: BaseDocument doc = (BaseDocument) textComp.getDocument();
194: for (int i = startPos - 1; i > 0; i--) {
195: char ch = doc.getChars(i, 1)[0];
196: if (ch == ';')
197: return false;
198: if (ch == ')')
199: level++;
200: if (ch == '(') {
201: if (level == 0) {
202: return true;
203: } else {
204: level--;
205: }
206: }
207: }
208: return false;
209: } catch (BadLocationException e) {
210: return false;
211: }
212: }
213:
214: /** Check and possibly popup, hide or refresh the completion */
215: public int checkCompletion(JTextComponent target, String typedText,
216: boolean visible) {
217: if (!visible) { // pane not visible yet
218: int dotPos = target.getCaret().getDot();
219: switch (typedText.charAt(0)) {
220: case ' ':
221: BaseDocument doc = (BaseDocument) target.getDocument();
222:
223: if (dotPos >= 2) { // last char before inserted space
224: int pos = Math.max(dotPos - 8, 0);
225: try {
226: String txtBeforeSpace = doc.getText(pos, dotPos
227: - pos);
228:
229: if (txtBeforeSpace.endsWith("new ")) {
230: if ((txtBeforeSpace.length() > 4)
231: && (!Character
232: .isJavaIdentifierPart(txtBeforeSpace
233: .charAt(txtBeforeSpace
234: .length() - 5)))) {
235: return ExtSyntaxSupport.COMPLETION_POPUP;
236: }
237: }
238:
239: if (txtBeforeSpace.endsWith("import ")
240: && !Character
241: .isJavaIdentifierPart(txtBeforeSpace
242: .charAt(0))) {
243: return ExtSyntaxSupport.COMPLETION_POPUP;
244: }
245:
246: if (txtBeforeSpace.endsWith(", ")) {
247: // autoPopup completion only if caret is inside
248: // method
249: if (insideMethod(target, dotPos))
250: return ExtSyntaxSupport.COMPLETION_POPUP;
251: }
252: } catch (BadLocationException e) {
253: }
254: }
255: break;
256:
257: case '.':
258: return ExtSyntaxSupport.COMPLETION_POPUP;
259: case ',':
260: // autoPopup completion only if caret is inside method
261: if (insideMethod(target, dotPos))
262: return ExtSyntaxSupport.COMPLETION_POPUP;
263: }
264: return ExtSyntaxSupport.COMPLETION_CANCEL;
265:
266: } else { // the pane is already visible
267: switch (typedText.charAt(0)) {
268: case '=':
269: case '{':
270: case ';':
271: return ExtSyntaxSupport.COMPLETION_HIDE;
272: default:
273: return ExtSyntaxSupport.COMPLETION_POST_REFRESH;
274: }
275: }
276: }
277:
278: public static class JavaDeclarationTokenProcessor implements
279: DeclarationTokenProcessor, VariableMapTokenProcessor {
280:
281: protected JavaSyntaxSupport sup;
282:
283: /** Position of the begining of the declaration to be returned */
284: int decStartPos = -1;
285:
286: int decArrayDepth;
287:
288: /** Starting position of the declaration type */
289: int typeStartPos;
290:
291: /** Position of the end of the type */
292: int typeEndPos;
293:
294: /** Offset of the name of the variable */
295: int decVarNameOffset;
296:
297: /** Length of the name of the variable */
298: int decVarNameLen;
299:
300: /** Currently inside parenthesis, i.e. comma delimits declarations */
301: int parenthesisCounter;
302:
303: /** Depth of the array when there is an array declaration */
304: int arrayDepth;
305:
306: char[] buffer;
307:
308: int bufferStartPos;
309:
310: String varName;
311:
312: int state;
313:
314: /** Map filled with the [varName, type] pairs */
315: HashMap varMap;
316:
317: /**
318: * Construct new token processor
319: *
320: * @param varName
321: * it contains valid varName name or null to search for all
322: * variables and construct the variable map.
323: */
324: public JavaDeclarationTokenProcessor(JavaSyntaxSupport sup,
325: String varName) {
326: this .sup = sup;
327: this .varName = varName;
328: if (varName == null) {
329: varMap = new HashMap();
330: }
331: }
332:
333: public int getDeclarationPosition() {
334: return decStartPos;
335: }
336:
337: public Map getVariableMap() {
338: return varMap;
339: }
340:
341: private void processDeclaration() {
342: if (varName == null) { // collect all variables
343: String decType = new String(buffer, typeStartPos
344: - bufferStartPos, typeEndPos - typeStartPos);
345: if (decType.indexOf(' ') >= 0) {
346: decType = Analyzer.removeSpaces(decType);
347: }
348: String decVarName = new String(buffer,
349: decVarNameOffset, decVarNameLen);
350: JCClass cls = sup.getClassFromName(decType, true);
351: if (cls != null) {
352: varMap.put(decVarName, JavaCompletion.getType(cls,
353: decArrayDepth));
354: } else {
355: // Maybe it's inner class. Stick an outerClass before it ...
356: JCClass outerCls = sup.getClass(decVarNameOffset);
357: if (outerCls != null) {
358: String outerClassName = outerCls.getFullName();
359: JCClass innerClass = JavaCompletion.getFinder()
360: .getExactClass(
361: outerClassName + "." + decType);
362: if (innerClass != null) {
363: varMap
364: .put(decVarName, JavaCompletion
365: .getType(innerClass,
366: decArrayDepth));
367: }
368: }
369: }
370: } else {
371: decStartPos = typeStartPos;
372: }
373: }
374:
375: public boolean token(TokenID tokenID,
376: TokenContextPath tokenContextPath, int tokenOffset,
377: int tokenLen) {
378: int pos = bufferStartPos + tokenOffset;
379:
380: // Check whether we are really recognizing the java tokens
381: if (!tokenContextPath
382: .contains(JavaTokenContext.contextPath)) {
383: state = INIT;
384: return true;
385: }
386:
387: switch (tokenID.getNumericID()) {
388: case JavaTokenContext.BOOLEAN_ID:
389: case JavaTokenContext.BYTE_ID:
390: case JavaTokenContext.CHAR_ID:
391: case JavaTokenContext.DOUBLE_ID:
392: case JavaTokenContext.FLOAT_ID:
393: case JavaTokenContext.INT_ID:
394: case JavaTokenContext.LONG_ID:
395: case JavaTokenContext.SHORT_ID:
396: case JavaTokenContext.VOID_ID:
397: typeStartPos = pos;
398: arrayDepth = 0;
399: typeEndPos = pos + tokenLen;
400: state = AFTER_TYPE;
401: break;
402:
403: case JavaTokenContext.DOT_ID:
404: switch (state) {
405: case AFTER_TYPE: // allowed only inside type
406: state = AFTER_DOT;
407: typeEndPos = pos + tokenLen;
408: break;
409:
410: case AFTER_EQUAL:
411: case AFTER_VARIABLE:
412: break;
413:
414: default:
415: state = INIT;
416: break;
417: }
418: break;
419:
420: case JavaTokenContext.LBRACKET_ID:
421: switch (state) {
422: case AFTER_TYPE:
423: state = AFTER_TYPE_LSB;
424: arrayDepth++;
425: break;
426:
427: case AFTER_MATCHING_VARIABLE:
428: state = AFTER_MATCHING_VARIABLE_LSB;
429: decArrayDepth++;
430: break;
431:
432: case AFTER_EQUAL:
433: break;
434:
435: default:
436: state = INIT;
437: break;
438: }
439: break;
440:
441: case JavaTokenContext.RBRACKET_ID:
442: switch (state) {
443: case AFTER_TYPE_LSB:
444: state = AFTER_TYPE;
445: break;
446:
447: case AFTER_MATCHING_VARIABLE_LSB:
448: state = AFTER_MATCHING_VARIABLE;
449: break;
450:
451: case AFTER_EQUAL:
452: break;
453:
454: default:
455: state = INIT;
456: break;
457: }
458: break; // both in type and varName
459:
460: case JavaTokenContext.LPAREN_ID:
461: parenthesisCounter++;
462: if (state != AFTER_EQUAL) {
463: state = INIT;
464: }
465: break;
466:
467: case JavaTokenContext.RPAREN_ID:
468: if (state == AFTER_MATCHING_VARIABLE) {
469: processDeclaration();
470: }
471: if (parenthesisCounter > 0) {
472: parenthesisCounter--;
473: }
474: if (state != AFTER_EQUAL) {
475: state = INIT;
476: }
477: break;
478:
479: case JavaTokenContext.LBRACE_ID:
480: case JavaTokenContext.RBRACE_ID:
481: if (parenthesisCounter > 0) {
482: parenthesisCounter--; // to tolerate opened parenthesis
483: }
484: state = INIT;
485: break;
486:
487: case JavaTokenContext.COMMA_ID:
488: if (parenthesisCounter > 0) { // comma is declaration
489: // separator in parenthesis
490: if (parenthesisCounter == 1
491: && state == AFTER_MATCHING_VARIABLE) {
492: processDeclaration();
493: }
494: if (state != AFTER_EQUAL) {
495: state = INIT;
496: }
497: } else { // not in parenthesis
498: switch (state) {
499: case AFTER_MATCHING_VARIABLE:
500: processDeclaration();
501: // let it flow to AFTER_VARIABLE
502: case AFTER_VARIABLE:
503: case AFTER_EQUAL:
504: state = AFTER_COMMA;
505: break;
506:
507: default:
508: state = INIT;
509: break;
510: }
511: }
512: break;
513:
514: case JavaTokenContext.NEW_ID:
515: if (state != AFTER_EQUAL) {
516: state = INIT;
517: }
518: break;
519:
520: case JavaTokenContext.EQ_ID:
521: switch (state) {
522: case AFTER_MATCHING_VARIABLE:
523: processDeclaration();
524: // flow to AFTER_VARIABLE
525:
526: case AFTER_VARIABLE:
527: state = AFTER_EQUAL;
528: break;
529:
530: case AFTER_EQUAL:
531: break;
532:
533: default:
534: state = INIT;
535: }
536: break;
537:
538: case JavaTokenContext.SEMICOLON_ID:
539: if (state == AFTER_MATCHING_VARIABLE) {
540: processDeclaration();
541: }
542: state = INIT;
543: break;
544:
545: case JavaTokenContext.IDENTIFIER_ID:
546: switch (state) {
547: case AFTER_TYPE:
548: case AFTER_COMMA:
549: if (varName == null
550: || Analyzer.equals(varName, buffer,
551: tokenOffset, tokenLen)) {
552: decArrayDepth = arrayDepth;
553: decVarNameOffset = tokenOffset;
554: decVarNameLen = tokenLen;
555: state = AFTER_MATCHING_VARIABLE;
556: } else {
557: state = AFTER_VARIABLE;
558: }
559: break;
560:
561: case AFTER_VARIABLE: // error
562: state = INIT;
563: break;
564:
565: case AFTER_EQUAL:
566: break;
567:
568: case AFTER_DOT:
569: typeEndPos = pos + tokenLen;
570: state = AFTER_TYPE;
571: break;
572:
573: case INIT:
574: typeStartPos = pos;
575: arrayDepth = 0;
576: typeEndPos = pos + tokenLen;
577: state = AFTER_TYPE;
578: break;
579:
580: default:
581: state = INIT;
582: break;
583: }
584: break;
585:
586: case JavaTokenContext.WHITESPACE_ID: // whitespace ignored
587: break;
588:
589: }
590:
591: return true;
592: }
593:
594: public int eot(int offset) {
595: return 0;
596: }
597:
598: public void nextBuffer(char[] buffer, int offset, int len,
599: int startPos, int preScan, boolean lastBuffer) {
600: this.buffer = buffer;
601: bufferStartPos = startPos - offset;
602: }
603:
604: }
605:
606: }
|