javadoc and formatting only
[groovy.git] / src / main / org / codehaus / groovy / tools / LoaderConfiguration.java
blob95eb16a4a7b07eb5b2724398ad4b9ebf24b6bcc9
1 /*
2 * Copyright 2003-2007 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.codehaus.groovy.tools;
18 import groovy.text.RegexUtils;
20 import java.io.*;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
28 /**
29 * Class used to configure a RootLoader from a stream or by using
30 * it's methods.
31 * <p/>
32 * The stream can be for example a FileInputStream from a file with
33 * the following format:
34 * <p/>
35 * <pre>
36 * # comment
37 * main is classname
38 * load path
39 * load file
40 * load pathWith${property}
41 * load pathWith!{required.property}
42 * load path/*.jar
43 * load path/&#42;&#42;/&#42;.jar
44 * </pre>
45 * <ul>
46 * <li>All lines starting with "#" are ignored.</li>
47 * <li>The "main is" part may only be once in the file. The String
48 * afterwards is the name of a class with a main method. </li>
49 * <li>The "load" command will add the given file or path to the
50 * classpath in this configuration object. If the path does not
51 * exist, the path will be ignored.
52 * </li>
53 * <li>properties referenced using !{x} are required.</li>
54 * <li>properties referenced using ${x} are not required. If the
55 * property does not exist the whole load instruction line will
56 * be ignored.</li>
57 * <li>* is used to match zero or more characters in a file.</li>
58 * <li>** is used to match zero or more directories.</li>
59 * </ul>
60 * <p/>
61 * Defining the main class is required unless setRequireMain(boolean) is
62 * called with false, before reading the configuration.
63 * You can use the wildcard "*" to filter the path, but only for files, not
64 * directories. To match directories use "**". The ${propertyname} is replaced by the value of the system's
65 * property name. You can use user.home here for example. If the property does
66 * not exist, an empty string will be used. If the path or file after the load
67 * command does not exist, the path will be ignored.
69 * @author Jochen Theodorou
70 * @version $Revision$
71 * @see RootLoader
73 public class LoaderConfiguration {
75 private static final String MAIN_PREFIX = "main is", LOAD_PREFIX = "load";
76 private List classPath = new ArrayList();
77 private String main;
78 private boolean requireMain;
79 private static final char WILDCARD = '*';
80 private static final String ALL_WILDCARD = "" + WILDCARD + WILDCARD;
81 private static final String MATCH_FILE_NAME = "\\\\E[^/]+?\\\\Q";
82 private static final String MATCH_ALL = "\\\\E.+?\\\\Q";
84 /**
85 * creates a new loader configuration
87 public LoaderConfiguration() {
88 this.requireMain = true;
91 /**
92 * configures this loader with a stream
94 * @param is stream used to read the configuration
95 * @throws IOException if reading or parsing the contents of the stream fails
97 public void configure(InputStream is) throws IOException {
98 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
99 int lineNumber = 0;
101 while (true) {
102 String line = reader.readLine();
103 if (line == null) break;
105 line = line.trim();
106 lineNumber++;
108 if (line.startsWith("#") || line.length() == 0) continue;
110 if (line.startsWith(LOAD_PREFIX)) {
111 String loadPath = line.substring(LOAD_PREFIX.length()).trim();
112 loadPath = assignProperties(loadPath);
113 loadFilteredPath(loadPath);
114 } else if (line.startsWith(MAIN_PREFIX)) {
115 if (main != null)
116 throw new IOException("duplicate definition of main in line " + lineNumber + " : " + line);
117 main = line.substring(MAIN_PREFIX.length()).trim();
118 } else {
119 throw new IOException("unexpected line in " + lineNumber + " : " + line);
123 if (requireMain && main == null) throw new IOException("missing main class definition in config file");
127 * Expands the properties inside the given string to it's values.
129 private String assignProperties(String str) {
130 int propertyIndexStart = 0, propertyIndexEnd = 0;
131 boolean requireProperty = false;
132 String result = "";
134 while (propertyIndexStart < str.length()) {
136 int i1 = str.indexOf("${", propertyIndexStart);
137 int i2 = str.indexOf("!{", propertyIndexStart);
138 if (i1 == -1) {
139 propertyIndexStart = i2;
140 } else if (i2 == -1) {
141 propertyIndexStart = i1;
142 } else {
143 propertyIndexStart = Math.min(i1, i2);
145 requireProperty = propertyIndexStart == i2;
147 if (propertyIndexStart == -1) break;
148 result += str.substring(propertyIndexEnd, propertyIndexStart);
150 propertyIndexEnd = str.indexOf("}", propertyIndexStart);
151 if (propertyIndexEnd == -1) break;
153 String propertyKey = str.substring(propertyIndexStart + 2, propertyIndexEnd);
154 String propertyValue = System.getProperty(propertyKey);
155 // assume properties contain paths
156 if (propertyValue == null) {
157 if (requireProperty) {
158 throw new IllegalArgumentException("Variable " + propertyKey + " in groovy-starter.conf references a non-existent System property! Try passing the property to the VM using -D" + propertyKey + "=myValue in JAVA_OPTS");
159 } else {
160 return null;
163 propertyValue = getSlashyPath(propertyValue);
164 result += propertyValue;
166 propertyIndexEnd++;
167 propertyIndexStart = propertyIndexEnd;
170 if (propertyIndexStart == -1 || propertyIndexStart >= str.length()) {
171 result += str.substring(propertyIndexEnd);
172 } else if (propertyIndexEnd == -1) {
173 result += str.substring(propertyIndexStart);
176 return result;
181 * Load a possibly filtered path. Filters are defined
182 * by using the * wildcard like in any shell.
184 private void loadFilteredPath(String filter) {
185 if (filter == null) return;
186 int starIndex = filter.indexOf(WILDCARD);
187 if (starIndex == -1) {
188 addFile(new File(filter));
189 return;
191 boolean recursive = filter.indexOf(ALL_WILDCARD) != -1;
193 String startDir = filter.substring(0, starIndex - 1);
194 File root = new File(startDir);
196 filter = RegexUtils.quote(filter);
197 filter = filter.replaceAll("\\" + WILDCARD + "\\" + WILDCARD, MATCH_ALL);
198 filter = filter.replaceAll("\\" + WILDCARD, MATCH_FILE_NAME);
199 Pattern pattern = Pattern.compile(filter);
201 final File[] files = root.listFiles();
202 if (files != null) {
203 findMatchingFiles(files, pattern, recursive);
207 private void findMatchingFiles(File[] files, Pattern pattern, boolean recursive) {
208 for (int i = 0; i < files.length; i++) {
209 File file = files[i];
210 String fileString = getSlashyPath(file.getPath());
211 Matcher m = pattern.matcher(fileString);
212 if (m.matches() && file.isFile()) {
213 addFile(file);
215 if (file.isDirectory() && recursive) {
216 final File[] dirFiles = file.listFiles();
217 if (dirFiles != null) {
218 findMatchingFiles(dirFiles, pattern, true);
224 // change path representation to something more system independent.
225 // This solution is based on an absolute path
226 private String getSlashyPath(final String path) {
227 String changedPath = path;
228 if (File.separatorChar != '/')
229 changedPath = changedPath.replace(File.separatorChar, '/');
231 return changedPath;
235 * return true if the parent of the path inside the given
236 * string does exist
238 private boolean parentPathDoesExist(String path) {
239 File dir = new File(path).getParentFile();
240 return dir.exists();
244 * seperates the given path at the last '/'
246 private String getParentPath(String filter) {
247 int index = filter.lastIndexOf('/');
248 if (index == -1) return "";
249 return filter.substring(index + 1);
253 * Adds a file to the classpath if it exists.
255 * @param file the file to add
257 public void addFile(File file) {
258 if (file != null && file.exists()) {
259 try {
260 classPath.add(file.toURI().toURL());
261 } catch (MalformedURLException e) {
262 throw new AssertionError("converting an existing file to an url should have never thrown an exception!");
268 * Adds a file to the classpath if it exists.
270 * @param filename the name of the file to add
272 public void addFile(String filename) {
273 if (filename != null) addFile(new File(filename));
277 * Adds a classpath to this configuration. It expects a string with
278 * multiple paths, seperated by the system dependent path separator
280 * @param path the path as a path separator delimited string
281 * @see java.io.File#pathSeparator
283 public void addClassPath(String path) {
284 String[] paths = path.split(File.pathSeparator);
285 for (int i = 0; i < paths.length; i++) {
286 addFile(new File(paths[i]));
291 * The classpath as URL[] from this configuration.
292 * This can be used to construct a class loader.
294 * @return the classpath
295 * @see java.net.URLClassLoader
297 public URL[] getClassPathUrls() {
298 return (URL[]) classPath.toArray(new URL[classPath.size()]);
302 * Returns the name of the main class for this configuration.
304 * @return the name of the main class or null if not defined
306 public String getMainClass() {
307 return main;
311 * Sets the main class. If there is already a main class
312 * it is overwritten. Calling @see #configure(InputStream)
313 * after calling this method does not require a main class
314 * definition inside the stream.
316 * @param classname the name to become the main class
318 public void setMainClass(String classname) {
319 main = classname;
320 requireMain = false;
324 * Determines if a main class is required when calling.
326 * @param requireMain set to false if no main class is required
327 * @see #configure(InputStream)
329 public void setRequireMain(boolean requireMain) {
330 this.requireMain = requireMain;