View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software 
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License.
16   */
17  
18  package org.apache.jdo.enhancer;
19  
20  import java.io.IOException;
21  import java.io.FileNotFoundException;
22  import java.io.File;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.io.DataInputStream;
26  import java.io.DataOutputStream;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.BufferedInputStream;
30  import java.io.BufferedOutputStream;
31  import java.io.PrintWriter;
32  import java.io.FileReader;
33  import java.io.BufferedReader;
34  
35  import java.util.Map;
36  import java.util.List;
37  import java.util.Collection;
38  import java.util.Enumeration;
39  import java.util.Iterator;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.Hashtable;
43  import java.util.Properties;
44  import java.util.StringTokenizer;
45  
46  import java.util.zip.ZipFile;
47  import java.util.zip.ZipEntry;
48  import java.util.zip.ZipInputStream;
49  import java.util.zip.ZipOutputStream;
50  import java.util.zip.ZipException;
51  
52  import java.net.URL;
53  
54  import org.apache.jdo.impl.enhancer.ClassFileEnhancer;
55  import org.apache.jdo.impl.enhancer.ClassFileEnhancerHelper;
56  import org.apache.jdo.impl.enhancer.ClassFileEnhancerTimer;
57  import org.apache.jdo.impl.enhancer.EnhancerFatalError;
58  import org.apache.jdo.impl.enhancer.EnhancerUserException;
59  import org.apache.jdo.impl.enhancer.OutputStreamWrapper;
60  import org.apache.jdo.impl.enhancer.core.EnhancerFilter;
61  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaData;
62  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataFatalError;
63  import org.apache.jdo.impl.enhancer.meta.model.EnhancerMetaDataJDOModelImpl;
64  import org.apache.jdo.impl.enhancer.meta.prop.EnhancerMetaDataPropertyImpl;
65  import org.apache.jdo.impl.enhancer.meta.util.EnhancerMetaDataBaseModel;
66  import org.apache.jdo.impl.enhancer.meta.util.EnhancerMetaDataTimer;
67  import org.apache.jdo.impl.enhancer.util.CombinedResourceLocator;
68  import org.apache.jdo.impl.enhancer.util.ListResourceLocator;
69  import org.apache.jdo.impl.enhancer.util.PathResourceLocator;
70  import org.apache.jdo.impl.enhancer.util.ResourceLocator;
71  import org.apache.jdo.impl.enhancer.util.ResourceLocatorTimer;
72  import org.apache.jdo.impl.enhancer.util.Support;
73  
74  
75  
76  
77  
78  /***
79   * Main is the starting point for the persistent filter tool.
80   */
81  public class Main
82      extends Support
83  {
84      // return values of main()
85      static public final int OK = 0;
86      static public final int USAGE_ERROR = -1;
87      static public final int METADATA_ERROR = -2;
88      static public final int CLASS_LOCATOR_ERROR = -3;
89      static public final int INTERNAL_ERROR = -4;
90  
91      /***
92       *  The stream to write messages to.
93       */
94      private final PrintWriter out = new PrintWriter(System.out, true);
95  
96      /***
97       *  The stream to write error messages to.
98       */
99      private final PrintWriter err = new PrintWriter(System.err, true);
100 
101     /***
102      *  The command line options.
103      */
104     private final CmdLineOptions opts = new CmdLineOptions();
105 
106     /***
107      *  The byte code enhancer.
108      */
109     private ClassFileEnhancer enhancer;
110 
111     /***
112      *  The locator for classes.
113      */
114     protected ResourceLocator classLocator;
115 
116     /***
117      *  The metadata for the enhancer.
118      */
119     private EnhancerMetaData jdoMetaData;
120 
121     /***
122      * Construct a filter tool instance
123      */
124     public Main()
125     {}
126 
127     // ----------------------------------------------------------------------
128 
129     /***
130      * This is where it all starts.
131      */
132     public static void main(String[] argv)
133     {
134         int res;
135         final Main main = new Main();
136 
137         //@olsen: added support for timing statistics
138         try {
139             res = main.process(argv);
140         } catch (RuntimeException ex) {
141             main.out.flush();
142             main.err.println("Internal error while postprocessing: "
143                              + ex.getMessage());
144             ex.printStackTrace(main.err);
145             main.err.flush();
146             res = INTERNAL_ERROR;
147         } finally {
148             //@olsen: added support for timing statistics
149             if (main.opts.doTiming) {
150                 Support.timer.print();
151             }
152         }
153         System.exit(res);
154     }
155 
156     /***
157      * Process command line options and run enhancer.
158      */
159     public int process(String[] argv)
160     {
161         int res;
162 
163         if ((res = opts.processArgs(argv)) != OK) {
164             printMessage("aborted with errors.");
165             return res;
166         }
167 
168         //@olsen: added support for timing statistics
169         try {
170             if (opts.doTiming) {
171                 timer.push("Main.process(String[])");
172             }
173 
174             if ((res = createEnhancer()) != OK) {
175                 printMessage("aborted with errors.");
176                 return res;
177             }
178 
179             if ((res = enhanceInputFiles(opts.classNames,
180                                          opts.classFileNames,
181                                          opts.zipFileNames,
182                                          opts.jdoFileNames)) != OK) {
183                 printMessage("aborted with errors.");
184                 return res;
185             }
186 
187             printMessage("done.");
188             return 0;
189         } finally {
190             if (opts.doTiming) {
191                 timer.pop();
192             }
193         }
194     }
195 
196     // ----------------------------------------------------------------------
197 
198     /***
199      *  A class for holding the command line options.
200      */
201     private class CmdLineOptions
202     {
203         final List classNames = new ArrayList();        
204         final List classFileNames = new ArrayList();        
205         final List zipFileNames = new ArrayList();        
206         final List jdoFileNames = new ArrayList();        
207         String sourcePath = null;
208         String destinationDirectory = null;
209         String propertiesFileName = null;
210         boolean doTiming = false;
211         boolean verbose = false;
212         boolean quiet = false;
213         boolean forceWrite = false;
214         boolean noWrite = false;
215         boolean dumpClass = false;
216         boolean noAugment = false;
217         boolean noAnnotate = false;
218 
219         /***
220          * Print a usage message to System.err.
221          */
222         public void usage() {
223             err.println("Usage: main <options> <arguments>...");
224             err.println("Options:");
225             err.println("  -h, --help               print usage message and exit gently");
226             err.println("  -v, --verbose            print verbose messages");
227             err.println("  -q, --quiet              supress warnings");
228             err.println("  -s, --sourcepath <path>  source path for jdo and classfiles");
229             err.println("  -d, --destdir <dir>      destination directory for output files");
230             err.println("  -f, --force              overwrite output files");
231             err.println("  -n, --nowrite            never write output files");
232             err.println("  -t, --timing             do timing messures");
233             err.println();
234             err.println("Debugging Options:");
235             err.println("      --properties <file>  use property file for meta data");
236             err.println("      --dumpclass          print out disassembled code of classes");
237             err.println("      --noaugment          do not enhance for persistence-capability");
238             err.println("      --noannotate         do not enhance for persistence-awareness");
239             err.println();
240             err.println("Arguments:");
241             //err.println("  <class>      the fully qualified name of a Java class");
242             err.println("  <jdofile>    the name of a .jdo file");
243             err.println("  <classfile>  the name of a .class file");
244             //err.println("  <zipfile>    the name of a .zip or .jar file");
245             err.println();
246             err.println("Returns a non-zero value in case of errors.");
247         }
248 
249         /***
250          * Process command line options.
251          */
252         protected int processArgs(String[] argv)
253         {
254             final Collection inputNames = new ArrayList();
255             for (int i = 0; i < argv.length; i++) {
256                 final String arg = argv[i];
257                 if (arg.equals("-h")
258                     || arg.equals("--help")) {
259                     usage();
260                     return OK;
261                 }
262                 if (arg.equals("-v")
263                     || arg.equals("--verbose")) {
264                     verbose = true;
265                     quiet = false;
266                     continue;
267                 }
268                 if (arg.equals("-q")
269                     || arg.equals("--quiet")) {
270                     quiet = true;
271                     verbose = false;
272                     continue;
273                 }
274                 if (arg.equals("-t") ||
275                     arg.equals("--timing")) {
276                     doTiming = true;
277                     continue;
278                 }
279                 if (arg.equals("-f")
280                     || arg.equals("--force")) {
281                     forceWrite = true;
282                     noWrite = false;
283                     continue;
284                 }
285                 if (arg.equals("-n")
286                     || arg.equals("--nowrite")) {
287                     noWrite = true;
288                     forceWrite = false;
289                     continue;
290                 }
291                 if (arg.equals("--dumpclass")) {
292                     dumpClass = true;
293                     continue;
294                 }
295                 if (arg.equals("--noaugment")) {
296                     noAugment = true;
297                     continue;
298                 }
299                 if (arg.equals("--noannotate")) {
300                     noAnnotate = true;
301                     continue;
302                 }
303                 if (arg.equals("-s")
304                     || arg.equals("--sourcepath")) {
305                     if (argv.length - i < 2) {
306                         printError("Missing argument to the -s/--sourcepath option", null);
307                         usage();
308                         return USAGE_ERROR;
309                     }
310                     sourcePath = argv[++i];
311                     continue;
312                 }
313                 if (arg.equals("-d")
314                     || arg.equals("--destdir")) {
315                     if (argv.length - i < 2) {
316                         printError("Missing argument to the -d/-destdir option", null);
317                         usage();
318                         return USAGE_ERROR;
319                     }
320                     destinationDirectory = argv[++i];
321                     continue;
322                 }
323                 if (arg.equals("--properties")) {
324                     if (argv.length - i < 2) {
325                         printError("Missing argument to the --properties option", null);
326                         usage();
327                         return USAGE_ERROR;
328                     }
329                     propertiesFileName = argv[++i];
330                     continue;
331                 }
332                 if (arg.length() > 0 && arg.charAt(0) == '-') {
333                     printError("Unrecognized option:" + arg, null);
334                     usage();
335                     return USAGE_ERROR;
336                 }
337                 if (arg.length() == 0) {
338                     printMessage("Ignoring empty command line argument.");
339                     continue;
340                 }
341 
342                 inputNames.add(arg);
343             }
344 
345             // group input file arguments
346             for (Iterator names = inputNames.iterator(); names.hasNext();) {
347                 final String name = (String)names.next();
348                 if (isJdoFileName(name)) {
349                     jdoFileNames.add(name);
350                 } else if (isClassFileName(name)) {
351                     classFileNames.add(name);
352                 } else if (isZipFileName(name)) {
353                     zipFileNames.add(name);
354                 } else {
355                     classNames.add(name);
356                 }
357             }
358 
359             if (verbose) {
360                 printArgs();
361             }
362             return checkArgs();
363         }
364 
365         /***
366          * Check command line options.
367          */
368         protected int checkArgs()
369         {
370             // at least one class must be specified
371             if (classNames.isEmpty()
372                 && classFileNames.isEmpty()
373                 && zipFileNames.isEmpty()) {
374                 final String msg
375                     = "No classes specified";
376                 printError(msg, null);
377                 usage();
378                 return USAGE_ERROR;
379             }
380 
381             // at least one meta-data source must be specified for classfiles
382             if (classFileNames.size() > 0
383                 && (jdoFileNames.isEmpty()
384                     && propertiesFileName == null
385                     && sourcePath == null)) {
386                 final String msg
387                     = "No JDO meta-data source specified for class files";
388                 printError(msg, null);
389                 usage();
390                 return USAGE_ERROR;
391             }
392 
393             // either jdo files or jdo properties specified
394             if (!jdoFileNames.isEmpty() && propertiesFileName != null) {
395                 final String msg
396                     = "Cannot have both jdo files and properties specified";
397                 printError(msg, null);
398                 usage();
399                 return USAGE_ERROR;
400             }
401 
402             return OK;
403         }
404 
405         /***
406          * Print command line options.
407          */
408         protected void printArgs()
409         {
410             out.println("Enhancer: options:");
411             out.println("    verbose = " + verbose);
412             out.println("    quiet = " + quiet);
413             out.println("    forceWrite = " + forceWrite);
414             out.println("    noWrite = " + noWrite);
415             out.println("    sourcePath = " + sourcePath);
416             out.println("    destinationDirectory = " + destinationDirectory);
417             out.println("    propertiesFileName = " + propertiesFileName);
418             out.println("    doTiming = " + doTiming);
419             out.println("    classNames = {");
420             for (Iterator i = classNames.iterator(); i.hasNext();) {
421                 out.println("        " + i.next());
422             }
423             out.println("    }");
424             out.println("    jdoFileNames = {");
425             for (Iterator i = jdoFileNames.iterator(); i.hasNext();) {
426                 out.println("        " + i.next());
427             }
428             out.println("    }");
429             out.println("    classFileNames = {");
430             for (Iterator i = classFileNames.iterator(); i.hasNext();) {
431                 out.println("        " + i.next());
432             }
433             out.println("    }");
434             out.println("    zipFileNames = {");
435             for (Iterator i = zipFileNames.iterator(); i.hasNext();) {
436                 out.println("        " + i.next());
437             }
438             out.println("    }");
439             out.println("    dumpClass = " + dumpClass);
440             out.println("    noAugment = " + noAugment);
441             out.println("    noAnnotate = " + noAnnotate);
442         }
443     }
444 
445     private int initClassLocator()
446     {
447         final boolean verbose = opts.verbose;
448         final List classFileNames = opts.classFileNames;
449         final List zipFileNames = opts.zipFileNames;
450         final String sourcePath = opts.sourcePath;
451         try {
452             final List locators = new ArrayList();
453 
454             // create resource locator for specified class files
455             if (classFileNames != null && !classFileNames.isEmpty()) {
456                 final ResourceLocator classes
457                     = new ListResourceLocator(out, verbose, classFileNames);
458                 if (verbose) {
459                     out.println("Class Locator: using class files: {");
460                     for (Iterator i = classFileNames.iterator(); i.hasNext();) {
461                         out.println("    " + i.next());
462                     }
463                     out.println("}");
464                 }
465                 locators.add(classes);
466             }
467 
468             // create resource locator for specified zip files
469             if (zipFileNames != null && !zipFileNames.isEmpty()) {
470                 final StringBuffer s = new StringBuffer();
471                 final Iterator i = zipFileNames.iterator();
472                 s.append(i.next());
473                 while (i.hasNext()) {
474                     s.append(File.pathSeparator + i.next());
475                 }
476                 final ResourceLocator zips
477                     = new PathResourceLocator(out, verbose, s.toString());
478                 if (verbose)
479                     out.println("Class Locator: using jar/zip files: "
480                                 + s.toString());
481                 locators.add(zips);
482             }
483 
484             // create resource locator for specified source path
485             if (sourcePath != null && sourcePath.length() > 0) {
486                 final ResourceLocator path
487                     = new PathResourceLocator(out, verbose, sourcePath);
488                 if (verbose)
489                     out.println("Class Locator: using source path: "
490                                 + sourcePath);
491                 locators.add(path);
492             }
493 
494             // print warning if no classes specified
495             affirm(!locators.isEmpty());
496             //if (locators.isEmpty()) {
497             //    printWarning(getI18N("enhancer.using_no_classes"));
498             //}
499 
500             // init class locators
501             classLocator
502                 = new CombinedResourceLocator(out, verbose, locators);
503 
504             // wrap with timing class locator
505             if (opts.doTiming) {
506                 classLocator = new ResourceLocatorTimer(classLocator);
507             }
508         } catch (IOException ex) {
509             printError("Cannot initialize resource locator for classes", ex);
510             return CLASS_LOCATOR_ERROR;
511         }
512         return OK;
513     }
514 
515     private int initEnhancerMetaData()
516     {
517         final boolean verbose = opts.verbose;
518         final String propertiesFileName = opts.propertiesFileName;
519         final List jdoFileNames = opts.jdoFileNames;
520         final List zipFileNames = opts.zipFileNames;
521         final String sourcePath = opts.sourcePath;
522         try {
523             if (propertiesFileName != null) {
524                 jdoMetaData
525                     = new EnhancerMetaDataPropertyImpl(out, verbose,
526                                                        propertiesFileName);
527             } else {
528                 jdoMetaData
529                     = new EnhancerMetaDataJDOModelImpl(out, verbose,
530                                                        jdoFileNames,
531                                                        zipFileNames,
532                                                        sourcePath);
533             }
534 
535             // wrap with timing meta data object
536             if (opts.doTiming) {
537                 jdoMetaData = new EnhancerMetaDataTimer(jdoMetaData);
538             }
539         } catch (EnhancerMetaDataFatalError ex) {
540             printError("Cannot initialize JDO meta-data source", ex);
541             return METADATA_ERROR;
542         }
543         return OK;
544     }
545     
546     private int createEnhancer()
547     {
548         int res0 = initClassLocator();
549         if (res0 < 0) {
550             return res0;
551         }
552         affirm(classLocator != null);
553 
554         int res = initEnhancerMetaData();
555         if (res < 0) {
556             return res;
557         }
558         affirm(jdoMetaData != null);
559 
560         final Properties props = new Properties();
561         if (opts.verbose) {
562             props.put(EnhancerFilter.VERBOSE_LEVEL,
563                       EnhancerFilter.VERBOSE_LEVEL_VERBOSE);
564         }
565         
566         if (opts.doTiming) {
567             props.put(EnhancerFilter.DO_TIMING_STATISTICS,
568                       Boolean.TRUE.toString());
569         }
570 
571         if (opts.dumpClass) {
572             props.put(EnhancerFilter.DUMP_CLASS,
573                       Boolean.TRUE.toString());
574         }
575 
576         if (opts.noAugment) {
577             props.put(EnhancerFilter.NO_AUGMENT,
578                       Boolean.TRUE.toString());
579         }
580 
581         if (opts.noAnnotate) {
582             props.put(EnhancerFilter.NO_ANNOTATE,
583                       Boolean.TRUE.toString());
584         }
585 
586         try {            
587             enhancer = new EnhancerFilter(jdoMetaData, props, out, err);
588             if (opts.doTiming) {
589                 // wrap with timing byte-code enhancer
590                 enhancer = new ClassFileEnhancerTimer(enhancer);
591             }
592             return 0;
593         } catch (EnhancerUserException ex) {
594             printError("Error while creating the enhancer", ex);
595             return -1;
596         } catch (EnhancerFatalError ex) {
597             // enhancer is not anymore guaranteed to be consistent
598             printError("Fatal error while creating the enhancer", ex);
599             enhancer = null;
600             return -1;
601         }
602     }
603 
604     // ----------------------------------------------------------------------
605 
606     /***
607      *  Enhances all files entered in the command line.
608      *
609      *  @param  classNames  List of class names.
610      *  @param  classFileNames  List of class file names.
611      *  @param  zipFileNames  List of zip file names.
612      *  @param  jdoFileNames  List of jdo file names.
613      */
614     private int enhanceInputFiles(List classNames,
615                                   List classFileNames,
616                                   List zipFileNames,
617                                   List jdoFileNames)
618     {
619         int res = 0;
620         try {
621             String name = null;
622             for (Iterator i = zipFileNames.iterator(); i.hasNext();) {
623                 try {
624                     name = (String)i.next();
625                     enhanceZipFile(name);
626                 } catch (EnhancerUserException ex) {
627                     printError("Error while enhancing " + name, ex);
628                     res++;
629                     continue;
630                 }
631             }
632             for (Iterator i = classFileNames.iterator(); i.hasNext();) {
633                 try {
634                     name = (String)i.next();
635                     enhanceClassFile(openFileInputStream(name));
636                 } catch (EnhancerUserException ex) {
637                     printError("Error while enhancing " + name, ex);
638                     res++;
639                     continue;
640                 }
641             }
642             for (Iterator i = classNames.iterator(); i.hasNext();) {
643                 try {
644                     name = (String)i.next();
645                     enhanceClassFile(openClassInputStream(name));
646                 } catch (EnhancerUserException ex) {
647                     printError("Error while enhancing " + name, ex);
648                     res++;
649                     continue;
650                 }
651             }
652         } catch (IOException ex) {
653             printError("IO Error while enhancing", ex);
654             return ++res;
655         } catch (EnhancerFatalError ex) {
656             // enhancer is not anymore guaranteed to be consistent
657             printError("Fatal error while enhancing", ex);
658             enhancer = null;
659             return ++res;
660         }
661         return res;
662     }
663 
664     /***
665      *  Enhances a classfile.
666      *
667      *  @param  in  The input stream of the classfile.
668      */
669     private void enhanceClassFile(InputStream in)
670         throws IOException, EnhancerUserException, EnhancerFatalError
671     {
672         OutputStream out = null;
673         try {
674             final File temp = File.createTempFile("enhancer", ".class");
675             out = new BufferedOutputStream(new FileOutputStream(temp));
676 
677             //enhance
678             final OutputStreamWrapper wrapper = new OutputStreamWrapper(out);
679             final boolean enhanced = enhancer.enhanceClassFile(in, wrapper);
680 
681             closeOutputStream(out);
682             out = null;
683             createOutputFile(enhanced,
684                              createClassFileName(wrapper.getClassName()),
685                              temp);
686         } finally {
687             closeInputStream(in);
688             closeOutputStream(out);
689         }
690     }
691 
692     /***
693      *  Enhances a zipfile.
694      *
695      *  @param  filename  The filename of the zipfile.
696      */
697     private void enhanceZipFile(String filename)
698         throws IOException, EnhancerUserException, EnhancerFatalError
699     {
700         ZipInputStream in = null;
701         ZipOutputStream out = null;
702         try {
703             final File temp = File.createTempFile("enhancer", ".zip");
704             in = new ZipInputStream(new BufferedInputStream(
705                 new FileInputStream(new File(filename))));
706             out = new ZipOutputStream(new BufferedOutputStream(
707                 new FileOutputStream(temp)));
708 
709             //enhance the zipfile
710             final boolean enhanced
711                 = ClassFileEnhancerHelper.enhanceZipFile(enhancer, in, out);
712 
713             //create the output file
714             closeOutputStream(out);
715             out = null;
716             createOutputFile(enhanced, new File(filename).getName(), temp);
717         } finally {
718             closeOutputStream(out);
719             closeInputStream(in);
720         }
721     }
722 
723     /***
724      *  Opens an input stream for the given filename
725      *
726      *  @param  filename  The name of the file.
727      *  @return  The input stream.
728      *  @exception  FileNotFoundException  If the file could not be found.
729      */
730     static private InputStream openFileInputStream(String filename)
731         throws FileNotFoundException
732     {
733      	return new BufferedInputStream(new FileInputStream(new File(filename)));
734     }
735 
736     /***
737      * Opens an input stream for the given classname. The input stream is
738      * created via an URL that is obtained by the value of the sourcepath
739      * option and zip/jar file arguments.
740      * 
741      * @param  classname  The name of the class (dot-notation).
742      * @return  The input stream.
743      * @exception IOException If an I/O error occured.
744      */
745     private InputStream openClassInputStream(String classname)
746         throws IOException
747     {
748         final String resourcename = createClassFileName(classname);
749         return classLocator.getInputStreamForResource(resourcename);
750     }
751 
752     /***
753      *  Creates a file object that represents the output zipfile for a given
754      *  zipfile to enhance.
755      *
756      *  @param  zipfilename  The input zipfile name.
757      *  @return  The output zipfile name.
758      */
759     private File createZipOutputFile(String zipfilename)
760     {
761         return new File(opts.destinationDirectory,
762                         new File(zipfilename).getName());
763     }
764 
765     /***
766      *  Creates the output file for an enhaced class- or zipfile. If the
767      *  enhanced file is written back depends on the command line options.
768      *
769      *  @param  enhanced  Has the input file been enhanced?
770      *  @param  filename  The name of the output file.
771      *  @param  temp      The temp file, the output is written to.
772      *  @exception  IOException  If the file could not be created.
773      */
774     private void createOutputFile(boolean  enhanced,
775                                   String   filename,
776                                   File     temp)
777         throws IOException
778     {
779         //noWrite or (not enhanced and not forceWrite)
780         if (opts.noWrite || (!enhanced && !opts.forceWrite)) {
781             temp.deleteOnExit();
782             return;
783         }
784 
785         File file = new File(opts.destinationDirectory, filename);
786         createPathOfFile(file);
787         file.delete();  //delete old file if exists
788         boolean renamed = temp.renameTo(file);
789         if (!renamed) {
790             //@dave: empirical evidence shows that renameTo does not allow for
791             // crossing filesystem boundaries.  If it fails, try "by hand".
792             InputStream in = null;
793             OutputStream out = null;
794             try {
795                 in = new FileInputStream(temp);
796                 out = new FileOutputStream(file);
797                 int PAGESIZE = 4096; // Suggest a better size?
798                 byte data[] = new byte[PAGESIZE];
799                 while (in.available() > 0) {
800                     int numRead = in.read(data, 0, PAGESIZE);
801                     out.write(data, 0, numRead);
802                 }
803                 renamed = true;
804             } catch (IOException ex) {
805                 throw new IOException("Could not rename temp file '" +
806                                       temp.getAbsolutePath() +
807                                       "' to '" + file.getAbsolutePath()
808                                       + "': " + ex);
809             } finally {
810                 closeInputStream(in);
811                 closeOutputStream(out);
812             }
813             if (renamed) {
814                 temp.delete();  //delete temporary file
815             }
816             else {
817                 throw new IOException("Could not rename temp file '" +
818                                       temp.getAbsolutePath() +
819                                       "' to '" + file.getAbsolutePath() + "'.");
820             }
821         }
822     }
823 
824     /***
825      *  Closes an input stream.
826      *
827      *  @param  in  The input stream.
828      */
829     private void closeInputStream(InputStream in)
830     {
831         if (in != null) {
832             try {
833                 in.close();
834             } catch (IOException ex) {
835                 printError(null, ex);
836             }
837         }
838     }
839 
840     /***
841      *  Closes an output stream.
842      *
843      *  @param  out  The output stream.
844      */
845     private void closeOutputStream(OutputStream out)
846     {
847         if (out != null) {
848             try {
849                 out.close();
850             } catch (IOException ex) {
851                 printError(null, ex);
852             }
853         }
854     }
855 
856     /***
857      *  Tests if a filename is a classfile name (by testing if the filename
858      *  ends with <code>".class"</code>).
859      *
860      *  @param  filename  The name of the file.
861      *  @return  Do we have a potential classfile?
862      */
863     static private boolean isClassFileName(String filename)
864     {
865         return filename.toLowerCase().endsWith(".class");
866     }
867 
868     /***
869      *  Tests if a filename is a zipfile (by testing if the filename
870      *  ends with <code>".zip"</code> or <code>".jar"</code>).
871      *
872      *  @param  filename  The name of the file.
873      */
874     static private boolean isZipFileName(String filename)
875     {
876         final int n = filename.length();
877         if (n < 5) {
878             return false;
879         }
880         final String ext = filename.substring(n - 4);
881         return ext.equalsIgnoreCase(".zip") || ext.equalsIgnoreCase(".jar");
882     }
883 
884     /***
885      *  Tests if a filename is a jdo file name (by testing if the filename
886      *  ends with <code>".jdo"</code>).
887      *
888      *  @param  filename  The name of the file.
889      *  @return  Do we have a potential jdo file?
890      */
891     static private boolean isJdoFileName(String filename)
892     {
893         return filename.toLowerCase().endsWith(".jdo");
894     }
895 
896     /***
897      *  Creates a filename from a classname.
898      *  This is done by replacing <code>'.'</code> by <code>'/'</code>.
899      *
900      *  @param  classname  The classname.
901      *  @return  The filename.
902      */
903     static private String createClassFileName(String classname)
904     {
905         return classname.replace('.', '/') + ".class";
906     }
907 
908     /***
909      *  Creates only the path of the given file.
910      *
911      *  @param  file  The file.
912      *  @exception  IOException  If an error occured.
913      */
914     static private void createPathOfFile(File file)
915         throws IOException
916     {
917         File dir = file.getAbsoluteFile().getParentFile();
918         if (!dir.exists() && !dir.mkdirs()) {
919             throw new IOException("Error creating directory '"
920                                   + dir.getAbsolutePath() + "'.");
921         }
922     }
923 
924     // ----------------------------------------------------------------------
925 
926     /***
927      *  Prints out an error.
928      *
929      *  @param  msg  The error message (can be <code>null</code>).
930      *  @param  ex   An optional exception (can be <code>null</code>).
931      */
932     private void printError(String msg,
933                             Throwable ex)
934     {
935         out.flush();
936         if (msg != null) {
937             err.println(msg);
938         }
939         if (ex != null) {
940             if (opts.verbose) {
941                 ex.printStackTrace(err);
942             }
943             else {
944                 err.println(ex.toString());
945             }
946         }
947     }
948 
949     /***
950      *  Prints out a message.
951      *
952      *  @param  msg  The message.
953      */
954     private void printMessage(String msg)
955     {
956         out.println(msg);
957     }
958 }