| /* |
| * Copyright 2003-2007 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.codehaus.groovy.tools; |
| |
| import java.io.*; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * class used to configure a RootLoader from a stream or by using |
| * it's methods. |
| * <p/> |
| * The stream can be for example a FileInputStream from a file with |
| * the following format: |
| * <p/> |
| * # comment |
| * main is classname |
| * load path |
| * load file |
| * load pathWith${property} |
| * load path/*.jar |
| * <p/> |
| * <ul> |
| * <li>All lines starting with "#" are ignored.</li> |
| * <li>The "main is" part may only be once in the file. The String |
| * afterwards is the name of a class if a main method. </li> |
| * <li>The "load" command will add the given file or path to the |
| * classpath in this configuration object. |
| * </li> |
| * </ul> |
| * <p/> |
| * Defining the main class is optional if @see #setRequireMain(boolean) was |
| * called with false, before reading the configuration. |
| * You can use the wildcard "*" to filter the path, but only for files, not |
| * directories. The ${propertyname} is replaced by the value of the system's |
| * propertyname. You can use user.home here for example. If the property does |
| * not exist, an empty string will be used. If the path or file after the load |
| * does not exist, the path will be ignored. |
| * |
| * @author Jochen Theodorou |
| * @version $Revision$ |
| * @see RootLoader |
| */ |
| public class LoaderConfiguration { |
| |
| private static final String MAIN_PREFIX = "main is", LOAD_PREFIX = "load"; |
| private List classPath = new ArrayList(); |
| private String main; |
| private boolean requireMain; |
| private static final char WILDCARD = '*'; |
| private static final String WILD_CARD_REGEX = "[^/]+?"; |
| |
| /** |
| * creates a new loader configuration |
| */ |
| public LoaderConfiguration() { |
| this.requireMain = true; |
| } |
| |
| /** |
| * configures this loader with a stream |
| * |
| * @param is stream used to read the configuration |
| * @throws IOException if reading or parsing the contents of the stream fails |
| */ |
| public void configure(InputStream is) throws IOException { |
| BufferedReader reader = new BufferedReader(new InputStreamReader(is)); |
| int lineNumber = 0; |
| |
| while (true) { |
| String line = reader.readLine(); |
| if (line == null) break; |
| |
| line = line.trim(); |
| lineNumber++; |
| |
| if (line.startsWith("#") || line.length() == 0) continue; |
| |
| if (line.startsWith(LOAD_PREFIX)) { |
| String loadPath = line.substring(LOAD_PREFIX.length()).trim(); |
| loadPath = assignProperties(loadPath); |
| loadFilteredPath(loadPath); |
| } else if (line.startsWith(MAIN_PREFIX)) { |
| if (main != null) |
| throw new IOException("duplicate definition of main in line " + lineNumber + " : " + line); |
| main = line.substring(MAIN_PREFIX.length()).trim(); |
| } else { |
| throw new IOException("unexpected line in " + lineNumber + " : " + line); |
| } |
| } |
| |
| if (requireMain && main == null) throw new IOException("missing main class definition in config file"); |
| } |
| |
| /** |
| * exapands the properties inside the given string to it's values |
| */ |
| private String assignProperties(String str) { |
| int propertyIndexStart = 0, propertyIndexEnd = 0; |
| String result = ""; |
| |
| while (propertyIndexStart < str.length()) { |
| propertyIndexStart = str.indexOf("${", propertyIndexStart); |
| if (propertyIndexStart == -1) break; |
| result += str.substring(propertyIndexEnd, propertyIndexStart); |
| |
| propertyIndexEnd = str.indexOf("}", propertyIndexStart); |
| if (propertyIndexEnd == -1) break; |
| |
| String propertyKey = str.substring(propertyIndexStart + 2, propertyIndexEnd); |
| String propertyValue = System.getProperty(propertyKey); |
| // assume properties contain paths |
| if (propertyValue == null) { |
| 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"); |
| } |
| propertyValue = getSlashyPath(propertyValue); |
| result += propertyValue; |
| |
| propertyIndexEnd++; |
| propertyIndexStart = propertyIndexEnd; |
| } |
| |
| if (propertyIndexStart == -1 || propertyIndexStart >= str.length()) { |
| result += str.substring(propertyIndexEnd); |
| } else if (propertyIndexEnd == -1) { |
| result += str.substring(propertyIndexStart); |
| } |
| |
| return result; |
| } |
| |
| |
| /** |
| * load a possible filtered path. Filters are defined |
| * by using the * wildcard like in any shell |
| */ |
| private void loadFilteredPath(String filter) { |
| int starIndex = filter.indexOf(WILDCARD); |
| if (starIndex == -1) { |
| addFile(new File(filter)); |
| return; |
| } |
| |
| String startDir = filter.substring(0, starIndex - 1); |
| File root = new File(startDir); |
| |
| filter = filter.replaceAll("\\.", "\\\\."); |
| filter = filter.replaceAll("\\" + WILDCARD, WILD_CARD_REGEX); |
| Pattern pattern = Pattern.compile(filter); |
| |
| final File[] files = root.listFiles(); |
| if (files != null) { |
| findMatchingFiles(files, pattern); |
| } |
| } |
| |
| private void findMatchingFiles(File[] files, Pattern pattern) { |
| for (int i = 0; i < files.length; i++) { |
| File file = files[i]; |
| Matcher m = pattern.matcher(getSlashyPath(file.getAbsolutePath())); |
| if (m.matches() && file.isFile()) { |
| addFile(file); |
| } |
| if (file.isDirectory()) { |
| final File[] dirFiles = file.listFiles(); |
| if (dirFiles != null) { |
| findMatchingFiles(dirFiles, pattern); |
| } |
| } |
| } |
| } |
| |
| // change path representation to something more system independent. |
| // This solution is based on an absolute path |
| private String getSlashyPath(final String path) { |
| String changedPath = path; |
| if (File.separatorChar != '/') |
| changedPath = changedPath.replace(File.separatorChar, '/'); |
| |
| return changedPath; |
| } |
| |
| /** |
| * return true if the parent of the path inside the given |
| * string does exist |
| */ |
| private boolean parentPathDoesExist(String path) { |
| File dir = new File(path).getParentFile(); |
| return dir.exists(); |
| } |
| |
| /** |
| * seperates the given path at the last '/' |
| */ |
| private String getParentPath(String filter) { |
| int index = filter.lastIndexOf('/'); |
| if (index == -1) return ""; |
| return filter.substring(index + 1); |
| } |
| |
| /** |
| * adds a file to the classpath if it does exist |
| */ |
| public void addFile(File f) { |
| if (f != null && f.exists()) { |
| try { |
| classPath.add(f.toURI().toURL()); |
| } catch (MalformedURLException e) { |
| throw new AssertionError("converting an existing file to an url should have never thrown an exception!"); |
| } |
| } |
| } |
| |
| /** |
| * adds a file to the classpath if it does exist |
| */ |
| public void addFile(String s) { |
| if (s != null) addFile(new File(s)); |
| } |
| |
| /** |
| * adds a classpath to this configuration. It expects a string |
| * with multiple paths, seperated by the system dependent |
| * |
| * @see java.io.File#pathSeparator |
| */ |
| public void addClassPath(String path) { |
| String[] paths = path.split(File.pathSeparator); |
| for (int i = 0; i < paths.length; i++) { |
| addFile(new File(paths[i])); |
| } |
| } |
| |
| /** |
| * gets a classpath as URL[] from this configuration. |
| * This can be used to construct a @see java.net.URLClassLoader |
| */ |
| public URL[] getClassPathUrls() { |
| return (URL[]) classPath.toArray(new URL[classPath.size()]); |
| } |
| |
| /** |
| * returns the main class or null is no is defined |
| */ |
| public String getMainClass() { |
| return main; |
| } |
| |
| /** |
| * sets the main class. If there is already a main class |
| * it is overwritten. Calling @see #configure(InputStream) |
| * after calling this method does not require a main class |
| * definition inside the stream |
| */ |
| public void setMainClass(String clazz) { |
| main = clazz; |
| requireMain = false; |
| } |
| |
| /** |
| * if set to false no main class is required when calling |
| * |
| * @see #configure(InputStream) |
| */ |
| public void setRequireMain(boolean requireMain) { |
| this.requireMain = requireMain; |
| } |
| } |