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 096 MAIN_PREFIX = "main is", 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( 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 }