/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006-2008 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.classfile.impl;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import edu.umd.cs.findbugs.FindBugs;
import edu.umd.cs.findbugs.SystemProperties;
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.IClassFactory;
import edu.umd.cs.findbugs.classfile.IClassPath;
import edu.umd.cs.findbugs.classfile.IClassPathBuilder;
import edu.umd.cs.findbugs.classfile.IClassPathBuilderProgress;
import edu.umd.cs.findbugs.classfile.ICodeBase;
import edu.umd.cs.findbugs.classfile.ICodeBaseEntry;
import edu.umd.cs.findbugs.classfile.ICodeBaseIterator;
import edu.umd.cs.findbugs.classfile.ICodeBaseLocator;
import edu.umd.cs.findbugs.classfile.IErrorLogger;
import edu.umd.cs.findbugs.classfile.IScannableCodeBase;
import edu.umd.cs.findbugs.classfile.InvalidClassFileFormatException;
import edu.umd.cs.findbugs.classfile.ResourceNotFoundException;
import edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo;
import edu.umd.cs.findbugs.classfile.engine.ClassParser;
import edu.umd.cs.findbugs.classfile.engine.ClassParserInterface;
import edu.umd.cs.findbugs.io.IO;
import edu.umd.cs.findbugs.util.Archive;
import edu.umd.cs.findbugs.util.ClassPathUtil;

/**
 * Implementation of IClassPathBuilder.
 * 
 * @author David Hovemeyer
 */
public class ClassPathBuilder implements IClassPathBuilder {
    private static final boolean VERBOSE = SystemProperties.getBoolean("findbugs2.builder.verbose");

    private static final boolean DEBUG = VERBOSE || SystemProperties.getBoolean("findbugs2.builder.debug");

    private static final boolean NO_PARSE_CLASS_NAMES = SystemProperties.getBoolean("findbugs2.builder.noparseclassnames");

    /**
     * Worklist item. Represents one codebase to be processed during the
     * classpath construction algorithm.
     */
    static class WorkListItem {
        private ICodeBaseLocator codeBaseLocator;

        private boolean isAppCodeBase;

        private int howDiscovered;

        @Override
        public String toString() {
            return "WorkListItem(" + codeBaseLocator + ", " + isAppCodeBase + ", " + howDiscovered + ")";
        }

        public WorkListItem(ICodeBaseLocator codeBaseLocator, boolean isApplication, int howDiscovered) {
            this.codeBaseLocator = codeBaseLocator;
            this.isAppCodeBase = isApplication;
            this.howDiscovered = howDiscovered;
        }

        public ICodeBaseLocator getCodeBaseLocator() {
            return codeBaseLocator;
        }

        public boolean isAppCodeBase() {
            return isAppCodeBase;
        }

        /**
         * @return Returns the howDiscovered.
         */
        public int getHowDiscovered() {
            return howDiscovered;
        }
    }

    /**
     * A codebase discovered during classpath building.
     */
    static class DiscoveredCodeBase {
        ICodeBase codeBase;

        LinkedList<ICodeBaseEntry> resourceList;

        public DiscoveredCodeBase(ICodeBase codeBase) {
            this.codeBase = codeBase;
            this.resourceList = new LinkedList<ICodeBaseEntry>();
        }

        public ICodeBase getCodeBase() {
            return codeBase;
        }

        public LinkedList<ICodeBaseEntry> getResourceList() {
            return resourceList;
        }

        public void addCodeBaseEntry(ICodeBaseEntry entry) {
            resourceList.add(entry);
        }

