/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.common.cfg;
import static java.lang.System.getProperty;
import static java.lang.System.getenv;
import static org.openbel.framework.common.BELUtilities.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.openbel.framework.common.BELUtilities;
import org.openbel.framework.common.MapFunction;
import org.openbel.framework.common.PathConstants;
/**
* This class encapsulates a map-based <i>configuration</i>.
* <p>
* This class contains a set of variables that will be automatically expanded if
* found as a value within a configuration file.
* </p>
* <p>
* <dl>
* <dt><tt>{tmp}</tt></dt>
* <dd>Expanded to the system temporary directory.
* <dt><tt>{home}</tt></dt>
* <dd>Expanded to the user's home directory.
* <dt><tt>{name}</tt></dt>
* <dd>Expanded to the user's name.</dd>
* <dt><tt>{dir}</tt></dt>
* <dd>Expanded to the current working directory.</dd>
* <dt><tt>{belframework_home}</tt></dt>
* <dd>Expanded to the {@code BELFRAMEWORK_HOME} environment variable</tt></dt>
* </dl>
* </p>
*/
public abstract class Configuration {
/**
* The comment prefix, {@value #COMMENT}. Lines beginning with this string
* will be ignored.
*/
public static final String COMMENT = "#";
/**
* The delimiter, {@value #NAME_VALUE_DELIMITER}. Name-value pairs are
* separated in configuration files by this string.
*/
public static final String NAME_VALUE_DELIMITER = "=";
/**
* The name-value configuration map, in expanded form.
*/
private Map<String, String> cfgMap;
/**
* Boolean {@code true} if defaults are used, {@code false} otherwise. The
* defaults provide a way for subclasses to use a predefined set of defaults
* in the event no configuration can be provided.
*/
protected final boolean defaults;
/**
* Creates a configuration instance derived from the supplied file. If the
* file is not {@link BELUtilities#readable(File) readable},
* {@link #initializeDefaults()} will be called on {@link #init()}.
*
* @param file File to use as configuration; may be null in which case
* {@link #defaults} are used
* @throws IOException Thrown if an I/O error occurs
*/
protected Configuration(final File file) throws IOException {
if (readable(file)) {
cfgMap = new HashMap<String, String>();
read(file);
defaults = false;
return;
}
cfgMap = null;
defaults = true;
}
/**
* Creates a configuration instance derived from the supplied map. If the
* map is null, {@link #initializeDefaults()} will be called on
* {@link #init()}.
*
* @param map Map to use as configuration; may be null in which case
* {@link #defaults} are used
*/
protected Configuration(final Map<String, String> map) {
if (noNulls(map)) {
cfgMap = new HashMap<String, String>();
read(map);
defaults = false;
return;
}
cfgMap = null;
defaults = true;
}
/**
* Creates a configuration instance using {@link #defaults}. This will
* result in {@link #initializeDefaults()} being called on {@link #init()}.
*/
protected Configuration() {
cfgMap = null;
defaults = true;
}
/**
* Initializes the configuration instance. The
* {@link Configuration#processSetting(String, String) processSetting}
* method will be invoked for each setting, or {@link #initializeDefaults()
* initializeDefaults} if defaults are being used.
*/
protected void init() {
if (defaults) {
initializeDefaults();
return;
}
mapfx(cfgMap, new MapFunction<String, String>() {
@Override
public void apply(String name, String value) {
processSetting(name, value);
}
});
readComplete();
}
/**
* Process a name-value setting during reading {@link #configurationFile} by
*
* @param name Non-null name
* @param value Non-null value
*/
protected abstract void processSetting(String name, String value);
/**
* Called at the end of {@link #read()}.
*/
protected abstract void readComplete();
/**
* Initializes the configuration to default settings.
*/
protected abstract void initializeDefaults();
/**
* Returns the name-value default settings.
*
* @return Name-value mappings
*/
protected abstract Map<String, String> defaults();
/**
* Returns the default configuration provided by {@link #defaults()}.
*
* @return Non-null string
*/
protected String defaultConfiguration() {
final StringBuilder bldr = new StringBuilder();
for (final Entry<String, String> entry : defaults().entrySet()) {
final String name = entry.getKey();
final String value = entry.getValue();
bldr.append(name);
bldr.append(" ");
bldr.append(NAME_VALUE_DELIMITER);
bldr.append(" ");
bldr.append(value);
bldr.append("\n");
}
return bldr.toString();
}
/*
* Reads a file populating a map for a call to read(Map) below.
*
* @throws IOException Thrown if an I/O error occurs
*/
private void read(final File f) throws IOException {
final FileReader fr = new FileReader(f);
final BufferedReader br = new BufferedReader(fr);
String input = null;
while ((input = br.readLine()) != null) {
// Trim whitespace.
input = input.trim();
// Skip comments
if (input.startsWith(COMMENT)) continue;
final int idx = input.indexOf(NAME_VALUE_DELIMITER);
if (idx == -1) continue;
String name, value;
try {
name = input.substring(0, idx);
value = input.substring(idx + 1);
} catch (IndexOutOfBoundsException e) {
continue;
}
name = name.trim();
value = value.trim();
cfgMap.put(name, valueSubstitution(value));
}
br.close();
}
/*
* Reads the configuration map of name/value strings, invoking the process
* setting callback.
*/
private void read(final Map<String, String> map) {
String name, value;
for (final Entry<String, String> entry : entries(map)) {
name = entry.getKey();
value = entry.getValue();
cfgMap.put(name, valueSubstitution(value));
}
}
/**
* Performs substitution against the configuration {@code value} for the
* system's temporary directory, user's home directory, user's name, or
* user's current working directory.
*
* @param value Non-null string
* @return String resulting from value replacement
*/
public static String valueSubstitution(final String value) {
final String tmpProp = "java.io.tmpdir";
final String tmpVar = "{tmp}";
final String tmpRE = "{tmp}";
final String homeProp = "user.home";
final String homeVar = "{home}";
final String homeRE = "{home}";
final String nameProp = "user.name";
final String nameVar = "{name}";
final String nameRE = "{name}";
final String cwdProp = "user.dir";
final String cwdVar = "{dir}";
final String cwdRE = "{dir}";
final String bfHomeEnv = PathConstants.BELFRAMEWORK_HOME_ENV_VAR;
final String bfHomeVar = "{belframework_home}";
final String bfHomeRE = "{belframework_home}";
String ret = value;
if (value.contains(tmpVar)) {
ret = ret.replace(tmpRE, getProperty(tmpProp));
}
if (value.contains(homeVar)) {
ret = ret.replace(homeRE, getProperty(homeProp));
}
if (value.contains(nameVar)) {
ret = ret.replace(nameRE, getProperty(nameProp));
}
if (value.contains(cwdVar)) {
ret = ret.replace(cwdRE, getProperty(cwdProp));
}
if (value.contains(bfHomeVar)) {
String bfHome = getenv(bfHomeEnv);
if (bfHome == null) {
bfHome = ""; //belframework home is not set. Use empty string.
}
ret = ret.replace(bfHomeRE, bfHome);
}
return ret;
}
}