/* * The Apache Software License, Version 1.1 * * Copyright (c) 2000, 2001, 2002, 2003 Jesse Stockall. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * */ package org.moxie.ant; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Manifest; import org.apache.tools.ant.taskdefs.Manifest.Section; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; import org.moxie.MoxieException; import org.moxie.utils.StringUtils; /** * Driver class for the <genjar> task. * <p> * * This class is instantiated when Ant encounters the <genjar> element. * * @author Original Code: <a href="mailto:jake@riggshill.com">John W. Kohler</a> * @author Jesse Stockall * @version $Revision: 1.11 $ $Date: 2003/03/06 01:22:00 $ */ public class GenJar extends Task { protected List<JarSpec> jarSpecs = new ArrayList<JarSpec>(); private List<LibrarySpec> libraries = new ArrayList<LibrarySpec>(); protected Manifest mft = Manifest.getDefaultManifest(); protected Path classpath = null; protected Path librarypath = null; private ClassFilter classFilter = null; protected File destFile = null; protected Set<String> resolvedLocal = new TreeSet<String>(); private List<PathResolver> resolvers = new ArrayList<PathResolver>(); private Set<String> resolved = new TreeSet<String>(); protected Set<String> exportedPackages = new TreeSet<String>(); private Logger logger = null; protected String version; boolean excludeClasspathJars; String excludes; /** * main execute for genjar * <ol> * <li>setup logger * <li>ensure classpath is setup (with any additions from sub-elements * <li>initialize file resolvers * <li>initialize the manifest * <li>resolve resource file paths resolve class file paths generate * dependency graphs for class files and resolve those paths check for * duplicates * <li>generate manifest entries for all candidate files * <li>build jar * </ol> * * * @throws BuildException * Oops! */ public void execute() throws BuildException { logger = new Logger(getProject()); if (classFilter == null) { classFilter = new ClassFilter(logger); } // // set up the classpath & resolvers - file/jar/zip // try { if (classpath == null) { classpath = new Path(getProject()); classpath.addExisting(Path.systemClasspath); } librarypath = new Path(getProject()); for (LibrarySpec lib : libraries) { Path p = lib.getPathElement(); if (p != null) { librarypath.addExisting(p); } } logger.verbose("Initializing Path Resolvers"); logger.verbose("Classpath:" + classpath); logger.verbose("Librarypath:" + librarypath); initPathResolvers(); } catch (IOException ioe) { throw new MoxieException("Unable to process classpath: " + ioe, getLocation()); } // // run over all the resource and class specifications // given in the project file // resources are resolved to full path names while // class specifications are exploded to dependency // graphs - when done, getJarEntries() returns a list // of all entries generated by this JarSpec // List<JarEntrySpec> entries = new ArrayList<JarEntrySpec>(); for (JarSpec js : jarSpecs) { try { js.resolve(this); } catch (FileNotFoundException ioe) { throw new ResolutionFailedException(js.getName(), ioe.getMessage()); } catch (IOException ioe) { throw new MoxieException("Unable to resolve: " + js.getName() + "\nMSG=" + ioe.getMessage(), ioe, getLocation()); } // // before adding a new jarspec - see if it already exists // first entry added to jar always wins // for (JarEntrySpec spec : js.getJarEntries()) { if (!entries.contains(spec)) { entries.add(spec); } else { logger.verbose("Duplicate (ignored): " + spec.getJarName()); } } } // // we have all the entries we're gonna jar - the manifest // must be fully built prior to jar generation, so run over // each entry and and add it to the manifest // for (JarEntrySpec jes : entries) { if (jes.getSourceFile() == null) { try { InputStream is = resolveEntry(jes); if (is != null) { is.close(); } } catch (IOException ioe) { throw new MoxieException( "Error while generating manifest entry for: " + jes.toString(), ioe, getLocation()); } } } JarOutputStream jout = null; InputStream is = null; try { jout = new JarOutputStream(new FileOutputStream(destFile), createJarManifest()); writeJarEntries(jout); for (JarEntrySpec jes : entries) { JarEntry entry = new JarEntry(jes.getJarName()); is = resolveEntry(jes); if (is == null) { logger.error("Unable to locate previously resolved resource"); logger.error(" Jar Name:" + jes.getJarName()); logger.error("Resolved Source:" + jes.getSourceFile()); try { if (jout != null) { jout.close(); } } catch (IOException ioe) { } throw new MoxieException("Jar component not found: " + jes.getJarName(), getLocation()); } jout.putNextEntry(entry); byte[] buff = new byte[4096]; // stream copy buffer int len; while ((len = is.read(buff, 0, buff.length)) != -1) { jout.write(buff, 0, len); } jout.closeEntry(); is.close(); logger.verbose("Added: " + jes.getJarName()); } } catch (IOException ioe) { throw new MoxieException("Unable to create jar: " + destFile.getName(), ioe, getLocation()); } finally { try { if (is != null) { is.close(); } } catch (IOException ioe) { } try { if (jout != null) { jout.close(); } } catch (IOException ioe) { } } // Close all the resolvers for (PathResolver resolver : resolvers) { try { resolver.close(); } catch (IOException ioe) { } } } protected void writeJarEntries(JarOutputStream jos) { } /** * Sets the classpath attribute. * * @param s * The new classpath. */ public void setClasspath(Path s) { createClasspath().append(s); } /** * Builds the classpath. * * @return A <path> * * element. */ public Path createClasspath() { if (classpath == null) { classpath = new Path(getProject()); } return classpath; } /** * Sets the Classpathref attribute. * * @param r * The new classpathRef. */ public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } /** * Builds a <class> element. * * @return A <class> element. */ public ClassSpec createClass() { ClassSpec cs = new ClassSpec(getProject()); jarSpecs.add(cs); return cs; } /** * Builds a manifest element. * * @return A <manifest> element. */ public Manifest createManifest() { return mft; } @SuppressWarnings("unchecked") private java.util.jar.Manifest createJarManifest() { java.util.jar.Manifest newManifest = new java.util.jar.Manifest(); newManifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); Section mainSection = mft.getMainSection(); Enumeration<String> mainAttributes = mainSection.getAttributeKeys(); while (mainAttributes.hasMoreElements()) { String aname = mainAttributes.nextElement(); String avalue = mainSection.getAttributeValue(aname); newManifest.getMainAttributes().putValue(aname, avalue); } // OSGI Export-Package if (!StringUtils.isEmpty(version)) { String vString = MessageFormat.format(";version=\"{0}\",", version); StringBuilder packages = new StringBuilder(); for (String packageName : exportedPackages) { packages.append(packageName); packages.append(vString); } if (packages.length() > 0) { packages.setLength(packages.length() - 1); newManifest.getMainAttributes().putValue("Export-Package", packages.toString()); } } // for (Map.Entry<Object, Object> entry : newManifest.getMainAttributes().entrySet()) { // System.out.println(entry.getKey() + "=" + entry.getValue()); // } Enumeration<String> sections = mft.getSectionNames(); while (sections.hasMoreElements()) { String sname = sections.nextElement(); Section section = mft.getSection(sname); Attributes newAttributes = new Attributes(); newManifest.getEntries().put(sname, newAttributes); Enumeration<String> attributes = section.getAttributeKeys(); while (attributes.hasMoreElements()) { String aname = attributes.nextElement(); String avalue = section.getAttributeValue(aname); newAttributes.putValue(aname, avalue); } // for (Map.Entry<Object, Object> entry : newManifest.getAttributes(sname).entrySet()) { // System.out.println(entry.getKey() + "=" + entry.getValue()); // } } return newManifest; } /** * Builds a resource element. * * @return A <resource> element. */ public Resource createResource() { Resource rsc = new Resource(getProject()); jarSpecs.add(rsc); return rsc; } /** * Builds a classfilter element. * * @return A <classfilter> element. */ public ClassFilter createClassfilter() { if (classFilter == null) { classFilter = new ClassFilter(new Logger(getProject())); } return classFilter; } /** * Builds a library element. * * @return A <library> element. */ public LibrarySpec createLibrary() { LibrarySpec lspec = new LibrarySpec(getProject().getBaseDir(), new Path(getProject())); jarSpecs.add(lspec); libraries.add(lspec); return lspec; } /** * Sets the name of the jar file to be created. * * @param destFile * The new destfile value */ public void setDestfile(File destFile) { this.destFile = destFile; } /** * Iterate through the classpath and create an array of all the * <code>PathResolver</code>s * * @throws IOException * Description of the Exception */ private void initPathResolvers() throws IOException { // classes classpath can be excluded if class source is jar initPathResolvers(classpath, excludeClasspathJars); // classes on the library path are never excluded initPathResolvers(librarypath, false); } private void initPathResolvers(Path path, boolean excludeJars) throws IOException { for (String pc : path.list()) { File f = new File(pc); if (!f.exists()) { continue; } PathResolver resolver = null; if (f.isDirectory()) { resolver = new FileResolver(f, logger); } else if (f.getName().toLowerCase().endsWith(".jar")) { resolver = new JarResolver(f, excludeJars, logger); } else if (f.getName().toLowerCase().endsWith(".zip")) { resolver = new ZipResolver(f, logger); } else { throw new MoxieException(f.getName() + " is not a valid classpath component", getLocation()); } logger.debug("added " + resolver); resolvers.add(resolver); } } /** * Description of the Method * * @param spec * Description of the Parameter * @return Description of the Return Value * @throws IOException * Description of the Exception */ InputStream resolveEntry(JarEntrySpec spec) throws IOException { InputStream is = null; for (PathResolver resolver : resolvers) { is = resolver.resolve(spec); if (is != null) { if (resolver instanceof FileResolver) { // keep track of class files and packages added from output folders if (spec.getJarName().endsWith(".class")) { String className = spec.getJarName(); resolvedLocal.add(className); if (className.lastIndexOf('/') > -1) { String packageName = className.substring(0, className.lastIndexOf('/')); exportedPackages.add(packageName); } } } return is; } } return null; } /** * Resolves a partial file name against the classpath elements * * @param cname * Description of the Parameter * @return An InputStream open on the named file or null * @throws IOException * Description of the Exception */ InputStream resolveEntry(String cname) throws IOException, ExcludedResolverException { InputStream is = null; for (PathResolver resolver : resolvers) { is = resolver.resolve(cname); if (is != null) { if (resolver.isExcluded()) { is.close(); throw new ExcludedResolverException(resolver.toString()); } return is; } } return null; } /** * Generates a list of all classes upon which the list of classes depend. * * @param entries * List of <code>JarEntrySpec</code>s used as a list of class * names from which to start. * @exception IOException * If there's an error reading a class file */ void generateDependencies(List<JarEntrySpec> entries) throws IOException { Set<String> dependents = new TreeSet<String>(); Iterator<JarEntrySpec> itr = entries.iterator(); while (itr.hasNext()) { JarEntrySpec js = itr.next(); if (!generateClassDependencies(js.getJarName(), dependents)) { // class is located in an excluded source, exclude entry itr.remove(); } } for (String dependent : dependents) { entries.add(new JarEntrySpec(dependent, null)); } } /** * Generates a list of classes upon which the named class is dependent. * * @param classes * A List into which the class names are placed * @param classFileName * Description of the Parameter * @throws IOException * Description of the Exception * @return true if the class should be kept */ boolean generateClassDependencies(String classFileName, Set<String> classes) throws IOException { if (!resolved.contains(classFileName)) { resolved.add(classFileName); InputStream is = null; try { is = resolveEntry(classFileName); } catch (ExcludedResolverException e) { // class is located in an excluded source // remove from entry list logger.debug(MessageFormat.format("{0} is located in {1}", classFileName, e.getMessage())); return false; } if (is == null) { throw new FileNotFoundException(classFileName); } List<String> referenced = ClassUtil.getDependencies(is); for (String name : referenced) { String cname = name + ".class"; if (!classFilter.include(cname) || resolved.contains(cname)) { continue; } classes.add(cname); if (!generateClassDependencies(cname, classes)) { // dependent class is located in an excluded source // remove from list classes.remove(cname); } } is.close(); } return true; } private static class ExcludedResolverException extends Exception { private static final long serialVersionUID = 1L; public ExcludedResolverException(String source) { super(source); } } }