        public ICodeBaseIterator iterator() throws InterruptedException {
            if (codeBase instanceof IScannableCodeBase) {
                return ((IScannableCodeBase) codeBase).iterator();
            } else {
                return new ICodeBaseIterator() {
                    public boolean hasNext() throws InterruptedException {
                        return false;
                    }

                    public ICodeBaseEntry next() throws InterruptedException {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        }
    }

    // Fields
    private IClassFactory classFactory;

    private IErrorLogger errorLogger;

    private LinkedList<WorkListItem> projectWorkList;

    private LinkedList<DiscoveredCodeBase> discoveredCodeBaseList;

    private Map<String, DiscoveredCodeBase> discoveredCodeBaseMap;

    private LinkedList<ClassDescriptor> appClassList;

    private boolean scanNestedArchives;

    /**
     * Constructor.
     * 
     * @param classFactory
     *            the class factory
     * @param errorLogger
     *            the error logger
     */
    ClassPathBuilder(IClassFactory classFactory, IErrorLogger errorLogger) {
        this.classFactory = classFactory;
        this.errorLogger = errorLogger;
        this.projectWorkList = new LinkedList<WorkListItem>();
        this.discoveredCodeBaseList = new LinkedList<DiscoveredCodeBase>();
        this.discoveredCodeBaseMap = new HashMap<String, DiscoveredCodeBase>();
        this.appClassList = new LinkedList<ClassDescriptor>();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * edu.umd.cs.findbugs.classfile.IClassPathBuilder#addCodeBase(edu.umd.cs
     * .findbugs.classfile.ICodeBaseLocator, boolean)
     */
    public void addCodeBase(ICodeBaseLocator locator, boolean isApplication) {
        addToWorkList(projectWorkList, new WorkListItem(locator, isApplication, ICodeBase.SPECIFIED));
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * edu.umd.cs.findbugs.classfile.IClassPathBuilder#scanNestedArchives(boolean
     * )
     */
    public void scanNestedArchives(boolean scanNestedArchives) {
        this.scanNestedArchives = scanNestedArchives;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * edu.umd.cs.findbugs.classfile.IClassPathBuilder#build(edu.umd.cs.findbugs
     * .classfile.IClassPath,
     * edu.umd.cs.findbugs.classfile.IClassPathBuilderProgress)
     */
    public void build(IClassPath classPath, IClassPathBuilderProgress progress) throws CheckedAnalysisException, IOException,
            InterruptedException {
        // Discover all directly and indirectly referenced codebases
        processWorkList(classPath, projectWorkList, progress);

        // If not already located, try to locate any additional codebases
        // containing classes required for analysis.
        locateCodebasesRequiredForAnalysis(classPath, progress);

        // Add all discovered codebases to the classpath
        for (DiscoveredCodeBase discoveredCodeBase : discoveredCodeBaseList) {
            classPath.addCodeBase(discoveredCodeBase.getCodeBase());
        }

        Set<ClassDescriptor> appClassSet = new HashSet<ClassDescriptor>();

        // Build collection of all application classes.
        // Also, add resource name -> codebase entry mappings for application
        // classes.
        for (DiscoveredCodeBase discoveredCodeBase : discoveredCodeBaseList) {
            if (!discoveredCodeBase.getCodeBase().isApplicationCodeBase()) {
                continue;
            }

            codeBaseEntryLoop: for (ICodeBaseIterator i = discoveredCodeBase.iterator(); i.hasNext();) {
                ICodeBaseEntry entry = i.next();
                if (!DescriptorFactory.isClassResource(entry.getResourceName())) {
                    continue;
                }

                ClassDescriptor classDescriptor = entry.getClassDescriptor();
                if (classDescriptor == null) {
                    throw new IllegalStateException();
                }

                if (appClassSet.contains(classDescriptor)) {
                    // An earlier entry takes precedence over this class
                    continue codeBaseEntryLoop;
                }
                appClassSet.add(classDescriptor);
                appClassList.add(classDescriptor);

                classPath.mapResourceNameToCodeBaseEntry(entry.getResourceName(), entry);
            }
        }

        if (DEBUG) {
            System.out.println("Classpath:");
            dumpCodeBaseList(classPath.appCodeBaseIterator(), "Application codebases");
            dumpCodeBaseList(classPath.auxCodeBaseIterator(), "Auxiliary codebases");
        }
    }

    /**
     * Make an effort to find the codebases containing any files required for
     * analysis.
     */
    private void locateCodebasesRequiredForAnalysis(IClassPath classPath, IClassPathBuilderProgress progress)
            throws InterruptedException, IOException, ResourceNotFoundException {
        boolean foundJavaLangObject = false;
        boolean foundFindBugsAnnotations = false;
        boolean foundJSR305Annotations = false;

        for (DiscoveredCodeBase discoveredCodeBase : discoveredCodeBaseList) {
            if (!foundJavaLangObject) {
                foundJavaLangObject = probeCodeBaseForResource(discoveredCodeBase, "java/lang/Object.class");
            }
            if (!foundFindBugsAnnotations) {
                foundFindBugsAnnotations = probeCodeBaseForResource(discoveredCodeBase,
                        "edu/umd/cs/findbugs/annotations/Nonnull.class");
            }
            if (!foundJSR305Annotations) {
                foundJSR305Annotations = probeCodeBaseForResource(discoveredCodeBase, "javax/annotation/meta/TypeQualifier.class");
                if (DEBUG) {
                    System.out.println("foundJSR305Annotations: " + foundJSR305Annotations);
                }
            }
        }

        if (!foundJavaLangObject) {
            processWorkList(classPath, buildSystemCodebaseList(), progress);
        }

        // If we're running findbugs-full.jar, IT contains the contents
        // of jsr305.jar and annotations.jar. So, add it to the classpath.
        if (runningFindBugsFullJar()) {
            processWorkList(classPath, buildFindBugsFullJarCodebaseList(), progress);
            return;
        }

        // Not running findbugs-full.jar: try to find jsr305.jar and
        // annotations.jar.

        if (!foundFindBugsAnnotations) {
            processWorkList(classPath, buildFindBugsAnnotationCodebaseList(), progress);
        }
        if (!foundJSR305Annotations) {
            processWorkList(classPath, buildJSR305AnnotationsCodebaseList(), progress);
        }
    }

    private boolean runningFindBugsFullJar() {
        String findbugsFullJar = ClassPathUtil.findCodeBaseInClassPath("findbugs-full.jar",
                SystemProperties.getProperty("java.class.path"));
        return findbugsFullJar != null;
    }

    private LinkedList<WorkListItem> buildFindBugsFullJarCodebaseList() {
        String findbugsFullJar = ClassPathUtil.findCodeBaseInClassPath("findbugs-full.jar",
                SystemProperties.getProperty("java.class.path"));

        LinkedList<WorkListItem> workList = new LinkedList<WorkListItem>();
        if (findbugsFullJar != null) {
            //
            // Found findbugs-full.jar: add it to the aux classpath.
            // (This is a bit weird, since we only want to resolve a subset
            // of its classes.)
            //
            ICodeBaseLocator loc = new FilesystemCodeBaseLocator(findbugsFullJar);
            workList.addLast(new WorkListItem(loc, false, ICodeBase.IN_SYSTEM_CLASSPATH));
        }
        return workList;
    }

    /**
     * Probe a codebase to see if a given source exists in that code base.
     * 
     * @param resourceName
     *            name of a resource
     * @return true if the resource exists in the codebase, false if not
     */
    private boolean probeCodeBaseForResource(DiscoveredCodeBase discoveredCodeBase, String resourceName) {
        ICodeBaseEntry resource = discoveredCodeBase.getCodeBase().lookupResource(resourceName);
        return resource != null;
    }

    private void dumpCodeBaseList(Iterator<? extends ICodeBase> i, String desc) throws InterruptedException {
        System.out.println("  " + desc + ":");
        while (i.hasNext()) {
            ICodeBase codeBase = i.next();
            System.out.println("    " + codeBase.getCodeBaseLocator().toString());
            if (codeBase.containsSourceFiles()) {
                System.out.println("      * contains source files");
            }
        }
    }

    private LinkedList<WorkListItem> buildSystemCodebaseList() {
        // This method is based on the
        // org.apache.bcel.util.ClassPath.getClassPath()
        // method.

        LinkedList<WorkListItem> workList = new LinkedList<WorkListItem>();

        String bootClassPath = SystemProperties.getProperty("sun.boot.class.path");
        // Seed worklist with system codebases.
        // addWorkListItemsForClasspath(workList,
        // SystemProperties.getProperty("java.class.path"));
        addWorkListItemsForClasspath(workList, bootClassPath);
        String extPath = SystemProperties.getProperty("java.ext.dirs");
        if (extPath != null) {
            StringTokenizer st = new StringTokenizer(extPath, File.pathSeparator);
            while (st.hasMoreTokens()) {
                String extDir = st.nextToken();
                addWorkListItemsForExtDir(workList, extDir);
            }
        }

        return workList;
    }

    /**
     * Create a worklist that will add the FindBugs lib/annotations.jar to the
     * classpath.
     */
    private LinkedList<WorkListItem> buildFindBugsAnnotationCodebaseList() {
        return createFindBugsLibWorkList("annotations.jar");
    }

    /**
     * Create a worklist that will add the FindBugs lib/jsr305.jar to the
     * classpath.
     */
    private LinkedList<WorkListItem> buildJSR305AnnotationsCodebaseList() {
        return createFindBugsLibWorkList("jsr305.jar");
    }

    private LinkedList<WorkListItem> createFindBugsLibWorkList(String jarFileName) {
        LinkedList<WorkListItem> workList = new LinkedList<WorkListItem>();

        boolean found = false;

        String findbugsHome = FindBugs.getHome();
        if (findbugsHome != null) {
            //
            // If the findbugs.home property is set,
            // we should be able to find the jar file in
            // the lib subdirectory.
            //
            File base = new File(findbugsHome);
            File loc1 = new File(new File(base, "lib"), jarFileName);
            File loc2 = new File(base, jarFileName);
            File loc = null;
            if (loc1.exists()) {
                loc = loc1;
            } else if (loc2.exists()) {
                loc = loc2;
            }
            if (loc != null) {
                found = true;
                ICodeBaseLocator codeBaseLocator = classFactory.createFilesystemCodeBaseLocator(loc.getPath());
                workList.add(new WorkListItem(codeBaseLocator, false, ICodeBase.IN_SYSTEM_CLASSPATH));
            }
        }

        if (!found) {
            if (DEBUG) {
                System.out.println("Looking for " + jarFileName + " on classpath...");
            }
            //
            // See if the required jar file is available on the class path.
            //
            String javaClassPath = SystemProperties.getProperty("java.class.path");
            StringTokenizer t = new StringTokenizer(javaClassPath, File.pathSeparator);
            while (t.hasMoreTokens()) {
                String entry = t.nextToken();
                if (DEBUG) {
                    System.out.print("  Checking " + entry + "...");
                }

                if (matchesJarFile(entry, jarFileName)) {
                    found = true;
                } else if (matchesJarFile(entry, "findbugs.jar")) {
                    // See if the searched-for jar file can be found
                    // alongside findbugs.jar.
                    File findbugsJar = new File(entry);
                    File loc = new File(findbugsJar.getParent() + File.separator + jarFileName);
                    if (DEBUG) {
                        System.out.print(" [findbugs.jar, checking " + loc.getPath() + "] ");
                    }
                    if (loc.exists()) {
                        entry = loc.getPath();
                        found = true;
                    }
                }

                if (DEBUG) {
                    System.out.println(found ? "FOUND" : "no");
                }
                if (found) {
                    ICodeBaseLocator codeBaseLocator = classFactory.createFilesystemCodeBaseLocator(entry);
                    workList.add(new WorkListItem(codeBaseLocator, false, ICodeBase.IN_SYSTEM_CLASSPATH));
                    break;
                }

            }
        }

        return workList;
    }

    private boolean matchesJarFile(String entry, String jarFileName) {
        return entry.equals(jarFileName) || entry.endsWith(File.separator + jarFileName) || entry.endsWith("/" + jarFileName);
    }

    /**
     * Add worklist items from given system classpath.
     * 
     * @param workList
     *            the worklist
     * @param path
     *            a system classpath
     */
    private void addWorkListItemsForClasspath(LinkedList<WorkListItem> workList, String path) {
        if (path == null) {
            return;
        }

        StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
        while (st.hasMoreTokens()) {
            String entry = st.nextToken();
            if (DEBUG) {
                System.out.println("System classpath entry: " + entry);
            }
            addToWorkList(workList, new WorkListItem(classFactory.createFilesystemCodeBaseLocator(entry), false,
                    ICodeBase.IN_SYSTEM_CLASSPATH));
        }
    }

    /**
     * Add worklist items from given extensions directory.
     * 
     * @param workList
     *            the worklist
     * @param extDir
     *            an extensions directory
     */
    private void addWorkListItemsForExtDir(LinkedList<WorkListItem> workList, String extDir) {
        File dir = new File(extDir);
        File[] fileList = dir.listFiles(new FileFilter() {
            /*
             * (non-Javadoc)
             * 
             * @see java.io.FileFilter#accept(java.io.File)
             */
            public boolean accept(File pathname) {
                String path = pathname.getPath();
                boolean isArchive = Archive.isArchiveFileName(path);
                return isArchive;
            }
        });
        if (fileList == null) {
            return;
        }

        for (File archive : fileList) {
            addToWorkList(workList, new WorkListItem(classFactory.createFilesystemCodeBaseLocator(archive.getPath()), false,
                    ICodeBase.IN_SYSTEM_CLASSPATH));
        }
    }

    /**
     * Process classpath worklist items. We will attempt to find all nested
     * archives and Class-Path entries specified in Jar manifests. This should
     * give us as good an idea as possible of all of the classes available (and
     * which are part of the application).
     * 
     * @param workList
     *            the worklist to process
     * @param progress
     *            IClassPathBuilderProgress callback
     * @throws InterruptedException
     * @throws IOException
     * @throws ResourceNotFoundException
     */
    private void processWorkList(IClassPath classPath, LinkedList<WorkListItem> workList, IClassPathBuilderProgress progress)
            throws InterruptedException, IOException, ResourceNotFoundException {
        // Build the classpath, scanning codebases for nested archives
        // and referenced codebases.
        while (!workList.isEmpty()) {
            WorkListItem item = workList.removeFirst();
            if (item.getHowDiscovered() == ICodeBase.SPECIFIED) {
                progress.startArchive(item.toString());
            }
            if (DEBUG) {
                System.out.println("Working: " + item.getCodeBaseLocator());
            }

            DiscoveredCodeBase discoveredCodeBase;

            // See if we have encountered this codebase before
            discoveredCodeBase = discoveredCodeBaseMap.get(item.getCodeBaseLocator().toString());
            if (discoveredCodeBase != null) {
                // If the codebase is not an app codebase and
                // the worklist item says that it is an app codebase,
                // change it. Otherwise, we have nothing to do.
                if (!discoveredCodeBase.getCodeBase().isApplicationCodeBase() && item.isAppCodeBase()) {
                    discoveredCodeBase.getCodeBase().setApplicationCodeBase(true);
                }

                continue;
            }

            // Detect .java files, which are probably human error
            if (item.getCodeBaseLocator() instanceof FilesystemCodeBaseLocator) {
                FilesystemCodeBaseLocator l = (FilesystemCodeBaseLocator) item.getCodeBaseLocator();
                if (l.getPathName().endsWith(".java")) {
                    System.err.println("Ignoring .java file \"" + l.getPathName() + "\" specified in classpath or auxclasspath");
                    continue;
                }
            }

            // If we are working on an application codebase,
            // then failing to open/scan it is a fatal error.
            // We issue warnings about problems with aux codebases,
            // but continue anyway.

            try {
                // Open the codebase and add it to the classpath
                discoveredCodeBase = new DiscoveredCodeBase(item.getCodeBaseLocator().openCodeBase());
                discoveredCodeBase.getCodeBase().setApplicationCodeBase(item.isAppCodeBase());
                discoveredCodeBase.getCodeBase().setHowDiscovered(item.getHowDiscovered());

                // Note that this codebase has been visited
                discoveredCodeBaseMap.put(item.getCodeBaseLocator().toString(), discoveredCodeBase);
                discoveredCodeBaseList.addLast(discoveredCodeBase);

                // If it is a scannable codebase, check it for nested archives.
                // In addition, if it is an application codebase then
                // make a list of application classes.
                if (discoveredCodeBase.getCodeBase() instanceof IScannableCodeBase
                        && discoveredCodeBase.codeBase.isApplicationCodeBase()) {
                    scanCodebase(classPath, workList, discoveredCodeBase);
                }

                // Check for a Jar manifest for additional aux classpath
                // entries.
                scanJarManifestForClassPathEntries(workList, discoveredCodeBase.getCodeBase());
            } catch (IOException e) {
                if (item.isAppCodeBase() || item.getHowDiscovered() == ICodeBase.SPECIFIED) {
                    errorLogger.logError("Cannot open codebase " + item.getCodeBaseLocator(), e);
                }
            } catch (ResourceNotFoundException e) {
                if (item.getHowDiscovered() == ICodeBase.SPECIFIED) {
                    errorLogger.logError("Cannot open codebase " + item.getCodeBaseLocator(), e);
                }
            }

            if (item.getHowDiscovered() == ICodeBase.SPECIFIED) {
                progress.finishArchive();
            }
        }
    }

    /**
     * Scan given codebase in order to
     * <ul>
     * <li>check the codebase for nested archives (adding any found to the
     * worklist)
     * <li>build a list of class resources found in the codebase
     * </ul>
     * 
     * @param workList
     *            the worklist
     * @param discoveredCodeBase
     *            the codebase to scan
     * @throws InterruptedException
     */
    private void scanCodebase(IClassPath classPath, LinkedList<WorkListItem> workList, DiscoveredCodeBase discoveredCodeBase)
            throws InterruptedException {
        if (DEBUG) {
            System.out.println("Scanning " + discoveredCodeBase.getCodeBase().getCodeBaseLocator());
        }

        IScannableCodeBase codeBase = (IScannableCodeBase) discoveredCodeBase.getCodeBase();

        ICodeBaseIterator i = codeBase.iterator();
        while (i.hasNext()) {
            ICodeBaseEntry entry = i.next();
            if (VERBOSE) {
                System.out.println("Entry: " + entry.getResourceName());
            }

            if (!NO_PARSE_CLASS_NAMES && codeBase.isApplicationCodeBase()
                    && DescriptorFactory.isClassResource(entry.getResourceName()) && !(entry instanceof SingleFileCodeBaseEntry)) {
                parseClassName(entry);
            }

            // Note the resource exists in this codebase
            discoveredCodeBase.addCodeBaseEntry(entry);

            // If resource is a nested archive, add it to the worklist
            if (scanNestedArchives && codeBase.isApplicationCodeBase() && Archive.isArchiveFileName(entry.getResourceName())) {
                if (VERBOSE) {
                    System.out.println("Entry is an archive!");
                }
                ICodeBaseLocator nestedArchiveLocator = classFactory.createNestedArchiveCodeBaseLocator(codeBase,
                        entry.getResourceName());
                addToWorkList(workList,
                        new WorkListItem(nestedArchiveLocator, codeBase.isApplicationCodeBase(), ICodeBase.NESTED));
            }
        }
    }

    /**
     * Attempt to parse data of given resource in order to divine the real name
     * of the class contained in the resource.
     * 
     * @param entry
     *            the resource
     */
    private void parseClassName(ICodeBaseEntry entry) {
        DataInputStream in = null;
        try {
            InputStream resourceIn = entry.openResource();
            if (resourceIn == null) {
                throw new NullPointerException("Got null resource");
            }
            in = new DataInputStream(resourceIn);
            ClassParserInterface parser = new ClassParser(in, null, entry);
            ClassNameAndSuperclassInfo.Builder builder = new ClassNameAndSuperclassInfo.Builder();
            parser.parse(builder);

            String trueResourceName = builder.build().getClassDescriptor().toResourceName();
            if (!trueResourceName.equals(entry.getResourceName())) {
                entry.overrideResourceName(trueResourceName);
            }
        } catch (IOException e) {
            errorLogger.logError("Invalid class resource " + entry.getResourceName() + " in " + entry, e);
        } catch (InvalidClassFileFormatException e) {
            errorLogger.logError("Invalid class resource " + entry.getResourceName() + " in " + entry, e);
        } finally {
            IO.close(in);
        }
    }

    /**
     * Check a codebase for a Jar manifest to examine for Class-Path entries.
     * 
     * @param workList
     *            the worklist
     * @param codeBase
     *            the codebase for examine for a Jar manifest
     * @throws IOException
     */
    private void scanJarManifestForClassPathEntries(LinkedList<WorkListItem> workList, ICodeBase codeBase) throws IOException {
        // See if this codebase has a jar manifest
        ICodeBaseEntry manifestEntry = codeBase.lookupResource("META-INF/MANIFEST.MF");
        if (manifestEntry == null) {
            // Do nothing - no Jar manifest found
            return;
        }

        // Try to read the manifest
        InputStream in = null;
        try {
            in = manifestEntry.openResource();
            Manifest manifest = new Manifest(in);

            Attributes mainAttrs = manifest.getMainAttributes();
            String classPath = mainAttrs.getValue("Class-Path");
            if (classPath != null) {
                String[] pathList = classPath.split("\\s+");

                for (String path : pathList) {
                    // Create a codebase locator for the classpath entry
                    // relative to the codebase in which we discovered the Jar
                    // manifest
                    ICodeBaseLocator relativeCodeBaseLocator = codeBase.getCodeBaseLocator().createRelativeCodeBaseLocator(path);

                    // Codebases found in Class-Path entries are always
                    // added to the aux classpath, not the application.
                    addToWorkList(workList, new WorkListItem(relativeCodeBaseLocator, false, ICodeBase.IN_JAR_MANIFEST));
                }
            }
        } finally {
            if (in != null) {
                IO.close(in);
            }
        }
    }

    /**
     * Add a worklist item to the worklist. This method maintains the invariant
     * that all of the worklist items representing application codebases appear
     * <em>before</em> all of the worklist items representing auxiliary
     * codebases.
     * 
     * @param projectWorkList
     *            the worklist
     * @param itemToAdd
     *            the worklist item to add
     */
    private void addToWorkList(LinkedList<WorkListItem> workList, WorkListItem itemToAdd) {
        if (DEBUG) {
            new RuntimeException("Adding work list item " + itemToAdd).printStackTrace(System.out);
        }
        if (!itemToAdd.isAppCodeBase()) {
            // Auxiliary codebases are always added at the end
            workList.addLast(itemToAdd);
            return;
        }

        // Adding an application codebase: position a ListIterator
        // just before first auxiliary codebase (or at the end of the list
        // if there are no auxiliary codebases)
        ListIterator<WorkListItem> i = workList.listIterator();
        while (i.hasNext()) {
            WorkListItem listItem = i.next();
            if (!listItem.isAppCodeBase()) {
                i.previous();
                break;
            }
        }

        // Add the codebase to the worklist
        i.add(itemToAdd);
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.umd.cs.findbugs.classfile.IClassPathBuilder#getAppClassList()
     */
    public List<ClassDescriptor> getAppClassList() {
        return appClassList;
    }
}
