View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.compiler;
9   
10  import org.codehaus.aspectwerkz.definition.DefinitionLoader;
11  import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
12  import org.codehaus.aspectwerkz.hook.ClassPreProcessor;
13  
14  import java.io.ByteArrayInputStream;
15  import java.io.ByteArrayOutputStream;
16  import java.io.File;
17  import java.io.FileInputStream;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URL;
22  import java.net.URLClassLoader;
23  import java.text.SimpleDateFormat;
24  import java.util.ArrayList;
25  import java.util.Date;
26  import java.util.Enumeration;
27  import java.util.HashMap;
28  import java.util.Hashtable;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.jar.Attributes;
33  import java.util.jar.Manifest;
34  import java.util.zip.CRC32;
35  import java.util.zip.ZipEntry;
36  import java.util.zip.ZipFile;
37  import java.util.zip.ZipOutputStream;
38  
39  /***
40   * AspectWerkzC allow for precompilation of class / jar / zip given a class preprocessor. <p/>
41   * <h2>Usage</h2>
42   * <p/>
43   * <pre>
44   *     java [-Daspectwerkz.classloader.preprocessor={ClassPreProcessorImpl}] -cp [...]
45   *     org.codehaus.aspectwerkz.compiler.AspectWerkzC [-verbose] [-haltOnError] [-verify] [-cp {additional cp i}]*  {target
46   *     1} .. {target n}
47   *       {ClassPreProcessorImpl} : full qualified name of the ClassPreProcessor implementation (must be in classpath)
48   *          defaults to org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor
49   *       {additional cp i} : additionnal classpath needed at compile time (eg: myaspect.jar)
50   *          use as many -cp options as needed
51   *          supports java classpath syntax for classpath separator: ; on windows, : on others
52   *       {target i} : exploded dir, jar, zip files to compile
53   *       Ant 1.5 must be in the classpath
54   * </pre>
55   * <p/>
56   * <h2>Classpath note</h2>
57   * At the beginning of the compilation, all {target i} are added to the classpath automatically. <br/>This is required
58   * to support caller side advices. <p/>
59   * <h2>Error handling</h2>
60   * For each target i, a backup copy is written in ./_aspectwerkzc/i/target <br/>Transformation occurs on original target
61   * class/dir/jar/zip file <br/>On failure, target backup is restored and stacktrace is given <br/><br/>If
62   * <i>-haltOnError </i> was set, compilations ends and a <b>complete </b> rollback occurs on all targets, else a status
63   * report is printed at the end of the compilation, indicating SUCCESS or ERROR for each given target. <br/>If
64   * <i>-verify </i> was set, all compiled class are verified during the compilation and an error is generated if the
65   * compiled class bytecode is corrupted. The error is then handled according to the <i>-haltOnError </i> option. <br/>
66   * <p/>
67   * <h2>Manifest.mf update</h2>
68   * The Manifest.mf if present is updated wit the following:
69   * <ul>
70   * <li>AspectWerkzC-created: date of the compilation</li>
71   * <li>AspectWerkzC-preprocessor: full qualified classname of the preprocessor used</li>
72   * <li>AspectWerkzC-comment: comments</li>
73   * </ul>
74   *
75   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
76   */
77  public class AspectWerkzC {
78      // COMMAND LINE OPTIONS
79      private static final String COMMAND_LINE_OPTION_DASH = "-";
80      private static final String COMMAND_LINE_OPTION_VERBOSE = "-verbose";
81      private static final String COMMAND_LINE_OPTION_HALT = "-haltOnError";
82      private static final String COMMAND_LINE_OPTION_VERIFY = "-verify";
83      private static final String COMMAND_LINE_OPTION_CLASSPATH = "-cp";
84      private static final String COMMAND_LINE_OPTION_TARGETS = "compile.targets";
85  
86      /***
87       * option used to defined the class preprocessor
88       */
89      private static final String PRE_PROCESSOR_CLASSNAME_PROPERTY = "aspectwerkz.classloader.preprocessor";
90  
91      /***
92       * default class preprocessor
93       */
94      private static final String PRE_PROCESSOR_CLASSNAME_DEFAULT = "org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor";
95  
96      private final static String MF_CUSTOM_DATE = "X-AspectWerkzC-created";
97  
98      private final static String MF_CUSTOM_PP = "X-AspectWerkzC-preprocessor";
99  
100     private final static String MF_CUSTOM_COMMENT = "X-AspectWerkzC-comment";
101 
102     private final static String MF_CUSTOM_COMMENT_VALUE = "AspectWerkzC - AspectWerkz compiler, aspectwerkz.codehaus.org";
103 
104     private final static SimpleDateFormat DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
105 
106     private final static String BACKUP_DIR = "_aspectwerkzc";
107 
108     private boolean verify = false;
109 
110     private boolean haltOnError = false;
111 
112     private String backupDir = BACKUP_DIR;
113 
114     /***
115      * class loader in which the effective compilation occurs, child of system classloader
116      */
117     private URLClassLoader compilationLoader = null;
118 
119     /***
120      * class preprocessor instance used to compile targets
121      */
122     private ClassPreProcessor preprocessor = null;
123 
124     /***
125      * index to keep track of {target i} backups
126      */
127     private int sourceIndex;
128 
129     /***
130      * Maps the target file to the target backup file
131      */
132     private Map backupMap = new HashMap();
133 
134     /***
135      * Maps the target file to a status indicating compilation was successfull
136      */
137     private Map successMap = new HashMap();
138 
139     private long timer;
140 
141     /***
142      * Utility for file manipulation
143      */
144     private Utility utility;
145 
146     /***
147      * Construct a new Utility, restore the index for backup
148      */
149     public AspectWerkzC() {
150         //@todo check for multiple transformation in compiler or in preprocessor ?
151         sourceIndex = 0;
152         utility = new Utility();
153         timer = System.currentTimeMillis();
154     }
155 
156     /*
157      * public void log(String msg) { utility.log(msg); } public void log(String msg, Throwable t) { utility.log(msg);
158      * t.printStackTrace(); }
159      */
160     public void setVerbose(boolean verbose) {
161         utility.setVerbose(verbose);
162     }
163 
164     public void setHaltOnError(boolean haltOnError) {
165         this.haltOnError = haltOnError;
166     }
167 
168     public void setVerify(boolean verify) {
169         this.verify = verify;
170     }
171 
172     public void setBackupDir(String backup) {
173         this.backupDir = backup;
174     }
175 
176     public Utility getUtility() {
177         return utility;
178     }
179 
180     /***
181      * Sets the ClassPreProcessor implementation to use. <p/>The ClassLoader will be set to System ClassLoader when
182      * transform(className, byteCode, callerClassLoader) will be called to compile a class.
183      */
184     public void setPreprocessor(String preprocessor) throws CompileException {
185         try {
186             Class pp = Class.forName(preprocessor);
187             this.preprocessor = (ClassPreProcessor) pp.newInstance();
188             this.preprocessor.initialize(new Hashtable());
189         } catch (Exception e) {
190             throw new CompileException("failed to instantiate preprocessor " + preprocessor, e);
191         }
192     }
193 
194     /***
195      * Backup source file in backup_dir/index/file. The backupMap is updated for further rollback
196      */
197     public void backup(File source, int index) {
198         // backup source in BACKUP/index dir
199         File dest = new File(this.backupDir + File.separator + index + File.separator + source.getName());
200         utility.backupFile(source, dest);
201 
202         // add to backupMap in case of rollback
203         backupMap.put(source, dest);
204     }
205 
206     /***
207      * Restore the backup registered
208      */
209     public void restoreBackup() {
210         for (Iterator i = backupMap.keySet().iterator(); i.hasNext();) {
211             File source = (File) i.next();
212             if (!successMap.containsKey(source)) {
213                 File dest = (File) backupMap.get(source);
214                 utility.backupFile(dest, source);
215             }
216         }
217     }
218 
219     /***
220      * Delete backup dir at the end of all compilation
221      */
222     public void postCompile(String message) {
223         restoreBackup();
224         utility.log(" [backup] removing backup");
225         utility.deleteDir(new File(this.backupDir));
226         long ms = Math.max(System.currentTimeMillis() - timer, 1 * 1000);
227         System.out.println("( " + (int) (ms / 1000) + " s ) " + message);
228         if (!haltOnError) {
229             for (Iterator i = backupMap.keySet().iterator(); i.hasNext();) {
230                 File source = (File) i.next();
231                 if (successMap.containsKey(source)) {
232                     System.out.println("SUCCESS: " + source);
233                 } else {
234                     System.out.println("FAILED : " + source);
235                 }
236             }
237         }
238     }
239 
240     /***
241      * Compile sourceFile. If prefixPackage is not null, assumes it is the class package information. <p/>Handles :
242      * <ul>
243      * <li>directory recursively (exploded jar)</li>
244      * <li>jar / zip file</li>
245      * </ul>
246      */
247     public void doCompile(File sourceFile, String prefixPackage) throws CompileException {
248         if (sourceFile.isDirectory()) {
249             File[] classes = sourceFile.listFiles();
250             for (int i = 0; i < classes.length; i++) {
251                 if (classes[i].isDirectory() && !(this.backupDir.equals(classes[i].getName()))) {
252                     String packaging = (prefixPackage != null) ? (prefixPackage + "." + classes[i]
253                             .getName()) : classes[i].getName();
254                     doCompile(classes[i], packaging);
255                 } else if (classes[i].getName().toLowerCase().endsWith(".class")) {
256                     compileClass(classes[i], prefixPackage);
257                 } else if (isJarFile(classes[i])) {
258                     //@todo: jar encountered in a dir - use case ??
259                     compileJar(classes[i]);
260                 }
261             }
262         } else if (sourceFile.getName().toLowerCase().endsWith(".class")) {
263             compileClass(sourceFile, null);
264         } else if (isJarFile(sourceFile)) {
265             compileJar(sourceFile);
266         }
267     }
268 
269     /***
270      * Compiles .class file using fileName as className and given packaging as package name
271      */
272     public void compileClass(File file, String packaging) throws CompileException {
273         InputStream in = null;
274         FileOutputStream fos = null;
275         try {
276             utility.log(" [compile] " + file.getCanonicalPath());
277 
278             // dump bytecode in byte[]
279             ByteArrayOutputStream bos = new ByteArrayOutputStream();
280             in = new FileInputStream(file);
281             byte[] buffer = new byte[1024];
282             while (in.available() > 0) {
283                 int length = in.read(buffer);
284                 if (length == -1) {
285                     break;
286                 }
287                 bos.write(buffer, 0, length);
288             }
289 
290             // rebuild className
291             String className = file.getName().substring(0, file.getName().length() - 6);
292             if (packaging != null) {
293                 className = packaging + '.' + className;
294             }
295 
296             // transform
297             byte[] transformed = null;
298             try {
299                 transformed = preprocessor.preProcess(className, bos.toByteArray(), compilationLoader);
300             } catch (Throwable t) {
301                 throw new CompileException("weaver failed for class: " + className, t);
302             }
303 
304             // override file
305             fos = new FileOutputStream(file);
306             fos.write(transformed);
307             fos.close();
308 
309             // verify modified class
310             if (verify) {
311                 URLClassLoader verifier = new VerifierClassLoader(
312                         compilationLoader.getURLs(),
313                         ClassLoader.getSystemClassLoader()
314                 );
315                 try {
316                     utility.log(" [verify] " + className);
317                     Class.forName(className, false, verifier);
318                 } catch (Throwable t) {
319                     utility.log(" [verify] corrupted class: " + className);
320                     throw new CompileException("corrupted class: " + className, t);
321                 }
322             }
323         } catch (IOException e) {
324             throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
325         } finally {
326             try {
327                 in.close();
328             } catch (Throwable e) {
329                 ;
330             }
331             try {
332                 fos.close();
333             } catch (Throwable e) {
334                 ;
335             }
336         }
337     }
338 
339     /***
340      * Compile all .class encountered in the .jar/.zip file. <p/>The target.jar is compiled in the
341      * target.jar.aspectwerkzc and the target.jar.aspectwerkzc then overrides target.jar on success.
342      */
343     public void compileJar(File file) throws CompileException {
344         utility.log(" [compilejar] " + file.getAbsolutePath());
345 
346         // create an empty jar target.jar.aspectwerkzc
347         File workingFile = new File(file.getAbsolutePath() + ".aspectwerkzc");
348         if (workingFile.exists()) {
349             workingFile.delete();
350         }
351         ZipFile zip = null;
352         ZipOutputStream zos = null;
353         try {
354             zip = new ZipFile(file);
355             zos = new ZipOutputStream(new FileOutputStream(workingFile));
356             for (Enumeration e = zip.entries(); e.hasMoreElements();) {
357                 ZipEntry ze = (ZipEntry) e.nextElement();
358 
359                 // dump bytes read in byte[]
360                 InputStream in = zip.getInputStream(ze);
361                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
362                 byte[] buffer = new byte[1024];
363                 while (in.available() > 0) {
364                     int length = in.read(buffer);
365                     if (length == -1) {
366                         break;
367                     }
368                     bos.write(buffer, 0, length);
369                 }
370                 in.close();
371 
372                 // transform only .class file
373                 byte[] transformed = null;
374                 if (ze.getName().toLowerCase().endsWith(".class")) {
375                     utility.log(" [compilejar] compile " + file.getName() + ":" + ze.getName());
376                     String className = ze.getName().substring(0, ze.getName().length() - 6);
377                     try {
378                         transformed = preprocessor.preProcess(
379                                 className, bos.toByteArray(),
380                                 compilationLoader
381                         );
382                     } catch (Throwable t) {
383                         throw new CompileException("weaver failed for class: " + className, t);
384                     }
385                 } else {
386                     transformed = bos.toByteArray();
387                 }
388 
389                 // customize Manifest.mf
390                 if (ze.getName().toLowerCase().equals("meta-inf/manifest.mf")) {
391                     try {
392                         Manifest mf = new Manifest(new ByteArrayInputStream(transformed));
393                         Attributes at = mf.getMainAttributes();
394                         at.putValue(MF_CUSTOM_DATE, DF.format(new Date()));
395                         at.putValue(MF_CUSTOM_PP, preprocessor.getClass().getName());
396                         at.putValue(MF_CUSTOM_COMMENT, MF_CUSTOM_COMMENT_VALUE);
397 
398                         // re read the updated manifest
399                         bos.reset();
400                         mf.write(bos);
401                         transformed = bos.toByteArray();
402                     } catch (Exception emf) {
403                         emf.printStackTrace();
404                     }
405                 }
406 
407                 // update target.jar.aspectwerkzc working file
408                 ZipEntry transformedZe = new ZipEntry(ze.getName());
409                 transformedZe.setSize(transformed.length);
410                 CRC32 crc = new CRC32();
411                 crc.update(transformed);
412                 transformedZe.setCrc(crc.getValue());
413                 transformedZe.setMethod(ze.getMethod());
414                 zos.putNextEntry(transformedZe);
415                 zos.write(transformed, 0, transformed.length);
416             }
417             zip.close();
418             zos.close();
419 
420             // replace file by workingFile
421             File swap = new File(file.getAbsolutePath() + ".swap.aspectwerkzc");
422             utility.backupFile(file, swap);
423             try {
424                 utility.backupFile(workingFile, new File(file.getAbsolutePath()));
425                 workingFile.delete();
426                 swap.delete();
427             } catch (Exception e) {
428                 // restore swapFile
429                 utility.backupFile(swap, new File(file.getAbsolutePath()));
430                 workingFile.delete();
431                 throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
432             }
433         } catch (IOException e) {
434             throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
435         } finally {
436             try {
437                 zos.close();
438             } catch (Throwable e) {
439                 ;
440             }
441             try {
442                 zip.close();
443             } catch (Throwable e) {
444                 ;
445             }
446         }
447     }
448 
449     /***
450      * Compile given target.
451      *
452      * @return false if process should stop
453      */
454     public boolean compile(File source) {
455         sourceIndex++;
456         backup(source, sourceIndex);
457         try {
458             doCompile(source, null);
459         } catch (CompileException e) {
460             utility.log(" [aspectwerkzc] compilation encountered an error");
461             e.printStackTrace();
462             return (!haltOnError);
463         }
464 
465         // compile sucessfull
466         successMap.put(source, Boolean.TRUE);
467         return true;
468     }
469 
470     /***
471      * Set up the compilation path by building a URLClassLoader with all targets in
472      *
473      * @param targets      to add to compilationLoader classpath
474      * @param parentLoader the parent ClassLoader used by the new one
475      */
476     public void setCompilationPath(File[] targets, ClassLoader parentLoader) {
477         URL[] urls = new URL[targets.length];
478         int j = 0;
479         for (int i = 0; i < targets.length; i++) {
480             try {
481                 urls[j] = targets[i].getCanonicalFile().toURL();
482                 j++;
483             } catch (IOException e) {
484                 System.err.println("bad target " + targets[i]);
485             }
486         }
487 
488         compilationLoader = new URLClassLoader(urls, parentLoader);
489     }
490 
491     /***
492      * Test if file is a zip/jar file
493      */
494     public static boolean isJarFile(File source) {
495         return (source.isFile() && (source.getName().toLowerCase().endsWith(".jar") || source
496                 .getName().toLowerCase().endsWith(".zip")));
497     }
498 
499     /***
500      * Usage message
501      */
502     public static void doHelp() {
503         System.out.println("--- AspectWerkzC compiler ---");
504         System.out.println("Usage:");
505         System.out
506                 .println(
507                         "java -cp ... org.codehaus.aspectwerkz.compiler.AspectWerkzC [-verbose] [-haltOnError] [-verify]  <target 1> .. <target n>"
508                 );
509         System.out.println("  <target i> : exploded dir, jar, zip files to compile");
510     }
511 
512     /***
513      * Creates and configures an AspectWerkzC compiler.
514      *
515      * @param params a map containing the compiler parameters
516      * @return a new and configured <CODE>AspectWerkzC</CODE>
517      */
518     private static AspectWerkzC createCompiler(Map params) {
519         AspectWerkzC compiler = new AspectWerkzC();
520 
521         for (Iterator it = params.entrySet().iterator(); it.hasNext();) {
522             Map.Entry param = (Map.Entry) it.next();
523 
524             if (COMMAND_LINE_OPTION_VERBOSE.equals(param.getKey())) {
525                 compiler.setVerbose(Boolean.TRUE.equals(param.getValue()));
526             } else if (COMMAND_LINE_OPTION_HALT.equals(param.getKey())) {
527                 compiler.setHaltOnError(Boolean.TRUE.equals(param.getValue()));
528             } else if (COMMAND_LINE_OPTION_VERIFY.equals(param.getKey())) {
529                 compiler.setVerify(Boolean.TRUE.equals(param.getValue()));
530             }
531         }
532 
533         return compiler;
534     }
535 
536     /***
537      * Runs the AspectWerkzC compiler for the <tt>targets</tt> files.
538      *
539      * @param compiler     a configured <CODE>AspectWerkzC</CODE>
540      * @param classLoader  the class loader to be used
541      * @param preProcessor fully qualified name of the preprocessor class.
542      *                     If <tt>null</tt> than the default is used
543      *                     (<CODE>org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor</CODE>)
544      * @param classpath    list of Files representing the classpath (List<File>)
545      * @param targets      the list of target files (List<File>)
546      */
547     public static void compile(AspectWerkzC compiler,
548                                ClassLoader classLoader,
549                                String preProcessor,
550                                List classpath,
551                                List targets) {
552 
553         List fullPath = new ArrayList();
554         if (classpath != null) {
555             fullPath.addAll(classpath);
556         }
557 
558         fullPath.addAll(targets);
559 
560         compiler.setCompilationPath((File[]) fullPath.toArray(new File[fullPath.size()]), classLoader);
561 
562         Thread.currentThread().setContextClassLoader(compiler.compilationLoader);
563 
564         // AOPC special fix
565         // turn off -Daspectwerkz.definition.file registration and register it at the
566         // compilationLoader level instead
567         SystemDefinitionContainer.disableSystemWideDefinition();
568         SystemDefinitionContainer.deploySystemDefinitions(
569                 compiler.compilationLoader,
570                 DefinitionLoader.getDefaultDefinition(compiler.compilationLoader)
571         );
572 
573         String preprocessorFqn = preProcessor == null ? PRE_PROCESSOR_CLASSNAME_DEFAULT
574                 : preProcessor;
575 
576         try {
577             compiler.setPreprocessor(preprocessorFqn);
578         } catch (CompileException e) {
579             System.err.println("Cannot instantiate ClassPreProcessor: " + preprocessorFqn);
580             e.printStackTrace();
581             System.exit(-1);
582         }
583 
584         cleanBackupDir(compiler);
585 
586         for (Iterator i = targets.iterator(); i.hasNext();) {
587             if (!compiler.compile((File) i.next())) {
588                 compiler.postCompile("*** An error occured ***");
589                 System.exit(-1);
590             }
591         }
592         compiler.postCompile("");
593     }
594 
595     private static void cleanBackupDir(AspectWerkzC compiler) {
596         // prepare backup directory
597         try {
598             File temp = new File(compiler.backupDir);
599             if (temp.exists()) {
600                 compiler.getUtility().deleteDir(temp);
601             }
602             temp.mkdir();
603             (new File(temp, "" + System.currentTimeMillis() + ".timestamp")).createNewFile();
604         } catch (Exception e) {
605             System.err.println("failed to prepare backup dir: " + compiler.backupDir);
606             e.printStackTrace();
607             System.exit(-1);
608         }
609     }
610 
611     public static void main(String[] args) {
612         if (args.length <= 0) {
613             doHelp();
614             return; //stop here
615         }
616 
617         Map options = parseOptions(args);
618         AspectWerkzC compiler = createCompiler(options);
619 
620         compiler.setBackupDir(BACKUP_DIR);
621 
622         compile(
623                 compiler,
624                 ClassLoader.getSystemClassLoader(),
625                 System.getProperty(
626                         PRE_PROCESSOR_CLASSNAME_PROPERTY,
627                         PRE_PROCESSOR_CLASSNAME_DEFAULT
628                 ),
629                 (List) options.get(COMMAND_LINE_OPTION_CLASSPATH),
630                 (List) options.get(COMMAND_LINE_OPTION_TARGETS)
631         );
632     }
633 
634     private static Map parseOptions(String[] args) {
635         Map options = new HashMap();
636         List targets = new ArrayList();
637 
638         for (int i = 0; i < args.length; i++) {
639             if (COMMAND_LINE_OPTION_VERBOSE.equals(args[i])) {
640                 options.put(COMMAND_LINE_OPTION_VERBOSE, Boolean.TRUE);
641             } else if (COMMAND_LINE_OPTION_HALT.equals(args[i])) {
642                 options.put(COMMAND_LINE_OPTION_HALT, Boolean.TRUE);
643             } else if (COMMAND_LINE_OPTION_VERIFY.equals(args[i])) {
644                 options.put(COMMAND_LINE_OPTION_VERIFY, Boolean.TRUE);
645             } else if (COMMAND_LINE_OPTION_CLASSPATH.equals(args[i])) {
646                 if (i == (args.length - 1)) {
647                     continue; //FIXME: this is an error
648                 } else {
649                     options.put(
650                             COMMAND_LINE_OPTION_CLASSPATH,
651                             toFileArray(args[++i], File.pathSeparator)
652                     );
653                 }
654             } else if (args[i].startsWith(COMMAND_LINE_OPTION_DASH)) {
655                 ; // nothing to be done about it
656             } else {
657                 File file = toFile(args[i]);
658                 if (file == null) {
659                     System.err.println("Ignoring inexistant target: " + args[i]);
660                 } else {
661                     targets.add(file);
662                 }
663             }
664         }
665 
666         options.put(COMMAND_LINE_OPTION_TARGETS, targets);
667 
668         return options;
669     }
670 
671     private static List toFileArray(String str, String sep) {
672         if (str == null || str.length() == 0) {
673             return new ArrayList();
674         }
675 
676         List files = new ArrayList();
677         int start = 0;
678         int idx = str.indexOf(sep, start);
679         int len = sep.length();
680 
681         while (idx != -1) {
682             files.add(new File(str.substring(start, idx)));
683             start = idx + len;
684             idx = str.indexOf(sep, start);
685         }
686 
687         files.add(new File(str.substring(start)));
688 
689         return files;
690     }
691 
692     private static File toFile(String path) {
693         File file = new File(path);
694 
695         return file.exists() ? file : null;
696     }
697 }