001 /*
002 $Id: LoaderConfiguration.java 3103 2005-11-13 16:28:18Z blackdrag $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046 package org.codehaus.groovy.tools;
047
048 import java.io.BufferedReader;
049 import java.io.File;
050 import java.io.FilenameFilter;
051 import java.io.IOException;
052 import java.io.InputStream;
053 import java.io.InputStreamReader;
054 import java.net.MalformedURLException;
055 import java.net.URL;
056 import java.util.ArrayList;
057
058 /**
059 * class used to configure a RootLoader from a stream or by using
060 * it's methods.
061 *
062 * The stream can be for example a FileInputStream from a file with
063 * the following format:
064 *
065 * # comment
066 * main is classname
067 * load path
068 * load file
069 * load pathWith${property}
070 * load path/*.jar
071 *
072 *<ul>
073 * <li>All lines starting with "#" are ignored.</li>
074 * <li>The "main is" part may only be once in the file. The String
075 * afterwards is the name of a class if a main method. </li>
076 * <li>The "load" command will add the given file or path to the
077 * classpath in this configuration object.
078 * </li>
079 *</ul>
080 *
081 * Defining the main class is optional if @see #setRequireMain(boolean) was
082 * called with false, before reading the configuration.
083 * You can use the wildcard "*" to filter the path, but only for files, not
084 * directories. The ${propertyname} is replaced by the value of the system's
085 * propertyname. You can use user.home here for example. If the property does
086 * not exist, an empty string will be used. If the path or file after the load
087 * does not exist, the path will be ignored.
088 *
089 * @see RootLoader
090 * @author Jochen Theodorou
091 * @version $Revision: 3103 $
092 */
093 public class LoaderConfiguration {
094
095 private final static String
096 MAIN_PREFIX = "main is", LOAD_PREFIX="load";
097 private ArrayList classPath = new ArrayList();
098 private String main;
099 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 }