View Javadoc

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