001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2000, 2001, 2002, 2003 Jesse Stockall. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution, if
019: * any, must include the following acknowlegement:
020: * "This product includes software developed by the
021: * Apache Software Foundation (http://www.apache.org/)."
022: * Alternately, this acknowlegement may appear in the software itself,
023: * if and wherever such third-party acknowlegements normally appear.
024: *
025: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
026: * Foundation" must not be used to endorse or promote products derived
027: * from this software without prior written permission. For written
028: * permission, please contact apache@apache.org.
029: *
030: * 5. Products derived from this software may not be called "Apache"
031: * nor may "Apache" appear in their names without prior written
032: * permission of the Apache Group.
033: *
034: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
038: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
039: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
040: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
041: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
043: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
044: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
045: * SUCH DAMAGE.
046: * ====================================================================
047: *
048: */
049: package org.apache.tools.ant.taskdefs.optional.genjar;
050:
051: import java.io.File;
052: import java.io.FileNotFoundException;
053: import java.io.FileOutputStream;
054: import java.io.IOException;
055: import java.io.InputStream;
056: import java.util.ArrayList;
057: import java.util.HashSet;
058: import java.util.Iterator;
059: import java.util.LinkedList;
060: import java.util.List;
061: import java.util.Set;
062: import java.util.jar.JarEntry;
063: import java.util.jar.JarOutputStream;
064:
065: import org.apache.tools.ant.BuildException;
066: import org.apache.tools.ant.Task;
067: import org.apache.tools.ant.types.Path;
068: import org.apache.tools.ant.types.Reference;
069:
070: /**
071: * Driver class for the <genjar> task.<p>
072: *
073: * This class is instantiated when Ant encounters the <genjar> element.
074: *
075: * @author Original Code: <a href="mailto:jake@riggshill.com">John W. Kohler</a>
076: * @author Jesse Stockall
077: * @version $Revision: 1.11 $ $Date: 2003/03/06 01:22:00 $
078: */
079: public class GenJar extends Task {
080: private List jarSpecs = new ArrayList(32);
081: private List libraries = new ArrayList(8);
082: private Mft mft = new Mft();
083: private Path classpath = null;
084: private ClassFilter classFilter = null;
085: private File jarFile = null;
086: private File destDir = null;
087: private PathResolver[] resolvers = null;
088: private Set resolved = new HashSet();
089: private Logger logger = null;
090:
091: /** Constructor for the GenJar object */
092: public GenJar() {
093: setTaskName("GenJar");
094: }
095:
096: /**
097: * main execute for genjar
098: * <ol>
099: * <li> setup logger
100: * <li> ensure classpath is setup (with any additions from sub-elements
101: * <li> initialize file resolvers
102: * <li> initialize the manifest
103: * <li> resolve resource file paths resolve class file paths generate dependancy graphs for
104: * class files and resolve those paths check for duplicates
105: * <li> generate manifest entries for all candidate files
106: * <li> build jar
107: * </ol>
108: *
109: *
110: * @throws BuildException Oops!
111: */
112: public void execute() throws BuildException {
113: long start = System.currentTimeMillis();
114:
115: logger = new Logger(getProject());
116: if (classFilter == null) {
117: classFilter = new ClassFilter(logger);
118: }
119:
120: logger.verbose("GenJar Ver: 1.0.2");
121: if ((jarFile == null) && (destDir == null)) {
122: throw new BuildException(
123: "GenJar: Either a jarfile or destdir attribute is required",
124: getLocation());
125: }
126: // if (mft == null)
127: // {
128: // throw new BuildException("No manifest specified", getLocation());
129: // }
130: mft.setBaseDir(getProject().getBaseDir());
131:
132: if (jarFile != null) {
133: log("Generating jar: " + jarFile);
134: }
135:
136: if (destDir != null) {
137: log("Generating class structure in: " + destDir);
138: }
139:
140: //
141: // set up the classpath & resolvers - file/jar/zip
142: //
143: try {
144: if (classpath == null) {
145: classpath = new Path(getProject());
146: }
147: if (!classpath.isReference()) {
148: //
149: // don't like this - I could find no way to build
150: // the classpath dynamically from the LibrarySpec
151: // objects - the path just disappeared (has something
152: // with actual execution order I think) - so here's the
153: // brute force approach - if the library is of jar type
154: // then it'll return a Path object that we can insert
155: //
156: for (Iterator it = libraries.iterator(); it.hasNext();) {
157: Path p = ((LibrarySpec) it.next()).getPathElement();
158: if (p != null) {
159: classpath.addExisting(p);
160: }
161: }
162:
163: //
164: // add the system path now - AFTER all other paths are
165: // specified
166: //
167: classpath.addExisting(Path.systemClasspath);
168: }
169: logger.verbose("Initializing Path Resolvers");
170: logger.verbose("Classpath:" + classpath);
171: initPathResolvers();
172: } catch (IOException ioe) {
173: throw new BuildException("Unable to process classpath: "
174: + ioe, getLocation());
175: }
176: //
177: // prep the manifest
178: //
179: mft.execute(logger);
180: //
181: // run over all the resource and clsss specifications
182: // given in the project file
183: // resources are resolved to full path names while
184: // class specifications are exploded to dependancy
185: // graphs - when done, getJarEntries() returns a list
186: // of all entries generated by this JarSpec
187: //
188: List entries = new LinkedList();
189:
190: for (Iterator it = jarSpecs.iterator(); it.hasNext();) {
191: JarSpec js = (JarSpec) it.next();
192: try {
193: js.resolve(this );
194: } catch (IOException ioe) {
195: throw new BuildException("Unable to resolve: "
196: + js.getName(), ioe, getLocation());
197: }
198: //
199: // before adding a new jarspec - see if it already exists
200: // first entry added to jar always wins
201: //
202: List jarEntries = js.getJarEntries();
203: for (Iterator iter = jarEntries.iterator(); iter.hasNext();) {
204: JarEntrySpec spec = (JarEntrySpec) iter.next();
205: if (!entries.contains(spec)) {
206: entries.add(spec);
207: } else {
208: logger.verbose("Duplicate (ignored): "
209: + spec.getJarName());
210: }
211: }
212: }
213: //
214: // we have all the entries we're gonna jar - the manifest
215: // must be fully built prior to jar generation, so run over
216: // each entry and and add it to the manifest
217: //
218: for (Iterator it = entries.iterator(); it.hasNext();) {
219: JarEntrySpec jes = (JarEntrySpec) it.next();
220: if (jes.getSourceFile() == null) {
221: try {
222: InputStream is = resolveEntry(jes);
223: if (is != null) {
224: is.close();
225: }
226: } catch (IOException ioe) {
227: throw new BuildException(
228: "Error while generating manifest entry for: "
229: + jes.toString(), ioe,
230: getLocation());
231: }
232: }
233: mft.addEntry(jes.getJarName(), jes.getAttributes());
234: }
235:
236: if (jarFile != null) {
237: JarOutputStream jout = null;
238: InputStream is = null;
239: try {
240: jout = new JarOutputStream(
241: new FileOutputStream(jarFile), mft
242: .getManifest());
243:
244: for (Iterator it = entries.iterator(); it.hasNext();) {
245: JarEntrySpec jes = (JarEntrySpec) it.next();
246: JarEntry entry = new JarEntry(jes.getJarName());
247: is = resolveEntry(jes);
248:
249: if (is == null) {
250: logger
251: .error("Unable to locate previously resolved resource");
252: logger.error(" Jar Name:"
253: + jes.getJarName());
254: logger.error(" Resoved Source:"
255: + jes.getSourceFile());
256: try {
257: if (jout != null) {
258: jout.close();
259: }
260: } catch (IOException ioe) {
261: }
262: throw new BuildException(
263: "Jar component not found: "
264: + jes.getJarName(),
265: getLocation());
266: }
267: jout.putNextEntry(entry);
268: byte[] buff = new byte[4096]; // stream copy buffer
269: int len;
270: while ((len = is.read(buff, 0, buff.length)) != -1) {
271: jout.write(buff, 0, len);
272: }
273: jout.closeEntry();
274: is.close();
275:
276: logger.verbose("Added: " + jes.getJarName());
277: }
278: } catch (IOException ioe) {
279: throw new BuildException("Unable to create jar: "
280: + jarFile.getName(), ioe, getLocation());
281: } finally {
282: try {
283: if (is != null) {
284: is.close();
285: }
286: } catch (IOException ioe) {
287: }
288: try {
289: if (jout != null) {
290: jout.close();
291: }
292: } catch (IOException ioe) {
293: }
294: }
295: log("Jar Generated ("
296: + (System.currentTimeMillis() - start) + " ms)");
297: }
298:
299: // Destdir has been specified, so try to generate the dependencies on disk
300: if (destDir != null) {
301: if (!destDir.exists()) {
302: throw new BuildException("Destination directory: \'"
303: + destDir + "\' does not exist", getLocation());
304: }
305:
306: if (!destDir.isDirectory()) {
307: throw new BuildException("Destination directory: \'"
308: + destDir + "\' is not a valid directory",
309: getLocation());
310: }
311:
312: FileOutputStream fileout = null;
313: InputStream is = null;
314:
315: try {
316: for (Iterator it = entries.iterator(); it.hasNext();) {
317: JarEntrySpec jes = (JarEntrySpec) it.next();
318: String classname = jes.getJarName();
319: int index = classname.lastIndexOf("/");
320: String path = "";
321: if (index > 0) {
322: path = classname.substring(0, index);
323: }
324: classname = classname.substring(index + 1);
325:
326: File filepath = new File(destDir, path);
327: if (!filepath.exists()) {
328: if (!filepath.mkdirs()) {
329: throw new BuildException(
330: "Unable to create directory: "
331: + filepath.getName(),
332: getLocation());
333: }
334: }
335: File classfile = new File(filepath, classname);
336: logger.debug("Writing: "
337: + classfile.getAbsolutePath());
338: fileout = new FileOutputStream(classfile);
339: is = resolveEntry(jes);
340:
341: if (is == null) {
342: logger
343: .error("Unable to locate previously resolved resource");
344: logger.error(" Jar Name:"
345: + jes.getJarName());
346: logger.error(" Resoved Source:"
347: + jes.getSourceFile());
348: try {
349: if (fileout != null) {
350: fileout.close();
351: }
352: } catch (IOException ioe) {
353: }
354: throw new BuildException("File not found \'"
355: + jes.getJarName() + "\'",
356: getLocation());
357: }
358: byte[] buff = new byte[4096]; // stream copy buffer
359: int len;
360: while ((len = is.read(buff, 0, buff.length)) != -1) {
361: fileout.write(buff, 0, len);
362: }
363: fileout.close();
364: is.close();
365:
366: logger.verbose("Wrote: " + classfile.getName());
367: }
368: } catch (IOException ioe) {
369: throw new BuildException("Unable to write classes to: "
370: + destDir.getName(), ioe, getLocation());
371: } finally {
372: try {
373: if (is != null) {
374: is.close();
375: }
376: } catch (IOException ioe) {
377: }
378: try {
379: if (fileout != null) {
380: fileout.close();
381: }
382: } catch (IOException ioe) {
383: }
384: }
385:
386: log("Class Structure Generated ("
387: + (System.currentTimeMillis() - start) + " ms)");
388: }
389:
390: // Close all the resolvers
391: for (int i = 0, length = resolvers.length; i < length; i++) {
392: try {
393: resolvers[i].close();
394: } catch (IOException ioe) {
395: }
396: }
397: }
398:
399: /**
400: * Sets the classpath attribute.
401: *
402: * @param s The new classpath.
403: */
404: public void setClasspath(Path s) {
405: createClasspath().append(s);
406: }
407:
408: /**
409: * Builds the classpath.
410: *
411: * @return A <path>
412: *
413: * element.
414: */
415: public Path createClasspath() {
416: if (classpath == null) {
417: classpath = new Path(getProject());
418: }
419: return classpath;
420: }
421:
422: /**
423: * Sets the Classpathref attribute.
424: *
425: * @param r The new classpathRef.
426: */
427: public void setClasspathRef(Reference r) {
428: createClasspath().setRefid(r);
429: }
430:
431: /**
432: * Builds a <class> element.
433: *
434: * @return A <class> element.
435: */
436: public ClassSpec createClass() {
437: ClassSpec cs = new ClassSpec(getProject());
438: jarSpecs.add(cs);
439: return cs;
440: }
441:
442: /**
443: * Builds a manifest element.
444: *
445: * @return A <manifest> element.
446: */
447: public Object createManifest() {
448: mft.setBaseDir(getProject().getBaseDir());
449: return mft;
450: }
451:
452: /**
453: * Builds a resource element.
454: *
455: * @return A <resource> element.
456: */
457: public Resource createResource() {
458: Resource rsc = new Resource(getProject());
459: jarSpecs.add(rsc);
460: return rsc;
461: }
462:
463: /**
464: * Builds a classfilter element.
465: *
466: * @return A <classfilter> element.
467: */
468: public ClassFilter createClassfilter() {
469: if (classFilter == null) {
470: classFilter = new ClassFilter(new Logger(getProject()));
471: }
472: return classFilter;
473: }
474:
475: /**
476: * Builds a library element.
477: *
478: * @return A
479: * <library> element.
480: */
481: public LibrarySpec createLibrary() {
482: LibrarySpec lspec = new LibrarySpec(getProject().getBaseDir(),
483: new Path(getProject()));
484: jarSpecs.add(lspec);
485: libraries.add(lspec);
486: return lspec;
487: }
488:
489: /**
490: * @param t
491: * @deprecated Use -verbose or -debug instead.
492: */
493: public void setTrace(String t) {
494: log("trace is deprecated and ignored, Use -verbose or -debug instead.");
495: }
496:
497: /**
498: * Sets the name of the jar file to be created.
499: *
500: * @param jarFile The new jarfile value
501: */
502: public void setJarfile(File jarFile) {
503: this .jarFile = jarFile;
504: }
505:
506: /**
507: * Sets the name of the directory where the classes will be copied.
508: *
509: * @param path The directory name.
510: */
511: public void setDestDir(Path path) {
512: destDir = getProject().resolveFile(path.toString());
513: }
514:
515: //
516: // TODO: path resolution needs to move to its own class
517: //
518:
519: /**
520: * Iterate through the classpath and create an array of all the <code>PathResolver</code>s
521: *
522: * @throws IOException Description of the Exception
523: */
524: private void initPathResolvers() throws IOException {
525: List l = new ArrayList(32);
526: String[] pc = classpath.list();
527:
528: for (int i = 0; i < pc.length; ++i) {
529: File f = new File(pc[i]);
530: if (!f.exists()) {
531: continue;
532: }
533:
534: if (f.isDirectory()) {
535: l.add(new FileResolver(f, logger));
536: } else if (f.getName().toLowerCase().endsWith(".jar")) {
537: l.add(new JarResolver(f, logger));
538: } else if (f.getName().toLowerCase().endsWith(".zip")) {
539: l.add(new ZipResolver(f, logger));
540: } else {
541: throw new BuildException(f.getName()
542: + " is not a valid classpath component",
543: getLocation());
544: }
545: }
546: resolvers = (PathResolver[]) l.toArray(new PathResolver[0]);
547: }
548:
549: /**
550: * Description of the Method
551: *
552: * @param spec Description of the Parameter
553: * @return Description of the Return Value
554: * @throws IOException Description of the Exception
555: */
556: InputStream resolveEntry(JarEntrySpec spec) throws IOException {
557: InputStream is = null;
558: for (int i = 0; i < resolvers.length; ++i) {
559: is = resolvers[i].resolve(spec);
560: if (is != null) {
561: return is;
562: }
563: }
564: return null;
565: }
566:
567: /**
568: * Resolves a partial file name against the classpath elements
569: *
570: * @param cname Description of the Parameter
571: * @return An InputStream open on the named file or null
572: * @throws IOException Description of the Exception
573: */
574: InputStream resolveEntry(String cname) throws IOException {
575: InputStream is = null;
576:
577: for (int i = 0; i < resolvers.length; ++i) {
578: is = resolvers[i].resolve(cname);
579: if (is != null) {
580: return is;
581: }
582: }
583: return null;
584: }
585:
586: //=====================================================================
587: // TODO: class dependancy determination needs to move to either its own
588: // class or to ClassSpec
589: //=====================================================================
590:
591: /**
592: * Generates a list of all classes upon which the list of classes depend.
593: *
594: * @param entries List of <code>JarEntrySpec</code>s used as a list of class names from
595: * which to start.
596: * @exception IOException If there's an error reading a class file
597: */
598: void generateDependancies(List entries) throws IOException {
599: List dependants = new LinkedList();
600:
601: for (Iterator it = entries.iterator(); it.hasNext();) {
602: JarEntrySpec js = (JarEntrySpec) it.next();
603: generateClassDependancies(js.getJarName(), dependants);
604: }
605:
606: for (Iterator it = dependants.iterator(); it.hasNext();) {
607: entries.add(new JarEntrySpec(it.next().toString(), null));
608: }
609: }
610:
611: /**
612: * Generates a list of classes upon which the named class is dependant.
613: *
614: * @param classes A List into which the class names are placed
615: * @param classFileName Description of the Parameter
616: * @throws IOException Description of the Exception
617: */
618: void generateClassDependancies(String classFileName, List classes)
619: throws IOException {
620: if (!resolved.contains(classFileName)) {
621: resolved.add(classFileName);
622: InputStream is = resolveEntry(classFileName);
623: if (is == null) {
624: throw new FileNotFoundException(classFileName);
625: }
626:
627: List referenced = ClassUtil.getDependancies(is);
628:
629: for (Iterator it = referenced.iterator(); it.hasNext();) {
630: String cname = it.next().toString() + ".class";
631:
632: if (!classFilter.include(cname)
633: || resolved.contains(cname)) {
634: continue;
635: }
636:
637: classes.add(cname);
638: generateClassDependancies(cname, classes);
639: }
640: is.close();
641: }
642: }
643: }
644: // vi:set ts=4 sw=4:
|