View Javadoc

    1   /*
    2   $Id: LoaderConfiguration.java 3103 2005-11-13 16:28:18Z blackdrag $
    3
    4   Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6   Redistribution and use of this software and associated documentation
    7   ("Software"), with or without modification, are permitted provided
    8   that the following conditions are met:
    9
    10  1. Redistributions of source code must retain copyright
    11     statements and notices.  Redistributions must also contain a
    12     copy of this document.
    13
    14  2. Redistributions in binary form must reproduce the
    15     above copyright notice, this list of conditions and the
    16     following disclaimer in the documentation and/or other
    17     materials provided with the distribution.
    18
    19  3. The name "groovy" must not be used to endorse or promote
    20     products derived from this Software without prior written
    21     permission of The Codehaus.  For written permission,
    22     please contact info@codehaus.org.
    23
    24  4. Products derived from this Software may not be called "groovy"
    25     nor may "groovy" appear in their names without prior written
    26     permission of The Codehaus. "groovy" is a registered
    27     trademark of The Codehaus.
    28
    29  5. Due credit should be given to The Codehaus -
    30     http://groovy.codehaus.org/
    31
    32  THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33  ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36  THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43  OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45  */
    46  package org.codehaus.groovy.tools;
    47
    48  import java.io.BufferedReader;
    49  import java.io.File;
    50  import java.io.FilenameFilter;
    51  import java.io.IOException;
    52  import java.io.InputStream;
    53  import java.io.InputStreamReader;
    54  import java.net.MalformedURLException;
    55  import java.net.URL;
    56  import java.util.ArrayList;
    57
    58  /***
    59   * class used to configure a RootLoader from a stream or by using
    60   * it's methods.
    61   *
    62   * The stream can be for example a FileInputStream from a file with
    63   * the following format:
    64   *
    65   * # comment
    66   * main is classname
    67   * load path
    68   * load file
    69   * load pathWith${property}
    70   * load path/*.jar
    71   *
    72   *<ul>
    73   * <li>All lines starting with "#" are ignored.</li>
    74   * <li>The "main is" part may only be once in the file. The String
    75   * afterwards is the name of a class if a main method. </li>
    76   * <li>The "load" command will add the given file or path to the
    77   * classpath in this configuration object.
    78   * </li>
    79   *</ul>
    80   *
    81   * Defining the main class is optional if @see #setRequireMain(boolean) was
    82   * called with false, before reading the configuration.
    83   * You can use the wildcard "*" to filter the path, but only for files, not
    84   * directories. The  ${propertyname} is replaced by the value of the system's
    85   * propertyname. You can use user.home here for example. If the property does
    86   * not exist, an empty string will be used. If the path or file after the load
    87   * does not exist, the path will be ignored.
    88   *
    89   * @see RootLoader
    90   * @author Jochen Theodorou
    91   * @version $Revision: 3103 $
    92   */
    93  public class LoaderConfiguration {
    94
    95      private final static String
    96          MAIN_PREFIX = "main is", LOAD_PREFIX="load";
    97      private ArrayList classPath = new ArrayList();
    98      private String main;
    99      private boolean requireMain;
    100
    101     /***
    102      * creates a new loader configuration
    103      */
    104     public LoaderConfiguration() {
    105         this.requireMain = true;
    106     }
    107
    108     /***
    109      * configures this loader with a stream
    110      *
    111      * @param is           stream used to read the configuration
    112      * @throws IOException if reading or parsing the contents of the stream fails
    113      */
    114     public void configure(InputStream is) throws IOException {
    115         BufferedReader reader = new BufferedReader( new InputStreamReader(is));
    116         int lineNumber=0;
    117
    118         while(true) {
    119             String line = reader.readLine();
    120             if (line==null) break;
    121
    122             line = line.trim();
    123             lineNumber++;
    124
    125             if (line.startsWith("#") || line.length()==0) continue;
    126
    127             if (line.startsWith(LOAD_PREFIX)) {
    128                 String loadPath = line.substring(LOAD_PREFIX.length()).trim();
    129                 loadPath = assignProperties(loadPath);
    130                 loadFilteredPath(loadPath);
    131             } else if (line.startsWith(MAIN_PREFIX)) {
    132                 if (main!=null) throw new IOException("duplicate definition of main in line "+lineNumber+" : "+line);
    133                 main = line.substring(MAIN_PREFIX.length()).trim();
    134             } else {
    135                 throw new IOException("unexpected line in "+lineNumber+" : "+line);
    136             }
    137         }
    138
    139         if (requireMain && main == null) throw new IOException("missing main class definition in config file");
    140     }
    141
    142     /***
    143      * exapands the properties inside the given string to it's values
    144      */
    145     private String assignProperties(String str) {
    146         int propertyIndexStart=0,propertyIndexEnd=0;
    147         String result="";
    148
    149         while (propertyIndexStart<str.length()) {
    150             propertyIndexStart=str.indexOf("${",propertyIndexStart);
    151             if (propertyIndexStart==-1) break;
    152             result += str.substring(propertyIndexEnd,propertyIndexStart);
    153
    154             propertyIndexEnd=str.indexOf("}",propertyIndexStart);
    155             if (propertyIndexEnd==-1) break;
    156
    157             String propertyKey = str.substring(propertyIndexStart+2,propertyIndexEnd);
    158             String propertyValue = System.getProperty(propertyKey);
    159             result+=propertyValue;
    160
    161             propertyIndexEnd++;
    162             propertyIndexStart=propertyIndexEnd;
    163         }
    164
    165         if (propertyIndexStart==-1 || propertyIndexStart>=str.length()) {
    166             result+=str.substring(propertyIndexEnd);
    167         } else if (propertyIndexEnd==-1) {
    168             result+=str.substring(propertyIndexStart);
    169         }
    170
    171         return result;
    172     }
    173
    174
    175     /***
    176      * load a possible filtered path. Filters are defined
    177      * by using the * wildcard like in any shell
    178      */
    179     private void loadFilteredPath(String filter) {
    180         int starIndex = filter.indexOf('*');
    181         if (starIndex==-1) {
    182             addFile(new File(filter));
    183             return;
    184         }
    185         if (!parentPathDoesExist(filter)) return;
    186         String filterPart = getParentPath(filter);
    187         int index = filterPart.indexOf('*');
    188         final String prefix = filterPart.substring(0,index);
    189         final String suffix = filterPart.substring(index+1);
    190         File dir = new File(filter.substring(0,filter.length()-filterPart.length()));
    191         FilenameFilter ff = new FilenameFilter() {
    192             public boolean accept(File dir, String name) {
    193                 if (!name.startsWith(prefix)) return false;
    194                 if (!name.endsWith(suffix)) return false;
    195                 return true;
    196             }
    197         };
    198         File[] matches = dir.listFiles(ff);
    199         for (int i=0; i<matches.length; i++) addFile(matches[i]);
    200     }
    201
    202     /***
    203      * return true if the parent of the path inside the given
    204      * string does exist
    205      */
    206     private boolean parentPathDoesExist(String path) {
    207         File dir = new File (path).getParentFile();
    208         return dir.exists();
    209     }
    210
    211     /***
    212      * seperates the given path at the last '/'
    213      */
    214     private String getParentPath(String filter) {
    215         int index = filter.lastIndexOf('/');
    216         if (index==-1) return "";
    217         return filter.substring(index+1);
    218     }
    219
    220     /***
    221      * adds a file to the classpath if it does exist
    222      */
    223     public void addFile(File f) {
    224         if (f!=null && f.exists()) {
    225             try {
    226                 classPath.add(f.toURI().toURL());
    227             } catch (MalformedURLException e) {
    228                 throw new AssertionError("converting an existing file to an url should have never thrown an exception!");
    229             }
    230         }
    231     }
    232
    233     /***
    234      * adds a file to the classpath if it does exist
    235      */
    236     public void addFile(String s) {
    237         if (s!=null) addFile(new File(s));
    238     }
    239
    240     /***
    241      * adds a classpath to this configuration. It expects a string
    242      * with multiple paths, seperated by the system dependent
    243      * @see java.io.File#pathSeparator
    244      */
    245     public void addClassPath(String path) {
    246         String[] paths = path.split(File.pathSeparator);
    247         for (int i=0; i<paths.length; i++) {
    248             addFile(new File(paths[i]));
    249         }
    250     }
    251
    252     /***
    253      * gets a classpath as URL[] from this configuration.
    254      * This can be used to construct a @see java.net.URLClassLoader
    255      */
    256     public URL[] getClassPathUrls() {
    257         return (URL[]) classPath.toArray(new URL[]{});
    258     }
    259
    260     /***
    261      * returns the main class or null is no is defined
    262      */
    263     public String getMainClass() {
    264         return main;
    265     }
    266
    267     /***
    268      * sets the main class. If there is already a main class
    269      * it is overwritten. Calling @see #configure(InputStream)
    270      * after calling this method does not require a main class
    271      * definition inside the stream
    272      */
    273     public void setMainClass(String clazz) {
    274         main = clazz;
    275         requireMain = false;
    276     }
    277
    278     /***
    279      * if set to false no main class is required when calling
    280      * @see #configure(InputStream)
    281      */
    282     public void setRequireMain(boolean requireMain) {
    283         this.requireMain = requireMain;
    284     }
    285 }