//
// Copyright (C) 2006 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import gov.nasa.jpf.util.FileUtils;
import gov.nasa.jpf.util.JPFSiteUtils;
/**
* class that encapsulates property-based JPF configuration. This is mainly an
* associative array with various typed accessors, and a structured
* initialization process. This implementation has the design constraint that it
* does not promote symbolic information to concrete types, which means that
* frequently accessed data should be promoted and cached in client classes.
* This in turn means we assume the data is not going to change at runtime.
* Major motivation for this mechanism is to avoid 'Option' classes that have
* concrete type fields, and hence are structural bottlenecks, i.e. every
* parameterized user extension (Heuristics, Scheduler etc.) require to update
* this single class. Note that Config is also not thread safe with respect to
* retrieving exceptions that occurred during instantiation
*
* Another important caveat for both implementation and usage of Config is that
* it is supposed to be our master configuration mechanism, i.e. it is also used
* to configure other core services like logging. This means that Config
* initialization should not depend on these services. Initialization has to
* return at all times, recording potential problems for later handling. This is
* why we have to keep the Config data model and initialization fairly simple
* and robust.
*
* Except of JPF and Config itself, all JPF classes are loaded by a
* Classloader that is constucted by Config (e.g. by collecting jars from
* known/configured locations), i.e. we SHOULD NOT rely on any 3rd party
* libraries within Config. The class should be as autarkical as possible.
*
*
* PROPERTY SOURCES
* ----------------
*
* (1) one site.properties - this file specifies the location of the jpf-core and
* installed extensions, like:
*
* jpf-core = /Users/pcmehlitz/projects/jpf/jpf-core
* ...
* jpf-numeric = /Users/pcmehlitz/projects/jpf/jpf-numeric
* ...
* extensions = ${jpf-core}
*
* Each key/directory that is in site.properties is used to locate a corresponding
* project property (jpf.properties) file
*
* (2) any number of jpf.properties project properties files - each directory
* entry in the 'extensions' list is checked for a jpf.properties file, which
* is automatically loaded if found. Project properties mostly contain path
* settings that are used to initialize class loading by the host VM and JPF
*
* (3) one *.jpf application properties - this specifies all the settings for a
* specific JPF run, esp. listener and target/target.args.
* app properties can be specified as the sole JPF argument, i.e. instead of
* a SUT classname
* ..
* target = x.Y.MySystemUnderTest
* target.args = one,two
* ..
* listener = z.MyListener
*
* (4) commandline properties - all start with '+', they can override all other props
*
*
* LOOKUP ORDER
* ------------
* property lookup
* property type : spec : default
* ----------------:-----------------------:----------
* | site : +site : "${user.home}/[.]jpf/site.properties"
* | : :
* | project : 'extensions' value : set in site.properties
* | : :
* | app : +app : -
* | : :
* v cmdline : +<key>=<val> : -
*
* * if there is an explicit spec and the pathname does not exist, throw a
* JPFConfigException
*
* * if the system properties cannot be found, throw a JPFConfigException
*
*
* <2do> need to make NumberFormatException handling consistent - should always
* throw an JPFConfigException, not silently returning the default value
*
*/
@SuppressWarnings({"serial", "unchecked"})
public class Config extends Properties {
static final char KEY_PREFIX = '@';
public static final String REQUIRES_KEY = "@requires";
public static final String INCLUDE_KEY = "@include";
public static final String INCLUDE_UNLESS_KEY = "@include_unless";
public static final String INCLUDE_IF_KEY = "@include_if";
public static final String USING_KEY = "@using";
static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final String LIST_SEPARATOR = ",";
public static final String PATH_SEPARATOR = ","; // the default for automatic appends
public static final Class<?>[] CONFIG_ARGTYPES = { Config.class };
public static final Class<?>[] NO_ARGTYPES = new Class<?>[0];
public static final Object[] NO_ARGS = new Object[0];
public static final String TRUE = "true";
public static final String FALSE = "false";
static final String MAX = "MAX";
static final String IGNORE_VALUE = "-";
// maximum number of processes for distributed applications
public static int MAX_NUM_PRC = 16;
// do we want to log the config init
public static boolean log = false;
// bad - a control exception
static class MissingRequiredKeyException extends RuntimeException {
MissingRequiredKeyException(String details){
super(details);
}
}
// it seems bad design to keep ClassLoader management in a glorified Properties object,
// but a lot of what Config does is to resolve configured types, for which we need
// control over the loader that is used for resolution
ClassLoader loader = Config.class.getClassLoader();
// where did we initialize from
ArrayList<Object> sources = new ArrayList<Object>();
ArrayList<ConfigChangeListener> changeListeners;
// Properties are simple Hashmaps, but we want to maintain the order of entries
LinkedList<String> entrySequence = new LinkedList<String>();
// an [optional] hashmap to keep objects we want to be singletons
HashMap<String,Object> singletons;
public final Object[] CONFIG_ARGS = { this };
// the original command line args that were passed into the constructor
String[] args;
// non-property/option command line args (starting from the first arg that is not prepened by '-','+')
String[] freeArgs;
/**
* the standard Config constructor that processes the whole properties stack
*/
public Config (String[] cmdLineArgs) {
args = cmdLineArgs;
String[] a = cmdLineArgs.clone(); // we might nullify some of them
// we need the app properties (*.jpf) pathname upfront because it might define 'site'
String appProperties = getAppPropertiesLocation(a);
//--- the site properties
String siteProperties = getSitePropertiesLocation( a, appProperties);
if (siteProperties != null){
loadProperties( siteProperties);
}
//--- get the project properties from current dir + site configured extensions
loadProjectProperties();
//--- the application properties
if (appProperties != null){
loadProperties( appProperties);
}
//--- at last, the (rest of the) command line properties
loadArgs(a);
// note that global path collection now happens from initClassLoader(), to
// accommodate for deferred project initialization when explicitly setting Config entries
//printEntries();
}
private Config() {
// just interal, for reloading
}
/**
* single source Config constructor (does not process stack)
* @param fileName - single properties filename to initialize from
*/
public Config (String fileName){
loadProperties(fileName);
}
public Config (Reader in){
try {
load(in);
} catch (IOException iox){
exception("error reading data: " + iox);
}
}
public static void enableLogging (boolean enableLogging){
log = enableLogging;
}
public void log (String msg){
if (log){ // very simplisitc, but we might do more in the future
System.out.println(msg);
}
}
String getAppPropertiesLocation(String[] args){
String path = null;
path = getPathArg(args, "app");
if (path == null){
// see if the first free arg is a *.jpf
path = getAppArg(args);
}
put("jpf.app", path);
return path;
}
String getSitePropertiesLocation(String[] args, String appPropPath){
String path = getPathArg(args, "site");
if (path == null){
// look into the app properties
// NOTE: we might want to drop this in the future because it constitutes
// a cyclic properties file dependency
if (appPropPath != null){
path = JPFSiteUtils.getMatchFromFile(appPropPath,"site");
}
if (path == null) {
File siteProps = JPFSiteUtils.getStandardSiteProperties();
if (siteProps != null){
path = siteProps.getAbsolutePath();
}
}
}
put("jpf.site", path);
return path;
}
// watch out - this does not reset the computed paths!
public Config reload() {
log("reloading config");
// just reload all our sources
Config newConfig = new Config();
for (Object src : sources){
if (src instanceof File) {
newConfig.loadProperties(((File)src).getPath());
} else if (src instanceof URL) {
newConfig.loadProperties((URL)src);
} else {
log("don't know how to reload: " + src);
}
}
// now reload command line args on top of that
newConfig.loadArgs(args);
newConfig.args = args;
return newConfig;
}
public String[] getArgs() {
return args;
}
/*
* note that matching args are expanded and stored here, to avoid any
* discrepancy with value expansions (which are order-dependent)
*/
protected String getPathArg (String[] args, String key){
int keyLen = key.length();
for (int i=0; i<args.length; i++){
String a = args[i];
if (a != null){
int len = a.length();
if (len > keyLen + 2){
if (a.charAt(0) == '+' && a.charAt(keyLen+1) == '='){
if (a.substring(1, keyLen+1).equals(key)){
String val = expandString(key, a.substring(keyLen+2));
args[i] = null; // processed
return val;
}
}
}
}
}
return null;
}
/*
* if the first freeArg is a JPF application property filename, use this
* as targetArg and set the "jpf.app" property accordingly
*/
protected String getAppArg (String[] args){
for (int i=0; i<args.length; i++){
String a = args[i];
if (a != null && a.length() > 0){
switch (a.charAt(0)) {
case '+': continue;
case '-': continue;
default:
if (a.endsWith(".jpf")){
String val = expandString("jpf.app", a);
args[i] = null; // processed
return val;
}
}
}
}
return null;
}
protected void loadProperties (URL url){
log("loading defaults from: " + url);
InputStream is = null;
try {
is = url.openStream();
load(is);
sources.add(url);
} catch (IOException iox){
log("error in input stream for: " + url + " : " + iox.getMessage());
} finally {
if (is != null){
try {
is.close();
} catch (IOException iox1){
log("error closing input stream for: " + url + " : " + iox1.getMessage());
}
}
}
}
protected void setConfigPathProperties (String fileName){
put("config", fileName);
int i = fileName.lastIndexOf(File.separatorChar);
if (i>=0){
put("config_path", fileName.substring(0,i));
} else {
put("config_path", ".");
}
}
protected boolean loadProperties (String fileName) {
if (fileName != null && fileName.length() > 0) {
FileInputStream is = null;
try {
File f = new File(fileName);
if (f.isFile()) {
log("loading property file: " + fileName);
setConfigPathProperties(f.getAbsolutePath());
sources.add(f);
is = new FileInputStream(f);
load(is);
return true;
} else {
throw exception("property file does not exist: " + f.getAbsolutePath());
}
} catch (MissingRequiredKeyException rkx){
// Hmpff - control exception
log("missing required key: " + rkx.getMessage() + ", skipping: " + fileName);
} catch (IOException iex) {
throw exception("error reading properties: " + fileName);
} finally {
if (is != null){
try {
is.close();
} catch (IOException iox1){
log("error closing input stream for file: " + fileName);
}
}
}
}
return false;
}
/**
* this holds the policy defining in which order we process directories
* containing JPF projects (i.e. jpf.properties files)
*/
protected void loadProjectProperties () {
// this is the list of directories holding jpf.properties files that
// have to be processed in order of entry (increasing priority)
LinkedList<File> jpfDirs = new LinkedList<File>();
// deduce the JPF projects in use (at least jpf-core) from the CL which
// defined this class
addJPFdirsFromClasspath(jpfDirs);
// add all the site configured extension dirs (but NOT jpf-core)
addJPFdirsFromSiteExtensions(jpfDirs);
// add the current dir, which has highest priority (this might bump up
// a previous entry by reodering it - which includes jpf-core)
addCurrentJPFdir(jpfDirs);
// now load all the jpf.property files we found in these dirs
// (later loads can override previous settings)
for (File dir : jpfDirs){
loadProperties(new File(dir,"jpf.properties").getAbsolutePath());
}
}
protected void appendPath (String pathKey, String key, String configPath){
String[] paths = getStringArray(key);
if (paths != null){
for (String e : paths) {
if (!e.startsWith("${") || !e.startsWith(File.separator)) {
e = configPath + File.separatorChar + e;
}
append(pathKey, e, PATH_SEPARATOR);
}
}
}
protected void addJPFdirs (List<File> jpfDirs, File dir){
while (dir != null) {
File jpfProp = new File(dir, "jpf.properties");
if (jpfProp.isFile()) {
registerJPFdir(jpfDirs, dir);
return; // we probably don't want recursion here
}
dir = getParentFile(dir);
}
}
/**
* add the current dir to the list of JPF components.
* Note: this includes the core, so that we maintain the general
* principle that the enclosing project takes precedence (imagine the opposite:
* if we want to test a certain feature that is overridden by another extension
* we don't know about)
*/
protected void addCurrentJPFdir(List<File> jpfDirs){
File dir = new File(System.getProperty("user.dir"));
while (dir != null) {
File jpfProp = new File(dir, "jpf.properties");
if (jpfProp.isFile()) {
registerJPFdir(jpfDirs, dir);
return;
}
dir = getParentFile(dir);
}
}
protected void addJPFdirsFromClasspath(List<File> jpfDirs) {
String cp = System.getProperty("java.class.path");
String[] cpEntries = cp.split(File.pathSeparator);
for (String p : cpEntries) {
File f = new File(p);
File dir = f.isFile() ? getParentFile(f) : f;
addJPFdirs(jpfDirs, dir);
}
}
protected void addJPFdirsFromSiteExtensions (List<File> jpfDirs){
String[] extensions = getCompactStringArray("extensions");
if (extensions != null){
for (String pn : extensions){
addJPFdirs( jpfDirs, new File(pn));
}
}
}
/**
* the obvious part is that it only adds to the list if the file is absent
* the not-so-obvious part is that it re-orders already present files
* to maintain the priority
*/
protected boolean registerJPFdir(List<File> list, File dir){
try {
dir = dir.getCanonicalFile();
for (File e : list) {
if (e.equals(dir)) {
list.remove(e);
list.add(e);
return false;
}
}
} catch (IOException iox) {
throw new JPFConfigException("illegal path spec: " + dir);
}
list.add(dir);
return true;
}
static File root = new File(File.separator);
protected File getParentFile(File f){
if (f == root){
return null;
} else {
File parent = f.getParentFile();
if (parent == null){
parent = new File(f.getAbsolutePath());
if (parent.getName().equals(root.getName())) {
return root;
} else {
return parent;
}
} else {
return parent;
}
}
}
/*
* argument syntax:
* {'+'<key>['='<val>'] | '-'<driver-arg>} {<free-arg>}
*
* (1) null cmdLineArgs are ignored
* (2) all config cmdLineArgs start with '+'
* (3) if '=' is ommitted, a 'true' value is assumed
* (4) if <val> is ommitted, a 'null' value is assumed
* (5) no spaces around '='
* (6) all '-' driver-cmdLineArgs are ignored
*/
protected void loadArgs (String[] cmdLineArgs) {
for (int i=0; i<cmdLineArgs.length; i++){
String a = cmdLineArgs[i];
if (a != null && a.length() > 0){
switch (a.charAt(0)){
case '+': // Config arg
processArg(a.substring(1));
break;
case '-': // driver arg, ignore
continue;
default: // free (non property/option) cmdLineArgs to follow
int n = cmdLineArgs.length - i;
freeArgs = new String[n];
System.arraycopy(cmdLineArgs, i, freeArgs, 0, n);
return;
}
}
}
}
/*
* this does not include the '+' prefix, just the
* <key>[=[<value>]]
*/
protected void processArg (String a) {
int idx = a.indexOf("=");
if (idx == 0){
throw new JPFConfigException("illegal option: " + a);
}
if (idx > 0) {
String key = a.substring(0, idx).trim();
String val = a.substring(idx + 1).trim();
if (val.length() == 0){
val = null;
}
setProperty(key, val);
} else {
setProperty(a.trim(), "true");
}
}
/**
* replace string constants with global static objects
*/
protected String normalize (String v) {
if (v == null){
return null; // ? maybe TRUE - check default loading of "key" or "key="
}
// trim leading and trailing blanks (at least Java 1.4.2 does not take care of trailing blanks)
v = v.trim();
// true/false
if ("true".equalsIgnoreCase(v)
|| "yes".equalsIgnoreCase(v)
|| "on".equalsIgnoreCase(v)) {
v = TRUE;
} else if ("false".equalsIgnoreCase(v)
|| "no".equalsIgnoreCase(v)
|| "off".equalsIgnoreCase(v)) {
v = FALSE;
}
// nil/null
if ("nil".equalsIgnoreCase(v) || "null".equalsIgnoreCase(v)){
v = null;
}
return v;
}
// our internal expander
// Note that we need to know the key this came from, to handle recursive expansion
protected String expandString (String key, String s) {
int i, j = 0;
if (s == null || s.length() == 0) {
return s;
}
while ((i = s.indexOf("${", j)) >= 0) {
if ((j = s.indexOf('}', i)) > 0) {
String k = s.substring(i + 2, j);
String v;
if ((key != null) && key.equals(k)) {
// that's expanding itself -> use what is there
v = getProperty(key);
} else {
// refers to another key, which is already expanded, so this
// can't get recursive (we expand during entry storage)
v = getProperty(k);
}
if (v == null) { // if we don't have it, fall back to system properties
v = System.getProperty(k);
}
if (v != null) {
s = s.substring(0, i) + v + s.substring(j + 1, s.length());
j = i + v.length();
} else {
s = s.substring(0, i) + s.substring(j + 1, s.length());
j = i;
}
}
}
return s;
}
boolean loadPropertiesRecursive (String fileName){
// save the current values of automatic properties
String curConfig = (String)get("config");
String curConfigPath = (String)get("config_path");
File propFile = new File(fileName);
if (!propFile.isAbsolute()){
propFile = new File(curConfigPath, fileName);
}
String absPath = propFile.getAbsolutePath();
if (!propFile.isFile()){
throw exception("property file does not exist: " + absPath);
}
boolean ret = loadProperties(absPath);
// restore the automatic properties
super.put("config", curConfig);
super.put("config_path", curConfigPath);
return ret;
}
void includePropertyFile(String key, String value){
value = expandString(key, value);
if (value != null && value.length() > 0){
loadPropertiesRecursive(value);
} else {
throw exception("@include pathname argument missing");
}
}
void includeCondPropertyFile(String key, String value, boolean keyPresent){
value = expandString(key, value);
if (value != null && value.length() > 0){
// check if it's a conditional "@include_unless/if = ?key?pathName"
if (value.charAt(0) == '?'){
int idx = value.indexOf('?', 1);
if (idx > 1){
String k = value.substring(1, idx);
if (containsKey(k) == keyPresent){
String v = value.substring(idx+1);
if (v.length() > 0){
loadPropertiesRecursive(v);
} else {
throw exception("@include_unless pathname argument missing (?<key>?<pathName>)");
}
}
} else {
throw exception("malformed @include_unless argument (?<key>?<pathName>), found: " + value);
}
} else {
throw exception("malformed @include_unless argument (?<key>?<pathName>), found: " + value);
}
} else {
throw exception("@include_unless missing ?<key>?<pathName> argument");
}
}
void includeProjectPropertyFile (String projectId){
String projectPath = getString(projectId);
if (projectPath != null){
File projectProps = new File(projectPath, "jpf.properties");
if (projectProps.isFile()){
loadPropertiesRecursive(projectProps.getAbsolutePath());
} else {
throw exception("project properties not found: " + projectProps.getAbsolutePath());
}
} else {
throw exception("unknown project id (check site.properties): " + projectId);
}
}
// we override this so that we can handle expansion for both key and value
// (value expansion can be recursive, i.e. refer to itself)
@Override
public Object put (Object keyObject, Object valueObject){
if (keyObject == null){
throw exception("no null keys allowed");
} else if (!(keyObject instanceof String)){
throw exception("only String keys allowed, got: " + keyObject);
}
if (valueObject != null && !(valueObject instanceof String)){
throw exception("only String or null values allowed, got: " + valueObject);
}
String key = (String)keyObject;
String value = (String)valueObject;
if (key.length() == 0){
throw exception("no empty keys allowed");
}
if (key.charAt(0) == KEY_PREFIX){
processPseudoProperty( key, value);
return null; // no value it replaces
} else {
// finally, a real key/value pair to add (or remove) - expand and store
String k = expandString(null, key);
if (!(value == null)) { // add or overwrite entry
String v = value;
if (k.charAt(k.length() - 1) == '+') { // the append hack
k = k.substring(0, k.length() - 1);
return append(k, v, null);
} else if (k.charAt(0) == '+') { // the prepend hack
k = k.substring(1);
return prepend(k, v, null);
} else { // normal value set
v = normalize(expandString(k, v));
if (v != null){
return setKey(k, v);
} else {
return removeKey(k);
}
}
} else { // setting a null value removes the entry
return removeKey(k);
}
}
}
protected void processPseudoProperty( String key, String value){
if (REQUIRES_KEY.equals(key)) {
// shortcircuit loading of property files - used to enforce order
// of properties, e.g. to model dependencies
for (String reqKey : split(value)) {
if (!containsKey(reqKey)) {
throw new MissingRequiredKeyException(reqKey);
}
}
} else if (INCLUDE_KEY.equals(key)) {
includePropertyFile(key, value);
} else if (INCLUDE_UNLESS_KEY.equals(key)) {
includeCondPropertyFile(key, value, false);
} else if (INCLUDE_IF_KEY.equals(key)) {
includeCondPropertyFile(key, value, true);
} else if (USING_KEY.equals(key)) {
// check if corresponding jpf.properties has already been loaded. If yes, skip
if (!haveSeenProjectProperty(value)){
includeProjectPropertyFile(value);
}
} else {
throw exception("unknown keyword: " + key);
}
}
protected boolean haveSeenProjectProperty (String key){
String pn = getString(key);
if (pn == null){
return false;
} else {
return sources.contains( new File( pn, "jpf.properties"));
}
}
private Object setKey (String k, String v){
Object oldValue = put0(k, v);
notifyPropertyChangeListeners(k, (String) oldValue, v);
return oldValue;
}
private Object removeKey (String k){
Object oldValue = super.get(k);
remove0(k);
notifyPropertyChangeListeners(k, (String) oldValue, null);
return oldValue;
}
private Object put0 (String k, Object v){
entrySequence.add(k);
return super.put(k, v);
}
private Object remove0 (String k){
entrySequence.add(k);
return super.remove(k);
}
public String prepend (String key, String value, String separator) {
String oldValue = getProperty(key);
value = normalize( expandString(key, value));
append0(key, oldValue, value, oldValue, separator);
return oldValue;
}
public String append (String key, String value, String separator) {
String oldValue = getProperty(key);
value = normalize( expandString(key, value));
append0(key, oldValue, oldValue, value, separator);
return oldValue;
}
private void append0 (String key, String oldValue, String a, String b, String separator){
String newValue;
if (a != null){
if (b != null) {
StringBuilder sb = new StringBuilder(a);
if (separator != null) {
sb.append(separator);
}
sb.append(b);
newValue = sb.toString();
} else { // b==null : nothing to append
if (oldValue == a){ // using reference compare is intentional here
return; // no change
} else {
newValue = a;
}
}
} else { // a==null : nothing to append to
if (oldValue == b || b == null){ // using reference compare is intentional here
return; // no change
} else {
newValue = b;
}
}
// if we get here, we have a newValue that differs from oldValue
put0(key, newValue);
notifyPropertyChangeListeners(key, oldValue, newValue);
}
protected String append (String key, String value) {
return append(key, value, LIST_SEPARATOR); // append with our standard list separator
}
/**
* check if we have a key.index entry. If not, fall back to key
* This simplifies clients that can have process id indexed properties
*/
public String getIndexableKey (String key, int index){
String k = key + '.' + index;
if (containsKey(k)){
return k;
} else {
return key;
}
}
public void setClassLoader (ClassLoader newLoader){
loader = newLoader;
}
public ClassLoader getClassLoader (){
return loader;
}
public boolean hasSetClassLoader (){
return Config.class.getClassLoader() != loader;
}
public JPFClassLoader initClassLoader (ClassLoader parent) {
ArrayList<String> list = new ArrayList<String>();
// we prefer to call this here automatically instead of allowing
// explicit collectGlobalPath() calls because (a) this could not preserve
// initial path settings, and (b) setting it *after* the JPFClassLoader got
// installed won't work (would have to add URLs explicitly, or would have
// to create a new JPFClassLoader, which then conflicts with classes already
// defined by the previous one)
collectGlobalPaths();
if (log){
log("collected native_classpath=" + get("native_classpath"));
log("collected native_libraries=" + get("native_libraries"));
}
String[] cp = getCompactStringArray("native_classpath");
cp = FileUtils.expandWildcards(cp);
for (String e : cp) {
list.add(e);
}
URL[] urls = FileUtils.getURLs(list);
String[] nativeLibs = getCompactStringArray("native_libraries");
JPFClassLoader cl;
if (parent instanceof JPFClassLoader){ // no need to create a new one, just initialize
cl = (JPFClassLoader)parent;
for (URL url : urls){
cl.addURL(url);
}
cl.setNativeLibs(nativeLibs);
} else {
cl = new JPFClassLoader( urls, nativeLibs, parent);
}
loader = cl;
return cl;
}
/**
* has to be called if 'native_classpath' gets explicitly changed
* USE WITH CARE - if this is messed up, it is hard to debug
*/
public void updateClassLoader (){
if (loader != null && loader instanceof JPFClassLoader){
JPFClassLoader jpfCl = (JPFClassLoader)loader;
// ArrayList<String> list = new ArrayList<String>();
String[] cp = getCompactStringArray("native_classpath");
cp = FileUtils.expandWildcards(cp);
for (String e : cp) {
URL url = FileUtils.getURL(e);
jpfCl.addURL(url); // this does not add if already present
}
String[] nativeLibs = getCompactStringArray("native_libraries");
jpfCl.setNativeLibs(nativeLibs);
}
}
//------------------------------ public methods - the Config API
public String[] getEntrySequence () {
// whoever gets this might add/append/remove items, so we have to
// avoid ConcurrentModificationExceptions
return entrySequence.toArray(new String[entrySequence.size()]);
}
public void addChangeListener (ConfigChangeListener l) {
if (changeListeners == null) {
changeListeners = new ArrayList<ConfigChangeListener>();
changeListeners.add(l);
} else {
if (!changeListeners.contains(l)) {
changeListeners.add(l);
}
}
}
public void removeChangeListener (ConfigChangeListener l) {
if (changeListeners != null) {
changeListeners.remove(l);
if (changeListeners.size() == 0) {
changeListeners = null;
}
}
}
// this shouldn't really be public but only accessible to JPF
public void jpfRunTerminated() {
if (changeListeners != null) {
// note we can't use the standard list iterator here because the sole purpose
// of having this notification is to remove the listener from the list during its enumeration
// which would give us ConcurrentModificationExceptions
ArrayList<ConfigChangeListener> list = (ArrayList<ConfigChangeListener>)changeListeners.clone();
for (ConfigChangeListener l : list) {
l.jpfRunTerminated(this);
}
}
}
public JPFException exception (String msg) {
String context = getString("config");
if (context != null){
msg = "error in " + context + " : " + msg;
}
return new JPFConfigException(msg);
}
public void throwException(String msg) {
throw new JPFConfigException(msg);
}
/**
* return any command line args that are not options or properties
* (this usually contains the application class and arguments)
*/
public String[] getFreeArgs(){
return freeArgs;
}
//--- special keys
/*
* target and its associated keys (target.args, target.entry) are now
* just ordinary key/value pairs and only here as convenience methods
* for JPF drivers/shells so that you don't have to remember the key names
*
* NOTE - this does only work for a SingleProcessVM, and only has the
* desired effect before the JPF object is created
*/
public void setTarget (String clsName) {
put("target", clsName);
}
public String getTarget(){
return getString("target");
}
public void setTargetArgs (String[] args) {
StringBuilder sb = new StringBuilder();
int i=0;
for (String a : args){
if (i++ > 0){
sb.append(',');
}
sb.append(a);
}
put("target.args", sb.toString());
}
public String[] getTargetArgs(){
String[] a = getStringArray("target.args");
if (a == null){
return new String[0];
} else {
return a;
}
}
public void setTargetEntry (String mthName) {
put("target.entry", mthName);
}
public String getTargetEntry(){
return getString("target.entry");
}
//----------------------- type specific accessors
public boolean getBoolean(String key) {
String v = getProperty(key);
return (v == TRUE);
}
public boolean getBoolean(String key, boolean def) {
String v = getProperty(key);
if (v != null) {
return (v == TRUE);
} else {
return def;
}
}
/**
* for a given <baseKey>, check if there are corresponding
* values for keys <baseKey>.0 ... <baseKey>.<maxSize>
* If a value is found, store it in an array at the respective index
*
* @param baseKey String with base key without trailing '.'
* @param maxSize maximum size of returned value array
* @return trimmed array with String values found in dictionary
*/
public String[] getStringEnumeration (String baseKey, int maxSize) {
String[] arr = new String[maxSize];
int max=-1;
StringBuilder sb = new StringBuilder(baseKey);
sb.append('.');
int len = baseKey.length()+1;
for (int i=0; i<maxSize; i++) {
sb.setLength(len);
sb.append(i);
String v = getString(sb.toString());
if (v != null) {
arr[i] = v;
max = i;
}
}
if (max >= 0) {
max++;
if (max < maxSize) {
String[] a = new String[max];
System.arraycopy(arr,0,a,0,max);
return a;
} else {
return arr;
}
} else {
return null;
}
}
public String[] getKeysStartingWith (String prefix){
ArrayList<String> list = new ArrayList<String>();
for (Enumeration<?> e = keys(); e.hasMoreElements(); ){
String k = e.nextElement().toString();
if (k.startsWith(prefix)){
list.add(k);
}
}
return list.toArray(new String[list.size()]);
}
public String[] getKeyComponents (String key){
return key.split("\\.");
}
public int[] getIntArray (String key) throws JPFConfigException {
String v = getProperty(key);
if (v != null) {
String[] sa = split(v);
int[] a = new int[sa.length];
int i = 0;
try {
for (; i<sa.length; i++) {
String s = sa[i];
int val;
if (s.startsWith("0x")){
val = Integer.parseInt(s.substring(2),16);
} else {
val = Integer.parseInt(s);
}
a[i] = val;
}
return a;
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal int[] element in '" + key + "' = \"" + sa[i] + '"');
}
} else {
return null;
}
}
public int[] getIntArray (String key, int... defaultValues){
int[] val = getIntArray(key);
if (val == null){
return defaultValues;
} else {
return val;
}
}
public long getDuration (String key, long defValue) {
String v = getProperty(key);
if (v != null) {
long d = 0;
if (v.indexOf(':') > 0){
String[] a = v.split(":");
if (a.length > 3){
//log.severe("illegal duration: " + key + "=" + v);
return defValue;
}
int m = 1000;
for (int i=a.length-1; i>=0; i--, m*=60){
try {
int n = Integer.parseInt(a[i]);
d += m*n;
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal duration element in '" + key + "' = \"" + v + '"');
}
}
} else {
try {
d = Long.parseLong(v);
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal duration element in '" + key + "' = \"" + v + '"');
}
}
return d;
}
return defValue;
}
public int getInt(String key) {
return getInt(key, 0);
}
public int getInt(String key, int defValue) {
String v = getProperty(key);
if (v != null) {
if (MAX.equals(v)){
return Integer.MAX_VALUE;
} else {
try {
return Integer.parseInt(v);
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal int element in '" + key + "' = \"" + v + '"');
}
}
}
return defValue;
}
public long getLong(String key) {
return getLong(key, 0L);
}
public long getLong(String key, long defValue) {
String v = getProperty(key);
if (v != null) {
if (MAX.equals(v)){
return Long.MAX_VALUE;
} else {
try {
return Long.parseLong(v);
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal long element in '" + key + "' = \"" + v + '"');
}
}
}
return defValue;
}
public long[] getLongArray (String key) throws JPFConfigException {
String v = getProperty(key);
if (v != null) {
String[] sa = split(v);
long[] a = new long[sa.length];
int i = 0;
try {
for (; i<sa.length; i++) {
a[i] = Long.parseLong(sa[i]);
}
return a;
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal long[] element in " + key + " = " + sa[i]);
}
} else {
return null;
}
}
public long[] getLongArray (String key, long... defaultValues){
long[] val = getLongArray(key);
if (val != null){
return val;
} else {
return defaultValues;
}
}
public float getFloat (String key) {
return getFloat(key, 0.0f);
}
public float getFloat (String key, float defValue) {
String v = getProperty(key);
if (v != null) {
try {
return Float.parseFloat(v);
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal float element in '" + key + "' = \"" + v + '"');
}
}
return defValue;
}
public float[] getFloatArray (String key) throws JPFConfigException {
String v = getProperty(key);
if (v != null) {
String[] sa = split(v);
float[] a = new float[sa.length];
int i = 0;
try {
for (; i<sa.length; i++) {
a[i] = Float.parseFloat(sa[i]);
}
return a;
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal float[] element in " + key + " = " + sa[i]);
}
} else {
return null;
}
}
public float[] getFloatArray (String key, float... defaultValues){
float[] v = getFloatArray( key);
if (v != null){
return v;
} else {
return defaultValues;
}
}
public double getDouble (String key) {
return getDouble(key, 0.0);
}
public double getDouble (String key, double defValue) {
String v = getProperty(key);
if (v != null) {
try {
return Double.parseDouble(v);
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal double element in '" + key + "' = \"" + v + '"');
}
}
return defValue;
}
public double[] getDoubleArray (String key) throws JPFConfigException {
String v = getProperty(key);
if (v != null) {
String[] sa = split(v);
double[] a = new double[sa.length];
int i = 0;
try {
for (; i<sa.length; i++) {
a[i] = Double.parseDouble(sa[i]);
}
return a;
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal double[] element in " + key + " = " + sa[i]);
}
} else {
return null;
}
}
public double[] getDoubleArray (String key, double... defaultValues){
double[] v = getDoubleArray( key);
if (v != null){
return v;
} else {
return defaultValues;
}
}
public <T extends Enum<T>> T getEnum( String key, T[] values, T defValue){
String v = getProperty(key);
if (v != null){
for (T t : values){
if (v.equalsIgnoreCase(t.name())){
return t;
}
}
throw new JPFConfigException("unknown enum value for " + key + " = " + v);
} else {
return defValue;
}
}
public String getString(String key) {
return getProperty(key);
}
public String getString(String key, String defValue) {
String s = getProperty(key);
if (s != null) {
return s;
} else {
return defValue;
}
}
/**
* return memory size in bytes, or 'defValue' if not in dictionary. Encoding
* can have a 'M' or 'k' postfix, values have to be positive integers (decimal
* notation)
*/
public long getMemorySize(String key, long defValue) {
String v = getProperty(key);
long sz = defValue;
if (v != null) {
int n = v.length() - 1;
try {
char c = v.charAt(n);
if ((c == 'M') || (c == 'm')) {
sz = Long.parseLong(v.substring(0, n)) << 20;
} else if ((c == 'K') || (c == 'k')) {
sz = Long.parseLong(v.substring(0, n)) << 10;
} else {
sz = Long.parseLong(v);
}
} catch (NumberFormatException nfx) {
throw new JPFConfigException("illegal memory size element in '" + key + "' = \"" + v + '"');
}
}
return sz;
}
public HashSet<String> getStringSet(String key){
String v = getProperty(key);
if (v != null && (v.length() > 0)) {
HashSet<String> hs = new HashSet<String>();
for (String s : split(v)) {
hs.add(s);
}
return hs;
}
return null;
}
public HashSet<String> getNonEmptyStringSet(String key){
HashSet<String> hs = getStringSet(key);
if (hs != null && hs.isEmpty()) {
return null;
} else {
return hs;
}
}
public String[] getStringArray(String key) {
String v = getProperty(key);
if (v != null && (v.length() > 0)) {
return split(v);
}
return null;
}
public String[] getStringArray(String key, char[] delims) {
String v = getProperty(key);
if (v != null && (v.length() > 0)) {
return split(v,delims);
}
return null;
}
public String[] getCompactTrimmedStringArray (String key){
String[] a = getStringArray(key);
if (a != null) {
for (int i = 0; i < a.length; i++) {
String s = a[i];
if (s != null && s.length() > 0) {
a[i] = s.trim();
}
}
return removeEmptyStrings(a);
} else {
return EMPTY_STRING_ARRAY;
}
}
public String[] getCompactStringArray(String key){
return removeEmptyStrings(getStringArray(key));
}
public String[] getStringArray(String key, String[] def){
String v = getProperty(key);
if (v != null && (v.length() > 0)) {
return split(v);
} else {
return def;
}
}
public static String[] removeEmptyStrings (String[] a){
if (a != null) {
int n = 0;
for (int i=0; i<a.length; i++){
if (a[i].length() > 0){
n++;
}
}
if (n < a.length){ // we have empty strings in the split
String[] r = new String[n];
for (int i=0, j=0; i<a.length; i++){
if (a[i].length() > 0){
r[j++] = a[i];
if (j == n){
break;
}
}
}
return r;
} else {
return a;
}
}
return null;
}
/**
* return an [optional] id part of a property value (all that follows the first '@')
*/
String getIdPart (String key) {
String v = getProperty(key);
if ((v != null) && (v.length() > 0)) {
int i = v.indexOf('@');
if (i >= 0){
return v.substring(i+1);
}
}
return null;
}
public Class<?> asClass (String v) throws JPFConfigException {
if ((v != null) && (v.length() > 0)) {
v = stripId(v);
v = expandClassName(v);
try {
return loader.loadClass(v);
} catch (ClassNotFoundException cfx) {
throw new JPFConfigException("class not found " + v + " by classloader: " + loader);
} catch (ExceptionInInitializerError ix) {
throw new JPFConfigException("class initialization of " + v + " failed: " + ix,
ix);
}
}
return null;
}
public <T> Class<? extends T> getClass(String key, Class<T> type) throws JPFConfigException {
Class<?> cls = asClass( getProperty(key));
if (cls != null) {
if (type.isAssignableFrom(cls)) {
return cls.asSubclass(type);
} else {
throw new JPFConfigException("classname entry for: \"" + key + "\" not of type: " + type.getName());
}
}
return null;
}
public Class<?> getClass(String key) throws JPFConfigException {
return asClass( getProperty(key));
}
public Class<?> getEssentialClass(String key) throws JPFConfigException {
Class<?> cls = getClass(key);
if (cls == null) {
throw new JPFConfigException("no classname entry for: \"" + key + "\"");
}
return cls;
}
String stripId (String v) {
int i = v.indexOf('@');
if (i >= 0) {
return v.substring(0,i);
} else {
return v;
}
}
String getId (String v){
int i = v.indexOf('@');
if (i >= 0) {
return v.substring(i+1);
} else {
return null;
}
}
String expandClassName (String clsName) {
if (clsName != null && clsName.length() > 0 && clsName.charAt(0) == '.') {
return "gov.nasa.jpf" + clsName;
} else {
return clsName;
}
}
public Class<?>[] getClasses(String key) throws JPFConfigException {
String[] v = getStringArray(key);
if (v != null) {
int n = v.length;
Class<?>[] a = new Class[n];
for (int i = 0; i < n; i++) {
String clsName = expandClassName(v[i]);
if (clsName != null && clsName.length() > 0){
try {
clsName = stripId(clsName);
a[i] = loader.loadClass(clsName);
} catch (ClassNotFoundException cnfx) {
throw new JPFConfigException("class not found " + v[i]);
} catch (ExceptionInInitializerError ix) {
throw new JPFConfigException("class initialization of " + v[i] + " failed: " + ix, ix);
}
}
}
return a;
}
return null;
}
/**
* this one is used to instantiate objects from a list of keys that share
* the same prefix, e.g.
*
* shell.panels = config,site
* shell.panels.site = .shell.panels.SitePanel
* shell.panels.config = .shell.panels.ConfigPanel
* ...
*
* note that we specify default class names, not classes, so that the classes
* get loaded through our own loader at call time (they might not be visible
* to our caller)
*/
public <T> T[] getGroupInstances (String keyPrefix, String keyPostfix, Class<T> type,
String... defaultClsNames) throws JPFConfigException {
String[] ids = getCompactTrimmedStringArray(keyPrefix);
if (ids.length > 0){
keyPrefix = keyPrefix + '.';
T[] arr = (T[]) Array.newInstance(type, ids.length);
for(int i = 0; i < ids.length; i++){
String key = keyPrefix + ids[i];
if (keyPostfix != null){
key = key + keyPostfix;
}
arr[i] = getEssentialInstance(key, type);
}
return arr;
} else {
T[] arr = (T[]) Array.newInstance(type, defaultClsNames.length);
for (int i=0; i<arr.length; i++){
arr[i] = getInstance((String)null, defaultClsNames[i], type);
if (arr[i] == null){
exception("cannot instantiate default type " + defaultClsNames[i]);
}
}
return arr;
}
}
// <2do> - that's kind of kludged together, not very efficient
String[] getIds (String key) {
String v = getProperty(key);
if (v != null) {
int i = v.indexOf('@');
if (i >= 0) { // Ok, we have ids
String[] a = split(v);
String[] ids = new String[a.length];
for (i = 0; i<a.length; i++) {
ids[i] = getId(a[i]);
}
return ids;
}
}
return null;
}
public <T> ArrayList<T> getInstances(String key, Class<T> type) throws JPFConfigException {
Class<?>[] argTypes = { Config.class };
Object[] args = { this };
return getInstances(key,type,argTypes,args);
}
public <T> ArrayList<T> getInstances(String key, Class<T> type, Class<?>[]argTypes, Object[] args)
throws JPFConfigException {
Class<?>[] c = getClasses(key);
if (c != null) {
String[] ids = getIds(key);
ArrayList<T> a = new ArrayList<T>(c.length);
for (int i = 0; i < c.length; i++) {
String id = (ids != null) ? ids[i] : null;
T listener = getInstance(key, c[i], type, argTypes, args, id);
if (listener != null) {
a.add( listener);
} else {
// should report here
}
}
return a;
} else {
// should report here
}
return null;
}
public <T> T getInstance(String key, Class<T> type, String defClsName) throws JPFConfigException {
Class<?>[] argTypes = CONFIG_ARGTYPES;
Object[] args = CONFIG_ARGS;
Class<?> cls = getClass(key);
String id = getIdPart(key);
if (cls == null) {
try {
cls = loader.loadClass(defClsName);
} catch (ClassNotFoundException cfx) {
throw new JPFConfigException("class not found " + defClsName);
} catch (ExceptionInInitializerError ix) {
throw new JPFConfigException("class initialization of " + defClsName + " failed: " + ix, ix);
}
}
return getInstance(key, cls, type, argTypes, args, id);
}
public <T> T getInstance(String key, Class<T> type) throws JPFConfigException {
Class<?>[] argTypes = CONFIG_ARGTYPES;
Object[] args = CONFIG_ARGS;
return getInstance(key, type, argTypes, args);
}
public <T> T getInstance(String key, Class<T> type, Class<?>[] argTypes,
Object[] args) throws JPFConfigException {
Class<?> cls = getClass(key);
String id = getIdPart(key);
if (cls != null) {
return getInstance(key, cls, type, argTypes, args, id);
} else {
return null;
}
}
public <T> T getInstance(String key, Class<T> type, Object arg1, Object arg2) throws JPFConfigException {
Class<?>[] argTypes = new Class<?>[2];
argTypes[0] = arg1.getClass();
argTypes[1] = arg2.getClass();
Object[] args = new Object[2];
args[0] = arg1;
args[1] = arg2;
return getInstance(key, type, argTypes, args);
}
public <T> T getEssentialInstance(String key, Class<T> type) throws JPFConfigException {
Class<?>[] argTypes = { Config.class };
Object[] args = { this };
return getEssentialInstance(key, type, argTypes, args);
}
/**
* just a convenience method for ctor calls that take two arguments
*/
public <T> T getEssentialInstance(String key, Class<T> type, Object arg1, Object arg2) throws JPFConfigException {
Class<?>[] argTypes = new Class<?>[2];
argTypes[0] = arg1.getClass();
argTypes[1] = arg2.getClass();
Object[] args = new Object[2];
args[0] = arg1;
args[1] = arg2;
return getEssentialInstance(key, type, argTypes, args);
}
public <T> T getEssentialInstance(String key, Class<T> type, Class<?>[] argTypes, Object[] args) throws JPFConfigException {
Class<?> cls = getEssentialClass(key);
String id = getIdPart(key);
return getInstance(key, cls, type, argTypes, args, id);
}
public <T> T getInstance (String id, String clsName, Class<T> type, Class<?>[] argTypes, Object[] args) throws JPFConfigException {
Class<?> cls = asClass(clsName);
if (cls != null) {
return getInstance(id, cls, type, argTypes, args, id);
} else {
return null;
}
}
public <T> T getInstance (String id, String clsName, Class<T> type) throws JPFConfigException {
Class<?>[] argTypes = CONFIG_ARGTYPES;
Object[] args = CONFIG_ARGS;
Class<?> cls = asClass(clsName);
if (cls != null) {
return getInstance(id, cls, type, argTypes, args, id);
} else {
return null;
}
}
/**
* this is our private instantiation workhorse - try to instantiate an object of
* class 'cls' by using the following ordered set of ctors 1. <cls>(
* <argTypes>) 2. <cls>(Config) 3. <cls>() if all of that fails, or there was
* a 'type' provided the instantiated object does not comply with, return null
*/
<T> T getInstance(String key, Class<?> cls, Class<T> type, Class<?>[] argTypes,
Object[] args, String id) throws JPFConfigException {
Object o = null;
Constructor<?> ctor = null;
if (cls == null) {
return null;
}
if (id != null) { // check first if we already have this one instantiated as a singleton
if (singletons == null) {
singletons = new HashMap<String,Object>();
} else {
o = type.cast(singletons.get(id));
}
}
while (o == null) {
try {
ctor = cls.getConstructor(argTypes);
o = ctor.newInstance(args);
} catch (NoSuchMethodException nmx) {
if ((argTypes.length > 1) || ((argTypes.length == 1) && (argTypes[0] != Config.class))) {
// fallback 1: try a single Config param
argTypes = CONFIG_ARGTYPES;
args = CONFIG_ARGS;
} else if (argTypes.length > 0) {
// fallback 2: try the default ctor
argTypes = NO_ARGTYPES;
args = NO_ARGS;
} else {
// Ok, there is no suitable ctor, bail out
throw new JPFConfigException(key, cls, "no suitable ctor found");
}
} catch (IllegalAccessException iacc) {
throw new JPFConfigException(key, cls, "\n> ctor not accessible: "
+ getMethodSignature(ctor));
} catch (IllegalArgumentException iarg) {
throw new JPFConfigException(key, cls, "\n> illegal constructor arguments: "
+ getMethodSignature(ctor));
} catch (InvocationTargetException ix) {
Throwable tx = ix.getTargetException();
if (tx instanceof JPFConfigException) {
throw new JPFConfigException(tx.getMessage() + "\n> used within \"" + key
+ "\" instantiation of " + cls);
} else {
throw new JPFConfigException(key, cls, "\n> exception in "
+ getMethodSignature(ctor) + ":\n>> " + tx, tx);
}
} catch (InstantiationException ivt) {
throw new JPFConfigException(key, cls,
"\n> abstract class cannot be instantiated");
} catch (ExceptionInInitializerError eie) {
throw new JPFConfigException(key, cls, "\n> static initialization failed:\n>> "
+ eie.getException(), eie.getException());
}
}
// check type
if (!type.isInstance(o)) {
throw new JPFConfigException(key, cls, "\n> instance not of type: "
+ type.getName());
}
if (id != null) { // add to singletons (in case it's not already in there)
singletons.put(id, o);
}
return type.cast(o); // safe according to above
}
public String getMethodSignature(Constructor<?> ctor) {
StringBuilder sb = new StringBuilder(ctor.getName());
sb.append('(');
Class<?>[] argTypes = ctor.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(argTypes[i].getName());
}
sb.append(')');
return sb.toString();
}
public boolean hasValue(String key) {
String v = getProperty(key);
return ((v != null) && (v.length() > 0));
}
public boolean hasValueIgnoreCase(String key, String value) {
String v = getProperty(key);
if (v != null) {
return v.equalsIgnoreCase(value);
}
return false;
}
public int getChoiceIndexIgnoreCase(String key, String[] choices) {
String v = getProperty(key);
if ((v != null) && (choices != null)) {
for (int i = 0; i < choices.length; i++) {
if (v.equalsIgnoreCase(choices[i])) {
return i;
}
}
}
return -1;
}
public URL getURL (String key){
String v = getProperty(key);
if (v != null) {
try {
return FileUtils.getURL(v);
} catch (Throwable x){
throw exception("malformed URL: " + v);
}
} else {
return null;
}
}
public File[] getPathArray (String key) {
String v = getProperty(key);
if (v != null) {
String[] pe = removeEmptyStrings( pathSplit(v));
if (pe != null && pe.length > 0) {
File[] files = new File[pe.length];
for (int i=0; i<files.length; i++) {
String path = FileUtils.asPlatformPath(pe[i]);
files[i] = new File(path);
}
return files;
}
}
return new File[0];
}
public File getPath (String key) {
String v = getProperty(key);
if (v != null) {
return new File(FileUtils.asPlatformPath(v));
}
return null;
}
static final char[] UNIX_PATH_SEPARATORS = {',', ';', ':' };
static final char[] WINDOWS_PATH_SEPARATORS = {',', ';' };
protected String[] pathSplit (String input){
if (File.pathSeparatorChar == ':'){
return split( input, UNIX_PATH_SEPARATORS);
} else {
return split( input, WINDOWS_PATH_SEPARATORS);
}
}
static final char[] DELIMS = { ',', ';' };
/**
* our own version of split, which handles "`" quoting, and breaks on non-quoted
* ',' and ';' chars. We need this so that we can use ';' separated lists in
* JPF property files, but still can use quoted ';' if we absolutely have to
* specify Java signatures. On the other hand, we can't quote with '\' because
* that would make Windows paths even more terrible.
* regexes are bad at quoting, and this is more efficient anyways
*/
protected String[] split (String input){
return split(input, DELIMS);
}
private boolean isDelim(char[] delim, char c){
for (int i=0; i<delim.length; i++){
if (c == delim[i]){
return true;
}
}
return false;
}
protected String[] split (String input, char[] delim){
int n = input.length();
ArrayList<String> elements = new ArrayList<String>();
boolean quote = false;
char[] buf = new char[128];
int k=0;
for (int i=0; i<n; i++){
char c = input.charAt(i);
if (!quote) {
if (isDelim(delim,c)){ // element separator
elements.add( new String(buf, 0, k));
k = 0;
continue;
} else if (c=='`') {
quote = true;
continue;
}
}
if (k >= buf.length){
char[] newBuf = new char[buf.length+128];
System.arraycopy(buf, 0, newBuf, 0, k);
buf = newBuf;
}
buf[k++] = c;
quote = false;
}
if (k>0){
elements.add( new String(buf, 0, k));
}
return elements.toArray(new String[elements.size()]);
}
static final String UNINITIALIZED = "uninitialized";
// this is where we store the initial values in case we have to recollect
String initialNativeClasspath = UNINITIALIZED,
initialClasspath = UNINITIALIZED,
initialSourcepath = UNINITIALIZED,
initialPeerPackages = UNINITIALIZED,
initialNativeLibraries = UNINITIALIZED;
/**
* this resets to what was explicitly set in the config files
*/
public void resetGlobalPaths() {
if (initialNativeClasspath == UNINITIALIZED){
initialNativeClasspath = getString("native_classpath");
} else {
put0( "native_classpath", initialNativeClasspath);
}
if (initialClasspath == UNINITIALIZED){
initialClasspath = getString("classpath");
} else {
put0( "classpath", initialClasspath);
}
if (initialSourcepath == UNINITIALIZED){
initialSourcepath = getString("sourcepath");
} else {
put0( "sourcepath", initialSourcepath);
}
if (initialPeerPackages == UNINITIALIZED){
initialPeerPackages = getString("peer_packages");
} else {
put0( "peer_packages", initialPeerPackages);
}
if (initialNativeLibraries == UNINITIALIZED){
initialNativeLibraries = getString("native_libraries");
} else {
put0( "native_libraries", initialNativeLibraries);
}
}
/**
* collect all the <project>.{native_classpath,classpath,sourcepath,peer_packages,native_libraries}
* and append them to the global settings
*
* NOTE - this is now called from within initClassLoader, which should only happen once and
* is the first time we really need the global paths.
*
* <2do> this is Ok for native_classpath and native_libraries, but we should probably do
* classpath, sourcepath and peer_packages separately (they can be collected later)
*/
public void collectGlobalPaths() {
// note - this is in the order of entry, i.e. reflects priorities
// we have to process this in reverse order so that later entries are prioritized
String[] keys = getEntrySequence();
String nativeLibKey = "." + System.getProperty("os.name") +
'.' + System.getProperty("os.arch") + ".native_libraries";
for (int i = keys.length-1; i>=0; i--){
String k = keys[i];
if (k.endsWith(".native_classpath")){
appendPath("native_classpath", k);
} else if (k.endsWith(".classpath")){
appendPath("classpath", k);
} else if (k.endsWith(".sourcepath")){
appendPath("sourcepath", k);
} else if (k.endsWith("peer_packages")){
append("peer_packages", getString(k), ",");
} else if (k.endsWith(nativeLibKey)){
appendPath("native_libraries", k);
}
}
}
static Pattern absPath = Pattern.compile("(?:[a-zA-Z]:)?[/\\\\].*");
void appendPath (String pathKey, String key){
String projName = key.substring(0, key.indexOf('.'));
String pathPrefix = null;
if (projName.isEmpty()){
pathPrefix = new File(".").getAbsolutePath();
} else {
pathPrefix = getString(projName);
}
if (pathPrefix != null){
pathPrefix += '/';
String[] elements = getCompactStringArray(key);
if (elements != null){
for (String e : elements) {
if (e != null && e.length()>0){
// if this entry is not an absolute path, or doesn't start with
// the project path, prepend the project path
if (!(absPath.matcher(e).matches()) && !e.startsWith(pathPrefix)) {
e = pathPrefix + e;
}
append(pathKey, e);
}
}
}
} else {
throw new JPFConfigException("no project path for " + key);
}
}
//--- our modification interface
/**
* iterate over all keys, if a key starts with the provided keyPrefix, add
* this value under the corresponding key suffix. For example:
*
* test.report.console.finished = result
*
* -> prompotePropertyCategory("test.") ->
*
* report.console.finished = result
*
* if a matching key has an IGNORE_VALUE value ("-"), the entry is *not* promoted
* (we need this to override promoted keys)
*/
public void promotePropertyCategory (String keyPrefix){
int prefixLen = keyPrefix.length();
// HashTable does not support adding elements while iterating over the entrySet
ArrayList<Map.Entry<Object,Object>> promoted = null;
for (Map.Entry<Object,Object> e : entrySet()){
Object k = e.getKey();
if (k instanceof String){
String key = (String)k;
if (key.startsWith(keyPrefix)){
Object v = e.getValue();
if (! IGNORE_VALUE.equals(v)){
if (promoted == null){
promoted = new ArrayList<Map.Entry<Object,Object>>();
}
promoted.add(e);
}
}
}
}
if (promoted != null){
for (Map.Entry<Object, Object> e : promoted) {
String key = (String) e.getKey();
key = key.substring(prefixLen);
put(key, e.getValue());
}
}
}
@Override
public Object setProperty (String key, String newValue) {
Object oldValue = put(key, newValue);
notifyPropertyChangeListeners(key, (String)oldValue, newValue);
return oldValue;
}
public void parse (String s) {
int i = s.indexOf("=");
if (i > 0) {
String key, val;
if (i > 1 && s.charAt(i-1)=='+') { // append
key = s.substring(0, i-1).trim();
val = s.substring(i+1); // it's going to be normalized anyways
append(key, val);
} else { // put
key = s.substring(0, i).trim();
val = s.substring(i+1);
setProperty(key, val);
}
}
}
protected void notifyPropertyChangeListeners (String key, String oldValue, String newValue) {
if (changeListeners != null) {
for (ConfigChangeListener l : changeListeners) {
l.propertyChanged(this, key, oldValue, newValue);
}
}
}
public String[] asStringArray (String s){
return split(s);
}
public TreeMap<Object,Object> asOrderedMap() {
TreeMap<Object,Object> map = new TreeMap<Object,Object>();
map.putAll(this);
return map;
}
//--- various debugging methods
public void print (PrintWriter pw) {
pw.println("----------- Config contents");
// just how much do you have to do to get a printout with keys in alphabetical order :<
TreeSet<String> kset = new TreeSet<String>();
for (Enumeration<?> e = propertyNames(); e.hasMoreElements();) {
Object k = e.nextElement();
if (k instanceof String) {
kset.add( (String)k);
}
}
for (String key : kset) {
String val = getProperty(key);
pw.print(key);
pw.print(" = ");
pw.println(val);
}
pw.flush();
}
public void printSources (PrintWriter pw) {
pw.println("----------- Config sources");
for (Object src : sources){
pw.println(src);
}
}
public void printEntries() {
PrintWriter pw = new PrintWriter(System.out);
print(pw);
}
public String getSourceName (Object src){
if (src instanceof File){
return ((File)src).getAbsolutePath();
} else if (src instanceof URL){
return ((URL)src).toString();
} else {
return src.toString();
}
}
public List<Object> getSources() {
return sources;
}
public void printStatus(Logger log) {
int idx = 0;
for (Object src : sources){
if (src instanceof File){
log.config("configuration source " + idx++ + " : " + getSourceName(src));
}
}
}
}