osd-contiki/tools/cooja/java/se/sics/cooja/ProjectConfig.java
2012-10-31 17:22:13 +01:00

539 lines
17 KiB
Java

/*
* Copyright (c) 2006, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
package se.sics.cooja;
import java.io.*;
import java.util.*;
import org.apache.log4j.Logger;
/**
* A project configuration may hold the configuration for one or several project
* directories as well as a general simulator configuration.
*
* The configuration for a project directory may for example consist of which
* plugins, interfaces and processes that the specific project directory supplies.
* Each project directory configuration is read from the property file cooja.config, a
* file which is required in each project directory.
*
* Values can be fetched as String, Boolean, Integer, Double or String array.
*
* Several configurations can be merged, together forming a final overall
* configuration. The order of the how configurations are merged matter - later
* values will overwrite earlier. For example merging two configurations with
* the key 'SOMEKEY' in the following order:
*
* SOMEKEY = a b c
*
* SOMEKEY = d e
*
* will result in the final value "d e".
*
* If a specific value should be extended instead of overwritten, the value must
* start with a single space-surrounded '+'. For example, merging two
* configurations with the key as above in the following order:
*
* SOMEKEY = a b c
*
* SOMEKEY = + d e
*
* will result in the final value "a b c d e".
*
* The simulator will hold a merged project configuration, depending on which
* project directories are used. Additionally. each mote type may also have a
* configuration of its own, that differs from the general simulator
* configuration.
*
* Often, but not necessarily, keys are named depending on which class is
* associated with the information. For example, let's say a battery interface
* wants to store its initial capacity (a double) using this approach. Data
* stored in the external configuration file can look like the following:
* se.sics.cooja.interfaces.Battery.initial_capacity 54.123321
*
* This value is then be read by: myMoteTypeConfig.getDoubleValue(Battery.class,
* "initial_capacity");
*
* @author Fredrik Osterlind
*/
public class ProjectConfig {
private static Logger logger = Logger.getLogger(ProjectConfig.class);
private Properties myConfig = null;
private Vector<File> myProjectDirHistory = null;
/**
* Creates new project configuration.
*
* @param useDefault
* If true the default configuration will be loaded
* @throws FileNotFoundException
* If file was not found
* @throws IOException
* Stream read error
*/
public ProjectConfig(boolean useDefault) throws IOException,
FileNotFoundException {
// Create empty configuration
myConfig = new Properties();
myProjectDirHistory = new Vector<File>();
if (useDefault) {
InputStream input = GUI.class
.getResourceAsStream(GUI.PROJECT_DEFAULT_CONFIG_FILENAME);
if (input != null) {
try {
appendConfigStream(input);
} finally {
input.close();
}
} else {
throw new FileNotFoundException(GUI.PROJECT_DEFAULT_CONFIG_FILENAME);
}
}
}
/**
* Appends the given project directory's config file. This method also saves a
* local history of which project directories has been loaded.
*
* @param projectDir
* Project directory
* @return True if loaded OK
* @throws FileNotFoundException
* If file was not found
* @throws IOException
* Stream read error
*/
public boolean appendProjectDir(File projectDir)
throws FileNotFoundException, IOException {
if (projectDir == null) {
throw new FileNotFoundException("No project directory specified");
}
if (!projectDir.exists()) {
throw new FileNotFoundException("Project directory does not exist: " + projectDir.getAbsolutePath());
}
File projectConfig = new File(projectDir.getPath(), GUI.PROJECT_CONFIG_FILENAME);
if (!projectConfig.exists()) {
throw new FileNotFoundException("Project config does not exist: " + projectConfig.getAbsolutePath());
}
myProjectDirHistory.add(projectDir);
return appendConfigFile(projectConfig);
}
/**
* Returns the project directory earlier appended to this configuration that
* defined the given key. If the key is of an array format and the given array
* element is non-null, then the project directory that added this element will be
* returned instead. If no such project directory can be found null is returned
* instead.
*
* @param callingClass
* Class which value belong to
* @param key
* Key
* @param arrayElement
* Value or array element
* @return Project directory defining arguments or null
*/
public File getUserProjectDefining(Class callingClass, String key, String arrayElement) {
// Check that key really exists in current config
if (getStringValue(callingClass, key, null) == null) {
return null;
}
// Check that element really exists, if any
if (arrayElement != null) {
String[] array = getStringArrayValue(callingClass, key);
boolean foundValue = false;
for (String element : array) {
if (element.equals(arrayElement)) {
foundValue = true;
}
}
if (!foundValue) {
return null;
}
}
// Search in all project directory in reversed order
try {
ProjectConfig remadeConfig = new ProjectConfig(false);
for (int i=myProjectDirHistory.size()-1; i >= 0; i--) {
remadeConfig.appendProjectDir(myProjectDirHistory.get(i));
if (arrayElement != null) {
// Look for array
String[] array = remadeConfig.getStringArrayValue(callingClass, key);
for (String element : array) {
if (element.equals(arrayElement)) {
return myProjectDirHistory.get(i);
}
}
} else {
// Look for key
if (remadeConfig.getStringValue(callingClass, key, null) != null) {
return myProjectDirHistory.get(i);
}
}
}
} catch (Exception e) {
logger.fatal("Exception when searching in project directory history: " + e);
return null;
}
return null;
}
/**
* Loads the given property file and appends it to the current configuration.
* If a property already exists it will be overwritten, unless the new value
* begins with a '+' in which case the old value will be extended.
*
* WARNING! The project directory history will not be saved if this method is
* called, instead the appendUserPlatform method should be used.
*
* @param propertyFile
* Property file to read
* @return True if file was read ok, false otherwise
* @throws FileNotFoundException
* If file was not found
* @throws IOException
* Stream read error
*/
public boolean appendConfigFile(File propertyFile)
throws FileNotFoundException, IOException {
if (!propertyFile.exists()) {
logger.warn("Trying to import non-existant project configuration: " + propertyFile.toString());
return true;
}
FileInputStream in = new FileInputStream(propertyFile);
return appendConfigStream(myConfig, in);
}
/**
* Reads properties from the given stream and appends them to the current
* configuration. If a property already exists it will be overwritten, unless
* the new value begins with a '+' in which case the old value will be
* extended.
*
* WARNING! The project directory history will not be saved if this method is
* called, instead the appendUserPlatform method should be used.
*
* @param configFileStream
* Stream to read from
* @return True if stream was read ok, false otherwise
* @throws IOException
* Stream read error
*/
public boolean appendConfigStream(InputStream configFileStream)
throws IOException {
return appendConfigStream(myConfig, configFileStream);
}
private static boolean appendConfigStream(Properties currentValues,
InputStream configFileStream) throws IOException {
// Read from stream
Properties newProps = new Properties();
newProps.load(configFileStream);
configFileStream.close();
// Read new properties
Enumeration en = newProps.keys();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
String property = newProps.getProperty(key);
if (property.startsWith("+ ")) {
if (currentValues.getProperty(key) != null) {
currentValues.setProperty(key, currentValues.getProperty(key) + " "
+ property.substring(1).trim());
} else {
currentValues.setProperty(key, property.substring(1).trim());
}
} else {
currentValues.setProperty(key, property);
}
}
return true;
}
public boolean appendConfig(ProjectConfig config) {
Enumeration<String> propertyNames = config.getPropertyNames();
while (propertyNames.hasMoreElements()) {
String key = propertyNames.nextElement();
String property = config.getStringValue(key);
if (property.startsWith("+ ")) {
if (myConfig.getProperty(key) != null) {
myConfig.setProperty(key, myConfig.getProperty(key) + " "
+ property.substring(1).trim());
} else {
myConfig.setProperty(key, property.substring(1).trim());
}
} else {
myConfig.setProperty(key, property);
}
}
return true;
}
/**
* @return All property names in configuration
*/
public Enumeration<String> getPropertyNames() {
return (Enumeration<String>) myConfig.propertyNames();
}
/**
* Get string value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @param defaultValue
* Default value to return if id is not found
* @return Value or defaultValue if id wasn't found
*/
public String getStringValue(Class callingClass, String id,
String defaultValue) {
return getStringValue(myConfig, callingClass, id, defaultValue);
}
private static String getStringValue(Properties currentValues,
Class callingClass, String id, String defaultValue) {
String val = currentValues.getProperty(callingClass.getName() + "." + id);
if (val == null) {
/*logger.warn("Could not find key named '" + callingClass.getName() + "."
+ id + "'");*/
return defaultValue;
}
return val;
}
/**
* Returns value of given name.
*
* @param name
* Name
* @return Value as string
*/
public String getStringValue(String name) {
if (!myConfig.containsKey(name)) {
/*logger.debug("Could not find key named '" + name + "'");*/
return null;
}
return myConfig.getProperty(name);
}
/**
* Get string value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or null if id wasn't found
*/
public String getStringValue(Class callingClass, String id) {
return getStringValue(callingClass, id, null);
}
/**
* Get string array value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or null if id wasn't found
*/
public String[] getStringArrayValue(Class callingClass, String id) {
String stringVal = getStringValue(callingClass, id, null);
if (stringVal == null) {
return new String[0];
}
return getStringValue(callingClass, id, "").split(" ");
}
/**
* Get string value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or null if id wasn't found
*/
public String getValue(Class callingClass, String id) {
return getStringValue(callingClass, id);
}
/**
* Get string array value with given id.
*
* @param id
* Id of value to return
* @return Value or null if id wasn't found
*/
public String[] getStringArrayValue(String id) {
String stringVal = getStringValue(id);
if (stringVal == null) {
return new String[0];
}
return getStringValue(id).split(" ");
}
/**
* Get integer value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @param defaultValue
* Default value to return if id is not found
* @return Value or defaultValue if id wasn't found
*/
public int getIntegerValue(Class callingClass, String id, int defaultValue) {
String str = getStringValue(callingClass, id);
if (str == null) {
return defaultValue;
}
return Integer.parseInt(str);
}
/**
* Get integer value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or 0 if id wasn't found
*/
public int getIntegerValue(Class callingClass, String id) {
return getIntegerValue(callingClass, id, 0);
}
/**
* Get double value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @param defaultValue
* Default value to return if id is not found
* @return Value or defaultValue if id wasn't found
*/
public double getDoubleValue(Class callingClass, String id,
double defaultValue) {
String str = getStringValue(callingClass, id);
if (str == null) {
return defaultValue;
}
return Double.parseDouble(str);
}
/**
* Get double value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or 0.0 if id wasn't found
*/
public double getDoubleValue(Class callingClass, String id) {
return getDoubleValue(callingClass, id, 0.0);
}
/**
* Get boolean value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @param defaultValue
* Default value to return if id is not found
* @return Value or defaultValue if id wasn't found
*/
public boolean getBooleanValue(Class callingClass, String id,
boolean defaultValue) {
String str = getStringValue(callingClass, id);
if (str == null) {
return defaultValue;
}
return Boolean.parseBoolean(str);
}
/**
* Get boolean value with given id.
*
* @param callingClass
* Class which value belongs to
* @param id
* Id of value to return
* @return Value or false if id wasn't found
*/
public boolean getBooleanValue(Class callingClass, String id) {
return getBooleanValue(callingClass, id, false);
}
public ProjectConfig clone() {
try {
ProjectConfig clone = new ProjectConfig(false);
clone.myConfig = (Properties) this.myConfig.clone();
clone.myProjectDirHistory = (Vector<File>) this.myProjectDirHistory.clone();
return clone;
} catch (Exception e) {
return null;
}
}
}