/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003-2006 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;
import java.util.Collection;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import net.jcip.annotations.NotThreadSafe;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;

import edu.umd.cs.findbugs.AbstractBugReporter;
import edu.umd.cs.findbugs.AnalysisLocal;
import edu.umd.cs.findbugs.BugInstance.NoSuchBugPattern;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SuppressionMatcher;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisFeatures.AnalysisFeature;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabase;
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabaseFormatException;
import edu.umd.cs.findbugs.ba.jsr305.DirectlyRelevantTypeQualifiersDatabase;
import edu.umd.cs.findbugs.ba.npe.ParameterNullnessPropertyDatabase;
import edu.umd.cs.findbugs.ba.npe.ReturnValueNullnessPropertyDatabase;
import edu.umd.cs.findbugs.ba.type.FieldStoreTypeDatabase;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.FieldOrMethodDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.analysis.ClassData;
import edu.umd.cs.findbugs.classfile.analysis.MethodInfo;
import edu.umd.cs.findbugs.detect.UnreadFields;
import edu.umd.cs.findbugs.detect.UnreadFieldsData;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;

/**
 * A context for analysis of a complete project. This serves as the repository
 * for whole-program information and data structures.
 *
 * <p>
 * <b>NOTE</b>: this class is slated to become obsolete. New code should use the
 * IAnalysisCache object returned by Global.getAnalysisCache() to access all
 * analysis information (global databases, class and method analyses, etc.)
 * </p>
 *
 * @author David Hovemeyer
 * @see edu.umd.cs.findbugs.classfile.IAnalysisCache
 * @see edu.umd.cs.findbugs.classfile.Global
 */
@NotThreadSafe
public abstract class AnalysisContext {
    public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.analysiscontext.debug");

    public static final boolean IGNORE_BUILTIN_MODELS = SystemProperties.getBoolean("findbugs.ignoreBuiltinModels");

    public static final String DEFAULT_NONNULL_PARAM_DATABASE_FILENAME = "nonnullParam.db";

    public static final String DEFAULT_CHECK_FOR_NULL_PARAM_DATABASE_FILENAME = "checkForNullParam.db";

    public static final String DEFAULT_NULL_RETURN_VALUE_ANNOTATION_DATABASE = "nullReturn.db";

    public static final String UNCONDITIONAL_DEREF_DB_FILENAME = "unconditionalDeref.db";

    public static final String NONNULL_RETURN_DB_FILENAME = "nonnullReturn.db";

    public static final String UNCONDITIONAL_DEREF_DB_RESOURCE = "jdkBaseUnconditionalDeref.db";

    public static final String NONNULL_RETURN_DB_RESOURCE = "jdkBaseNonnullReturn.db";

    public static final String DEFAULT_NULL_RETURN_VALUE_DB_FILENAME = "mayReturnNull.db";

    private static InheritableThreadLocal<AnalysisContext> currentAnalysisContext = new InheritableThreadLocal<AnalysisContext>() {
        @Override
        public AnalysisContext initialValue() {
            // throw new
            // IllegalStateException("currentAnalysisContext should be set by AnalysisContext.setCurrentAnalysisContext");
            return null;
        }
    };

    private static AnalysisLocal<XFactory> currentXFactory = new AnalysisLocal<XFactory>() {
        @Override
        public XFactory initialValue() {
            throw new IllegalStateException("currentXFactory should be set by AnalysisContext.setCurrentAnalysisContext");
        }
    };

    public abstract INullnessAnnotationDatabase getNullnessAnnotationDatabase();

    public abstract CheckReturnAnnotationDatabase getCheckReturnAnnotationDatabase();

    public abstract AnnotationRetentionDatabase getAnnotationRetentionDatabase();

    public abstract JCIPAnnotationDatabase getJCIPAnnotationDatabase();

    /**
     * save the original SyntheticRepository so we may obtain JavaClass objects
     * which we can reuse. (A URLClassPathRepository gets closed after
     * analysis.)
     */
    private static final org.apache.bcel.util.Repository originalRepository = Repository.getRepository(); // BCEL
                                                                                                          // SyntheticRepository

    /**
     * Default maximum number of ClassContext objects to cache. FIXME: need to
     * evaluate this parameter. Need to keep stats about accesses.
     */
    private static final int DEFAULT_CACHE_SIZE = 3;

