/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.util.depend; import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.Vector; import java.util.zip.ZipFile; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.util.VectorSet; /** * An abstract implementation of the analyzer interface providing support * for the bulk of interface methods. * */ public abstract class AbstractAnalyzer implements DependencyAnalyzer { /** Maximum number of loops for looking for indirect dependencies. */ public static final int MAX_LOOPS = 1000; /** The source path for the source files */ private Path sourcePath = new Path(null); /** The classpath containg dirs and jars of class files */ private Path classPath = new Path(null); /** The list of root classes */ private final Vector<String> rootClasses = new VectorSet<>(); /** true if dependencies have been determined */ private boolean determined = false; /** the list of File objects that the root classes depend upon */ private Vector<File> fileDependencies; /** the list of java classes the root classes depend upon */ private Vector<String> classDependencies; /** true if indirect dependencies should be gathered */ private boolean closure = true; /** Setup the analyzer */ protected AbstractAnalyzer() { reset(); } /** * Set the closure flag. If this flag is true the analyzer will traverse * all class relationships until it has collected the entire set of * direct and indirect dependencies * * @param closure true if dependencies should be traversed to determine * indirect dependencies. */ @Override public void setClosure(boolean closure) { this.closure = closure; } /** * Get the list of files in the file system upon which the root classes * depend. The files will be either the classfiles or jar files upon * which the root classes depend. * * @return an enumeration of File instances. */ @Override public Enumeration<File> getFileDependencies() { if (!supportsFileDependencies()) { throw new BuildException( "File dependencies are not supported by this analyzer"); } if (!determined) { determineDependencies(fileDependencies, classDependencies); } return fileDependencies.elements(); } /** * Get the list of classes upon which root classes depend. This is a * list of Java classnames in dot notation. * * @return an enumeration of Strings, each being the name of a Java * class in dot notation. */ @Override public Enumeration<String> getClassDependencies() { if (!determined) { determineDependencies(fileDependencies, classDependencies); } return classDependencies.elements(); } /** * Get the file that contains the class definition * * @param classname the name of the required class * @return the file instance, zip or class, containing the * class or null if the class could not be found. * @exception IOException if the files in the classpath cannot be read. */ @Override public File getClassContainer(String classname) throws IOException { String classLocation = classname.replace('.', '/') + ".class"; // we look through the classpath elements. If the element is a dir // we look for the file. IF it is a zip, we look for the zip entry return getResourceContainer(classLocation, classPath.list()); } /** * Get the file that contains the class source. * * @param classname the name of the required class * @return the file instance, zip or java, containing the * source or null if the source for the class could not be found. * @exception IOException if the files in the sourcepath cannot be read. */ @Override public File getSourceContainer(String classname) throws IOException { String sourceLocation = classname.replace('.', '/') + ".java"; // we look through the source path elements. If the element is a dir // we look for the file. If it is a zip, we look for the zip entry. // This isn't normal for source paths but we get it for free return getResourceContainer(sourceLocation, sourcePath.list()); } /** * Add a source path to the source path used by this analyzer. The * elements in the given path contain the source files for the classes * being analyzed. Not all analyzers will use this information. * * @param sourcePath The Path instance specifying the source path * elements. */ @Override public void addSourcePath(Path sourcePath) { if (sourcePath == null) { return; } this.sourcePath.append(sourcePath); this.sourcePath.setProject(sourcePath.getProject()); } /** * Add a classpath to the classpath being used by the analyzer. The * classpath contains the binary classfiles for the classes being * analyzed The elements may either be the directories or jar files.Not * all analyzers will use this information. * * @param classPath the Path instance specifying the classpath elements */ @Override public void addClassPath(Path classPath) { if (classPath == null) { return; } this.classPath.append(classPath); this.classPath.setProject(classPath.getProject()); } /** * Add a root class. The root classes are used to drive the * determination of dependency information. The analyzer will start at * the root classes and add dependencies from there. * * @param className the name of the class in Java dot notation. */ @Override public void addRootClass(String className) { if (className == null) { return; } if (!rootClasses.contains(className)) { rootClasses.addElement(className); } } /** * Configure an aspect of the analyzer. The set of aspects that are * supported is specific to each analyzer instance. * * @param name the name of the aspect being configured * @param info the configuration info. */ @Override public void config(String name, Object info) { // do nothing by default } /** * Reset the dependency list. This will reset the determined * dependencies and the also list of root classes. */ @Override public void reset() { rootClasses.removeAllElements(); determined = false; fileDependencies = new Vector<>(); classDependencies = new Vector<>(); } /** * Get an enumeration of the root classes * * @return an enumeration of Strings, each of which is a class name * for a root class. */ protected Enumeration<String> getRootClasses() { return rootClasses.elements(); } /** * Indicate if the analyzer is required to follow * indirect class relationships. * * @return true if indirect relationships should be followed. */ protected boolean isClosureRequired() { return closure; } /** * Determine the dependencies of the current set of root classes * * @param files a vector into which Files upon which the root classes * depend should be placed. * @param classes a vector of Strings into which the names of classes * upon which the root classes depend should be placed. */ protected abstract void determineDependencies(Vector<File> files, Vector<String> classes); /** * Indicate if the particular subclass supports file dependency * information. * * @return true if file dependencies are supported. */ protected abstract boolean supportsFileDependencies(); /** * Get the file that contains the resource * * @param resourceLocation the name of the required resource. * @param paths the paths which will be searched for the resource. * @return the file instance, zip or class, containing the * class or null if the class could not be found. * @exception IOException if the files in the given paths cannot be read. */ private File getResourceContainer(String resourceLocation, String[] paths) throws IOException { for (int i = 0; i < paths.length; ++i) { File element = new File(paths[i]); if (!element.exists()) { continue; } if (element.isDirectory()) { File resource = new File(element, resourceLocation); if (resource.exists()) { return resource; } } else { // must be a zip of some sort try (ZipFile zipFile = new ZipFile(element)) { if (zipFile.getEntry(resourceLocation) != null) { return element; } } } } return null; } }