001: /*
002: * Bytecode Analysis Framework
003: * Copyright (C) 2003-2006 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.BitSet;
026: import java.util.Collection;
027: import java.util.List;
028:
029: import net.jcip.annotations.NotThreadSafe;
030:
031: import org.apache.bcel.Repository;
032: import org.apache.bcel.classfile.JavaClass;
033:
034: import edu.umd.cs.findbugs.AbstractBugReporter;
035: import edu.umd.cs.findbugs.AnalysisLocal;
036: import edu.umd.cs.findbugs.SourceLineAnnotation;
037: import edu.umd.cs.findbugs.SystemProperties;
038: import edu.umd.cs.findbugs.annotations.NonNull;
039: import edu.umd.cs.findbugs.ba.ch.Subtypes;
040: import edu.umd.cs.findbugs.ba.ch.Subtypes2;
041: import edu.umd.cs.findbugs.ba.interproc.PropertyDatabase;
042: import edu.umd.cs.findbugs.ba.interproc.PropertyDatabaseFormatException;
043: import edu.umd.cs.findbugs.ba.jsr305.DirectlyRelevantTypeQualifiersDatabase;
044: import edu.umd.cs.findbugs.ba.npe.ParameterNullnessPropertyDatabase;
045: import edu.umd.cs.findbugs.ba.npe.ReturnValueNullnessPropertyDatabase;
046: import edu.umd.cs.findbugs.ba.type.FieldStoreTypeDatabase;
047: import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
048: import edu.umd.cs.findbugs.classfile.ClassDescriptor;
049: import edu.umd.cs.findbugs.classfile.DescriptorFactory;
050: import edu.umd.cs.findbugs.classfile.FieldOrMethodDescriptor;
051: import edu.umd.cs.findbugs.classfile.Global;
052: import edu.umd.cs.findbugs.classfile.IAnalysisCache;
053: import edu.umd.cs.findbugs.detect.UnreadFields;
054: import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
055:
056: /**
057: * A context for analysis of a complete project.
058: * This serves as the repository for whole-program information
059: * and data structures.
060: *
061: * <p>
062: * <b>NOTE</b>: this class is slated to become obsolete.
063: * New code should use the IAnalysisCache object
064: * returned by Global.getAnalysisCache() to access all analysis
065: * information (global databases, class and method analyses, etc.)
066: * </p>
067: *
068: * @author David Hovemeyer
069: * @see IAnalysisCache
070: * @see edu.umd.cs.findbugs.classfile.Global
071: */
072: @NotThreadSafe
073: public abstract class AnalysisContext {
074: public static final boolean DEBUG = SystemProperties
075: .getBoolean("findbugs.analysiscontext.debug");
076: public static final boolean IGNORE_BUILTIN_MODELS = SystemProperties
077: .getBoolean("findbugs.ignoreBuiltinModels");
078:
079: public static final String DEFAULT_NONNULL_PARAM_DATABASE_FILENAME = "nonnullParam.db";
080:
081: public static final String DEFAULT_CHECK_FOR_NULL_PARAM_DATABASE_FILENAME = "checkForNullParam.db";
082: public static final String DEFAULT_NULL_RETURN_VALUE_ANNOTATION_DATABASE = "nullReturn.db";
083:
084: public static final String UNCONDITIONAL_DEREF_DB_FILENAME = "unconditionalDeref.db";
085: public static final String NONNULL_RETURN_DB_FILENAME = "nonnullReturn.db";
086:
087: public static final String UNCONDITIONAL_DEREF_DB_RESOURCE = "jdkBaseUnconditionalDeref.db";
088: public static final String NONNULL_RETURN_DB_RESOURCE = "jdkBaseNonnullReturn.db";
089:
090: public static final String DEFAULT_NULL_RETURN_VALUE_DB_FILENAME = "mayReturnNull.db";
091:
092: private static InheritableThreadLocal<AnalysisContext> currentAnalysisContext = new InheritableThreadLocal<AnalysisContext>() {
093: @Override
094: public AnalysisContext initialValue() {
095: // throw new IllegalStateException("currentAnalysisContext should be set by AnalysisContext.setCurrentAnalysisContext");
096: return null;
097: }
098: };
099:
100: private static AnalysisLocal<XFactory> currentXFactory = new AnalysisLocal<XFactory>() {
101: @Override
102: public XFactory initialValue() {
103: throw new IllegalStateException(
104: "currentXFactory should be set by AnalysisContext.setCurrentAnalysisContext");
105: }
106: };
107:
108: public abstract INullnessAnnotationDatabase getNullnessAnnotationDatabase();
109:
110: public abstract CheckReturnAnnotationDatabase getCheckReturnAnnotationDatabase();
111:
112: public abstract AnnotationRetentionDatabase getAnnotationRetentionDatabase();
113:
114: public abstract JCIPAnnotationDatabase getJCIPAnnotationDatabase();
115:
116: /** save the original SyntheticRepository so we may
117: * obtain JavaClass objects which we can reuse.
118: * (A URLClassPathRepository gets closed after analysis.) */
119: private static final org.apache.bcel.util.Repository originalRepository = Repository
120: .getRepository(); // BCEL SyntheticRepository
121:
122: /**
123: * Default maximum number of ClassContext objects to cache.
124: * FIXME: need to evaluate this parameter. Need to keep stats about accesses.
125: */
126: private static final int DEFAULT_CACHE_SIZE = 3;
127:
128: // Instance fields
129: private BitSet boolPropertySet;
130: private String databaseInputDir;
131: private String databaseOutputDir;
132:
133: protected AnalysisContext() {
134: this .boolPropertySet = new BitSet();
135: }
136:
137: private void clear() {
138: boolPropertySet = null;
139: databaseInputDir = null;
140: databaseOutputDir = null;
141: }
142:
143: /**
144: * Create a new AnalysisContext.
145: *
146: * @param lookupFailureCallback the RepositoryLookupFailureCallback that
147: * the AnalysisContext should use to report errors
148: * @return a new AnalysisContext
149: */
150: public static AnalysisContext create(
151: RepositoryLookupFailureCallback lookupFailureCallback) {
152: AnalysisContext analysisContext = new LegacyAnalysisContext(
153: lookupFailureCallback);
154: setCurrentAnalysisContext(analysisContext);
155: return analysisContext;
156: }
157:
158: /** Instantiate the CheckReturnAnnotationDatabase.
159: * Do this after the repository has been set up.
160: */
161: public abstract void initDatabases();
162:
163: /**
164: * After a pass has been completed, allow the analysis context to update information.
165: * @param pass -- the first pass is pass 0
166: */
167: public abstract void updateDatabases(int pass);
168:
169: /**
170: * Get the AnalysisContext associated with this thread
171: */
172: static public AnalysisContext currentAnalysisContext() {
173: return currentAnalysisContext.get();
174: }
175:
176: static public XFactory currentXFactory() {
177: return currentXFactory.get();
178: }
179:
180: UnreadFields unreadFields;
181:
182: public UnreadFields getUnreadFields() {
183: if (unreadFields == null)
184: throw new IllegalStateException(
185: "UnreadFields detector not set");
186: return unreadFields;
187: }
188:
189: public void setUnreadFields(@NonNull
190: UnreadFields unreadFields) {
191: if (this .unreadFields != null)
192: throw new IllegalStateException(
193: "UnreadFields detector already set");
194: this .unreadFields = unreadFields;
195: }
196:
197: DirectlyRelevantTypeQualifiersDatabase directlyRelevantTypeQualifiersDatabase;
198:
199: public DirectlyRelevantTypeQualifiersDatabase getDirectlyRelevantTypeQualifiersDatabase() {
200: if (directlyRelevantTypeQualifiersDatabase == null)
201: throw new IllegalStateException(
202: "DirectlyRelevantTypeQualifiersDatabase not set");
203: return directlyRelevantTypeQualifiersDatabase;
204: }
205:
206: public void setDirectlyRelevantTypeQualifiersDatabase(
207: @NonNull
208: DirectlyRelevantTypeQualifiersDatabase directlyRelevantTypeQualifiersDatabase) {
209: if (this .directlyRelevantTypeQualifiersDatabase != null)
210: throw new IllegalStateException(
211: "DirectlyRelevantTypeQualifiersDatabase already set");
212: this .directlyRelevantTypeQualifiersDatabase = directlyRelevantTypeQualifiersDatabase;
213: }
214:
215: /**
216: * file a ClassNotFoundException with the lookupFailureCallback
217: * @see #getLookupFailureCallback()
218: */
219: static public void reportMissingClass(ClassNotFoundException e) {
220: if (e == null)
221: throw new NullPointerException("argument is null");
222: String missing = AbstractBugReporter.getMissingClassName(e);
223: if (missing.length() == 0) {
224: // AnalysisContext.logError("Empty missing class name", new RuntimeException(e));
225: return;
226: }
227: if (missing.charAt(0) == '[')
228: return;
229: if (missing.endsWith("package-info"))
230: return;
231: AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
232: if (currentAnalysisContext2 == null)
233: return;
234: if (currentAnalysisContext2.missingClassWarningsSuppressed)
235: return;
236: RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2
237: .getLookupFailureCallback();
238: if (lookupFailureCallback != null)
239: lookupFailureCallback.reportMissingClass(e);
240: }
241:
242: /**
243: * Report an error
244: */
245: static public void logError(String msg, Exception e) {
246: if (e instanceof MissingClassException) {
247: reportMissingClass(((MissingClassException) e)
248: .getClassNotFoundException());
249: return;
250: }
251: if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
252: reportMissingClass(((edu.umd.cs.findbugs.classfile.MissingClassException) e)
253: .toClassNotFoundException());
254: return;
255: }
256: AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
257: if (currentAnalysisContext2 == null)
258: return;
259: RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2
260: .getLookupFailureCallback();
261: if (lookupFailureCallback != null)
262: lookupFailureCallback.logError(msg, e);
263: }
264:
265: /**
266: * Report an error
267: */
268: static public void logError(String msg) {
269: AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
270: if (currentAnalysisContext2 == null)
271: return;
272: RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2
273: .getLookupFailureCallback();
274: if (lookupFailureCallback != null)
275: lookupFailureCallback.logError(msg);
276: }
277:
278: boolean missingClassWarningsSuppressed = false;
279:
280: public boolean setMissingClassWarningsSuppressed(boolean value) {
281: boolean oldValue = missingClassWarningsSuppressed;
282: missingClassWarningsSuppressed = value;
283: return oldValue;
284: }
285:
286: /**
287: * Get the lookup failure callback.
288: */
289: public abstract RepositoryLookupFailureCallback getLookupFailureCallback();
290:
291: /**
292: * Set the source path.
293: */
294: public final void setSourcePath(List<String> sourcePath) {
295: getSourceFinder().setSourceBaseList(sourcePath);
296: }
297:
298: /**
299: * Get the SourceFinder, for finding source files.
300: */
301: public abstract SourceFinder getSourceFinder();
302:
303: /**
304: * Get the Subtypes database.
305: *
306: * @return the Subtypes database
307: */
308: @Deprecated
309: // use Subtypes2 instead
310: public abstract Subtypes getSubtypes();
311:
312: /**
313: * Clear the BCEL Repository in preparation for analysis.
314: */
315: public abstract void clearRepository();
316:
317: /**
318: * Clear the ClassContext cache.
319: * This should be done between analysis passes.
320: */
321: public abstract void clearClassContextCache();
322:
323: /**
324: * Add an entry to the Repository's classpath.
325: *
326: * @param url the classpath entry URL
327: * @throws IOException
328: */
329: public abstract void addClasspathEntry(String url)
330: throws IOException;
331:
332: /**
333: * Add an application class to the repository.
334: *
335: * @param appClass the application class
336: */
337: public abstract void addApplicationClassToRepository(
338: JavaClass appClass);
339:
340: /**
341: * Return whether or not the given class is an application class.
342: *
343: * @param cls the class to lookup
344: * @return true if the class is an application class, false if not
345: * an application class or if the class cannot be located
346: */
347: public boolean isApplicationClass(JavaClass cls) {
348: return getSubtypes().isApplicationClass(cls);
349: }
350:
351: /**
352: * Return whether or not the given class is an application class.
353: *
354: * @param className name of a class
355: * @return true if the class is an application class, false if not
356: * an application class or if the class cannot be located
357: */
358: public boolean isApplicationClass(@DottedClassName
359: String className) {
360: try {
361: JavaClass javaClass = lookupClass(className);
362: return isApplicationClass(javaClass);
363: } catch (ClassNotFoundException e) {
364: AnalysisContext.reportMissingClass(e);
365: return false;
366: }
367: }
368:
369: public boolean isApplicationClass(ClassDescriptor desc) {
370: return getSubtypes2().isApplicationClass(desc);
371: }
372:
373: /**
374: * Lookup a class.
375: * <em>Use this method instead of Repository.lookupClass().</em>
376: *
377: * @param className the name of the class
378: * @return the JavaClass representing the class
379: * @throws ClassNotFoundException (but not really)
380: */
381: public abstract JavaClass lookupClass(@NonNull
382: @DottedClassName
383: String className) throws ClassNotFoundException;
384:
385: /**
386: * Lookup a class.
387: * <em>Use this method instead of Repository.lookupClass().</em>
388: *
389: * @param classDescriptor descriptor specifying the class to look up
390: * @return the class
391: * @throws ClassNotFoundException if the class can't be found
392: */
393: public JavaClass lookupClass(@NonNull
394: ClassDescriptor classDescriptor) throws ClassNotFoundException {
395: return lookupClass(classDescriptor.toDottedClassName());
396: }
397:
398: /**
399: * This is equivalent to Repository.lookupClass() or this.lookupClass(),
400: * except it uses the original Repository instead of the current one.
401: *
402: * This can be important because URLClassPathRepository objects are
403: * closed after an analysis, so JavaClass objects obtained from them
404: * are no good on subsequent runs.
405: *
406: * @param className the name of the class
407: * @return the JavaClass representing the class
408: * @throws ClassNotFoundException
409: */
410: public static JavaClass lookupSystemClass(@NonNull
411: String className) throws ClassNotFoundException {
412: // TODO: eventually we should move to our own thread-safe repository implementation
413: if (className == null)
414: throw new IllegalArgumentException("className is null");
415: if (originalRepository == null)
416: throw new IllegalStateException(
417: "originalRepository is null");
418:
419: JavaClass clazz = originalRepository.findClass(className);
420: return (clazz == null ? originalRepository.loadClass(className)
421: : clazz);
422: }
423:
424: /**
425: * Lookup a class's source file
426: *
427: * @param className the name of the class
428: * @return the source file for the class, or SourceLineAnnotation.UNKNOWN_SOURCE_FILE if unable to determine
429: */
430: public final String lookupSourceFile(@NonNull
431: @DottedClassName
432: String dottedClassName) {
433: if (dottedClassName == null)
434: throw new IllegalArgumentException("className is null");
435: try {
436: XClass xClass = Global
437: .getAnalysisCache()
438: .getClassAnalysis(
439: XClass.class,
440: DescriptorFactory
441: .createClassDescriptorFromDottedClassName(dottedClassName));
442: String name = xClass.getSource();
443: if (name == null) {
444: return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
445: }
446: return name;
447: } catch (CheckedAnalysisException e) {
448: return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
449: }
450: }
451:
452: /**
453: * Get the ClassContext for a class.
454: *
455: * @param javaClass the class
456: * @return the ClassContext for that class
457: */
458: public abstract ClassContext getClassContext(JavaClass javaClass);
459:
460: /**
461: * Get stats about hit rate for ClassContext cache.
462: *
463: * @return stats about hit rate for ClassContext cache
464: */
465: public abstract String getClassContextStats();
466:
467: /**
468: * If possible, load interprocedural property databases.
469: */
470: public final void loadInterproceduralDatabases() {
471: loadPropertyDatabase(getFieldStoreTypeDatabase(),
472: FieldStoreTypeDatabase.DEFAULT_FILENAME,
473: "field store type database");
474: loadPropertyDatabase(getUnconditionalDerefParamDatabase(),
475: UNCONDITIONAL_DEREF_DB_FILENAME,
476: "unconditional param deref database");
477: loadPropertyDatabase(getReturnValueNullnessPropertyDatabase(),
478: NONNULL_RETURN_DB_FILENAME,
479: "nonnull return db database");
480: }
481:
482: /**
483: * If possible, load default (built-in) interprocedural property databases.
484: * These are the databases for things like Java core APIs that
485: * unconditional dereference parameters.
486: */
487: public final void loadDefaultInterproceduralDatabases() {
488: if (IGNORE_BUILTIN_MODELS)
489: return;
490: loadPropertyDatabaseFromResource(
491: getUnconditionalDerefParamDatabase(),
492: UNCONDITIONAL_DEREF_DB_RESOURCE,
493: "unconditional param deref database");
494: loadPropertyDatabaseFromResource(
495: getReturnValueNullnessPropertyDatabase(),
496: NONNULL_RETURN_DB_RESOURCE,
497: "nonnull return db database");
498: }
499:
500: /**
501: * Set a boolean property.
502: *
503: * @param prop the property to set
504: * @param value the value of the property
505: */
506: public final void setBoolProperty(int prop, boolean value) {
507: boolPropertySet.set(prop, value);
508: }
509:
510: /**
511: * Get a boolean property.
512: *
513: * @param prop the property
514: * @return value of the property; defaults to false if the property
515: * has not had a value assigned explicitly
516: */
517: public final boolean getBoolProperty(int prop) {
518: return boolPropertySet.get(prop);
519: }
520:
521: /**
522: * Get the SourceInfoMap.
523: */
524: public abstract SourceInfoMap getSourceInfoMap();
525:
526: /**
527: * Set the interprocedural database input directory.
528: *
529: * @param databaseInputDir the interprocedural database input directory
530: */
531: public final void setDatabaseInputDir(String databaseInputDir) {
532: if (DEBUG)
533: System.out.println("Setting database input directory: "
534: + databaseInputDir);
535: this .databaseInputDir = databaseInputDir;
536: }
537:
538: /**
539: * Get the interprocedural database input directory.
540: *
541: * @return the interprocedural database input directory
542: */
543: public final String getDatabaseInputDir() {
544: return databaseInputDir;
545: }
546:
547: /**
548: * Set the interprocedural database output directory.
549: *
550: * @param databaseOutputDir the interprocedural database output directory
551: */
552: public final void setDatabaseOutputDir(String databaseOutputDir) {
553: if (DEBUG)
554: System.out.println("Setting database output directory: "
555: + databaseOutputDir);
556: this .databaseOutputDir = databaseOutputDir;
557: }
558:
559: /**
560: * Get the interprocedural database output directory.
561: *
562: * @return the interprocedural database output directory
563: */
564: public final String getDatabaseOutputDir() {
565: return databaseOutputDir;
566: }
567:
568: /**
569: * Get the property database recording the types of values stored
570: * into fields.
571: *
572: * @return the database, or null if there is no database available
573: */
574: public abstract FieldStoreTypeDatabase getFieldStoreTypeDatabase();
575:
576: /**
577: * Get the property database recording which methods unconditionally
578: * dereference parameters.
579: *
580: * @return the database, or null if there is no database available
581: */
582: public abstract ParameterNullnessPropertyDatabase getUnconditionalDerefParamDatabase();
583:
584: /**
585: * Get the property database recording which methods always return nonnull values
586: *
587: * @return the database, or null if there is no database available
588: */
589: public abstract ReturnValueNullnessPropertyDatabase getReturnValueNullnessPropertyDatabase();
590:
591: /**
592: * Load an interprocedural property database.
593: *
594: * @param <DatabaseType> actual type of the database
595: * @param <KeyType> type of key (e.g., method or field)
596: * @param <Property> type of properties stored in the database
597: * @param database the empty database object
598: * @param fileName file to load database from
599: * @param description description of the database (for diagnostics)
600: * @return the database object, or null if the database couldn't be loaded
601: */
602: public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabase(
603: DatabaseType database, String fileName, String description) {
604: try {
605: File dbFile = new File(getDatabaseInputDir(), fileName);
606: if (DEBUG)
607: System.out.println("Loading " + description + " from "
608: + dbFile.getPath() + "...");
609:
610: database.readFromFile(dbFile.getPath());
611: return database;
612: } catch (IOException e) {
613: getLookupFailureCallback().logError(
614: "Error loading " + description, e);
615: } catch (PropertyDatabaseFormatException e) {
616: getLookupFailureCallback().logError(
617: "Invalid " + description, e);
618: }
619:
620: return null;
621: }
622:
623: /**
624: * Load an interprocedural property database.
625: *
626: * @param <DatabaseType> actual type of the database
627: * @param <KeyType> type of key (e.g., method or field)
628: * @param <Property> type of properties stored in the database
629: * @param database the empty database object
630: * @param resourceName name of resource to load the database from
631: * @param description description of the database (for diagnostics)
632: * @return the database object, or null if the database couldn't be loaded
633: */
634: public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabaseFromResource(
635: DatabaseType database, String resourceName,
636: String description) {
637: try {
638: if (DEBUG)
639: System.out.println("Loading default "
640: + description
641: + " from "
642: + resourceName
643: + " @ "
644: + PropertyDatabase.class
645: .getResource(resourceName) + " ... ");
646: InputStream in = PropertyDatabase.class
647: .getResourceAsStream(resourceName);
648: database.read(in);
649: in.close();
650: return database;
651: } catch (IOException e) {
652: getLookupFailureCallback().logError(
653: "Error loading " + description, e);
654: } catch (PropertyDatabaseFormatException e) {
655: getLookupFailureCallback().logError(
656: "Invalid " + description, e);
657: }
658:
659: return null;
660: }
661:
662: /**
663: * Write an interprocedural property database.
664: *
665: * @param <DatabaseType> actual type of the database
666: * @param <KeyType> type of key (e.g., method or field)
667: * @param <Property> type of properties stored in the database
668: * @param database the database
669: * @param fileName name of database file
670: * @param description description of the database
671: */
672: public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> void storePropertyDatabase(
673: DatabaseType database, String fileName, String description) {
674:
675: try {
676: File dbFile = new File(getDatabaseOutputDir(), fileName);
677: if (DEBUG)
678: System.out.println("Writing " + description + " to "
679: + dbFile.getPath() + "...");
680: database.writeToFile(dbFile.getPath());
681: } catch (IOException e) {
682: getLookupFailureCallback().logError(
683: "Error writing " + description, e);
684: }
685: }
686:
687: public abstract InnerClassAccessMap getInnerClassAccessMap();
688:
689: /**
690: * Set the current analysis context for this thread.
691: *
692: * @param analysisContext the current analysis context for this thread
693: */
694: public static void setCurrentAnalysisContext(
695: AnalysisContext analysisContext) {
696: currentAnalysisContext.set(analysisContext);
697: currentXFactory.set(new XFactory());
698: }
699:
700: public static void removeCurrentAnalysisContext() {
701: AnalysisContext context = currentAnalysisContext();
702: if (context != null)
703: context.clear();
704: currentAnalysisContext.remove();
705: }
706:
707: /**
708: * Get the Subtypes2 inheritance hierarchy database.
709: */
710: public abstract Subtypes2 getSubtypes2();
711:
712: /**
713: * Get Collection of all XClass objects seen so far.
714: *
715: * @return Collection of all XClass objects seen so far
716: */
717: public Collection<XClass> getXClassCollection() {
718: return getSubtypes2().getXClassCollection();
719: }
720: }
721:
722: // vim:ts=4
|