    // Instance fields
    private BitSet boolPropertySet;

    private String databaseInputDir;

    private String databaseOutputDir;

    protected AnalysisContext() {
        this.boolPropertySet = new BitSet();
    }

    private void clear() {
        boolPropertySet = null;
        databaseInputDir = null;
        databaseOutputDir = null;
    }


    /**
     * Instantiate the CheckReturnAnnotationDatabase. Do this after the
     * repository has been set up.
     */
    public abstract void initDatabases();

    /**
     * After a pass has been completed, allow the analysis context to update
     * information.
     *
     * @param pass
     *            -- the first pass is pass 0
     */
    public abstract void updateDatabases(int pass);

    /**
     * Get the AnalysisContext associated with this thread
     */
    static public AnalysisContext currentAnalysisContext() {
        return currentAnalysisContext.get();
    }

    static public XFactory currentXFactory() {
        return currentXFactory.get();
    }

    ClassSummary classSummary;

    ClassDescriptor classBeingAnalyzed;

    public ClassDescriptor getClassBeingAnalyzed() {
        return classBeingAnalyzed;
    }

    public void setClassBeingAnalyzed(@Nonnull ClassDescriptor classBeingAnalyzed) {
        this.classBeingAnalyzed = classBeingAnalyzed;
    }
    public void clearClassBeingAnalyzed() {
        this.classBeingAnalyzed = null;
    }
    public ClassSummary getClassSummary() {
        if (classSummary == null)
            throw new IllegalStateException("ClassSummary not set");
        return classSummary;
    }

    public void setClassSummary(@Nonnull ClassSummary classSummary) {
        if (this.classSummary != null)
            throw new IllegalStateException("ClassSummary already set");
        this.classSummary = classSummary;
    }

    final EqualsKindSummary equalsKindSummary = new EqualsKindSummary();

    public EqualsKindSummary getEqualsKindSummary() {
        return equalsKindSummary;
    }

    FieldSummary fieldSummary;

    public FieldSummary getFieldSummary() {
        if (fieldSummary == null) {
            AnalysisContext.logError("Field Summary not set", new IllegalStateException());
            fieldSummary = new FieldSummary();
        }
        return fieldSummary;
    }

    public void setFieldSummary(@Nonnull FieldSummary fieldSummary) {
        if (this.fieldSummary != null) {
            AnalysisContext.logError("Field Summary already set", new IllegalStateException());
        }
        this.fieldSummary = fieldSummary;
    }

    final UnreadFieldsData unreadFieldsData = new UnreadFieldsData();
    UnreadFields unreadFields;

    public @Nonnull UnreadFieldsData getUnreadFieldsData() {
        return unreadFieldsData;
    }

    public @Nonnull UnreadFields getUnreadFields() {
        if (unreadFieldsAvailable())
            throw new IllegalStateException();
        return unreadFields;
    }

    public boolean unreadFieldsAvailable() {
        return unreadFields != null;
    }

    public void setUnreadFields(@Nonnull UnreadFields unreadFields) {
        if (this.unreadFields != null)
            throw new IllegalStateException("UnreadFields detector already set");
        this.unreadFields = unreadFields;
    }

    public abstract DirectlyRelevantTypeQualifiersDatabase getDirectlyRelevantTypeQualifiersDatabase();

    private static boolean skipReportingMissingClass(@CheckForNull @DottedClassName String missing) {
        return missing == null || missing.length() == 0 || missing.charAt(0) == '[' || missing.endsWith("package-info");
    }

    private static @CheckForNull
    RepositoryLookupFailureCallback getCurrentLookupFailureCallback() {
        AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
        if (currentAnalysisContext2 == null)
            return null;
        if (currentAnalysisContext2.missingClassWarningsSuppressed)
            return null;
        return currentAnalysisContext2.getLookupFailureCallback();
    }

    /**
     * file a ClassNotFoundException with the lookupFailureCallback
     *
     * @see #getLookupFailureCallback()
     */
    static public void reportMissingClass(ClassNotFoundException e) {
        if (e == null)
            throw new NullPointerException("argument is null");
        String missing = AbstractBugReporter.getMissingClassName(e);
        if (skipReportingMissingClass(missing))
            return;
        if (!analyzingApplicationClass())
            return;

        RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
        if (lookupFailureCallback != null)
            lookupFailureCallback.reportMissingClass(e);
    }

