1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 }