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 MAIN_PREFIX = "main is",
096: 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(
116: new InputStreamReader(is));
117: int lineNumber = 0;
118:
119: while (true) {
120: String line = reader.readLine();
121: if (line == null)
122: break;
123:
124: line = line.trim();
125: lineNumber++;
126:
127: if (line.startsWith("#") || line.length() == 0)
128: continue;
129:
130: if (line.startsWith(LOAD_PREFIX)) {
131: String loadPath = line.substring(LOAD_PREFIX.length())
132: .trim();
133: loadPath = assignProperties(loadPath);
134: loadFilteredPath(loadPath);
135: } else if (line.startsWith(MAIN_PREFIX)) {
136: if (main != null)
137: throw new IOException(
138: "duplicate definition of main in line "
139: + lineNumber + " : " + line);
140: main = line.substring(MAIN_PREFIX.length()).trim();
141: } else {
142: throw new IOException("unexpected line in "
143: + lineNumber + " : " + line);
144: }
145: }
146:
147: if (requireMain && main == null)
148: throw new IOException(
149: "missing main class definition in config file");
150: }
151:
152: /**
153: * exapands the properties inside the given string to it's values
154: */
155: private String assignProperties(String str) {
156: int propertyIndexStart = 0, propertyIndexEnd = 0;
157: String result = "";
158:
159: while (propertyIndexStart < str.length()) {
160: propertyIndexStart = str.indexOf("${", propertyIndexStart);
161: if (propertyIndexStart == -1)
162: break;
163: result += str.substring(propertyIndexEnd,
164: propertyIndexStart);
165:
166: propertyIndexEnd = str.indexOf("}", propertyIndexStart);
167: if (propertyIndexEnd == -1)
168: break;
169:
170: String propertyKey = str.substring(propertyIndexStart + 2,
171: propertyIndexEnd);
172: String propertyValue = System.getProperty(propertyKey);
173: result += propertyValue;
174:
175: propertyIndexEnd++;
176: propertyIndexStart = propertyIndexEnd;
177: }
178:
179: if (propertyIndexStart == -1
180: || propertyIndexStart >= str.length()) {
181: result += str.substring(propertyIndexEnd);
182: } else if (propertyIndexEnd == -1) {
183: result += str.substring(propertyIndexStart);
184: }
185:
186: return result;
187: }
188:
189: /**
190: * load a possible filtered path. Filters are defined
191: * by using the * wildcard like in any shell
192: */
193: private void loadFilteredPath(String filter) {
194: int starIndex = filter.indexOf('*');
195: if (starIndex == -1) {
196: addFile(new File(filter));
197: return;
198: }
199: if (!parentPathDoesExist(filter))
200: return;
201: String filterPart = getParentPath(filter);
202: int index = filterPart.indexOf('*');
203: final String prefix = filterPart.substring(0, index);
204: final String suffix = filterPart.substring(index + 1);
205: File dir = new File(filter.substring(0, filter.length()
206: - filterPart.length()));
207: FilenameFilter ff = new FilenameFilter() {
208: public boolean accept(File dir, String name) {
209: if (!name.startsWith(prefix))
210: return false;
211: if (!name.endsWith(suffix))
212: return false;
213: return true;
214: }
215: };
216: File[] matches = dir.listFiles(ff);
217: for (int i = 0; i < matches.length; i++)
218: addFile(matches[i]);
219: }
220:
221: /**
222: * return true if the parent of the path inside the given
223: * string does exist
224: */
225: private boolean parentPathDoesExist(String path) {
226: File dir = new File(path).getParentFile();
227: return dir.exists();
228: }
229:
230: /**
231: * seperates the given path at the last '/'
232: */
233: private String getParentPath(String filter) {
234: int index = filter.lastIndexOf('/');
235: if (index == -1)
236: return "";
237: return filter.substring(index + 1);
238: }
239:
240: /**
241: * adds a file to the classpath if it does exist
242: */
243: public void addFile(File f) {
244: if (f != null && f.exists()) {
245: try {
246: classPath.add(f.toURI().toURL());
247: } catch (MalformedURLException e) {
248: throw new AssertionError(
249: "converting an existing file to an url should have never thrown an exception!");
250: }
251: }
252: }
253:
254: /**
255: * adds a file to the classpath if it does exist
256: */
257: public void addFile(String s) {
258: if (s != null)
259: addFile(new File(s));
260: }
261:
262: /**
263: * adds a classpath to this configuration. It expects a string
264: * with multiple paths, seperated by the system dependent
265: * @see java.io.File#pathSeparator
266: */
267: public void addClassPath(String path) {
268: String[] paths = path.split(File.pathSeparator);
269: for (int i = 0; i < paths.length; i++) {
270: addFile(new File(paths[i]));
271: }
272: }
273:
274: /**
275: * gets a classpath as URL[] from this configuration.
276: * This can be used to construct a @see java.net.URLClassLoader
277: */
278: public URL[] getClassPathUrls() {
279: return (URL[]) classPath.toArray(new URL[] {});
280: }
281:
282: /**
283: * returns the main class or null is no is defined
284: */
285: public String getMainClass() {
286: return main;
287: }
288:
289: /**
290: * sets the main class. If there is already a main class
291: * it is overwritten. Calling @see #configure(InputStream)
292: * after calling this method does not require a main class
293: * definition inside the stream
294: */
295: public void setMainClass(String clazz) {
296: main = clazz;
297: requireMain = false;
298: }
299:
300: /**
301: * if set to false no main class is required when calling
302: * @see #configure(InputStream)
303: */
304: public void setRequireMain(boolean requireMain) {
305: this.requireMain = requireMain;
306: }
307: }
|