    static public void reportMissingClass(edu.umd.cs.findbugs.ba.MissingClassException e) {
        if (e == null)
            throw new NullPointerException("argument is null");
        reportMissingClass(e.getClassDescriptor());
    }

    static public boolean analyzingApplicationClass() {
        AnalysisContext context = AnalysisContext.currentAnalysisContext();
        if (context == null)
            return false;
        ClassDescriptor clazz = context.getClassBeingAnalyzed();
        if (clazz == null)
            return false;
        return context.isApplicationClass(clazz);
    }
    static public void reportMissingClass(edu.umd.cs.findbugs.classfile.MissingClassException e) {
        if (e == null)
            throw new NullPointerException("argument is null");
        reportMissingClass(e.getClassDescriptor());
    }


    static public void reportMissingClass(ClassDescriptor c) {
        if (c == null)
            throw new NullPointerException("argument is null");
        if (!analyzingApplicationClass())
            return;
        String missing = c.getDottedClassName();
        if (missing.length() == 1)
            System.out.println(c);
        if (skipReportingMissingClass(missing))
            return;
        RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
        if (lookupFailureCallback != null)
            lookupFailureCallback.reportMissingClass(c);
    }

    /**
     * Report an error
     */
    static public void logError(String msg, Exception e) {
        AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
        if (currentAnalysisContext2 == null) {
            if (e instanceof NoSuchBugPattern)
                return;
            if (false && SystemProperties.ASSERTIONS_ENABLED) {
                AssertionError e2 = new AssertionError("Exception logged with no analysis context");
                e2.initCause(e);
                throw e2;
            }
            e.printStackTrace(System.err);
            return;
        }
        if (e instanceof MissingClassException) {
            reportMissingClass(((MissingClassException) e).getClassNotFoundException());
            return;
        }
        if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
            reportMissingClass(((edu.umd.cs.findbugs.classfile.MissingClassException) e).toClassNotFoundException());
            return;
        }

        RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2.getLookupFailureCallback();
        if (lookupFailureCallback != null)
            lookupFailureCallback.logError(msg, e);
    }

    /**
     * Report an error
     */
    static public void logError(String msg) {
        AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
        if (currentAnalysisContext2 == null)
            return;
        RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2.getLookupFailureCallback();
        if (lookupFailureCallback != null)
            lookupFailureCallback.logError(msg);
    }

    boolean missingClassWarningsSuppressed = false;

    protected Project project;

    public boolean setMissingClassWarningsSuppressed(boolean value) {
        boolean oldValue = missingClassWarningsSuppressed;
        missingClassWarningsSuppressed = value;
        return oldValue;
    }

    /**
     * Get the lookup failure callback.
     */
    public abstract RepositoryLookupFailureCallback getLookupFailureCallback();

    /**
     * Set the source path.
     */
    public final void setProject(Project project) {
        this.project = project;
    }

    /**
     * Get the SourceFinder, for finding source files.
     */
    public abstract SourceFinder getSourceFinder();

    // /**
    // * Get the Subtypes database.
    // *
    // * @return the Subtypes database
    // */
    // @Deprecated // use Subtypes2 instead
    // public abstract Subtypes getSubtypes();

    /**
     * Clear the BCEL Repository in preparation for analysis.
     */
    public abstract void clearRepository();

    /**
     * Clear the ClassContext cache. This should be done between analysis
     * passes.
     */
    public abstract void clearClassContextCache();

    /**
     * Add an entry to the Repository's classpath.
     *
     * @param url
     *            the classpath entry URL
     * @throws IOException
     */
    public abstract void addClasspathEntry(String url) throws IOException;

    // /**
    // * Add an application class to the repository.
    // *
    // * @param appClass the application class
    // */
    // public abstract void addApplicationClassToRepository(JavaClass appClass);

    /**
     * Return whether or not the given class is an application class.
     *
     * @param cls
     *            the class to lookup
     * @return true if the class is an application class, false if not an
     *         application class or if the class cannot be located
     */
    public boolean isApplicationClass(JavaClass cls) {
        // return getSubtypes().isApplicationClass(cls);
        return getSubtypes2().isApplicationClass(DescriptorFactory.createClassDescriptor(cls));
    }

    /**
     * Return whether or not the given class is an application class.
     *
     * @param className
     *            name of a class
     * @return true if the class is an application class, false if not an
     *         application class or if the class cannot be located
     */
    public boolean isApplicationClass(@DottedClassName String className) {
        // try {
        // JavaClass javaClass = lookupClass(className);
        // return isApplicationClass(javaClass);
        // } catch (ClassNotFoundException e) {
        // AnalysisContext.reportMissingClass(e);
        // return false;
        // }
        ClassDescriptor classDesc = DescriptorFactory.createClassDescriptorFromDottedClassName(className);
        return getSubtypes2().isApplicationClass(classDesc);
    }

    public boolean isApplicationClass(ClassDescriptor desc) {
        return getSubtypes2().isApplicationClass(desc);
    }

    public int getClassSize(ClassDescriptor desc) {
        IAnalysisCache analysisCache = Global.getAnalysisCache();

        try {
            ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, desc);
            ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc);
            return classData.getData().length;

        } catch (RuntimeException e) {
            AnalysisContext.logError("Error getting class data for " + desc, e);
            return 10000;
        } catch (CheckedAnalysisException e) {
            AnalysisContext.logError("Could not get class context for "  + desc, e);
            return 10000;
        }

    }

    public boolean isTooBig(ClassDescriptor desc) {
        IAnalysisCache analysisCache = Global.getAnalysisCache();

        try {
            ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, desc);
            ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc);
            if (classData.getData().length > 1000000)
                return true;
            try {
            JavaClass javaClass = classContext.getJavaClass();
            if (javaClass.getMethods().length > 1000)
                return true;
            } catch (RuntimeException e) {
                AnalysisContext.logError("Error parsing class " + desc
                        + " from " + classData.getCodeBaseEntry().getCodeBase(), e);
                return true;
            }

        } catch (RuntimeException e) {
            AnalysisContext.logError("Error getting class data for " + desc, e);
            return true;
        } catch (CheckedAnalysisException e) {
            AnalysisContext.logError("Could not get class context for "  + desc, e);
            return true;
        }
        return false;

    }

    /**
     * Lookup a class.
     * <em>Use this method instead of Repository.lookupClass().</em>
     *
     * @param className
     *            the name of the class
     * @return the JavaClass representing the class
     * @throws ClassNotFoundException
     *             (but not really)
     */
    public abstract JavaClass lookupClass(@Nonnull @DottedClassName String className) throws ClassNotFoundException;

    /**
     * Lookup a class.
     * <em>Use this method instead of Repository.lookupClass().</em>
     *
     * @param classDescriptor
     *            descriptor specifying the class to look up
     * @return the class
     * @throws ClassNotFoundException
     *             if the class can't be found
     */
    public JavaClass lookupClass(@Nonnull ClassDescriptor classDescriptor) throws ClassNotFoundException {
        return lookupClass(classDescriptor.toDottedClassName());
    }

    /**
     * This is equivalent to Repository.lookupClass() or this.lookupClass(),
     * except it uses the original Repository instead of the current one.
     *
     * This can be important because URLClassPathRepository objects are closed
     * after an analysis, so JavaClass objects obtained from them are no good on
     * subsequent runs.
     *
     * @param className
     *            the name of the class
     * @return the JavaClass representing the class
     * @throws ClassNotFoundException
     */
    public static JavaClass lookupSystemClass(@Nonnull String className) throws ClassNotFoundException {
        // TODO: eventually we should move to our own thread-safe repository
        // implementation
        if (className == null)
            throw new IllegalArgumentException("className is null");
        if (originalRepository == null)
            throw new IllegalStateException("originalRepository is null");

        JavaClass clazz = originalRepository.findClass(className);
        return (clazz == null ? originalRepository.loadClass(className) : clazz);
    }

    /**
     * Lookup a class's source file
     *
     * @param dottedClassName
     *            the name of the class
     * @return the source file for the class, or
     *         {@link SourceLineAnnotation#UNKNOWN_SOURCE_FILE} if unable to
     *         determine
     */
    public final String lookupSourceFile(@Nonnull @DottedClassName String dottedClassName) {
        if (dottedClassName == null)
            throw new IllegalArgumentException("className is null");
        try {
            XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class,
                    DescriptorFactory.createClassDescriptorFromDottedClassName(dottedClassName));
            String name = xClass.getSource();
            if (name == null) {
                return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
            }
            return name;
        } catch (CheckedAnalysisException e) {
            return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
        }
    }

    /**
     * Get the ClassContext for a class.
     *
     * @param javaClass
     *            the class
     * @return the ClassContext for that class
     */
    public abstract ClassContext getClassContext(JavaClass javaClass);

    /**
     * Get stats about hit rate for ClassContext cache.
     *
     * @return stats about hit rate for ClassContext cache
     */
    public abstract String getClassContextStats();

    /**
     * If possible, load interprocedural property databases.
     */
    public final void loadInterproceduralDatabases() {
        loadPropertyDatabase(getFieldStoreTypeDatabase(), FieldStoreTypeDatabase.DEFAULT_FILENAME, "field store type database");
        loadPropertyDatabase(getUnconditionalDerefParamDatabase(), UNCONDITIONAL_DEREF_DB_FILENAME,
                "unconditional param deref database");
        loadPropertyDatabase(getReturnValueNullnessPropertyDatabase(), NONNULL_RETURN_DB_FILENAME, "nonnull return db database");
    }

    /**
     * If possible, load default (built-in) interprocedural property databases.
     * These are the databases for things like Java core APIs that unconditional
     * dereference parameters.
     */
    public final void loadDefaultInterproceduralDatabases() {
        if (IGNORE_BUILTIN_MODELS)
            return;
        loadPropertyDatabaseFromResource(getUnconditionalDerefParamDatabase(), UNCONDITIONAL_DEREF_DB_RESOURCE,
                "unconditional param deref database");
        loadPropertyDatabaseFromResource(getReturnValueNullnessPropertyDatabase(), NONNULL_RETURN_DB_RESOURCE,
                "nonnull return db database");
    }

    /**
     * Set a boolean property.
     *
     * @param prop
     *            the property to set
     * @param value
     *            the value of the property
     */
    public final void setBoolProperty(@AnalysisFeature int prop, boolean value) {
        boolPropertySet.set(prop, value);
    }

    /**
     * Get a boolean property.
     *
     * @param prop
     *            the property
     * @return value of the property; defaults to false if the property has not
     *         had a value assigned explicitly
     */
    public final boolean getBoolProperty(@AnalysisFeature int prop) {
        return boolPropertySet.get(prop);
    }

    /**
     * Get the SourceInfoMap.
     */
    public abstract SourceInfoMap getSourceInfoMap();

    /**
     * Set the interprocedural database input directory.
     *
     * @param databaseInputDir
     *            the interprocedural database input directory
     */
    public final void setDatabaseInputDir(String databaseInputDir) {
        if (DEBUG)
            System.out.println("Setting database input directory: " + databaseInputDir);
        this.databaseInputDir = databaseInputDir;
    }

    /**
     * Get the interprocedural database input directory.
     *
     * @return the interprocedural database input directory
     */
    public final String getDatabaseInputDir() {
        return databaseInputDir;
    }

    /**
     * Set the interprocedural database output directory.
     *
     * @param databaseOutputDir
     *            the interprocedural database output directory
     */
    public final void setDatabaseOutputDir(String databaseOutputDir) {
        if (DEBUG)
            System.out.println("Setting database output directory: " + databaseOutputDir);
        this.databaseOutputDir = databaseOutputDir;
    }

    /**
     * Get the interprocedural database output directory.
     *
     * @return the interprocedural database output directory
     */
    public final String getDatabaseOutputDir() {
        return databaseOutputDir;
    }

    /**
     * Get the property database recording the types of values stored into
     * fields.
     *
     * @return the database, or null if there is no database available
     */
    public abstract FieldStoreTypeDatabase getFieldStoreTypeDatabase();

    /**
     * Get the property database recording which methods unconditionally
     * dereference parameters.
     *
     * @return the database, or null if there is no database available
     */
    public abstract ParameterNullnessPropertyDatabase getUnconditionalDerefParamDatabase();

    /**
     * Get the property database recording which methods always return nonnull
     * values
     *
     * @return the database, or null if there is no database available
     */
    public abstract ReturnValueNullnessPropertyDatabase getReturnValueNullnessPropertyDatabase();

    /**
     * Load an interprocedural property database.
     *
     * @param <DatabaseType>
     *            actual type of the database
     * @param <KeyType>
     *            type of key (e.g., method or field)
     * @param <Property>
     *            type of properties stored in the database
     * @param database
     *            the empty database object
     * @param fileName
     *            file to load database from
     * @param description
     *            description of the database (for diagnostics)
     * @return the database object, or null if the database couldn't be loaded
     */
    public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabase(
            DatabaseType database, String fileName, String description) {
        try {
            File dbFile = new File(getDatabaseInputDir(), fileName);
            if (DEBUG)
                System.out.println("Loading " + description + " from " + dbFile.getPath() + "...");

            database.readFromFile(dbFile.getPath());
            return database;
        } catch (IOException e) {
            getLookupFailureCallback().logError("Error loading " + description, e);
        } catch (PropertyDatabaseFormatException e) {
            getLookupFailureCallback().logError("Invalid " + description, e);
        }

        return null;
    }

    /**
     * Load an interprocedural property database.
     *
     * @param <DatabaseType>
     *            actual type of the database
     * @param <KeyType>
     *            type of key (e.g., method or field)
     * @param <Property>
     *            type of properties stored in the database
     * @param database
     *            the empty database object
     * @param resourceName
     *            name of resource to load the database from
     * @param description
     *            description of the database (for diagnostics)
     * @return the database object, or null if the database couldn't be loaded
     */
    public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabaseFromResource(
            DatabaseType database, String resourceName, String description) {
        try {
            if (DEBUG)
                System.out.println("Loading default " + description + " from " + resourceName + " @ "
                        + database.getClass().getResource(resourceName) + " ... ");
            InputStream in = database.getClass().getResourceAsStream(resourceName);
            if (in == null) {
                AnalysisContext.logError("Unable to load " + description + " from resource " + resourceName);
            } else {
                database.read(in);
                in.close();
            }
            return database;
        } catch (IOException e) {
            getLookupFailureCallback().logError("Error loading " + description, e);
        } catch (PropertyDatabaseFormatException e) {
            getLookupFailureCallback().logError("Invalid " + description, e);
        }

        return null;
    }

    /**
     * Write an interprocedural property database.
     *
     * @param <DatabaseType>
     *            actual type of the database
     * @param <KeyType>
     *            type of key (e.g., method or field)
     * @param <Property>
     *            type of properties stored in the database
     * @param database
     *            the database
     * @param fileName
     *            name of database file
     * @param description
     *            description of the database
     */
    public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> void storePropertyDatabase(
            DatabaseType database, String fileName, String description) {

        try {
            File dbFile = new File(getDatabaseOutputDir(), fileName);
            if (DEBUG)
                System.out.println("Writing " + description + " to " + dbFile.getPath() + "...");
            database.writeToFile(dbFile.getPath());
        } catch (IOException e) {
            getLookupFailureCallback().logError("Error writing " + description, e);
        }
    }

    public abstract InnerClassAccessMap getInnerClassAccessMap();

    /**
     * Set the current analysis context for this thread.
     *
     * @param analysisContext
     *            the current analysis context for this thread
     */
    public static void setCurrentAnalysisContext(AnalysisContext analysisContext) {
        currentAnalysisContext.set(analysisContext);
        if (Global.getAnalysisCache() != null)
            currentXFactory.set(new XFactory());
    }

    public static void removeCurrentAnalysisContext() {
        AnalysisContext context = currentAnalysisContext();
        if (context != null)
            context.clear();
        currentAnalysisContext.remove();
    }

    /**
     * Get the Subtypes2 inheritance hierarchy database.
     */
    public abstract Subtypes2 getSubtypes2();

    /**
     * Get Collection of all XClass objects seen so far.
     *
     * @return Collection of all XClass objects seen so far
     */
    public Collection<XClass> getXClassCollection() {
        return getSubtypes2().getXClassCollection();
    }

    public abstract @CheckForNull
    XMethod getBridgeTo(MethodInfo m);

    public abstract @CheckForNull
    XMethod getBridgeFrom(MethodInfo m);

    public abstract void setBridgeMethod(MethodInfo from, MethodInfo to);

    private final SuppressionMatcher suppressionMatcher = new SuppressionMatcher();

    public SuppressionMatcher getSuppressionMatcher() {
        return suppressionMatcher;
    }
}

// vim:ts=4
