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.ArrayList;
017: import java.util.HashMap;
018: import java.util.List;
019:
020: import javax.swing.text.BadLocationException;
021:
022: import org.netbeans.editor.BaseDocument;
023: import org.netbeans.editor.TokenContextPath;
024: import org.netbeans.editor.TokenID;
025: import org.netbeans.editor.TokenProcessor;
026:
027: /**
028: * Mapping of colorings to particular token types
029: *
030: * @author Miloslav Metelka
031: * @version 1.00
032: */
033:
034: public class JavaImport implements TokenProcessor {
035:
036: /**
037: * Initial length of the document to be scanned. It should be big enough so
038: * that only one pass is necessary. If the initial section is too long, then
039: * this value is doubled and the whole parsing restarted.
040: */
041: private static final int INIT_SCAN_LEN = 4096;
042:
043: private static final int INIT = 0; // at the line begining before import
044: // kwd
045: private static final int AFTER_IMPORT = 1; // right after the import kwd
046: private static final int INSIDE_EXP = 2; // inside import expression
047: // inside import expression mixed from several different tokens
048: // exp string buffer is used in this case
049: private static final int INSIDE_MIXED_EXP = 3;
050:
051: /** Short names to classes map */
052: private HashMap name2Class = new HashMap(501);
053:
054: private char[] buffer;
055:
056: private ArrayList infoList = new ArrayList();
057:
058: /** Current state of the imports parsing */
059: private int state;
060:
061: /**
062: * Whether parsing package statement instead of import statment. They have
063: * similair syntax so only this flag distinguishes them.
064: */
065: private boolean parsingPackage;
066:
067: /** Start of the whole import statement */
068: private int startPos;
069:
070: /** Start position of the particular import expression */
071: private int expPos;
072:
073: private boolean eotReached;
074:
075: private StringBuffer exp = new StringBuffer();
076:
077: /** Whether the star was found at the end of package expression */
078: private boolean star;
079:
080: /** The end of the import section. Used for optimized reparsing */
081: private int posEndOfImportSection;
082:
083: /** Disable reparing when change is not in import section */
084: private boolean disableReparsing;
085:
086: JavaSyntax debugSyntax = new JavaSyntax(); // !!! debugging syntax
087:
088: public JavaImport() {
089: posEndOfImportSection = -1;
090: disableReparsing = false;
091: }
092:
093: public synchronized void update(BaseDocument doc) {
094:
095: // optimalization of the parsing
096: if (disableReparsing)
097: return;
098:
099: doc.readLock();
100: try {
101: int scanLen = INIT_SCAN_LEN;
102: int docLen = doc.getLength();
103: boolean wholeDoc = false;
104: do {
105: if (scanLen >= docLen) {
106: scanLen = docLen;
107: wholeDoc = true;
108: }
109: eotReached = false;
110: init();
111: try {
112: doc.getSyntaxSupport().tokenizeText(this , 0,
113: scanLen, false);
114: } catch (BadLocationException e) {
115: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
116: e.printStackTrace();
117: }
118: }
119: scanLen *= 4; // increase the scanning size
120: } while (!wholeDoc && eotReached);
121: } finally {
122: doc.readUnlock();
123: }
124: buffer = null;
125: }
126:
127: protected void init() {
128: exp.setLength(0);
129: star = false;
130: parsingPackage = false;
131: infoList.clear();
132:
133: name2Class.clear(); // clear current mappings
134: // add java.lang package by default
135: JCPackage pkg = JavaCompletion.getFinder().getExactPackage(
136: "java.lang"); // NOI18N
137: if (pkg != null) {
138: JCClass[] classes = pkg.getClasses();
139: for (int i = 0; i < classes.length; i++) {
140: name2Class.put(classes[i].getName(), classes[i]);
141: }
142: }
143:
144: }
145:
146: public JCClass getClazz(String className) {
147: JCFinder finder = JavaCompletion.getFinder();
148: JCClass ret = (JCClass) name2Class.get(className);// first try package
149: // scope
150: if (ret == null) {
151: ret = finder.getExactClass(className);
152: }
153: return ret;
154: }
155:
156: protected void packageStatementFound(int packageStartPos,
157: int packageEndPos, String packageExp) {
158: JCPackage pkg = JavaCompletion.getFinder().getExactPackage(
159: packageExp);
160: if (pkg != null) {
161: JCClass[] classes = pkg.getClasses();
162: for (int i = 0; i < classes.length; i++) {
163: name2Class.put(classes[i].getName(), classes[i]);
164: }
165: }
166: }
167:
168: protected void importStatementFound(int importStartPos,
169: int importEndPos, String importExp, boolean starAtEnd) {
170: JCFinder finder = JavaCompletion.getFinder();
171: Info info = new Info(importStartPos, importEndPos, starAtEnd);
172: JCClass cls = finder.getExactClass(importExp);
173: if (cls != null) {
174: info.cls = cls;
175: if (star) { // !!! dodelat
176: } else { // only this single class
177: name2Class.put(cls.getName(), cls);
178: }
179: } else { // not a direct class, try package
180: JCPackage pkg = finder.getExactPackage(importExp);
181: if (pkg != null) {
182: info.pkg = pkg;
183: if (starAtEnd) { // only useful with star
184: JCClass[] classes = pkg.getClasses();
185: for (int i = 0; i < classes.length; i++) {
186: name2Class
187: .put(classes[i].getName(), classes[i]);
188: }
189: }
190: } else { // not package, will be class
191: String pkgName = importExp;
192: String simplePkgName = null;
193: int ind;
194: while ((ind = pkgName.lastIndexOf('.')) >= 0) {
195: pkgName = pkgName.substring(0, ind);
196: if (simplePkgName == null) {
197: simplePkgName = pkgName;
198: }
199: pkg = finder.getExactPackage(pkgName);
200: if (pkg != null) { // found valid package, but unknown
201: // class
202: cls = JavaCompletion.getSimpleClass(importExp,
203: pkgName.length());
204: info.cls = cls;
205: info.unknownImport = importExp;
206: if (star) {
207: // don't add in this case, can change in the future
208: } else {
209: name2Class.put(cls.getName(), cls);
210: }
211: break;
212: }
213: }
214:
215: if (cls == null) {
216: // didn't found a direct package, assume last is class name
217: if (simplePkgName != null) { // at least one dot in
218: // importExp
219: cls = JavaCompletion.getSimpleClass(importExp,
220: simplePkgName.length());
221: if (star) {
222: // don't add in this case, can change in the future
223: } else {
224: name2Class.put(cls.getName(), cls);
225: }
226: }
227: }
228: }
229: }
230: if ((info.cls == null) && (info.pkg == null)) {
231: info.unknownImport = importExp;
232: }
233: infoList.add(info);
234: }
235:
236: /**
237: * Returns true if className is in import, but in a package, that hasn't
238: * updated DB
239: */
240: public boolean isUnknownImport(String className) {
241: for (int i = 0; i < infoList.size(); i++) {
242: String unknown = ((Info) infoList.get(i)).unknownImport;
243: if ((unknown != null) && (unknown.indexOf(className) > -1))
244: return true;
245: }
246: return false;
247: }
248:
249: /** Returns all imports that aren't in parser DB yet */
250: protected List getUnknownImports() {
251: ArrayList ret = new ArrayList();
252: for (int i = 0; i < infoList.size(); i++) {
253: String unknownImport = ((Info) infoList.get(i)).unknownImport;
254: if (unknownImport != null) {
255: if (((Info) infoList.get(i)).star)
256: unknownImport = unknownImport + ".*"; // NOI18N
257: ret.add(unknownImport);
258: }
259: }
260: return ret;
261: }
262:
263: /**
264: * Returns true if the given class is in the import statement directly or
265: * indirectly (package.name.*)
266: */
267: public boolean isImported(JCClass cls) {
268: if (cls == null)
269: return false;
270:
271: String clsName = cls.getFullName();
272: String pkgName = cls.getPackageName();
273:
274: for (int i = 0; i < infoList.size(); i++) {
275: JCClass infoClass = ((Info) infoList.get(i)).cls;
276: JCPackage infoPackage = ((Info) infoList.get(i)).pkg;
277:
278: if ((clsName != null) && (infoClass != null)) {
279: if (clsName.equals(infoClass.getFullName())) {
280: return true;
281: }
282: }
283: if ((pkgName != null) && (infoPackage != null)) {
284: if (pkgName.equals(infoPackage.getName())) {
285: return true;
286: }
287: }
288:
289: }
290: return false;
291: }
292:
293: public boolean token(TokenID tokenID,
294: TokenContextPath tokenContextPath, int tokenOffset,
295: int tokenLen) {
296: boolean cont = true;
297:
298: switch (tokenID.getNumericID()) {
299: case JavaTokenContext.IDENTIFIER_ID:
300: switch (state) {
301: case AFTER_IMPORT:
302: expPos = tokenOffset;
303: state = INSIDE_EXP;
304: break;
305:
306: case INSIDE_MIXED_EXP:
307: exp.append(buffer, tokenOffset, tokenLen);
308: // let it flow to INSIDE_EXP
309: case INSIDE_EXP:
310: if (star) { // not allowed after star was found
311: cont = false;
312: }
313: break;
314: }
315: break;
316:
317: case JavaTokenContext.DOT_ID:
318: switch (state) {
319: case INIT: // ignore standalone dot
320: break;
321:
322: case AFTER_IMPORT:
323: cont = false; // dot after import keyword
324: break;
325:
326: case INSIDE_MIXED_EXP:
327: exp.append('.');
328: // let it flow to INSIDE_EXP
329: case INSIDE_EXP:
330: if (star) { // not allowed after star was found
331: cont = false;
332: }
333: break;
334: }
335: break;
336:
337: case JavaTokenContext.SEMICOLON_ID:
338: String impExp = null;
339: switch (state) {
340: case INIT: // ignore semicolon
341: break;
342:
343: case AFTER_IMPORT: // semicolon after import kwd
344: cont = false;
345: break;
346:
347: case INSIDE_EXP:
348: impExp = new String(buffer, expPos,
349: (star ? (tokenOffset - 2) : tokenOffset)
350: - expPos);
351: break;
352:
353: case INSIDE_MIXED_EXP:
354: impExp = exp.toString();
355: exp.setLength(0);
356: break;
357: }
358:
359: if (impExp != null) {
360: if (parsingPackage) {
361: packageStatementFound(startPos, tokenOffset + 1,
362: impExp);
363: } else { // parsing import statement
364: importStatementFound(startPos, tokenOffset + 1,
365: impExp, star);
366: }
367: star = false;
368: parsingPackage = false;
369: state = INIT;
370: }
371: break;
372:
373: case JavaTokenContext.MUL_ID:
374: if (star || parsingPackage) {
375: cont = false;
376: } else {
377: switch (state) {
378: case INIT: // ignore star at the begining
379: break;
380:
381: case AFTER_IMPORT:
382: cont = false; // star after import kwd
383: break;
384:
385: case INSIDE_EXP:
386: star = true;
387: if (tokenOffset == 0
388: || buffer[tokenOffset - 1] != '.') {
389: cont = false;
390: }
391: break;
392:
393: case INSIDE_MIXED_EXP:
394: int len = exp.length();
395: if (len > 0 && exp.charAt(len - 1) == '.') {
396: exp.setLength(len - 1); // remove ending dot
397: star = true;
398: } else { // error
399: cont = false;
400: }
401: break;
402: }
403: }
404: break;
405:
406: case JavaTokenContext.PACKAGE_ID:
407: switch (state) {
408: case INIT:
409: parsingPackage = true;
410: state = AFTER_IMPORT; // the same state is used
411: break;
412:
413: default:
414: cont = false; // error in other states
415: break;
416: }
417: break;
418:
419: case JavaTokenContext.IMPORT_ID:
420: switch (state) {
421: case INIT:
422: parsingPackage = false;
423: state = AFTER_IMPORT;
424: startPos = tokenOffset;
425: break;
426:
427: default:
428: cont = false; // error in other states
429: break;
430: }
431: break;
432:
433: case JavaTokenContext.WHITESPACE_ID:
434: case JavaTokenContext.LINE_COMMENT_ID:
435: case JavaTokenContext.BLOCK_COMMENT_ID:
436: switch (state) {
437: case INSIDE_EXP:
438: if (tokenOffset - expPos < 0) {
439: cont = false;
440: break;
441: }
442: // Need to continue as string
443: exp.append(buffer, expPos, tokenOffset - expPos);
444: state = INSIDE_MIXED_EXP;
445: break;
446: }
447: break;
448:
449: default:
450: // when we get here, it means that all packages and imports
451: // were already parsed. the rest of the document will be skipped
452: // and so this is right place to set end of import section
453: if (posEndOfImportSection == -1
454: || tokenOffset + tokenLen > posEndOfImportSection)
455: posEndOfImportSection = tokenOffset + tokenLen;
456: cont = false;
457: break;
458: }
459:
460: return cont;
461: }
462:
463: private String debugState(int state) {
464: switch (state) {
465: case INIT:
466: return "INIT"; // NOI18N
467: case AFTER_IMPORT:
468: return "AFTER_IMPORT"; // NOI18N
469: case INSIDE_EXP:
470: return "INSIDE_EXP"; // NOI18N
471: case INSIDE_MIXED_EXP:
472: return "INSIDE_MIXED_EXP"; // NOI18N
473: }
474: return "UNKNOWN STATE"; // NOI18N
475: }
476:
477: public int eot(int offset) {
478: eotReached = true; // will be rescanned
479: return 0;
480: }
481:
482: public void nextBuffer(char[] buffer, int offset, int len,
483: int startPos, int preScan, boolean lastBuffer) {
484: this .buffer = buffer;
485: }
486:
487: /**
488: * Optimalization for document parsing. The owner of JavaImport instance can
489: * call this function to inform the JavaImport where the change has occured
490: * in the document. If this function is not called, the whole document is
491: * parsed. If it is, the parsing is done only when the import section of the
492: * document is being modified.
493: *
494: * @param offset
495: * offset of the change in document
496: */
497: public void documentModifiedAtPosition(int offset) {
498: // if end of import section has already been found, then check
499: // if change is in import section or not
500: if (posEndOfImportSection != -1) {
501: if (offset > posEndOfImportSection) {
502: // reparing is not necessary, because change is after the import
503: // section
504: disableReparsing = true;
505: } else {
506: // the document must be completely reparsed
507: disableReparsing = false;
508: posEndOfImportSection = -1;
509: }
510: }
511: }
512:
513: class Info {
514:
515: Info(int startPos, int endPos, boolean star) {
516: this .startPos = startPos;
517: this .endPos = endPos;
518: this .star = star;
519: }
520:
521: int startPos;
522:
523: int endPos;
524:
525: boolean star;
526:
527: JCPackage pkg;
528:
529: JCClass cls;
530:
531: String unknownImport;
532:
533: }
534:
535: }
|