/* * 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 org.contikios.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: * org.contikios.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 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(); if (useDefault) { InputStream input = Cooja.class .getResourceAsStream(Cooja.PROJECT_DEFAULT_CONFIG_FILENAME); if (input != null) { try { appendConfigStream(input); } finally { input.close(); } } else { throw new FileNotFoundException(Cooja.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(), Cooja.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 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 getPropertyNames() { return (Enumeration) 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) this.myProjectDirHistory.clone(); return clone; } catch (Exception e) { return null; } } }