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.lang.reflect.Modifier;
017: import java.util.ArrayList;
018: import java.util.Arrays;
019: import java.util.Comparator;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.TreeSet;
025:
026: /**
027: * Java completion support finder
028: *
029: * @author Miloslav Metelka
030: * @version 1.00
031: */
032:
033: public class JCBaseFinder extends JavaCompletion.AbstractProvider
034: implements JCFinder {
035:
036: static final Comparator CLASS_NAME_COMPARATOR = new DefaultClassNameComparator();
037:
038: private static final int PACKAGE_PRE_ALLOC = 1009;
039:
040: private static final int CLASS_PRE_ALLOC = 5003;
041:
042: protected JCPackage[] allPackages;
043:
044: protected JCClass[] allClassesByName;
045:
046: protected HashMap allPackagesMap = new HashMap(PACKAGE_PRE_ALLOC);
047:
048: protected HashMap allClassesMap = new HashMap(CLASS_PRE_ALLOC);
049:
050: public Iterator getClasses() {
051: return new ArrayList(Arrays.asList(getAllClasses())).iterator();
052: }
053:
054: public synchronized boolean append(JCClassProvider cp) {
055: if (!super .append(cp)) {
056: return false;
057: }
058: return true;
059: }
060:
061: protected boolean appendClass(JCClass cls) {
062: if (!cheapUpdate(cls)) { // try cheap update first
063: invalidate(); // reset all if failed
064: }
065: return true;
066: }
067:
068: public synchronized void reset() {
069: allClassesMap.clear();
070: invalidate();
071: }
072:
073: protected void invalidate() {
074: allPackagesMap.clear();
075: allPackages = null;
076: allClassesByName = null;
077: }
078:
079: public synchronized JCPackage getExactPackage(String packageName) {
080: if (allPackages == null) { // not initialized yet
081: build();
082: }
083: return (JCPackage) allPackagesMap.get(packageName);
084: }
085:
086: public synchronized JCClass getExactClass(String classFullName) {
087: return (JCClass) allClassesMap.get(classFullName);
088: }
089:
090: protected JCPackage[] getAllPackages() {
091: if (allPackages == null) {
092: build();
093: }
094: return allPackages;
095: }
096:
097: protected JCClass[] getAllClassesByName() {
098: if (allClassesByName == null) {
099: build();
100: }
101: return allClassesByName;
102: }
103:
104: protected JCClass[] getAllClasses() {
105: JCClass[] allClasses = (JCClass[]) getAllClassesByName()
106: .clone();
107: Arrays.sort(allClasses);
108: return allClasses;
109: }
110:
111: private boolean cheapUpdate(JCClass cls) {
112: Object o = allClassesMap.put(cls.getFullName(), cls);
113:
114: if (allClassesByName != null && o != null) { // inited and class
115: // already there, can do
116: // cheap update
117: String pkgName = cls.getPackageName();
118: JCPackage pkg = (JCPackage) allPackagesMap.get(pkgName);
119: if (pkg == null) { // strange - package missing in package map
120: return false;
121: }
122: JCClass[] clist = pkg.getClasses();
123: int ind = Arrays.binarySearch(clist, cls);
124: if (ind < 0) { // strange - class is missing in the package class
125: // list
126: return false;
127: }
128: clist[ind] = cls;
129:
130: // Update allClassesByName array - can be more with the same name
131: ind = Arrays.binarySearch(allClassesByName, cls,
132: CLASS_NAME_COMPARATOR);
133: // adjust start index
134: if (ind < 0) { // not exact match
135: ind = -ind - 1;
136: }
137:
138: // position to start of matching classes
139: String name = cls.getName();
140: while (ind >= 0 && ind < allClassesByName.length) {
141: if (!allClassesByName[ind].getName().startsWith(name)) {
142: break;
143: }
144: ind--;
145: }
146: ind++;
147:
148: // replace the matching class in the list
149: boolean updated = false;
150: while (ind < allClassesByName.length) {
151: if (cls.equals(allClassesByName[ind])) {
152: allClassesByName[ind] = cls;
153: updated = true;
154: break;
155: }
156: if (!name.equals(allClassesByName[ind].getName())) {
157: break;
158: }
159: ind++;
160: }
161: return updated;
162: }
163: return false;
164: }
165:
166: private void addPackage(JCPackage pkg, boolean force) {
167: if (force || !allPackagesMap.containsKey(pkg)) {
168: allPackagesMap.put(pkg.getName(), pkg);
169: String name = pkg.getName();
170: int i = name.lastIndexOf('.');
171: if (i >= 0) {
172: addPackage(new JavaCompletion.BasePackage(name
173: .substring(0, i)), false);
174: }
175: }
176: }
177:
178: protected void build() {
179: // Build class array and class by name array
180: JCClass[] allClasses = new JCClass[allClassesMap.size()];
181: allClassesByName = new JCClass[allClasses.length];
182:
183: Iterator itr = allClassesMap.values().iterator();
184: int ind = 0;
185: while (itr.hasNext()) {
186: allClasses[ind] = (JCClass) itr.next();
187: allClassesByName[ind] = allClasses[ind];
188: ind++;
189: }
190:
191: Arrays.sort(allClasses);
192: Arrays.sort(allClassesByName, CLASS_NAME_COMPARATOR);
193:
194: // Build package array
195: allPackagesMap.clear();
196: allPackages = JavaCompletion.EMPTY_PACKAGES;
197:
198: if (allClasses.length > 0) {
199: ArrayList pkgClassList = new ArrayList();
200: JCPackage curPkg = new JavaCompletion.BasePackage(
201: allClasses[0].getPackageName());
202:
203: for (int i = 0; i < allClasses.length; i++) {
204: String pkgName = allClasses[i].getPackageName();
205: if (!curPkg.equals(pkgName)) {
206: JCClass classes[] = new JCClass[pkgClassList.size()];
207: pkgClassList.toArray(classes);
208: curPkg.setClasses(classes);
209: pkgClassList.clear();
210: addPackage(curPkg, true);
211: curPkg = new JavaCompletion.BasePackage(pkgName);
212: }
213: pkgClassList.add(allClasses[i]);
214: }
215: JCClass classes[] = new JCClass[pkgClassList.size()];
216: pkgClassList.toArray(classes);
217: curPkg.setClasses(classes);
218: addPackage(curPkg, true);
219:
220: allPackages = new JCPackage[allPackagesMap.size()];
221: itr = allPackagesMap.values().iterator();
222: ind = 0;
223: while (itr.hasNext()) {
224: allPackages[ind] = (JCPackage) itr.next();
225: ind++;
226: }
227: }
228: Arrays.sort(allPackages);
229: }
230:
231: public synchronized List findPackages(String name,
232: boolean exactMatch, boolean subPackages) {
233: List ret = new ArrayList();
234: if (exactMatch) {
235: JCPackage pkg = getExactPackage(name);
236: if (pkg != null) {
237: ret.add(pkg);
238: }
239: if (!subPackages) {
240: return ret;
241: }
242: }
243:
244: JCPackage packages[] = getAllPackages();
245: JCPackage key = new JavaCompletion.BasePackage(name);
246: int ind = Arrays.binarySearch(packages, key);
247: int nameLen = name.length();
248:
249: // adjust start index
250: if (ind < 0) { // not exact match
251: ind = -ind - 1;
252: }
253:
254: // position to start of matching package names
255: while (ind >= 0 && ind < packages.length) {
256: if (!packages[ind].getName().startsWith(name)) {
257: break;
258: }
259: ind--;
260: }
261: ind++;
262:
263: // add the matching packages to the list
264: int reqDotCount = key.getDotCount();
265: while (ind < packages.length) {
266: String pkgName = packages[ind].getName();
267:
268: if (!pkgName.startsWith(name)) {
269: break;
270: }
271:
272: if (exactMatch ? (pkgName.length() > nameLen && pkgName
273: .charAt(nameLen) == '.')
274: : (subPackages || packages[ind].getDotCount() == reqDotCount)) {
275: ret.add(packages[ind]);
276: }
277:
278: ind++;
279: }
280:
281: return ret;
282: }
283:
284: /**
285: * Find classes by name and possibly in some package
286: *
287: * @param pkg
288: * package where the classes should be searched for. It can be
289: * null
290: * @param begining
291: * of the name of the class. The package name must be omitted.
292: * @param exactMatch
293: * whether the given name is the exact requested name of the
294: * class or not.
295: * @return list of the matching classes
296: */
297: public synchronized List findClasses(JCPackage pkg, String name,
298: boolean exactMatch) {
299: List ret = new ArrayList();
300: JCClass[] classes;
301: int ind;
302: JCClass key = new JavaCompletion.SimpleClass(name, ""); // NOI18N
303: int nameLen = name.length();
304: if (pkg != null) {
305: classes = pkg.getClasses();
306: } else { // pkg is null
307: classes = getAllClassesByName();
308: }
309: ind = Arrays.binarySearch(classes, key, CLASS_NAME_COMPARATOR);
310:
311: // adjust start index
312: if (ind < 0) { // not exact match
313: ind = -ind - 1;
314: }
315:
316: // position to start of matching classes
317: while (ind >= 0 && ind < classes.length) {
318: if (!classes[ind].getName().startsWith(name)) {
319: break;
320: }
321: ind--;
322: }
323: ind++;
324:
325: // add the matching classes to the list
326: while (ind < classes.length) {
327: String className = classes[ind].getName();
328: if (!className.startsWith(name)) {
329: break;
330: }
331:
332: if (!exactMatch || className.length() == nameLen) {
333: ret.add(classes[ind]);
334: }
335: ind++;
336: }
337:
338: return ret;
339: }
340:
341: /**
342: * Get outer classes to search the fields and methods there. The original
343: * class is added as the first member of the resulting list.
344: */
345: private List getOuterClasses(JCClass cls) {
346: JCFinder finder = JavaCompletion.getFinder();
347: ArrayList outers = new ArrayList();
348: outers.add(cls);
349: int lastDotInd = cls.getName().lastIndexOf('.');
350: while (lastDotInd >= 0) {
351: int pkgLen = cls.getPackageName().length();
352: String fullName = cls.getFullName().substring(0,
353: ((pkgLen > 0) ? (pkgLen + 1) : 0) + lastDotInd);
354: cls = finder.getExactClass(fullName);
355: if (cls != null) {
356: outers.add(cls);
357: lastDotInd = cls.getName().lastIndexOf('.');
358: } else {
359: break;
360: }
361: }
362: return outers;
363: }
364:
365: /**
366: * Find fields by name in a given class.
367: *
368: * @param cls
369: * class which is searched for the fields.
370: * @param name
371: * start of the name of the field
372: * @param exactMatch
373: * whether the given name of the field is exact
374: * @param staticOnly
375: * whether search for the static fields only
376: * @return list of the matching fields
377: */
378: public synchronized List findFields(JCClass cls, String name,
379: boolean exactMatch, boolean staticOnly,
380: boolean inspectOuterClasses) {
381: TreeSet ts = new TreeSet();
382: List clsList = getClassList(cls);
383: String pkgName = cls.getPackageName();
384: HashSet ifaces = new HashSet(); // The set for temporal storage of all
385: // implemented interfaces
386: JCClass innerClass = cls;
387:
388: for (int i = clsList.size() - 1; i >= 0; i--) {
389: cls = getExactClass(((JCClass) clsList.get(i))
390: .getFullName());
391: if (cls != null) {
392: // remember all the interfaces along the way through hierarchy
393: ifaces.addAll(JCUtilities.getAllInterfaces(cls));
394: boolean difPkg = !cls.getPackageName().equals(pkgName);
395: List outerList = (i == 0 && inspectOuterClasses && cls
396: .getName().indexOf('.') >= 0) ? getOuterClasses(cls)
397: : null;
398: int outerInd = (outerList != null) ? (outerList.size() - 1)
399: : -1;
400: do {
401: if (outerInd >= 0) {
402: cls = (JCClass) outerList.get(outerInd--);
403: }
404: JCField[] fields = cls.getFields();
405: for (int j = 0; j < fields.length; j++) {
406: JCField fld = fields[j];
407: int mods = fld.getModifiers();
408: if ((staticOnly && (mods & Modifier.STATIC) == 0)
409: || (i > 0 && (mods & Modifier.PRIVATE) != 0)
410: || (difPkg && (mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0)
411: || ((outerInd > -1)
412: && ((innerClass.getModifiers() & Modifier.STATIC) != 0) && ((mods & Modifier.STATIC) == 0))) {
413: continue;
414: }
415: if (exactMatch) {
416: if (!fld.getName().equals(name)) {
417: continue;
418: }
419: } else {
420: if (!fld.getName().startsWith(name)) {
421: continue;
422: }
423: }
424: ts.add(fld);
425: }
426: } while (outerInd >= 0);
427: }
428: }
429: // add ALL known fields from interfaces, ALL as they are public static
430: for (Iterator it = ifaces.iterator(); it.hasNext();) {
431: cls = getExactClass(((JCClass) it.next()).getFullName());
432: if (cls != null) {
433: JCField[] fields = cls.getFields();
434: for (int j = 0; j < fields.length; j++) {
435: JCField fld = fields[j];
436: if (exactMatch ? !fld.getName().equals(name) : !fld
437: .getName().startsWith(name))
438: continue;
439:
440: ts.add(fld);
441: }
442: }
443: }
444: return new ArrayList(ts);
445: }
446:
447: /**
448: * Find methods by name in a given class.
449: *
450: * @param cls
451: * class which is searched for the methods.
452: * @param name
453: * start of the name of the method
454: * @param exactMatch
455: * whether the given name of the method is exact
456: * @param staticOnly
457: * whether search for the static methods only
458: * @return list of the matching methods
459: */
460: public synchronized List findMethods(JCClass cls, String name,
461: boolean exactMatch, boolean staticOnly,
462: boolean inspectOuterClasses) {
463: TreeSet ts = new TreeSet();
464: List clsList = getClassList(cls);
465: String pkgName = cls.getPackageName();
466: JCClass innerClass = cls;
467:
468: for (int i = clsList.size() - 1; i >= 0; i--) {
469: cls = getExactClass(((JCClass) clsList.get(i))
470: .getFullName());
471: if (cls != null) {
472: boolean difPkg = !cls.getPackageName().equals(pkgName);
473: List outerList = (i == 0 && inspectOuterClasses && cls
474: .getName().indexOf('.') >= 0) ? getOuterClasses(cls)
475: : null;
476: int outerInd = (outerList != null) ? (outerList.size() - 1)
477: : -1;
478: do {
479: if (outerInd >= 0) {
480: cls = (JCClass) outerList.get(outerInd--);
481: }
482: JCMethod[] methods = cls.getMethods();
483: for (int j = 0; j < methods.length; j++) {
484: JCMethod mtd = methods[j];
485: int mods = mtd.getModifiers();
486: if ((staticOnly && (mods & Modifier.STATIC) == 0)
487: || (i > 0 && (mods & Modifier.PRIVATE) != 0)
488: || (difPkg && (mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0)
489: || ((outerInd > -1)
490: && ((innerClass.getModifiers() & Modifier.STATIC) != 0) && ((mods & Modifier.STATIC) == 0))) {
491: continue;
492: }
493: if (exactMatch) {
494: if (!mtd.getName().equals(name)) {
495: continue;
496: }
497: } else { // match begining
498: if (!mtd.getName().startsWith(name)) {
499: continue;
500: }
501: }
502:
503: // override the method from superclass (throwing
504: // exceptions could differ)
505: if (ts.contains(mtd))
506: ts.remove(mtd);
507:
508: ts.add(mtd);
509: }
510: } while (outerInd >= 0);
511: }
512: }
513:
514: return new ArrayList(ts);
515: }
516:
517: private List getClassList(JCClass cls) {
518: JCFinder finder = JavaCompletion.getFinder();
519: cls = finder.getExactClass(cls.getFullName());
520: List ret;
521: if (cls != null) {
522: if (cls.isInterface()) {
523: ret = JCUtilities.getAllInterfaces(cls);
524: // #16252 it is legal to call methods for java.lang.Object from
525: // an interface
526: ret.add(JavaCompletion.OBJECT_CLASS);
527: } else {
528: ret = JCUtilities.getSuperclasses(cls);
529: if ((cls.getModifiers() & Modifier.ABSTRACT) != 0) {
530: // in the case of abstract implementor of interface
531: ret.addAll(JCUtilities.getAllInterfaces(cls));
532: }
533: }
534:
535: ret.add(0, cls);
536:
537: } else { // class not found
538: ret = new ArrayList(); // return empty list
539: }
540:
541: return ret;
542: }
543:
544: public String dumpClasses() {
545: StringBuffer sb = new StringBuffer(8192); // expect huge growth
546: JCClass[] ac = getAllClasses();
547: for (int i = 0; i < ac.length; i++) {
548: sb.append(JCUtilities.dumpClass(ac[i]));
549: sb.append("\n\n"); // NOI18N
550: }
551: return sb.toString();
552: }
553:
554: public static final class DefaultClassNameComparator implements
555: Comparator {
556:
557: public int compare(Object o1, Object o2) {
558: if (o1 == o2) {
559: return 0;
560: }
561: return ((JCClass) o1).getName().compareTo(
562: ((JCClass) o2).getName());
563: }
564:
565: }
566:
567: }
|