org.codehaus.groovy.tools.LoaderConfiguration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-all-minimal Show documentation
Show all versions of groovy-all-minimal Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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.
*
* The stream can be for example a FileInputStream from a file with
* the following format:
*
*
* # comment
* main is classname
* load path
* load file
* load pathWith${property}
* load pathWith!{required.property}
* load path/*.jar
* load path/**/*.jar
*
*
* - All lines starting with "#" are ignored.
* - The "main is" part may only be once in the file. The String
* afterwards is the name of a class with a main method.
* - The "load" command will add the given file or path to the
* classpath in this configuration object. If the path does not
* exist, the path will be ignored.
*
* - properties referenced using !{x} are required.
* - properties referenced using ${x} are not required. If the
* property does not exist the whole load instruction line will
* be ignored.
* - * is used to match zero or more characters in a file.
* - ** is used to match zero or more directories.
*
*
* Defining the main class is optional if 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. to match directories use "**". The ${propertyname} is replaced by the value of the system's
* property name. 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
* command does not exist, the path will be ignored.
*
* @author Jochen Theodorou
* @version $Revision: 9261 $
* @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 ALL_WILDCARD = ""+WILDCARD+WILDCARD;
private static final String MATCH_FILE_NAME = "\\\\E[^/]+?\\\\Q";
private static final String MATCH_ALL = "\\\\E.+?\\\\Q";
/**
* 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;
boolean requireProperty = false;
String result = "";
while (propertyIndexStart < str.length()) {
{
int i1 = str.indexOf("${", propertyIndexStart);
int i2 = str.indexOf("!{", propertyIndexStart);
if (i1==-1) {
propertyIndexStart = i2;
} else if (i2==-1) {
propertyIndexStart = i1;
} else {
propertyIndexStart = Math.min(i1,i2);
}
requireProperty=propertyIndexStart==i2;
}
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) {
if (requireProperty) {
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");
} else {
return null;
}
}
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) {
if (filter==null) return;
int starIndex = filter.indexOf(WILDCARD);
if (starIndex == -1) {
addFile(new File(filter));
return;
}
boolean recursive = filter.indexOf(ALL_WILDCARD) != -1;
String startDir = filter.substring(0, starIndex - 1);
File root = new File(startDir);
filter = quote(filter);
filter = filter.replaceAll("\\"+WILDCARD+"\\"+WILDCARD, MATCH_ALL);
filter = filter.replaceAll("\\" + WILDCARD, MATCH_FILE_NAME);
Pattern pattern = Pattern.compile(filter);
final File[] files = root.listFiles();
if (files != null) {
findMatchingFiles(files, pattern, recursive);
}
}
private void findMatchingFiles(File[] files, Pattern pattern, boolean recursive) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
String fileString = getSlashyPath(file.getPath());
Matcher m = pattern.matcher(fileString);
if (m.matches() && file.isFile()) {
addFile(file);
}
if (file.isDirectory() && recursive) {
final File[] dirFiles = file.listFiles();
if (dirFiles != null) {
findMatchingFiles(dirFiles, pattern, true);
}
}
}
}
// 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;
}
// Replacement for Pattern.quote from JDK 1.5
private static String quote(String s) {
StringBuffer sb = new StringBuffer(s.length() * 2);
int eIndex = s.indexOf("\\E");
if (eIndex == -1)
return sb.append("\\Q").append(s).append("\\E").toString();
sb.append("\\Q");
eIndex = 0;
int cur = 0;
while ((eIndex = s.indexOf("\\E", cur)) != -1) {
sb.append(s.substring(cur, eIndex)).append("\\E\\\\E\\Q");
cur = eIndex + 2;
}
return sb.append(s.substring(cur, s.length())).append("\\E").toString();
}
}