View Javadoc

1   /*
2   $Id: LoaderConfiguration.java,v 1.2 2005/11/13 16:28:18 blackdrag Exp $
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: 1.2 $
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 }