2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
6 * The Java Pathfinder core (jpf-core) platform is licensed under the
7 * Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0.
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
21 import gov.nasa.jpf.util.FileUtils;
22 import gov.nasa.jpf.util.JPFSiteUtils;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.PrintWriter;
29 import java.io.Reader;
30 import java.lang.reflect.Array;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.InvocationTargetException;
34 import java.util.ArrayList;
35 import java.util.Enumeration;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.LinkedList;
39 import java.util.List;
41 import java.util.Properties;
42 import java.util.TreeMap;
43 import java.util.TreeSet;
44 import java.util.logging.Logger;
45 import java.util.regex.Pattern;
49 * class that encapsulates property-based JPF configuration. This is mainly an
50 * associative array with various typed accessors, and a structured
51 * initialization process. This implementation has the design constraint that it
52 * does not promote symbolic information to concrete types, which means that
53 * frequently accessed data should be promoted and cached in client classes.
54 * This in turn means we assume the data is not going to change at runtime.
55 * Major motivation for this mechanism is to avoid 'Option' classes that have
56 * concrete type fields, and hence are structural bottlenecks, i.e. every
57 * parameterized user extension (Heuristics, Scheduler etc.) require to update
58 * this single class. Note that Config is also not thread safe with respect to
59 * retrieving exceptions that occurred during instantiation
61 * Another important caveat for both implementation and usage of Config is that
62 * it is supposed to be our master configuration mechanism, i.e. it is also used
63 * to configure other core services like logging. This means that Config
64 * initialization should not depend on these services. Initialization has to
65 * return at all times, recording potential problems for later handling. This is
66 * why we have to keep the Config data model and initialization fairly simple
69 * Except of JPF and Config itself, all JPF classes are loaded by a
70 * Classloader that is constucted by Config (e.g. by collecting jars from
71 * known/configured locations), i.e. we SHOULD NOT rely on any 3rd party
72 * libraries within Config. The class should be as autarkical as possible.
78 * (1) one site.properties - this file specifies the location of the jpf-core and
79 * installed extensions, like:
81 * jpf-core = /Users/pcmehlitz/projects/jpf/jpf-core
83 * jpf-numeric = /Users/pcmehlitz/projects/jpf/jpf-numeric
85 * extensions = ${jpf-core}
87 * Each key/directory that is in site.properties is used to locate a corresponding
88 * project property (jpf.properties) file
90 * (2) any number of jpf.properties project properties files - each directory
91 * entry in the 'extensions' list is checked for a jpf.properties file, which
92 * is automatically loaded if found. Project properties mostly contain path
93 * settings that are used to initialize class loading by the host VM and JPF
95 * (3) one *.jpf application properties - this specifies all the settings for a
96 * specific JPF run, esp. listener and target/target.args.
97 * app properties can be specified as the sole JPF argument, i.e. instead of
100 * target = x.Y.MySystemUnderTest
101 * target.args = one,two
103 * listener = z.MyListener
105 * (4) commandline properties - all start with '+', they can override all other props
111 * property type : spec : default
112 * ----------------:-----------------------:----------
113 * | site : +site : "${user.home}/[.]jpf/site.properties"
115 * | project : 'extensions' value : set in site.properties
119 * v cmdline : +<key>=<val> : -
121 * * if there is an explicit spec and the pathname does not exist, throw a
124 * * if the system properties cannot be found, throw a JPFConfigException
127 * <2do> need to make NumberFormatException handling consistent - should always
128 * throw an JPFConfigException, not silently returning the default value
133 @SuppressWarnings("serial")
134 public class Config extends Properties {
136 static final char KEY_PREFIX = '@';
137 public static final String REQUIRES_KEY = "@requires";
138 public static final String INCLUDE_KEY = "@include";
139 public static final String INCLUDE_UNLESS_KEY = "@include_unless";
140 public static final String INCLUDE_IF_KEY = "@include_if";
141 public static final String USING_KEY = "@using";
143 static final String[] EMPTY_STRING_ARRAY = new String[0];
145 public static final String LIST_SEPARATOR = ",";
146 public static final String PATH_SEPARATOR = ","; // the default for automatic appends
148 public static final Class<?>[] CONFIG_ARGTYPES = { Config.class };
149 public static final Class<?>[] NO_ARGTYPES = new Class<?>[0];
150 public static final Object[] NO_ARGS = new Object[0];
152 public static final String TRUE = "true";
153 public static final String FALSE = "false";
155 static final String MAX = "MAX";
157 static final String IGNORE_VALUE = "-";
159 // maximum number of processes for distributed applications
160 public static int MAX_NUM_PRC = 16;
162 // do we want to log the config init
163 public static boolean log = false;
165 // bad - a control exception
166 static class MissingRequiredKeyException extends RuntimeException {
167 MissingRequiredKeyException(String details){
172 // it seems bad design to keep ClassLoader management in a glorified Properties object,
173 // but a lot of what Config does is to resolve configured types, for which we need
174 // control over the loader that is used for resolution
175 ClassLoader loader = Config.class.getClassLoader();
177 // where did we initialize from
178 ArrayList<Object> sources = new ArrayList<Object>();
180 ArrayList<ConfigChangeListener> changeListeners;
182 // Properties are simple Hashmaps, but we want to maintain the order of entries
183 LinkedList<String> entrySequence = new LinkedList<String>();
185 // an [optional] hashmap to keep objects we want to be singletons
186 HashMap<String,Object> singletons;
188 public final Object[] CONFIG_ARGS = { this };
190 // the original command line args that were passed into the constructor
193 // non-property/option command line args (starting from the first arg that is not prepened by '-','+')
197 * the standard Config constructor that processes the whole properties stack
199 public Config (String[] cmdLineArgs) {
201 String[] a = cmdLineArgs.clone(); // we might nullify some of them
203 // we need the app properties (*.jpf) pathname upfront because it might define 'site'
204 String appProperties = getAppPropertiesLocation(a);
206 //--- the site properties
207 String siteProperties = getSitePropertiesLocation( a, appProperties);
208 if (siteProperties != null){
209 loadProperties( siteProperties);
212 //--- get the project properties from current dir + site configured extensions
213 loadProjectProperties();
215 //--- the application properties
216 if (appProperties != null){
217 loadProperties( appProperties);
220 //--- at last, the (rest of the) command line properties
223 // note that global path collection now happens from initClassLoader(), to
224 // accommodate for deferred project initialization when explicitly setting Config entries
230 // just interal, for reloading
234 * single source Config constructor (does not process stack)
235 * @param fileName - single properties filename to initialize from
237 public Config (String fileName){
238 loadProperties(fileName);
241 public Config (Reader in){
244 } catch (IOException iox){
245 exception("error reading data: " + iox);
249 public static void enableLogging (boolean enableLogging){
253 public void log (String msg){
254 if (log){ // very simplisitc, but we might do more in the future
255 System.out.println(msg);
260 String getAppPropertiesLocation(String[] args){
263 path = getPathArg(args, "app");
265 // see if the first free arg is a *.jpf
266 path = getAppArg(args);
269 put("jpf.app", path);
274 String getSitePropertiesLocation(String[] args, String appPropPath){
275 String path = getPathArg(args, "site");
278 // look into the app properties
279 // NOTE: we might want to drop this in the future because it constitutes
280 // a cyclic properties file dependency
281 if (appPropPath != null){
282 path = JPFSiteUtils.getMatchFromFile(appPropPath,"site");
286 File siteProps = JPFSiteUtils.getStandardSiteProperties();
287 if (siteProps != null){
288 path = siteProps.getAbsolutePath();
293 put("jpf.site", path);
299 // watch out - this does not reset the computed paths!
300 public Config reload() {
301 log("reloading config");
303 // just reload all our sources
304 Config newConfig = new Config();
305 for (Object src : sources){
306 if (src instanceof File) {
307 newConfig.loadProperties(((File)src).getPath());
308 } else if (src instanceof URL) {
309 newConfig.loadProperties((URL)src);
311 log("don't know how to reload: " + src);
315 // now reload command line args on top of that
316 newConfig.loadArgs(args);
317 newConfig.args = args;
322 public String[] getArgs() {
327 * note that matching args are expanded and stored here, to avoid any
328 * discrepancy with value expansions (which are order-dependent)
330 protected String getPathArg (String[] args, String key){
331 int keyLen = key.length();
333 for (int i=0; i<args.length; i++){
336 int len = a.length();
337 if (len > keyLen + 2){
338 if (a.charAt(0) == '+' && a.charAt(keyLen+1) == '='){
339 if (a.substring(1, keyLen+1).equals(key)){
340 String val = expandString(key, a.substring(keyLen+2));
341 args[i] = null; // processed
353 * if the first freeArg is a JPF application property filename, use this
354 * as targetArg and set the "jpf.app" property accordingly
356 protected String getAppArg (String[] args){
358 for (int i=0; i<args.length; i++){
360 if (a != null && a.length() > 0){
361 switch (a.charAt(0)) {
365 if (a.endsWith(".jpf")){
366 String val = expandString("jpf.app", a);
367 args[i] = null; // processed
378 protected void loadProperties (URL url){
379 log("loading defaults from: " + url);
381 InputStream is = null;
383 is = url.openStream();
386 } catch (IOException iox){
387 log("error in input stream for: " + url + " : " + iox.getMessage());
392 } catch (IOException iox1){
393 log("error closing input stream for: " + url + " : " + iox1.getMessage());
399 protected void setConfigPathProperties (String fileName){
400 put("config", fileName);
401 int i = fileName.lastIndexOf(File.separatorChar);
403 put("config_path", fileName.substring(0,i));
405 put("config_path", ".");
410 protected boolean loadProperties (String fileName) {
411 if (fileName != null && fileName.length() > 0) {
412 FileInputStream is = null;
414 File f = new File(fileName);
416 log("loading property file: " + fileName);
418 setConfigPathProperties(f.getAbsolutePath());
420 is = new FileInputStream(f);
424 throw exception("property file does not exist: " + f.getAbsolutePath());
426 } catch (MissingRequiredKeyException rkx){
427 // Hmpff - control exception
428 log("missing required key: " + rkx.getMessage() + ", skipping: " + fileName);
429 } catch (IOException iex) {
430 throw exception("error reading properties: " + fileName);
435 } catch (IOException iox1){
436 log("error closing input stream for file: " + fileName);
447 * this holds the policy defining in which order we process directories
448 * containing JPF projects (i.e. jpf.properties files)
450 protected void loadProjectProperties () {
451 // this is the list of directories holding jpf.properties files that
452 // have to be processed in order of entry (increasing priority)
453 LinkedList<File> jpfDirs = new LinkedList<File>();
455 // deduce the JPF projects in use (at least jpf-core) from the CL which
456 // defined this class
457 addJPFdirsFromClasspath(jpfDirs);
459 // add all the site configured extension dirs (but NOT jpf-core)
460 addJPFdirsFromSiteExtensions(jpfDirs);
462 // add the current dir, which has highest priority (this might bump up
463 // a previous entry by reodering it - which includes jpf-core)
464 addCurrentJPFdir(jpfDirs);
466 // now load all the jpf.property files we found in these dirs
467 // (later loads can override previous settings)
468 for (File dir : jpfDirs){
469 loadProperties(new File(dir,"jpf.properties").getAbsolutePath());
473 protected void appendPath (String pathKey, String key, String configPath){
474 String[] paths = getStringArray(key);
476 for (String e : paths) {
477 if (!e.startsWith("${") || !e.startsWith(File.separator)) {
478 e = configPath + File.separatorChar + e;
480 append(pathKey, e, PATH_SEPARATOR);
485 protected void addJPFdirs (List<File> jpfDirs, File dir){
486 while (dir != null) {
487 File jpfProp = new File(dir, "jpf.properties");
488 if (jpfProp.isFile()) {
489 registerJPFdir(jpfDirs, dir);
490 return; // we probably don't want recursion here
492 dir = getParentFile(dir);
497 * add the current dir to the list of JPF components.
498 * Note: this includes the core, so that we maintain the general
499 * principle that the enclosing project takes precedence (imagine the opposite:
500 * if we want to test a certain feature that is overridden by another extension
501 * we don't know about)
503 protected void addCurrentJPFdir(List<File> jpfDirs){
504 File dir = new File(System.getProperty("user.dir"));
505 while (dir != null) {
506 File jpfProp = new File(dir, "jpf.properties");
507 if (jpfProp.isFile()) {
508 registerJPFdir(jpfDirs, dir);
511 dir = getParentFile(dir);
515 protected void addJPFdirsFromClasspath(List<File> jpfDirs) {
516 String cp = System.getProperty("java.class.path");
517 String[] cpEntries = cp.split(File.pathSeparator);
519 for (String p : cpEntries) {
520 File f = new File(p);
521 File dir = f.isFile() ? getParentFile(f) : f;
523 addJPFdirs(jpfDirs, dir);
527 protected void addJPFdirsFromSiteExtensions (List<File> jpfDirs){
528 String[] extensions = getCompactStringArray("extensions");
529 if (extensions != null){
530 for (String pn : extensions){
531 addJPFdirs( jpfDirs, new File(pn));
537 * the obvious part is that it only adds to the list if the file is absent
538 * the not-so-obvious part is that it re-orders already present files
539 * to maintain the priority
541 protected boolean registerJPFdir(List<File> list, File dir){
543 dir = dir.getCanonicalFile();
545 for (File e : list) {
552 } catch (IOException iox) {
553 throw new JPFConfigException("illegal path spec: " + dir);
560 static File root = new File(File.separator);
562 protected File getParentFile(File f){
566 File parent = f.getParentFile();
568 parent = new File(f.getAbsolutePath());
570 if (parent.getName().equals(root.getName())) {
584 * {'+'<key>['='<val>'] | '-'<driver-arg>} {<free-arg>}
586 * (1) null cmdLineArgs are ignored
587 * (2) all config cmdLineArgs start with '+'
588 * (3) if '=' is ommitted, a 'true' value is assumed
589 * (4) if <val> is ommitted, a 'null' value is assumed
590 * (5) no spaces around '='
591 * (6) all '-' driver-cmdLineArgs are ignored
594 protected void loadArgs (String[] cmdLineArgs) {
596 for (int i=0; i<cmdLineArgs.length; i++){
597 String a = cmdLineArgs[i];
599 if (a != null && a.length() > 0){
600 switch (a.charAt(0)){
601 case '+': // Config arg
602 processArg(a.substring(1));
605 case '-': // driver arg, ignore
608 default: // free (non property/option) cmdLineArgs to follow
610 int n = cmdLineArgs.length - i;
611 freeArgs = new String[n];
612 System.arraycopy(cmdLineArgs, i, freeArgs, 0, n);
622 * this does not include the '+' prefix, just the
625 protected void processArg (String a) {
627 int idx = a.indexOf("=");
630 throw new JPFConfigException("illegal option: " + a);
634 String key = a.substring(0, idx).trim();
635 String val = a.substring(idx + 1).trim();
637 if (val.length() == 0){
641 setProperty(key, val);
644 setProperty(a.trim(), "true");
651 * replace string constants with global static objects
653 protected String normalize (String v) {
655 return null; // ? maybe TRUE - check default loading of "key" or "key="
658 // trim leading and trailing blanks (at least Java 1.4.2 does not take care of trailing blanks)
662 if ("true".equalsIgnoreCase(v)
663 || "yes".equalsIgnoreCase(v)
664 || "on".equalsIgnoreCase(v)) {
666 } else if ("false".equalsIgnoreCase(v)
667 || "no".equalsIgnoreCase(v)
668 || "off".equalsIgnoreCase(v)) {
673 if ("nil".equalsIgnoreCase(v) || "null".equalsIgnoreCase(v)){
681 // our internal expander
682 // Note that we need to know the key this came from, to handle recursive expansion
683 protected String expandString (String key, String s) {
685 if (s == null || s.length() == 0) {
689 while ((i = s.indexOf("${", j)) >= 0) {
690 if ((j = s.indexOf('}', i)) > 0) {
691 String k = s.substring(i + 2, j);
694 if ((key != null) && key.equals(k)) {
695 // that's expanding itself -> use what is there
696 v = getProperty(key);
698 // refers to another key, which is already expanded, so this
699 // can't get recursive (we expand during entry storage)
703 if (v == null) { // if we don't have it, fall back to system properties
704 v = System.getProperty(k);
708 s = s.substring(0, i) + v + s.substring(j + 1, s.length());
711 s = s.substring(0, i) + s.substring(j + 1, s.length());
721 boolean loadPropertiesRecursive (String fileName){
722 // save the current values of automatic properties
723 String curConfig = (String)get("config");
724 String curConfigPath = (String)get("config_path");
726 File propFile = new File(fileName);
727 if (!propFile.isAbsolute()){
728 propFile = new File(curConfigPath, fileName);
730 String absPath = propFile.getAbsolutePath();
732 if (!propFile.isFile()){
733 throw exception("property file does not exist: " + absPath);
736 boolean ret = loadProperties(absPath);
738 // restore the automatic properties
739 super.put("config", curConfig);
740 super.put("config_path", curConfigPath);
745 void includePropertyFile(String key, String value){
746 value = expandString(key, value);
747 if (value != null && value.length() > 0){
748 loadPropertiesRecursive(value);
750 throw exception("@include pathname argument missing");
754 void includeCondPropertyFile(String key, String value, boolean keyPresent){
755 value = expandString(key, value);
756 if (value != null && value.length() > 0){
757 // check if it's a conditional "@include_unless/if = ?key?pathName"
758 if (value.charAt(0) == '?'){
759 int idx = value.indexOf('?', 1);
761 String k = value.substring(1, idx);
762 if (containsKey(k) == keyPresent){
763 String v = value.substring(idx+1);
765 loadPropertiesRecursive(v);
767 throw exception("@include_unless pathname argument missing (?<key>?<pathName>)");
772 throw exception("malformed @include_unless argument (?<key>?<pathName>), found: " + value);
775 throw exception("malformed @include_unless argument (?<key>?<pathName>), found: " + value);
778 throw exception("@include_unless missing ?<key>?<pathName> argument");
783 void includeProjectPropertyFile (String projectId){
784 String projectPath = getString(projectId);
785 if (projectPath != null){
786 File projectProps = new File(projectPath, "jpf.properties");
787 if (projectProps.isFile()){
788 loadPropertiesRecursive(projectProps.getAbsolutePath());
791 throw exception("project properties not found: " + projectProps.getAbsolutePath());
795 throw exception("unknown project id (check site.properties): " + projectId);
799 // we override this so that we can handle expansion for both key and value
800 // (value expansion can be recursive, i.e. refer to itself)
802 public Object put (Object keyObject, Object valueObject){
804 if (keyObject == null){
805 throw exception("no null keys allowed");
806 } else if (!(keyObject instanceof String)){
807 throw exception("only String keys allowed, got: " + keyObject);
809 if (valueObject != null && !(valueObject instanceof String)){
810 throw exception("only String or null values allowed, got: " + valueObject);
813 String key = (String)keyObject;
814 String value = (String)valueObject;
816 if (key.length() == 0){
817 throw exception("no empty keys allowed");
820 if (key.charAt(0) == KEY_PREFIX){
821 processPseudoProperty( key, value);
822 return null; // no value it replaces
825 // finally, a real key/value pair to add (or remove) - expand and store
826 String k = expandString(null, key);
828 if (!(value == null)) { // add or overwrite entry
831 if (k.charAt(k.length() - 1) == '+') { // the append hack
832 k = k.substring(0, k.length() - 1);
833 return append(k, v, null);
835 } else if (k.charAt(0) == '+') { // the prepend hack
837 return prepend(k, v, null);
839 } else { // normal value set
840 v = normalize(expandString(k, v));
848 } else { // setting a null value removes the entry
854 protected void processPseudoProperty( String key, String value){
855 if (REQUIRES_KEY.equals(key)) {
856 // shortcircuit loading of property files - used to enforce order
857 // of properties, e.g. to model dependencies
858 for (String reqKey : split(value)) {
859 if (!containsKey(reqKey)) {
860 throw new MissingRequiredKeyException(reqKey);
864 } else if (INCLUDE_KEY.equals(key)) {
865 includePropertyFile(key, value);
867 } else if (INCLUDE_UNLESS_KEY.equals(key)) {
868 includeCondPropertyFile(key, value, false);
870 } else if (INCLUDE_IF_KEY.equals(key)) {
871 includeCondPropertyFile(key, value, true);
873 } else if (USING_KEY.equals(key)) {
874 // check if corresponding jpf.properties has already been loaded. If yes, skip
875 if (!haveSeenProjectProperty(value)){
876 includeProjectPropertyFile(value);
880 throw exception("unknown keyword: " + key);
884 protected boolean haveSeenProjectProperty (String key){
885 String pn = getString(key);
889 return sources.contains( new File( pn, "jpf.properties"));
893 private Object setKey (String k, String v){
894 Object oldValue = put0(k, v);
895 notifyPropertyChangeListeners(k, (String) oldValue, v);
899 private Object removeKey (String k){
900 Object oldValue = super.get(k);
902 notifyPropertyChangeListeners(k, (String) oldValue, null);
906 private Object put0 (String k, Object v){
907 entrySequence.add(k);
908 return super.put(k, v);
911 private Object remove0 (String k){
912 entrySequence.add(k);
913 return super.remove(k);
916 public String prepend (String key, String value, String separator) {
917 String oldValue = getProperty(key);
918 value = normalize( expandString(key, value));
920 append0(key, oldValue, value, oldValue, separator);
925 public String append (String key, String value, String separator) {
926 String oldValue = getProperty(key);
927 value = normalize( expandString(key, value));
929 append0(key, oldValue, oldValue, value, separator);
935 private void append0 (String key, String oldValue, String a, String b, String separator){
940 StringBuilder sb = new StringBuilder(a);
941 if (separator != null) {
942 sb.append(separator);
945 newValue = sb.toString();
947 } else { // b==null : nothing to append
948 if (oldValue == a){ // using reference compare is intentional here
955 } else { // a==null : nothing to append to
956 if (oldValue == b || b == null){ // using reference compare is intentional here
963 // if we get here, we have a newValue that differs from oldValue
965 notifyPropertyChangeListeners(key, oldValue, newValue);
968 protected String append (String key, String value) {
969 return append(key, value, LIST_SEPARATOR); // append with our standard list separator
973 * check if we have a key.index entry. If not, check the non-indexed key. If no
974 * key found return null
975 * This simplifies clients that can have process id indexed properties
977 public String getIndexableKey (String key, int index){
978 String k = key + '.' + index;
982 if (containsKey(key)){
987 return null; // neither indexed nor non-indexed key in dictionary
990 public void setClassLoader (ClassLoader newLoader){
994 public ClassLoader getClassLoader (){
998 public boolean hasSetClassLoader (){
999 return Config.class.getClassLoader() != loader;
1002 public JPFClassLoader initClassLoader (ClassLoader parent) {
1003 ArrayList<String> list = new ArrayList<String>();
1005 // we prefer to call this here automatically instead of allowing
1006 // explicit collectGlobalPath() calls because (a) this could not preserve
1007 // initial path settings, and (b) setting it *after* the JPFClassLoader got
1008 // installed won't work (would have to add URLs explicitly, or would have
1009 // to create a new JPFClassLoader, which then conflicts with classes already
1010 // defined by the previous one)
1011 collectGlobalPaths();
1013 log("collected native_classpath=" + get("native_classpath"));
1014 log("collected native_libraries=" + get("native_libraries"));
1018 String[] cp = getCompactStringArray("native_classpath");
1019 cp = FileUtils.expandWildcards(cp);
1020 for (String e : cp) {
1023 URL[] urls = FileUtils.getURLs(list);
1025 String[] nativeLibs = getCompactStringArray("native_libraries");
1028 if (parent instanceof JPFClassLoader){ // no need to create a new one, just initialize
1029 cl = (JPFClassLoader)parent;
1030 for (URL url : urls){
1033 cl.setNativeLibs(nativeLibs);
1036 cl = new JPFClassLoader( urls, nativeLibs, parent);
1044 * has to be called if 'native_classpath' gets explicitly changed
1045 * USE WITH CARE - if this is messed up, it is hard to debug
1047 public void updateClassLoader (){
1048 if (loader != null && loader instanceof JPFClassLoader){
1049 JPFClassLoader jpfCl = (JPFClassLoader)loader;
1051 ArrayList<String> list = new ArrayList<String>();
1052 String[] cp = getCompactStringArray("native_classpath");
1053 cp = FileUtils.expandWildcards(cp);
1054 for (String e : cp) {
1055 URL url = FileUtils.getURL(e);
1056 jpfCl.addURL(url); // this does not add if already present
1059 String[] nativeLibs = getCompactStringArray("native_libraries");
1060 jpfCl.setNativeLibs(nativeLibs);
1065 //------------------------------ public methods - the Config API
1068 public String[] getEntrySequence () {
1069 // whoever gets this might add/append/remove items, so we have to
1070 // avoid ConcurrentModificationExceptions
1071 return entrySequence.toArray(new String[entrySequence.size()]);
1074 public void addChangeListener (ConfigChangeListener l) {
1075 if (changeListeners == null) {
1076 changeListeners = new ArrayList<ConfigChangeListener>();
1077 changeListeners.add(l);
1079 if (!changeListeners.contains(l)) {
1080 changeListeners.add(l);
1085 public void removeChangeListener (ConfigChangeListener l) {
1086 if (changeListeners != null) {
1087 changeListeners.remove(l);
1089 if (changeListeners.size() == 0) {
1090 changeListeners = null;
1095 // this shouldn't really be public but only accessible to JPF
1096 public void jpfRunTerminated() {
1097 if (changeListeners != null) {
1098 // note we can't use the standard list iterator here because the sole purpose
1099 // of having this notification is to remove the listener from the list during its enumeration
1100 // which would give us ConcurrentModificationExceptions
1101 ArrayList<ConfigChangeListener> list = (ArrayList<ConfigChangeListener>)changeListeners.clone();
1102 for (ConfigChangeListener l : list) {
1103 l.jpfRunTerminated(this);
1108 public JPFException exception (String msg) {
1109 String context = getString("config");
1110 if (context != null){
1111 msg = "error in " + context + " : " + msg;
1114 return new JPFConfigException(msg);
1117 public void throwException(String msg) {
1118 throw new JPFConfigException(msg);
1122 * return any command line args that are not options or properties
1123 * (this usually contains the application class and arguments)
1125 public String[] getFreeArgs(){
1132 * target and its associated keys (target.args, target.entry) are now
1133 * just ordinary key/value pairs and only here as convenience methods
1134 * for JPF drivers/shells so that you don't have to remember the key names
1136 * NOTE - this does only work for a SingleProcessVM, and only has the
1137 * desired effect before the JPF object is created
1140 public void setTarget (String clsName) {
1141 put("target", clsName);
1143 public String getTarget(){
1144 return getString("target");
1147 public void setTargetArgs (String[] args) {
1148 StringBuilder sb = new StringBuilder();
1150 for (String a : args){
1156 put("target.args", sb.toString());
1158 public String[] getTargetArgs(){
1159 String[] a = getStringArray("target.args");
1161 return new String[0];
1167 public void setTargetEntry (String mthName) {
1168 put("target.entry", mthName);
1170 public String getTargetEntry(){
1171 return getString("target.entry");
1175 //----------------------- type specific accessors
1177 public boolean getBoolean(String key) {
1178 String v = getProperty(key);
1182 public boolean getBoolean(String key, boolean def) {
1183 String v = getProperty(key);
1192 * for a given <baseKey>, check if there are corresponding
1193 * values for keys <baseKey>.0 ... <baseKey>.<maxSize>
1194 * If a value is found, store it in an array at the respective index
1196 * @param baseKey String with base key without trailing '.'
1197 * @param maxSize maximum size of returned value array
1198 * @return trimmed array with String values found in dictionary
1200 public String[] getStringEnumeration (String baseKey, int maxSize) {
1201 String[] arr = new String[maxSize];
1204 StringBuilder sb = new StringBuilder(baseKey);
1206 int len = baseKey.length()+1;
1208 for (int i=0; i<maxSize; i++) {
1212 String v = getString(sb.toString());
1221 if (max < maxSize) {
1222 String[] a = new String[max];
1223 System.arraycopy(arr,0,a,0,max);
1233 public String[] getKeysStartingWith (String prefix){
1234 ArrayList<String> list = new ArrayList<String>();
1236 for (Enumeration e = keys(); e.hasMoreElements(); ){
1237 String k = e.nextElement().toString();
1238 if (k.startsWith(prefix)){
1243 return list.toArray(new String[list.size()]);
1246 public String[] getKeyComponents (String key){
1247 return key.split("\\.");
1250 public int[] getIntArray (String key) throws JPFConfigException {
1251 String v = getProperty(key);
1254 String[] sa = split(v);
1255 int[] a = new int[sa.length];
1258 for (; i<sa.length; i++) {
1261 if (s.startsWith("0x")){
1262 val = Integer.parseInt(s.substring(2),16);
1264 val = Integer.parseInt(s);
1269 } catch (NumberFormatException nfx) {
1270 throw new JPFConfigException("illegal int[] element in '" + key + "' = \"" + sa[i] + '"');
1276 public int[] getIntArray (String key, int... defaultValues){
1277 int[] val = getIntArray(key);
1279 return defaultValues;
1285 public long getDuration (String key, long defValue) {
1286 String v = getProperty(key);
1290 if (v.indexOf(':') > 0){
1291 String[] a = v.split(":");
1293 //log.severe("illegal duration: " + key + "=" + v);
1297 for (int i=a.length-1; i>=0; i--, m*=60){
1299 int n = Integer.parseInt(a[i]);
1301 } catch (NumberFormatException nfx) {
1302 throw new JPFConfigException("illegal duration element in '" + key + "' = \"" + v + '"');
1308 d = Long.parseLong(v);
1309 } catch (NumberFormatException nfx) {
1310 throw new JPFConfigException("illegal duration element in '" + key + "' = \"" + v + '"');
1320 public int getInt(String key) {
1321 return getInt(key, 0);
1324 public int getInt(String key, int defValue) {
1325 String v = getProperty(key);
1328 return Integer.MAX_VALUE;
1331 return Integer.parseInt(v);
1332 } catch (NumberFormatException nfx) {
1333 throw new JPFConfigException("illegal int element in '" + key + "' = \"" + v + '"');
1341 public long getLong(String key) {
1342 return getLong(key, 0L);
1345 public long getLong(String key, long defValue) {
1346 String v = getProperty(key);
1349 return Long.MAX_VALUE;
1352 return Long.parseLong(v);
1353 } catch (NumberFormatException nfx) {
1354 throw new JPFConfigException("illegal long element in '" + key + "' = \"" + v + '"');
1362 public long[] getLongArray (String key) throws JPFConfigException {
1363 String v = getProperty(key);
1366 String[] sa = split(v);
1367 long[] a = new long[sa.length];
1370 for (; i<sa.length; i++) {
1371 a[i] = Long.parseLong(sa[i]);
1374 } catch (NumberFormatException nfx) {
1375 throw new JPFConfigException("illegal long[] element in " + key + " = " + sa[i]);
1382 public long[] getLongArray (String key, long... defaultValues){
1383 long[] val = getLongArray(key);
1387 return defaultValues;
1391 public float getFloat (String key) {
1392 return getFloat(key, 0.0f);
1395 public float getFloat (String key, float defValue) {
1396 String v = getProperty(key);
1399 return Float.parseFloat(v);
1400 } catch (NumberFormatException nfx) {
1401 throw new JPFConfigException("illegal float element in '" + key + "' = \"" + v + '"');
1408 public float[] getFloatArray (String key) throws JPFConfigException {
1409 String v = getProperty(key);
1412 String[] sa = split(v);
1413 float[] a = new float[sa.length];
1416 for (; i<sa.length; i++) {
1417 a[i] = Float.parseFloat(sa[i]);
1420 } catch (NumberFormatException nfx) {
1421 throw new JPFConfigException("illegal float[] element in " + key + " = " + sa[i]);
1427 public float[] getFloatArray (String key, float... defaultValues){
1428 float[] v = getFloatArray( key);
1432 return defaultValues;
1437 public double getDouble (String key) {
1438 return getDouble(key, 0.0);
1441 public double getDouble (String key, double defValue) {
1442 String v = getProperty(key);
1445 return Double.parseDouble(v);
1446 } catch (NumberFormatException nfx) {
1447 throw new JPFConfigException("illegal double element in '" + key + "' = \"" + v + '"');
1454 public double[] getDoubleArray (String key) throws JPFConfigException {
1455 String v = getProperty(key);
1458 String[] sa = split(v);
1459 double[] a = new double[sa.length];
1462 for (; i<sa.length; i++) {
1463 a[i] = Double.parseDouble(sa[i]);
1466 } catch (NumberFormatException nfx) {
1467 throw new JPFConfigException("illegal double[] element in " + key + " = " + sa[i]);
1473 public double[] getDoubleArray (String key, double... defaultValues){
1474 double[] v = getDoubleArray( key);
1478 return defaultValues;
1482 public <T extends Enum<T>> T getEnum( String key, T[] values, T defValue){
1483 String v = getProperty(key);
1487 if (v.equalsIgnoreCase(t.name())){
1492 throw new JPFConfigException("unknown enum value for " + key + " = " + v);
1499 public String getString(String key) {
1500 return getProperty(key);
1503 public String getString(String key, String defValue) {
1504 String s = getProperty(key);
1513 * return memory size in bytes, or 'defValue' if not in dictionary. Encoding
1514 * can have a 'M' or 'k' postfix, values have to be positive integers (decimal
1517 public long getMemorySize(String key, long defValue) {
1518 String v = getProperty(key);
1522 int n = v.length() - 1;
1524 char c = v.charAt(n);
1526 if ((c == 'M') || (c == 'm')) {
1527 sz = Long.parseLong(v.substring(0, n)) << 20;
1528 } else if ((c == 'K') || (c == 'k')) {
1529 sz = Long.parseLong(v.substring(0, n)) << 10;
1531 sz = Long.parseLong(v);
1534 } catch (NumberFormatException nfx) {
1535 throw new JPFConfigException("illegal memory size element in '" + key + "' = \"" + v + '"');
1542 public HashSet<String> getStringSet(String key){
1543 String v = getProperty(key);
1544 if (v != null && (v.length() > 0)) {
1545 HashSet<String> hs = new HashSet<String>();
1546 for (String s : split(v)) {
1556 public HashSet<String> getNonEmptyStringSet(String key){
1557 HashSet<String> hs = getStringSet(key);
1558 if (hs != null && hs.isEmpty()) {
1565 public String[] getStringArray(String key) {
1566 String v = getProperty(key);
1567 if (v != null && (v.length() > 0)) {
1574 public String[] getStringArray(String key, char[] delims) {
1575 String v = getProperty(key);
1576 if (v != null && (v.length() > 0)) {
1577 return split(v,delims);
1583 public String[] getCompactTrimmedStringArray (String key){
1584 String[] a = getStringArray(key);
1587 for (int i = 0; i < a.length; i++) {
1589 if (s != null && s.length() > 0) {
1594 return removeEmptyStrings(a);
1597 return EMPTY_STRING_ARRAY;
1601 public String[] getCompactStringArray(String key){
1602 return removeEmptyStrings(getStringArray(key));
1606 public String[] getStringArray(String key, String[] def){
1607 String v = getProperty(key);
1608 if (v != null && (v.length() > 0)) {
1615 public static String[] removeEmptyStrings (String[] a){
1618 for (int i=0; i<a.length; i++){
1619 if (a[i].length() > 0){
1624 if (n < a.length){ // we have empty strings in the split
1625 String[] r = new String[n];
1626 for (int i=0, j=0; i<a.length; i++){
1627 if (a[i].length() > 0){
1646 * return an [optional] id part of a property value (all that follows the first '@')
1648 String getIdPart (String key) {
1649 String v = getProperty(key);
1650 if ((v != null) && (v.length() > 0)) {
1651 int i = v.indexOf('@');
1653 return v.substring(i+1);
1660 public Class<?> asClass (String v) throws JPFConfigException {
1661 if ((v != null) && (v.length() > 0)) {
1663 v = expandClassName(v);
1665 return loader.loadClass(v);
1666 } catch (ClassNotFoundException cfx) {
1667 throw new JPFConfigException("class not found " + v + " by classloader: " + loader);
1668 } catch (ExceptionInInitializerError ix) {
1669 throw new JPFConfigException("class initialization of " + v + " failed: " + ix,
1677 public <T> Class<? extends T> getClass(String key, Class<T> type) throws JPFConfigException {
1678 Class<?> cls = asClass( getProperty(key));
1680 if (type.isAssignableFrom(cls)) {
1681 return cls.asSubclass(type);
1683 throw new JPFConfigException("classname entry for: \"" + key + "\" not of type: " + type.getName());
1690 public Class<?> getClass(String key) throws JPFConfigException {
1691 return asClass( getProperty(key));
1694 public Class<?> getEssentialClass(String key) throws JPFConfigException {
1695 Class<?> cls = getClass(key);
1697 throw new JPFConfigException("no classname entry for: \"" + key + "\"");
1703 String stripId (String v) {
1704 int i = v.indexOf('@');
1706 return v.substring(0,i);
1712 String getId (String v){
1713 int i = v.indexOf('@');
1715 return v.substring(i+1);
1721 String expandClassName (String clsName) {
1722 if (clsName != null && clsName.length() > 0 && clsName.charAt(0) == '.') {
1723 return "gov.nasa.jpf" + clsName;
1730 public Class<?>[] getClasses(String key) throws JPFConfigException {
1731 String[] v = getStringArray(key);
1734 Class<?>[] a = new Class[n];
1735 for (int i = 0; i < n; i++) {
1736 String clsName = expandClassName(v[i]);
1737 if (clsName != null && clsName.length() > 0){
1739 clsName = stripId(clsName);
1740 a[i] = loader.loadClass(clsName);
1741 } catch (ClassNotFoundException cnfx) {
1742 throw new JPFConfigException("class not found " + v[i]);
1743 } catch (ExceptionInInitializerError ix) {
1744 throw new JPFConfigException("class initialization of " + v[i] + " failed: " + ix, ix);
1756 * this one is used to instantiate objects from a list of keys that share
1757 * the same prefix, e.g.
1759 * shell.panels = config,site
1760 * shell.panels.site = .shell.panels.SitePanel
1761 * shell.panels.config = .shell.panels.ConfigPanel
1764 * note that we specify default class names, not classes, so that the classes
1765 * get loaded through our own loader at call time (they might not be visible
1768 public <T> T[] getGroupInstances (String keyPrefix, String keyPostfix, Class<T> type,
1769 String... defaultClsNames) throws JPFConfigException {
1771 String[] ids = getCompactTrimmedStringArray(keyPrefix);
1773 if (ids.length > 0){
1774 keyPrefix = keyPrefix + '.';
1775 T[] arr = (T[]) Array.newInstance(type, ids.length);
1777 for(int i = 0; i < ids.length; i++){
1778 String key = keyPrefix + ids[i];
1779 if (keyPostfix != null){
1780 key = key + keyPostfix;
1782 arr[i] = getEssentialInstance(key, type);
1788 T[] arr = (T[]) Array.newInstance(type, defaultClsNames.length);
1790 for (int i=0; i<arr.length; i++){
1791 arr[i] = getInstance((String)null, defaultClsNames[i], type);
1792 if (arr[i] == null){
1793 exception("cannot instantiate default type " + defaultClsNames[i]);
1801 // <2do> - that's kind of kludged together, not very efficient
1802 String[] getIds (String key) {
1803 String v = getProperty(key);
1806 int i = v.indexOf('@');
1807 if (i >= 0) { // Ok, we have ids
1808 String[] a = split(v);
1809 String[] ids = new String[a.length];
1810 for (i = 0; i<a.length; i++) {
1811 ids[i] = getId(a[i]);
1820 public <T> ArrayList<T> getInstances(String key, Class<T> type) throws JPFConfigException {
1822 Class<?>[] argTypes = { Config.class };
1823 Object[] args = { this };
1825 return getInstances(key,type,argTypes,args);
1828 public <T> ArrayList<T> getInstances(String key, Class<T> type, Class<?>[]argTypes, Object[] args)
1829 throws JPFConfigException {
1830 Class<?>[] c = getClasses(key);
1833 String[] ids = getIds(key);
1835 ArrayList<T> a = new ArrayList<T>(c.length);
1837 for (int i = 0; i < c.length; i++) {
1838 String id = (ids != null) ? ids[i] : null;
1839 T listener = getInstance(key, c[i], type, argTypes, args, id);
1840 if (listener != null) {
1843 // should report here
1850 // should report here
1856 public <T> T getInstance(String key, Class<T> type, String defClsName) throws JPFConfigException {
1857 Class<?>[] argTypes = CONFIG_ARGTYPES;
1858 Object[] args = CONFIG_ARGS;
1860 Class<?> cls = getClass(key);
1861 String id = getIdPart(key);
1865 cls = loader.loadClass(defClsName);
1866 } catch (ClassNotFoundException cfx) {
1867 throw new JPFConfigException("class not found " + defClsName);
1868 } catch (ExceptionInInitializerError ix) {
1869 throw new JPFConfigException("class initialization of " + defClsName + " failed: " + ix, ix);
1873 return getInstance(key, cls, type, argTypes, args, id);
1876 public <T> T getInstance(String key, Class<T> type) throws JPFConfigException {
1877 Class<?>[] argTypes = CONFIG_ARGTYPES;
1878 Object[] args = CONFIG_ARGS;
1880 return getInstance(key, type, argTypes, args);
1883 public <T> T getInstance(String key, Class<T> type, Class<?>[] argTypes,
1884 Object[] args) throws JPFConfigException {
1885 Class<?> cls = getClass(key);
1886 String id = getIdPart(key);
1889 return getInstance(key, cls, type, argTypes, args, id);
1895 public <T> T getInstance(String key, Class<T> type, Object arg1, Object arg2) throws JPFConfigException {
1896 Class<?>[] argTypes = new Class<?>[2];
1897 argTypes[0] = arg1.getClass();
1898 argTypes[1] = arg2.getClass();
1900 Object[] args = new Object[2];
1904 return getInstance(key, type, argTypes, args);
1908 public <T> T getEssentialInstance(String key, Class<T> type) throws JPFConfigException {
1909 Class<?>[] argTypes = { Config.class };
1910 Object[] args = { this };
1911 return getEssentialInstance(key, type, argTypes, args);
1915 * just a convenience method for ctor calls that take two arguments
1917 public <T> T getEssentialInstance(String key, Class<T> type, Object arg1, Object arg2) throws JPFConfigException {
1918 Class<?>[] argTypes = new Class<?>[2];
1919 argTypes[0] = arg1.getClass();
1920 argTypes[1] = arg2.getClass();
1922 Object[] args = new Object[2];
1926 return getEssentialInstance(key, type, argTypes, args);
1929 public <T> T getEssentialInstance(String key, Class<T> type, Class<?>[] argTypes, Object[] args) throws JPFConfigException {
1930 Class<?> cls = getEssentialClass(key);
1931 String id = getIdPart(key);
1933 return getInstance(key, cls, type, argTypes, args, id);
1936 public <T> T getInstance (String id, String clsName, Class<T> type, Class<?>[] argTypes, Object[] args) throws JPFConfigException {
1937 Class<?> cls = asClass(clsName);
1940 return getInstance(id, cls, type, argTypes, args, id);
1946 public <T> T getInstance (String id, String clsName, Class<T> type) throws JPFConfigException {
1947 Class<?>[] argTypes = CONFIG_ARGTYPES;
1948 Object[] args = CONFIG_ARGS;
1950 Class<?> cls = asClass(clsName);
1953 return getInstance(id, cls, type, argTypes, args, id);
1960 * this is our private instantiation workhorse - try to instantiate an object of
1961 * class 'cls' by using the following ordered set of ctors 1. <cls>(
1962 * <argTypes>) 2. <cls>(Config) 3. <cls>() if all of that fails, or there was
1963 * a 'type' provided the instantiated object does not comply with, return null
1965 <T> T getInstance(String key, Class<?> cls, Class<T> type, Class<?>[] argTypes,
1966 Object[] args, String id) throws JPFConfigException {
1968 Constructor<?> ctor = null;
1974 if (id != null) { // check first if we already have this one instantiated as a singleton
1975 if (singletons == null) {
1976 singletons = new HashMap<String,Object>();
1978 o = type.cast(singletons.get(id));
1984 ctor = cls.getConstructor(argTypes);
1985 o = ctor.newInstance(args);
1986 } catch (NoSuchMethodException nmx) {
1988 if ((argTypes.length > 1) || ((argTypes.length == 1) && (argTypes[0] != Config.class))) {
1989 // fallback 1: try a single Config param
1990 argTypes = CONFIG_ARGTYPES;
1993 } else if (argTypes.length > 0) {
1994 // fallback 2: try the default ctor
1995 argTypes = NO_ARGTYPES;
1999 // Ok, there is no suitable ctor, bail out
2000 throw new JPFConfigException(key, cls, "no suitable ctor found");
2002 } catch (IllegalAccessException iacc) {
2003 throw new JPFConfigException(key, cls, "\n> ctor not accessible: "
2004 + getMethodSignature(ctor));
2005 } catch (IllegalArgumentException iarg) {
2006 throw new JPFConfigException(key, cls, "\n> illegal constructor arguments: "
2007 + getMethodSignature(ctor));
2008 } catch (InvocationTargetException ix) {
2009 Throwable tx = ix.getTargetException();
2010 if (tx instanceof JPFConfigException) {
2011 throw new JPFConfigException(tx.getMessage() + "\n> used within \"" + key
2012 + "\" instantiation of " + cls);
2014 throw new JPFConfigException(key, cls, "\n> exception in "
2015 + getMethodSignature(ctor) + ":\n>> " + tx, tx);
2017 } catch (InstantiationException ivt) {
2018 throw new JPFConfigException(key, cls,
2019 "\n> abstract class cannot be instantiated");
2020 } catch (ExceptionInInitializerError eie) {
2021 throw new JPFConfigException(key, cls, "\n> static initialization failed:\n>> "
2022 + eie.getException(), eie.getException());
2027 if (!type.isInstance(o)) {
2028 throw new JPFConfigException(key, cls, "\n> instance not of type: "
2032 if (id != null) { // add to singletons (in case it's not already in there)
2033 singletons.put(id, o);
2036 return type.cast(o); // safe according to above
2039 public String getMethodSignature(Constructor<?> ctor) {
2040 StringBuilder sb = new StringBuilder(ctor.getName());
2042 Class<?>[] argTypes = ctor.getParameterTypes();
2043 for (int i = 0; i < argTypes.length; i++) {
2047 sb.append(argTypes[i].getName());
2050 return sb.toString();
2053 public boolean hasValue(String key) {
2054 String v = getProperty(key);
2055 return ((v != null) && (v.length() > 0));
2058 public boolean hasValueIgnoreCase(String key, String value) {
2059 String v = getProperty(key);
2061 return v.equalsIgnoreCase(value);
2067 public int getChoiceIndexIgnoreCase(String key, String[] choices) {
2068 String v = getProperty(key);
2070 if ((v != null) && (choices != null)) {
2071 for (int i = 0; i < choices.length; i++) {
2072 if (v.equalsIgnoreCase(choices[i])) {
2081 public URL getURL (String key){
2082 String v = getProperty(key);
2085 return FileUtils.getURL(v);
2086 } catch (Throwable x){
2087 throw exception("malformed URL: " + v);
2094 public File[] getPathArray (String key) {
2095 String v = getProperty(key);
2097 String[] pe = removeEmptyStrings( pathSplit(v));
2099 if (pe != null && pe.length > 0) {
2100 File[] files = new File[pe.length];
2101 for (int i=0; i<files.length; i++) {
2102 String path = FileUtils.asPlatformPath(pe[i]);
2103 files[i] = new File(path);
2112 public File getPath (String key) {
2113 String v = getProperty(key);
2115 return new File(FileUtils.asPlatformPath(v));
2121 static final char[] UNIX_PATH_SEPARATORS = {',', ';', ':' };
2122 static final char[] WINDOWS_PATH_SEPARATORS = {',', ';' };
2124 protected String[] pathSplit (String input){
2125 if (File.pathSeparatorChar == ':'){
2126 return split( input, UNIX_PATH_SEPARATORS);
2128 return split( input, WINDOWS_PATH_SEPARATORS);
2132 static final char[] DELIMS = { ',', ';' };
2135 * our own version of split, which handles "`" quoting, and breaks on non-quoted
2136 * ',' and ';' chars. We need this so that we can use ';' separated lists in
2137 * JPF property files, but still can use quoted ';' if we absolutely have to
2138 * specify Java signatures. On the other hand, we can't quote with '\' because
2139 * that would make Windows paths even more terrible.
2140 * regexes are bad at quoting, and this is more efficient anyways
2142 protected String[] split (String input){
2143 return split(input, DELIMS);
2146 private boolean isDelim(char[] delim, char c){
2147 for (int i=0; i<delim.length; i++){
2155 protected String[] split (String input, char[] delim){
2156 int n = input.length();
2157 ArrayList<String> elements = new ArrayList<String>();
2158 boolean quote = false;
2160 char[] buf = new char[128];
2163 for (int i=0; i<n; i++){
2164 char c = input.charAt(i);
2167 if (isDelim(delim,c)){ // element separator
2168 elements.add( new String(buf, 0, k));
2171 } else if (c=='`') {
2177 if (k >= buf.length){
2178 char[] newBuf = new char[buf.length+128];
2179 System.arraycopy(buf, 0, newBuf, 0, k);
2187 elements.add( new String(buf, 0, k));
2190 return elements.toArray(new String[elements.size()]);
2193 static final String UNINITIALIZED = "uninitialized";
2194 // this is where we store the initial values in case we have to recollect
2195 String initialNativeClasspath = UNINITIALIZED,
2196 initialClasspath = UNINITIALIZED,
2197 initialSourcepath = UNINITIALIZED,
2198 initialPeerPackages = UNINITIALIZED,
2199 initialNativeLibraries = UNINITIALIZED;
2203 * this resets to what was explicitly set in the config files
2205 public void resetGlobalPaths() {
2206 if (initialNativeClasspath == UNINITIALIZED){
2207 initialNativeClasspath = getString("native_classpath");
2209 put0( "native_classpath", initialNativeClasspath);
2212 if (initialClasspath == UNINITIALIZED){
2213 initialClasspath = getString("classpath");
2215 put0( "classpath", initialClasspath);
2218 if (initialSourcepath == UNINITIALIZED){
2219 initialSourcepath = getString("sourcepath");
2221 put0( "sourcepath", initialSourcepath);
2224 if (initialPeerPackages == UNINITIALIZED){
2225 initialPeerPackages = getString("peer_packages");
2227 put0( "peer_packages", initialPeerPackages);
2230 if (initialNativeLibraries == UNINITIALIZED){
2231 initialNativeLibraries = getString("native_libraries");
2233 put0( "native_libraries", initialNativeLibraries);
2238 * collect all the <project>.{native_classpath,classpath,sourcepath,peer_packages,native_libraries}
2239 * and append them to the global settings
2241 * NOTE - this is now called from within initClassLoader, which should only happen once and
2242 * is the first time we really need the global paths.
2244 * <2do> this is Ok for native_classpath and native_libraries, but we should probably do
2245 * classpath, sourcepath and peer_packages separately (they can be collected later)
2247 public void collectGlobalPaths() {
2249 // note - this is in the order of entry, i.e. reflects priorities
2250 // we have to process this in reverse order so that later entries are prioritized
2251 String[] keys = getEntrySequence();
2253 String nativeLibKey = "." + System.getProperty("os.name") +
2254 '.' + System.getProperty("os.arch") + ".native_libraries";
2256 for (int i = keys.length-1; i>=0; i--){
2258 if (k.endsWith(".native_classpath")){
2259 appendPath("native_classpath", k);
2261 } else if (k.endsWith(".classpath")){
2262 appendPath("classpath", k);
2264 } else if (k.endsWith(".sourcepath")){
2265 appendPath("sourcepath", k);
2267 } else if (k.endsWith("peer_packages")){
2268 append("peer_packages", getString(k), ",");
2270 } else if (k.endsWith(nativeLibKey)){
2271 appendPath("native_libraries", k);
2277 static Pattern absPath = Pattern.compile("(?:[a-zA-Z]:)?[/\\\\].*");
2279 void appendPath (String pathKey, String key){
2280 String projName = key.substring(0, key.indexOf('.'));
2281 String pathPrefix = null;
2283 if (projName.isEmpty()){
2284 pathPrefix = new File(".").getAbsolutePath();
2286 pathPrefix = getString(projName);
2289 if (pathPrefix != null){
2292 String[] elements = getCompactStringArray(key);
2293 if (elements != null){
2294 for (String e : elements) {
2295 if (e != null && e.length()>0){
2297 // if this entry is not an absolute path, or doesn't start with
2298 // the project path, prepend the project path
2299 if (!(absPath.matcher(e).matches()) && !e.startsWith(pathPrefix)) {
2309 //throw new JPFConfigException("no project path for " + key);
2314 //--- our modification interface
2317 * iterate over all keys, if a key starts with the provided keyPrefix, add
2318 * this value under the corresponding key suffix. For example:
2320 * test.report.console.finished = result
2322 * -> prompotePropertyCategory("test.") ->
2324 * report.console.finished = result
2326 * if a matching key has an IGNORE_VALUE value ("-"), the entry is *not* promoted
2327 * (we need this to override promoted keys)
2329 public void promotePropertyCategory (String keyPrefix){
2330 int prefixLen = keyPrefix.length();
2332 // HashTable does not support adding elements while iterating over the entrySet
2333 ArrayList<Map.Entry<Object,Object>> promoted = null;
2335 for (Map.Entry<Object,Object> e : entrySet()){
2336 Object k = e.getKey();
2337 if (k instanceof String){
2338 String key = (String)k;
2339 if (key.startsWith(keyPrefix)){
2340 Object v = e.getValue();
2341 if (! IGNORE_VALUE.equals(v)){
2342 if (promoted == null){
2343 promoted = new ArrayList<Map.Entry<Object,Object>>();
2351 if (promoted != null){
2352 for (Map.Entry<Object, Object> e : promoted) {
2353 String key = (String) e.getKey();
2354 key = key.substring(prefixLen);
2356 put(key, e.getValue());
2363 public Object setProperty (String key, String newValue) {
2364 Object oldValue = put(key, newValue);
2365 notifyPropertyChangeListeners(key, (String)oldValue, newValue);
2369 public void parse (String s) {
2371 int i = s.indexOf("=");
2375 if (i > 1 && s.charAt(i-1)=='+') { // append
2376 key = s.substring(0, i-1).trim();
2377 val = s.substring(i+1); // it's going to be normalized anyways
2381 key = s.substring(0, i).trim();
2382 val = s.substring(i+1);
2383 setProperty(key, val);
2389 protected void notifyPropertyChangeListeners (String key, String oldValue, String newValue) {
2390 if (changeListeners != null) {
2391 for (ConfigChangeListener l : changeListeners) {
2392 l.propertyChanged(this, key, oldValue, newValue);
2397 public String[] asStringArray (String s){
2401 public TreeMap<Object,Object> asOrderedMap() {
2402 TreeMap<Object,Object> map = new TreeMap<Object,Object>();
2407 //--- various debugging methods
2409 public void print (PrintWriter pw) {
2410 pw.println("----------- Config contents");
2412 // just how much do you have to do to get a printout with keys in alphabetical order :<
2413 TreeSet<String> kset = new TreeSet<String>();
2414 for (Enumeration<?> e = propertyNames(); e.hasMoreElements();) {
2415 Object k = e.nextElement();
2416 if (k instanceof String) {
2417 kset.add( (String)k);
2421 for (String key : kset) {
2422 String val = getProperty(key);
2431 public void printSources (PrintWriter pw) {
2432 pw.println("----------- Config sources");
2433 for (Object src : sources){
2438 public void printEntries() {
2439 PrintWriter pw = new PrintWriter(System.out);
2443 public String getSourceName (Object src){
2444 if (src instanceof File){
2445 return ((File)src).getAbsolutePath();
2446 } else if (src instanceof URL){
2447 return ((URL)src).toString();
2449 return src.toString();
2453 public List<Object> getSources() {
2457 public void printStatus(Logger log) {
2460 for (Object src : sources){
2461 if (src instanceof File){
2462 log.config("configuration source " + idx++ + " : " + getSourceName(src));