/********************************************************************** * Copyright (c) 2005-2009 ant4eclipse project team. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich **********************************************************************/ package org.ant4eclipse.lib.jdt.ecj.internal.tools.loader; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.ant4eclipse.lib.core.Assure; import org.ant4eclipse.lib.core.ClassName; import org.ant4eclipse.lib.core.exception.Ant4EclipseException; import org.ant4eclipse.lib.jdt.ecj.ClassFile; import org.ant4eclipse.lib.jdt.ecj.ClassFileLoader; import org.ant4eclipse.lib.jdt.ecj.EcjExceptionCodes; import org.ant4eclipse.lib.jdt.ecj.ReferableSourceFile; import org.ant4eclipse.lib.jdt.ecj.internal.tools.ReferableSourceFileImpl; /** * <p> * Implementation of a class path based {@link ClassFileLoader}. An instance of this class contains an array of files * (jar files or directories) where the class file loader searches for classes. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) */ public class ClasspathClassFileLoaderImpl implements ClassFileLoader { /** the class path entries */ private File[] _classpathEntries; /** the class path entries */ private File[] _sourcepathEntries; /** the source */ private File _location; /** the type of the associated bundle (PROJECT or LIBRARY) */ private byte _type; /** maps packages to package providers */ private Map<String, PackageProvider> _allPackages; /** * <p> * Creates a new instance of type ClasspathClassFileLoaderImpl. * </p> * * @param entry * the file entry * @param type * type */ public ClasspathClassFileLoaderImpl(File entry, byte type) { Assure.notNull("entry", entry); this._location = entry; this._type = type; // initialize initialize(new File[] { entry }, new File[] {}); } public ClasspathClassFileLoaderImpl(File classPathEntry, byte type, File sourcePathEntry) { Assure.notNull("classPathEntry", classPathEntry); Assure.notNull("sourcePathEntry", sourcePathEntry); this._location = classPathEntry; this._type = type; // initialize initialize(new File[] { classPathEntry }, new File[] { sourcePathEntry }); } /** * <p> * Creates a new instance of type {@link FilteredClasspathClassFileLoader}. * </p> * * @param location * @param type * @param classpathEntries */ public ClasspathClassFileLoaderImpl(File location, byte type, File[] classpathEntries) { Assure.notNull("location", location); this._location = location; this._type = type; // initialize initialize(classpathEntries, new File[] {}); } public ClasspathClassFileLoaderImpl(File location, byte type, File[] classpathEntries, File[] sourcePathEntries) { Assure.notNull("location", location); this._location = location; this._type = type; // initialize initialize(classpathEntries, sourcePathEntries); } /** * <p> * Creates a new instance of type {@link FilteredClasspathClassFileLoader}. * </p> */ protected ClasspathClassFileLoaderImpl() { // nothing to do here... } /** * {@inheritDoc} */ public boolean hasPackage(String packageName) { return this._allPackages.containsKey(packageName); } /** * {@inheritDoc} */ public String[] getAllPackages() { Set<String> keys = this._allPackages.keySet(); return keys.toArray(new String[0]); } /** * <p> * Sets the location. * </p> * * @param location */ protected void setLocation(File location) { this._location = location; } /** * <p> * Sets the type. * </p> * * @param type */ protected void setType(byte type) { this._type = type; } /** * <p> * </p> * * @return */ protected File getLocation() { return this._location; } /** * <p> * </p> * * @return */ protected byte getType() { return this._type; } /** * <p> * Returns all class path entries of this {@link ClassFileLoader}. * </p> * * @return all class path entries of this {@link ClassFileLoader}. */ protected File[] getClasspathEntries() { return this._classpathEntries; } /** * {@inheritDoc} */ public File[] getClasspath() { return getClasspathEntries(); } /** * <p> * Initializes this class file loader. * </p> * * @param classpathEntries * the class path entries. */ protected void initialize(File[] classpathEntries, File[] sourcepathEntries) { // assert not null Assure.notNull("classpathEntries", classpathEntries); Assure.notNull("sourcepathEntries", sourcepathEntries); // assert that each entry is not null for (File classpathEntrie : classpathEntries) { Assure.notNull("classpathEntrie", classpathEntrie); } for (File sourcepathEntry : sourcepathEntries) { Assure.notNull("sourcepathEntry", sourcepathEntry); } // assign path entries this._classpathEntries = classpathEntries; this._sourcepathEntries = sourcepathEntries; // create allPackages hash map this._allPackages = new HashMap<String, PackageProvider>(); // add all existing packages to the hash map for (File file : this._classpathEntries) { if (file.isDirectory()) { String[] allPackages = getAllPackagesFromDirectory(file); addAllPackagesFromClassPathEntry(allPackages, file); } else if (file.isFile()) { String[] allPackages = getAllPackagesFromJar(file); addAllPackagesFromClassPathEntry(allPackages, file); } } // add all existing packages to the hash map for (File file : this._sourcepathEntries) { if (file.isDirectory()) { String[] allPackages = getAllPackagesFromDirectory(file); addAllPackagesFromSourcePathEntry(allPackages, file); } // we do not support source in jars or zips } } /** * <p> * Returns a new {@link PackageProvider}. This method returns a {@link PackageProvider} of type * {@link PackageProvider}. * </p> * <p> * You can override this method to provide your own {@link PackageProvider} implementation. * </p> * * @param classpathEntry * @return */ protected PackageProvider newPackageProvider() { return new PackageProvider(); } /** * <p> * </p> * * @param packageName * @return */ protected final PackageProvider getPackageProvider(String packageName) { return this._allPackages.get(packageName); } /** * @param allPackages * @param classPathEntry */ private void addAllPackagesFromClassPathEntry(String[] allPackages, File classPathEntry) { for (String aPackage : allPackages) { if (this._allPackages.containsKey(aPackage)) { PackageProvider provider = this._allPackages.get(aPackage); provider.addClasspathEntry(classPathEntry); } else { PackageProvider provider = newPackageProvider(); provider.addClasspathEntry(classPathEntry); this._allPackages.put(aPackage, provider); } } } /** * <p> * </p> * * @param allPackages * @param sourcePathEntry */ private void addAllPackagesFromSourcePathEntry(String[] allPackages, File sourcePathEntry) { for (String aPackage : allPackages) { if (this._allPackages.containsKey(aPackage)) { PackageProvider provider = this._allPackages.get(aPackage); provider.addSourcepathEntry(sourcePathEntry); } else { PackageProvider provider = newPackageProvider(); provider.addSourcepathEntry(sourcePathEntry); this._allPackages.put(aPackage, provider); } } } /** * <p> * Returns all the names of the packages that are contained in the specified jar file. The package list contains the * packages that contain classes as well as all parent packages of those. * </p> * * @param jar * @return * @throws IOException */ private String[] getAllPackagesFromJar(File jar) { Assure.isFile("jar", jar); // prepare result... List<String> result = new LinkedList<String>(); // create the jarFile wrapper... JarFile jarFile = null; try { jarFile = new JarFile(jar); } catch (IOException e) { throw new Ant4EclipseException(EcjExceptionCodes.COULD_NOT_CREATE_JAR_FILE_FROM_FILE_EXCEPTION, jar.getAbsolutePath()); } // Iterate over entries... Enumeration<?> enumeration = jarFile.entries(); while (enumeration.hasMoreElements()) { JarEntry jarEntry = (JarEntry) enumeration.nextElement(); // add package for each found directory... String directoryName = null; // if the jar entry is a directory, the directory name is the name of the jar entry... if (jarEntry.isDirectory()) { directoryName = jarEntry.getName(); } // otherwise the directory name has to be computed else { int splitIndex = jarEntry.getName().lastIndexOf('/'); if (splitIndex != -1) { directoryName = jarEntry.getName().substring(0, splitIndex); } } // directoryName can be null if a top level entry is processed if (directoryName != null) { // convert path to package name String packageName = directoryName.replace('/', '.'); packageName = packageName.endsWith(".") ? packageName.substring(0, packageName.length() - 1) : packageName; // at package with all the parent packages (!) to the result list String[] packages = allPackages(packageName); for (int i = 0; i < packages.length; i++) { if (!result.contains(packages[i])) { result.add(packages[i]); } } } } // return result... return result.toArray(new String[0]); } /** * <p> * Returns all package names (including parent package names) for the specified package. * </p> * <p> * <b>Example:</b><br/> * Given the package name <code>net.sf.ant4eclipse.tools</code> this method will return {"net", "net.sf", * "net.sf.ant4eclipse", "net.sf.ant4eclipse.tools"}. * </p> * * @param packageName * the name of the package. * @return all package names (including parent package names) for the specified package. */ private String[] allPackages(String packageName) { // split the package name StringTokenizer tokenizer = new StringTokenizer(packageName, "."); // declare result String[] result = new String[tokenizer.countTokens()]; // compute result for (int i = 0; i < result.length; i++) { if (i == 0) { result[i] = tokenizer.nextToken(); } else { result[i] = result[i - 1] + "." + tokenizer.nextToken(); } } // return result return result; } /** * @param directory * @return */ private String[] getAllPackagesFromDirectory(File directory) { List<String> result = new LinkedList<String>(); File[] children = directory.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isDirectory(); } }); if (children != null) { for (File element : children) { getAllPackagesFromDirectory(null, element, result); } } return result.toArray(new String[0]); } /** * @param prefix * @param directory * @param result */ private void getAllPackagesFromDirectory(String prefix, File directory, List<String> result) { String newPrefix = prefix == null ? "" : prefix + "."; result.add(newPrefix + directory.getName()); File[] children = directory.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isDirectory(); } }); if (children != null) { for (File element : children) { getAllPackagesFromDirectory(newPrefix + directory.getName(), element, result); } } } /** * {@inheritDoc} */ public ClassFile loadClass(ClassName className) { if (!hasPackage(className.getPackageName())) { return null; } return getPackageProvider(className.getPackageName()).loadClassFile(className); } /** * {@inheritDoc} */ public ReferableSourceFile loadSource(ClassName className) { if (!hasPackage(className.getPackageName())) { return null; } return getPackageProvider(className.getPackageName()).loadSourceFile(className); } /** * {@inheritDoc} */ @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("[ClasspathClassFileLoader:"); buffer.append(" { "); for (int i0 = 0; (this._classpathEntries != null) && (i0 < this._classpathEntries.length); i0++) { buffer.append(" _classpathEntries[" + i0 + "]: "); buffer.append(this._classpathEntries[i0]); } buffer.append(" } "); buffer.append(" _location: "); buffer.append(this._location); // buffer.append(" _type: "); // buffer.append(this._type); // buffer.append(" _allPackages: "); // buffer.append(this._allPackages); buffer.append("]"); return buffer.toString(); } /** * <p> * Encapsulates all class and source path entries that provide a specific package. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) */ public class PackageProvider { /** the class path entries */ private List<File> _classpathEntries; /** the source path entries */ private List<File> _sourcepathEntries; /** * <p> * Creates a new instance of type {@link PackageProvider}. * </p> */ public PackageProvider() { this._classpathEntries = new LinkedList<File>(); this._sourcepathEntries = new LinkedList<File>(); } /** * <p> * Adds the specified file to the class path. * </p> * * @param classpathEntry * the class path entry to add. */ public void addClasspathEntry(File classpathEntry) { Assure.exists("classpathEntry", classpathEntry); this._classpathEntries.add(classpathEntry); } /** * <p> * Adds the specified file to the source path. * </p> * * @param sourcepathEntry * the source path entry to add. */ public void addSourcepathEntry(File sourcepathEntry) { Assure.isDirectory("sourcepathEntry", sourcepathEntry); this._sourcepathEntries.add(sourcepathEntry); } /** * <p> * </p> * * @param className * @return */ public ClassFile loadClassFile(ClassName className) { for (File file : this._classpathEntries) { File classpathEntry = file; if (classpathEntry.isDirectory()) { File result = new File(classpathEntry, className.asClassFileName()); if (result.exists()) { try { if (result.getName().equals(result.getCanonicalFile().getName())) { return new FileClassFileImpl(result, classpathEntry.getAbsolutePath(), ClasspathClassFileLoaderImpl.this._type); } } catch (IOException e) { // do nothing } } } else { try { JarFile jarFile = new JarFile(classpathEntry); JarEntry entry = jarFile.getJarEntry(className.asClassFileName()); if ((entry != null)) { return new JarClassFileImpl(className.asClassFileName(), jarFile, classpathEntry.getAbsolutePath(), ClasspathClassFileLoaderImpl.this._type); } } catch (IOException e) { // nothing to do here... } } } return null; } /** * <p> * </p> * * @param className * @return */ public ReferableSourceFile loadSourceFile(ClassName className) { String javaFileName = className.getClassName() + ".java"; for (File classpathEntry : this._sourcepathEntries) { if (classpathEntry.isDirectory()) { File packageDir = new File(classpathEntry, className.getPackageAsDirectoryName()); if (packageDir.isDirectory()) { for (String name : packageDir.list()) { if (javaFileName.equals(name) && new File(classpathEntry, className.asSourceFileName().replace('/', File.separatorChar) .replace('\\', File.separatorChar)).exists()) { return new ReferableSourceFileImpl(classpathEntry, className.asSourceFileName() .replace('/', File.separatorChar).replace('\\', File.separatorChar), classpathEntry.getAbsolutePath(), ClasspathClassFileLoaderImpl.this._type); } } } } // we do not support source jars here... // else { // try { // JarFile jarFile = new JarFile(classpathEntry); // // JarEntry entry = jarFile.getJarEntry(className.asClassFileName()); // // if ((entry != null)) { // return new JarClassFileImpl(className.asClassFileName(), jarFile, classpathEntry.getAbsolutePath(), // ClasspathClassFileLoaderImpl.this._type); // } // } catch (IOException e) { // // nothing to do here... // } // } } return null; } } }