View Javadoc

1   /***
2    * Cecilia ADL Compiler
3    * Copyright (C) 2006-2008 STMicroelectronics
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   *
19   * Contact: fractal@objectweb.org
20   *
21   * Author: Matthieu Leclercq
22   */
23  
24  package org.objectweb.fractal.cecilia.adl;
25  
26  import java.io.File;
27  import java.io.PrintStream;
28  import java.net.MalformedURLException;
29  import java.net.URL;
30  import java.net.URLClassLoader;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.LinkedHashMap;
35  import java.util.LinkedHashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  /**
41   * Abstract Cecilia compiler Launcher.
42   */
43  public abstract class AbstractLauncher {
44  
45    protected final CmdPathOption srcPathOpt = new CmdPathOption(
46                                                 "S",
47                                                 "src-path",
48                                                 "the search path of ADL,IDL and implementation files (list of path separated by '"
49                                                     + File.pathSeparator + "')",
50                                                 "<path list>");
51  
52    protected final CmdArgument   outDirOpt  = new CmdArgument(
53                                                 "o",
54                                                 "out-path",
55                                                 "the path where generated files will be put",
56                                                 "<output path>", ".", false);
57  
58    protected final CmdFlag       helpOpt    = new CmdFlag("h", "help",
59                                                 "Print this help and exit");
60  
61    protected final Options       options    = new Options();
62    {
63      options.addOptions(helpOpt, srcPathOpt, outDirOpt);
64    }
65  
66    // ---------------------------------------------------------------------------
67    // Utility methods
68    // ---------------------------------------------------------------------------
69  
70    protected ClassLoader getSourceClassLoader(final List<String> srcPath) {
71      final List<String> validatedPaths = new ArrayList<String>(srcPath.size());
72  
73      // check source paths
74      for (final String path : srcPath) {
75        final File f = new File(path);
76        if (!f.exists()) {
77          System.out.println("Warning '" + f.getAbsolutePath()
78              + "' source path can't be found ");
79        } else if (!f.isDirectory()) {
80          System.out.println("Warning: \"" + path
81              + "\" is not a directory, path ignored.");
82        } else {
83          validatedPaths.add(path);
84        }
85      }
86  
87      // build URL array of source path
88      final URL[] urls = new URL[validatedPaths.size()];
89      for (int i = 0; i < urls.length; i++) {
90        final String path = validatedPaths.get(i);
91        final File f = new File(path);
92        try {
93          urls[i] = f.toURI().toURL();
94        } catch (final MalformedURLException e) {
95          // never append
96          throw new Error(e);
97        }
98      }
99  
100     return new URLClassLoader(urls, getClass().getClassLoader());
101   }
102 
103   protected ClassLoader getSourceClassLoader(final CommandLine cmdLine) {
104     List<String> srcPath = srcPathOpt.getPathValue(cmdLine);
105     if (srcPath == null) {
106       srcPath = new ArrayList<String>(1);
107       srcPath.add(".");
108     }
109 
110     return getSourceClassLoader(srcPath);
111   }
112 
113   protected static File newBuildDir(final File outDir, final String dirName)
114       throws InvalidCommandLineException {
115     final File d = new File(outDir, dirName);
116     checkDir(d);
117     return d;
118   }
119 
120   protected static File newBuildDir(final String name)
121       throws InvalidCommandLineException {
122     final File d = new File(name);
123     checkDir(d);
124     return d;
125   }
126 
127   protected static void checkDir(final File d)
128       throws InvalidCommandLineException {
129     if (d.exists() && !d.isDirectory())
130       throw new InvalidCommandLineException("Invalid build directory '"
131           + d.getAbsolutePath() + "' not a directory", 6);
132   }
133 
134   protected static List<String> parsePathList(String paths) {
135     final List<String> l = new ArrayList<String>();
136     int index = paths.indexOf(File.pathSeparatorChar);
137     while (index != -1) {
138       l.add(paths.substring(0, index));
139       paths = paths.substring(index + 1);
140       index = paths.indexOf(File.pathSeparatorChar);
141     }
142     l.add(paths);
143     return l;
144   }
145 
146   protected static Map<String, List<String>> argsToMap(final String... args)
147       throws InvalidCommandLineException {
148     final Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
149     List<String> nonOptList = null;
150 
151     for (final String arg : args) {
152       if (arg.startsWith("-")) {
153 
154         final String argName;
155         final String argValue;
156 
157         final int startIndex;
158         if (arg.startsWith("--"))
159           startIndex = 2;
160         else
161           startIndex = 1;
162 
163         final int index = arg.indexOf('=');
164         if (index == -1) {
165           argName = arg.substring(startIndex);
166           argValue = null;
167         } else {
168           if (index < startIndex + 1) {
169             throw new InvalidCommandLineException("Invalid option '" + arg
170                 + "'", 1);
171           }
172           argName = arg.substring(startIndex, index);
173           argValue = arg.substring(index + 1);
174         }
175 
176         List<String> l = map.get(argName);
177         if (l == null) {
178           l = new ArrayList<String>();
179           map.put(argName, l);
180         }
181         l.add(argValue);
182 
183       } else {
184         if (nonOptList == null) {
185           nonOptList = new ArrayList<String>();
186           map.put(null, nonOptList);
187         }
188         nonOptList.add(arg);
189       }
190     }
191 
192     return map;
193   }
194 
195   protected abstract void printUsage(PrintStream ps);
196 
197   protected void printHelp(final PrintStream ps) {
198     printUsage(ps);
199     ps.println();
200     ps.println("Available options are :");
201     int maxCol = 0;
202 
203     for (final CmdOption opt : options.getOptions()) {
204       final int col = 2 + opt.getPrototype().length();
205       if (col > maxCol) maxCol = col;
206     }
207     for (final CmdOption opt : options.getOptions()) {
208       final StringBuffer sb = new StringBuffer("  ");
209       sb.append(opt.getPrototype());
210       while (sb.length() < maxCol)
211         sb.append(' ');
212       sb.append("  ").append(opt.getDescription());
213       ps.println(sb);
214     }
215   }
216 
217   // ---------------------------------------------------------------------------
218   // Internal classes
219   // ---------------------------------------------------------------------------
220 
221   /**
222    * Exception thrown when an error on the command line has been detected.
223    */
224   public static class InvalidCommandLineException extends Exception {
225 
226     protected final int exitValue;
227 
228     /**
229      * @param message detail message.
230      * @param exitValue exit value.
231      */
232     public InvalidCommandLineException(final String message, final int exitValue) {
233       super(message);
234       this.exitValue = exitValue;
235     }
236 
237     /**
238      * @return the exit value.
239      */
240     public int getExitValue() {
241       return exitValue;
242     }
243   }
244 
245   /** Set of available command-line options. */
246   public static class Options {
247     protected final Set<CmdOption>             optionSet          = new LinkedHashSet<CmdOption>();
248     protected final Map<String, CmdOption>     optionsByShortName = new HashMap<String, CmdOption>();
249     protected final Map<String, CmdOption>     optionsByLongName  = new HashMap<String, CmdOption>();
250     protected final Map<String, CmdProperties> optionsByPrefix    = new HashMap<String, CmdProperties>();
251 
252     /**
253      * Add an option
254      * 
255      * @param option an option to add.
256      */
257     public void addOption(final CmdOption option) {
258       if (option instanceof CmdProperties) {
259         final CmdOption prevOpt = optionsByPrefix.put(option.shortName,
260             (CmdProperties) option);
261         if (prevOpt != null || optionsByShortName.containsKey(option.shortName)) {
262           throw new IllegalArgumentException("short name '" + option.shortName
263               + "' already used");
264         }
265       } else {
266         if (option.shortName != null) {
267           final CmdOption prevOpt = optionsByShortName.put(option.shortName,
268               option);
269           if (prevOpt != null || optionsByPrefix.containsKey(option.shortName)) {
270             throw new IllegalArgumentException("short name '"
271                 + option.shortName + "' already used");
272           }
273         }
274         if (option.longName != null) {
275           final CmdOption prevOpt = optionsByLongName.put(option.longName,
276               option);
277           if (prevOpt != null) {
278             throw new IllegalArgumentException("long name '" + option.longName
279                 + "' already used");
280           }
281         }
282       }
283 
284       optionSet.add(option);
285     }
286 
287     /**
288      * Add a set of options
289      * 
290      * @param options the options to add.
291      */
292     public void addOptions(final CmdOption... options) {
293       for (final CmdOption option : options) {
294         addOption(option);
295       }
296     }
297 
298     /** @return the available options. */
299     public Collection<CmdOption> getOptions() {
300       return optionSet;
301     }
302 
303     CmdOption getByShortName(final String shortName) {
304       return optionsByShortName.get(shortName);
305     }
306 
307     CmdOption getByLongName(final String longName) {
308       return optionsByLongName.get(longName);
309     }
310 
311     CmdOption getByName(final String name) {
312       final String prefix = name.substring(0, 1);
313 
314       CmdOption option = optionsByPrefix.get(prefix);
315       if (option != null) return option;
316 
317       option = optionsByShortName.get(name);
318       if (option != null) return option;
319 
320       return optionsByLongName.get(name);
321     }
322   }
323 
324   /**
325    * A command line is the result of parsing a list of string arguments with a
326    * set of options.
327    */
328   public static class CommandLine {
329     protected final Options                options;
330     protected final Map<CmdOption, Object> optionValues = new LinkedHashMap<CmdOption, Object>();
331     protected final List<String>           arguments    = new ArrayList<String>();
332 
333     /**
334      * Parse the given arguments to a CommandLine.
335      * 
336      * @param options the available options.
337      * @param allowUnknownOption if true, unrecognized options will be added to
338      *          list of arguments.
339      * @param args the list of argument to parse.
340      * @return a CommandLine object.
341      * @throws InvalidCommandLineException if the list of argument is invalid.
342      */
343     public static CommandLine parseArgs(final Options options,
344         final boolean allowUnknownOption, final String... args)
345         throws InvalidCommandLineException {
346       final CommandLine cmdLine = new CommandLine(options);
347 
348       for (final String arg : args) {
349         if (arg.startsWith("-")) {
350 
351           final String argName;
352           final String argValue;
353 
354           boolean longName;
355           final int startIndex;
356           if (arg.startsWith("--")) {
357             startIndex = 2;
358             longName = true;
359           } else {
360             startIndex = 1;
361             longName = false;
362           }
363 
364           final int index = arg.indexOf('=');
365           if (index == -1) {
366             argName = arg.substring(startIndex);
367             argValue = null;
368           } else {
369             if (index < startIndex + 1) {
370               throw new InvalidCommandLineException("Invalid option '" + arg
371                   + "'", 1);
372             }
373             argName = arg.substring(startIndex, index);
374             argValue = arg.substring(index + 1);
375           }
376 
377           final CmdOption opt;
378           if (longName)
379             opt = cmdLine.options.getByLongName(argName);
380           else
381             opt = cmdLine.options.getByName(argName);
382 
383           if (opt == null) {
384             if (allowUnknownOption) {
385               cmdLine.arguments.add(arg);
386             } else {
387               throw new InvalidCommandLineException("Unknown option '"
388                   + argName + "'", 1);
389             }
390           } else {
391             if (opt instanceof CmdFlag) {
392               if (argValue != null) {
393                 throw new InvalidCommandLineException("Invalid option '"
394                     + argName + "' do not accept value", 1);
395               }
396 
397               ((CmdFlag) opt).setPresent(cmdLine);
398 
399             } else if (opt instanceof CmdProperties) {
400               if (argValue == null) {
401                 throw new InvalidCommandLineException("Invalid option '"
402                     + argName + "' expects a value", 1);
403               }
404               ((CmdProperties) opt).setValue(cmdLine, argName.substring(1),
405                   argValue);
406             } else { // opt instanceof CmdArgument
407               if (argValue == null) {
408                 throw new InvalidCommandLineException("Invalid option '"
409                     + argName + "' expects a value", 1);
410               }
411               ((CmdArgument) opt).setValue(cmdLine, argValue);
412             }
413           }
414         } else {
415           cmdLine.arguments.add(arg);
416         }
417       }
418       return cmdLine;
419     }
420 
421     protected CommandLine(final Options options) {
422       this.options = options;
423     }
424 
425     protected Object setOptionValue(final CmdOption option, final Object value) {
426       return optionValues.put(option, value);
427     }
428 
429     /** @return the list of arguments. */
430     public List<String> getArguments() {
431       return arguments;
432     }
433 
434     /**
435      * @param option an option.
436      * @return <code>true</code> is the given option is present on this command
437      *         line.
438      */
439     public boolean isOptionPresent(final CmdOption option) {
440       return optionValues.containsKey(option);
441     }
442 
443     Object getOptionValue(final CmdOption option) {
444       return optionValues.get(option);
445     }
446   }
447 
448   /**
449    * Base class of command line options.
450    */
451   public abstract static class CmdOption {
452     protected final String shortName;
453     protected final String longName;
454     protected final String description;
455 
456     /**
457      * @param shortName the short name of the option. Must have one and only one
458      *          character. May be <code>null</code>.
459      * @param longName the long name of the option. Must have more than one
460      *          character. May be <code>null</code>.
461      * @param description the description of the option (used to generate help
462      *          message).
463      */
464     public CmdOption(final String shortName, final String longName,
465         final String description) {
466       if (shortName == null && longName == null)
467         throw new IllegalArgumentException("Invalid option names");
468       if (shortName != null && shortName.length() > 1)
469         throw new IllegalArgumentException("Invalid shortName");
470       if (longName != null && longName.length() <= 1)
471         throw new IllegalArgumentException("Invalid longName");
472 
473       this.shortName = shortName;
474       this.longName = longName;
475       this.description = description;
476     }
477 
478     /** @return the prototype of the options (used to generate help message). */
479     public String getPrototype() {
480       String desc;
481       if (shortName != null) {
482         desc = "-" + shortName;
483         if (longName != null) {
484           desc += ", --" + longName;
485         }
486       } else {
487         desc = "--" + longName;
488       }
489       return desc;
490     }
491 
492     /** @return the short name of the option. */
493     public String getShortName() {
494       return shortName;
495     }
496 
497     /** @return the long name of the option. */
498     public String getLongName() {
499       return longName;
500     }
501 
502     /** @return the description of the option. */
503     public String getDescription() {
504       return description;
505     }
506 
507     /**
508      * @param commandLine a command-line.
509      * @return <code>true</code> if this option is present on the given
510      *         command-line
511      */
512     public boolean isPresent(final CommandLine commandLine) {
513       return commandLine.isOptionPresent(this);
514     }
515 
516     @Override
517     public boolean equals(final Object obj) {
518       if (this == obj) return true;
519 
520       if (!(obj instanceof CmdOption)) return false;
521 
522       final CmdOption opt = (CmdOption) obj;
523       if (shortName == null) {
524         return opt.shortName == null && opt.longName.equals(longName);
525       } else {
526         return shortName.equals(opt.shortName);
527       }
528     }
529 
530     @Override
531     public int hashCode() {
532       if (shortName == null)
533         return longName.hashCode();
534       else
535         return shortName.hashCode();
536     }
537   }
538 
539   /**
540    * An option that may be present or not on a command line.
541    */
542   public static class CmdFlag extends CmdOption {
543 
544     /** @see CmdOption#CmdOption(String, String, String) */
545     public CmdFlag(final String shortName, final String longName,
546         final String description) {
547       super(shortName, longName, description);
548     }
549 
550     void setPresent(final CommandLine commandLine) {
551       commandLine.setOptionValue(this, "");
552     }
553   }
554 
555   /**
556    * A command line option that have a value.
557    */
558   public static class CmdArgument extends CmdOption {
559     protected final String  argDesc;
560     protected final String  defaultValue;
561     protected final boolean allowMultiple;
562 
563     /**
564      * @param shortName the short name of the option. Must have one and only one
565      *          character. May be <code>null</code>.
566      * @param longName the long name of the option. Must have more than one
567      *          character. May be <code>null</code>.
568      * @param description the description of the option (used to generate help
569      *          message).
570      * @param argDesc the description of the argument value (used to generate
571      *          help message).
572      * @param defaultValue the default value of this option. May be
573      *          <code>null</code>.
574      * @param allowMultiple if <code>true</code>, this option can be specified
575      *          several time on a command-line. This that case, the last
576      *          occurrence is used.
577      */
578     public CmdArgument(final String shortName, final String longName,
579         final String description, final String argDesc,
580         final String defaultValue, final boolean allowMultiple) {
581       super(shortName, longName, (defaultValue == null)
582           ? description
583           : description + " (default is '" + defaultValue + "')");
584       this.argDesc = argDesc;
585       this.defaultValue = defaultValue;
586       this.allowMultiple = allowMultiple;
587     }
588 
589     /**
590      * Constructor for CmdArgument that has no default value and that does not
591      * allow multiple occurrences.
592      * 
593      * @param shortName the short name of the option. Must have one and only one
594      *          character. May be <code>null</code>.
595      * @param longName the long name of the option. Must have more than one
596      *          character. May be <code>null</code>.
597      * @param description the description of the option (used to generate help
598      *          message).
599      * @param argDesc the description of the argument value (used to generate
600      *          help message).
601      */
602     public CmdArgument(final String shortName, final String longName,
603         final String description, final String argDesc) {
604       this(shortName, longName, description, argDesc, null, false);
605     }
606 
607     void setValue(final CommandLine commandLine, final String value)
608         throws InvalidCommandLineException {
609       if (value == null) return;
610       final Object prevValue = commandLine.setOptionValue(this, value);
611       if (!allowMultiple && prevValue != null) {
612         throw new InvalidCommandLineException("'" + longName
613             + "' can't be specified several times.", 1);
614       }
615     }
616 
617     /**
618      * Return the value of this option in the given command-line.
619      * 
620      * @param commandLine a command line.
621      * @return the value of this option in the given command-line, or the
622      *         {@link #getDefaultValue() default value}, or <code>null</code> if
623      *         the given command line does not contains this option and this
624      *         option has no default value.
625      */
626     public String getValue(final CommandLine commandLine) {
627       final String optionValue = (String) commandLine.getOptionValue(this);
628       return optionValue == null ? defaultValue : optionValue;
629     }
630 
631     /** @return the default value. */
632     public String getDefaultValue() {
633       return defaultValue;
634     }
635 
636     @Override
637     public String getPrototype() {
638       String desc;
639       if (shortName != null) {
640         desc = "-" + shortName + "=" + argDesc;
641         if (longName != null) {
642           desc += ", --" + longName;
643         }
644       } else {
645         desc = "--" + longName + "=" + argDesc;
646       }
647       return desc;
648     }
649 
650     @Override
651     public String getDescription() {
652       if (allowMultiple)
653         return super.getDescription()
654             + ". This option may be specified several times.";
655       else
656         return super.getDescription();
657     }
658   }
659 
660   /**
661    * An option that associate name to value.
662    */
663   public static class CmdProperties extends CmdOption {
664     protected final String argNameDesc;
665     protected final String argValueDesc;
666 
667     /**
668      * @param shortName the short name of the option. Must have one and only one
669      *          character. May be <code>null</code>.
670      * @param description the description of the option (used to generate help
671      *          message).
672      * @param argNameDesc the description of the argument name (used to generate
673      *          help message).
674      * @param argValueDesc the description of the argument name (used to
675      *          generate help message).
676      */
677     public CmdProperties(final String shortName, final String description,
678         final String argNameDesc, final String argValueDesc) {
679       super(shortName, null, description
680           + ". This option may be specified several times.");
681       this.argNameDesc = argNameDesc;
682       this.argValueDesc = argValueDesc;
683     }
684 
685     @Override
686     public String getPrototype() {
687       return "-" + shortName + argNameDesc + "=" + argValueDesc;
688     }
689 
690     @SuppressWarnings("unchecked")
691     void setValue(final CommandLine commandLine, final String name,
692         final String value) throws InvalidCommandLineException {
693       if (name == null || value == null) return;
694       Map<String, String> values = (Map<String, String>) commandLine
695           .getOptionValue(this);
696       if (values == null) {
697         values = new HashMap<String, String>();
698         commandLine.setOptionValue(this, values);
699       }
700       values.put(name, value);
701     }
702 
703     /**
704      * Returns the value of this option in the given command-line.
705      * 
706      * @param commandLine a command-line.
707      * @return A map associating name to value, or <code>null</code> if this
708      *         option is not specified on the given command line.
709      */
710     @SuppressWarnings("unchecked")
711     public Map<String, String> getValue(final CommandLine commandLine) {
712       return (Map<String, String>) commandLine.getOptionValue(this);
713     }
714   }
715 
716   /**
717    * An option that have a value and that may be specified several time on a
718    * command-line. The resulting option value is the concatenation of the values
719    * of each occurrence of this option.
720    */
721   public static class CmdAppendOption extends CmdArgument {
722 
723     protected final String separator;
724 
725     /**
726      * Constructor for CmdAppendOption that has no default value and that use
727      * <code>" "</code> as separator.
728      * 
729      * @param shortName the short name of the option. Must have one and only one
730      *          character. May be <code>null</code>.
731      * @param longName the long name of the option. Must have more than one
732      *          character. May be <code>null</code>.
733      * @param description the description of the option (used to generate help
734      *          message).
735      * @param argDesc the description of the argument value (used to generate
736      *          help message).
737      */
738     public CmdAppendOption(final String shortName, final String longName,
739         final String description, final String argDesc) {
740       this(shortName, longName, description, argDesc, null, " ");
741     }
742 
743     /**
744      * @param shortName the short name of the option. Must have one and only one
745      *          character. May be <code>null</code>.
746      * @param longName the long name of the option. Must have more than one
747      *          character. May be <code>null</code>.
748      * @param description the description of the option (used to generate help
749      *          message).
750      * @param argDesc the description of the argument value (used to generate
751      *          help message).
752      * @param defaultValue the default value of this option. May be
753      *          <code>null</code>.
754      * @param separator the string used to separate individual value.
755      */
756     public CmdAppendOption(final String shortName, final String longName,
757         final String description, final String argDesc,
758         final String defaultValue, final String separator) {
759       super(shortName, longName, description, argDesc, defaultValue, true);
760       this.separator = separator;
761     }
762 
763     @Override
764     void setValue(final CommandLine commandLine, final String value)
765         throws InvalidCommandLineException {
766       if (value == null) return;
767       final String prevValue = (String) commandLine.getOptionValue(this);
768 
769       if (prevValue == null) {
770         commandLine.setOptionValue(this, value);
771       } else {
772         commandLine.setOptionValue(this, prevValue + separator + value);
773       }
774     }
775   }
776 
777   /**
778    * An option that have a value and that may be specified several time on a
779    * command-line. The resulting option value is the concatenation of the values
780    * of each occurrence of this option separated by {@link File#pathSeparator}.
781    */
782   public static class CmdPathOption extends CmdAppendOption {
783 
784     /**
785      * @param shortName the short name of the option. Must have one and only one
786      *          character. May be <code>null</code>.
787      * @param longName the long name of the option. Must have more than one
788      *          character. May be <code>null</code>.
789      * @param description the description of the option (used to generate help
790      *          message).
791      * @param argDesc the description of the argument value (used to generate
792      *          help message).
793      */
794     public CmdPathOption(final String shortName, final String longName,
795         final String description, final String argDesc) {
796       super(shortName, longName, description, argDesc, null, File.pathSeparator);
797     }
798 
799     /**
800      * @param commandLine a command-line
801      * @return the value of this option on the given command-line as a list of
802      *         String, or <code>null</code>.
803      */
804     public List<String> getPathValue(final CommandLine commandLine) {
805       final String value = getValue(commandLine);
806       if (value == null)
807         return null;
808       else
809         return parsePathList(value);
810     }
811   }
812 }