/* * 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()) { throw e; } else if (item.getHowDiscovered() == ICodeBase.SPECIFIED) { errorLogger.logError("Cannot open codebase " + item.getCodeBaseLocator(), e); } } catch (ResourceNotFoundException e) { if (item.isAppCodeBase()) { throw e; } else 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; } }