ProGenContext.java
package progen.context;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Clase que unifica la recuperación de las distintas propiedades definidas en
* los ficheros de configuración.
* <p/>
* Provee de métodos para recuperar propiedades obligatorias, optativas,
* porcentajes, etc.
*
* @author jirsis
*/
public final class ProGenContext {
private static final int HUNDRED_PERCENT = 100;
private static final String PROGEN_OPTIONAL_FILES_PROPERTY = "progen.optional.files";
private static final String DOT_SYMBOL = ".";
private static final String PROGEN_USER_HOME_PROPERTY = "progen.user.home";
private static final String PROGEN_EXPERIMENT_FILE_PROPERTY = "progen.experiment.file";
private static final String PROGEN_EXPERIMENT_ABSOLUTE_FILE_PROPERTY = "progen.experiment.file.absolute";
private static final String EQUAL_SYMBOL = "=";
private static final String COMA_SYMBOL = ",";
private static final String PARAMETERS_DELIMITER = "\\(";
private static ProGenContext proGenProps;
private Properties properties;
private ProGenContext() {
properties = new Properties();
}
public static ProGenContext makeInstance(String masterFile) {
if (masterFile == null) {
throw new MissingContextFileException();
} else {
try {
execMakeInstance(masterFile);
} catch (NullPointerException e) {
throw new MissingContextFileException(masterFile, e);
} catch (IOException e) {
throw new MissingContextFileException(e.getMessage(), e);
}
}
return proGenProps;
}
@Override
public String toString(){
return properties.toString();
}
private static void execMakeInstance(String file) throws IOException {
proGenProps = new ProGenContext();
proGenProps.loadOtherPropertiesFile("ProGen.conf");
final InputStream fis = getResourceInClassPath(file);
proGenProps.properties.load(fis);
proGenProps.properties.setProperty("progen.masterfile", ProGenContext.class.getClassLoader().getResource(file).getFile());
fis.close();
}
private static InputStream getResourceInClassPath(String file) throws FileNotFoundException {
return new FileInputStream(new File(ProGenContext.class.getClassLoader().getResource(file).getFile()));
}
private static InputStream getResource(String file) throws FileNotFoundException {
return ProGenContext.class.getClassLoader().getResourceAsStream(file);
}
/**
* Devuelve la única instancia que existe de las propiedades. En caso de no
* existir, crea las propiedades vacías y lo devuelve tal cual.
*
* @return La referencia a la única instancia de propiedades de ProGen
*/
public static synchronized ProGenContext makeInstance() {
if (proGenProps == null) {
proGenProps = new ProGenContext();
}
return proGenProps;
}
/**
* Forma de obtener el valor de la propiedad identificada por parámetro. Al
* tener caracter opcional, en caso de no estar definida, se devolverá el
* valor pasado como segundo parámetro.
*
* @param property
* identificador de la propiedad.
* @param defaultValue
* valor por defecto
* @return valor de la propiedad solicitada recuperada desde los ficheros de
* configuración o valor por defecto en caso de no estar definida.
*/
public static int getOptionalProperty(String property, int defaultValue) {
int value;
if (ProGenContext.getProperty(property) == null) {
value = defaultValue;
} else {
value = Integer.parseInt(ProGenContext.getProperty(property).split(PARAMETERS_DELIMITER)[0]);
}
return value;
}
/**
* Forma de obtener el valor de la propiedad identificada por parámetro. Al
* tener caracter opcional, en caso de no estar definida, se devolverá el
* valor pasado como segundo parámetro.
*
* @param property
* identificador de la propiedad.
* @param defaultValue
* valor por defecto
* @return valor de la propiedad solicitada recuperada desde los ficheros de
* configuración o valor por defecto en caso de no estar definida.
*/
public static String getOptionalProperty(String property, String defaultValue) {
String value;
if (ProGenContext.getProperty(property) == null) {
value = defaultValue;
} else {
value = ProGenContext.getProperty(property).split(PARAMETERS_DELIMITER)[0];
}
return value;
}
/**
* Forma de obtener el valor de la propiedad identificada por parámetro. Al
* tener caracter opcional, en caso de no estar definida, se devolverá el
* valor pasado como segundo parámetro.
*
* @param property
* identificador de la propiedad.
* @param defaultValue
* valor por defecto
* @return valor de la propiedad solicitada recuperada desde los ficheros de
* configuración o valor por defecto en caso de no estar definida.
*/
public static double getOptionalProperty(String property, double defaultValue) {
double value;
if (ProGenContext.getProperty(property) == null) {
value = defaultValue;
} else {
value = Double.parseDouble(ProGenContext.getProperty(property).split(PARAMETERS_DELIMITER)[0]);
}
return value;
}
/**
* Forma de obtener el valor de la propiedad identificada por parámetro. Al
* tener caracter opcional, en caso de no estar definida, se devolverá el
* valor pasado como segundo parámetro.
*
* @param property
* identificador de la propiedad.
* @param defaultValue
* valor por defecto
* @return valor de la propiedad solicitada recuperada desde los ficheros de
* configuración o valor por defecto en caso de no estar definida.
*/
public static boolean getOptionalProperty(String property, boolean defaultValue) {
boolean value;
if (ProGenContext.getProperty(property) == null) {
value = defaultValue;
} else {
value = Boolean.parseBoolean(ProGenContext.getProperty(property).split(PARAMETERS_DELIMITER)[0]);
}
return value;
}
/**
* Forma de obtener el valor de una propiedad definida en los ficheros de
* configuración, identificada por el parámetro definido. Será responsabilidad
* de la parte que realiza esta llamada, comprobar si el contenido de esta
* propiedad es correcto y acorde a lo que se espera.
*
* @param key
* Identificador de la propiedad a recuperar
* @return <code>String</code> con el valor de la propiedad tal y como aparece
* en el fichero de configuración.
*/
public static String getMandatoryProperty(String key) {
final String property = getProperty(key);
if (property == null)
throw new UndefinedMandatoryPropertyException(key);
return property.split(PARAMETERS_DELIMITER)[0];
}
/**
* Metodo de acceso para recuperar cualquier propiedad definida, identificada
* por el parámetro
*
* @param key
* Identificador de la propiedad a recuperar
* @return valor de la propiedad recuperada
* @see #makeInstance(String)
*/
private static String getProperty(String key) {
if (proGenProps == null) {
throw new UninitializedContextException();
}
return proGenProps.properties.getProperty(key);
}
/**
* Devuelve el valor del porcentaje de uso de una subopción definida en el
* contexto del dominio. Para ello, es necesario definir la subopción
* separándola de la opción con dos puntos (:) y el porcentaje definido como
* tanto por ciento o como un valor entre 0 y 1. A su vez, las distintas
* subopciones, tienen que ir separadas por comas (,).
*
* @param key
* La opción a recuperar.
* @param subOption
* La subopción a recuperar dentro de la opción.
* @param defaultPercent
* El porcentaje por defecto, en caso de que no exista esta
* subopción.
* @return Devuelve un valor entre 0 y 1, que representa dicho porcentaje.
*/
public static double getSuboptionPercent(String key, String subOption, double defaultPercent) {
double value = defaultPercent;
final String suboption = getParameter(key, subOption);
if (suboption != null) {
value = getPercent(suboption);
}
return value;
}
/**
* Recupera un porcentaje definido en una opción, de tal forma que esta opción
* no es obligatoria que se defina.
*
* @param key
* La propiedad a recuperar.
* @param defaultPercent
* El valor por defecto en caso de no haberse definido la propiedad.
* @return Un valor entre 0 y 1, que representa dicho porcentaje.
*/
public static double getOptionalPercent(String key, String defaultPercent) {
final String percent = getOptionalProperty(key, defaultPercent);
return getPercent(percent);
}
/**
* Recupera un porcentaje definido en una opción de forma obligatoria.
*
* @param key
* La propiedad a recuperar.
* @return Un valor entre 0 y 1, que representa dicho porcentaje.
*/
public static double getMandatoryPercent(String key) {
final String property = getProperty(key);
if (property == null)
throw new UndefinedMandatoryPropertyException(key);
return getPercent(property);
}
/**
* Recupera una colección con todas las opciones que coinciden comparten una
* parte del nombre, definido en el parámetro.
*
* @param family
* La parte común del nombre de la propiedad.
* @return Una colección con todas las propiedades que cumplen con el
* criterio.
*/
public static List<String> getFamilyOptions(String family) {
final List<String> options = new ArrayList<String>();
final Iterator<Object> propertyKey = proGenProps.properties.keySet().iterator();
String option;
while (propertyKey.hasNext()) {
option = (String) propertyKey.next();
if (option.indexOf(family) == 0) {
options.add(option);
}
}
return options;
}
/**
* Recupera el valor numérico de un tanto por ciento, representado en una
* cadena como valor entre 0 y 1 o con un valor entre 0 y 100 y acabado con el
* símbolo por ciento (%).
*
* @param percent
* La cadena a convertir en valor numérico.
* @return Un número comprendido entre 0 y 1.
*/
private static double getPercent(String percent) {
double value = 0.0;
final String percentNormalized = percent.replaceAll(" ", "");
if (percentNormalized.endsWith("%")) {
value = Double.parseDouble(percentNormalized.substring(0, percentNormalized.length() - 1)) / HUNDRED_PERCENT;
} else {
value = Double.parseDouble(percentNormalized);
}
return value;
}
/**
* Añade una propiedad al conjunto de propiedades definidas en ProGen.
*
* @param key
* Identificador de la propiedad.
* @param value
* Valor concreto que tomará la propiedad.
*/
public static void setProperty(String key, String value) {
proGenProps.properties.setProperty(key, value);
proGenProps.calculateProperties();
}
/**
* Elimina todas las propiedades que estuvieran definidas en el contexto.
*/
public static void clearContext() {
proGenProps = null;
}
/**
* Devuelve el valor concreto de un parámetro de configuración de una opción.
* En caso de que no exista la opción o la subopción de la que se quiere
* obtener el valor, se devolverá el valor null; y en caso de que haya algún
* parámetro no definido según la forma indicada a continuación, se lanzará
* una {@link MalformedParameterException} indicando la pareja
* opción-subopción que se no cumple con el formato. El formato específico
* para definir los parámetros es: <code>
* progen.opcion=valor(subopcion1=valor1, subopcion2=valor2, ...)<br/>
* </code>
*
* @param option
* Opción de la que se obtendrá un parámetro.
* @param parameter
* Parámetro de la que se quiere obtener un valor concreto.
* @return El valor del parámetro o null en caso de no encontrarse.
*/
public static String getParameter(String option, String parameter) {
String value = null;
value = getParameters(option).get(parameter);
return value;
}
/**
* Devuelve todos los parámetros asociados a una opción determinada. En caso
* de no estar definida la opción o que no tenga parámetros extra, se
* devolverá un Map vacío, es decir, de tamaño 0.
*
* @param option
* La opción para recuperar los parámetros.
* @return La colección de parámetros asociados a dicha opción.
*/
public static Map<String, String> getParameters(String option) {
Map<String, String> parameters = new HashMap<String, String>();
String parametersContext = getProperty(option);
if (checkAtLeastOneParameter(parametersContext)) {
parametersContext = normalizeParameters(parametersContext);
parameters = splitParameters(option, parametersContext);
}
return parameters;
}
private static String normalizeParameters(String parametersContext) {
final String parameterContextNormalized = parametersContext.split(PARAMETERS_DELIMITER)[1];
return parameterContextNormalized.replace(")", "");
}
private static boolean checkAtLeastOneParameter(String parametersContext) {
return parametersContext != null && parametersContext.split(PARAMETERS_DELIMITER).length > 1;
}
private static Map<String, String> splitParameters(String option, String parametersContext) {
String parameterValue;
String parameterKey = null;
final Map<String, String> parameters = new HashMap<String, String>();
try {
for (String parameter : parametersContext.split(COMA_SYMBOL)) {
parameterKey = parameter.split(EQUAL_SYMBOL)[0].trim();
parameterValue = parameter.split(EQUAL_SYMBOL)[1].trim();
parameters.put(parameterKey, parameterValue);
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new MalformedParameterException(option + COMA_SYMBOL + parameterKey, e);
}
return parameters;
}
public static void loadExtraConfiguration() {
try {
proGenProps.calculateProperties();
proGenProps.loadOtherProperties();
} catch (FileNotFoundException fnfe) {
throw new MissingContextFileException(fnfe.getMessage(), fnfe);
} catch (IOException ioe) {
throw new MissingContextFileException(ioe.getMessage(), ioe);
}
}
/**
* Carga las opciones definidas en otros ficheros a parte del fichero maestro.
* Carga también las definidas en el fichero especificado en la propiedad
* <code>progen.experiment-file</code> en el fichero maestro y las que están
* definidas en los ficheros del dominio de especificación de experimentos (
* <code>-experiment</code>) y hypergp (<code>-hypergp</code> ).
*
* @throws FileNotFoundException
* Si el fichero del dominio no existe.
* @throws IOException
* Si ocurre un error de E/S.
*/
private void loadOtherProperties() throws IOException {
loadOtherProperties(PROGEN_EXPERIMENT_FILE_PROPERTY);
loadOptionalFile("-experimenter");
loadOptionalFile("-hypergp");
}
/**
* Carga directamente un fichero de propiedades al conjunto de las que ya
* estén definidas en ProGen. Primero buscará en el directorio raíz del
* proyecto y en caso de no encontrarlo, buscará en el directorio
* <code>home</code> del experimento.
*
* @param propertyFile
* El nombre del fichero de propiedades.
* @throws IOException
*/
private void loadOtherPropertiesFile(String propertyFile) throws IOException {
Properties otherProperties;
if (propertyFile != null) {
otherProperties = new Properties();
try {
findPropertiesAbsolutePath(propertyFile, otherProperties);
} catch (FileNotFoundException fnfe) {
findPropertiesUserProjectPath(propertyFile, otherProperties);
}
chekProperties(otherProperties);
}
}
private void chekProperties(Properties otherProperties) {
Enumeration<Object> keys;
String key;
String value;
keys = otherProperties.keys();
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
if (proGenProps.properties.containsKey(key)) {
throw new DuplicatedPropertyException(key);
} else {
value = otherProperties.getProperty(key);
proGenProps.properties.put(key, value);
}
}
}
private void findPropertiesUserProjectPath(String propertyFile, Properties otherProperties) throws IOException {
final String propertyFileNormalized = proGenProps.properties.getProperty(PROGEN_USER_HOME_PROPERTY).replace('.', File.separatorChar) + propertyFile;
findPropertiesAbsolutePath(propertyFileNormalized, otherProperties);
}
private void findPropertiesAbsolutePath(String propertyFile, Properties otherProperties) throws IOException {
final InputStream fis = getResource(propertyFile);
otherProperties.load(fis);
fis.close();
}
/**
* Carga algún fichero de configuración especéfica del dominio del problema.
*
* @param propertyFile
* el fichero a cargar.
* @throws FileNotFoundException
* Si no existe el fichero.
* @throws IOException
* Si ocurre un error de E/S.
*/
private void loadOtherProperties(String propertyFile) throws IOException {
Properties otherProperties;
String otherFile = proGenProps.properties.getProperty(propertyFile);
if (otherFile != null) {
otherFile = convertClasspath2Path(otherFile);
otherProperties = new Properties();
try {
lookForInAbsolutePath(otherProperties, otherFile);
} catch (FileNotFoundException fnfe) {
lookForInUserProject(propertyFile, otherProperties);
}
chekProperties(otherProperties);
}
}
private void lookForInAbsolutePath(Properties otherProperties, String otherFile) throws IOException {
final InputStream inputStream = getResource(otherFile);
try {
otherProperties.load(inputStream);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
private FileInputStream lookForInUserProject(String propertyFile, Properties otherProperties) throws IOException {
FileInputStream fileInputStream = null;
final String otherFile = ProGenContext.getProperty(PROGEN_USER_HOME_PROPERTY).replace('.', File.separatorChar) + proGenProps.properties.getProperty(propertyFile);
try {
fileInputStream = new FileInputStream(otherFile);
otherProperties.load(fileInputStream);
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
return fileInputStream;
}
private String convertClasspath2Path(String otherFile) {
final int lastDot = otherFile.lastIndexOf(DOT_SYMBOL);
final String path = otherFile.substring(0, lastDot);
final String ext = otherFile.substring(lastDot);
return path.replace('.', File.separatorChar) + ext;
}
/**
* Método que define algunas propiedades a partir de otras ya existentes.
* <p/>
* Por ahora se definen:
* <ul>
* <li>
* <code>progen.user.home</code>: ruta donde se encuentran los elementos que
* definen el dominio concreto sobre el que se está trabajando.</li>
* </ul>
*/
private void calculateProperties() {
final String experimentFile = ProGenContext.getProperty(PROGEN_EXPERIMENT_FILE_PROPERTY);
if (experimentFile != null) {
setUserHome(experimentFile);
}
updateExperimentFileProperty();
}
private void updateExperimentFileProperty() {
String experimentFile = ProGenContext.getOptionalProperty(PROGEN_EXPERIMENT_FILE_PROPERTY, "");
final int lastDotPosition = experimentFile.lastIndexOf('.');
if (lastDotPosition >= 0) {
experimentFile = experimentFile.replaceAll("\\.", File.separator);
final StringBuilder builder = new StringBuilder(experimentFile);
builder.setCharAt(lastDotPosition, '.');
experimentFile = builder.toString();
experimentFile = ProGenContext.class.getClassLoader().getResource(experimentFile).getFile();
proGenProps.properties.setProperty(PROGEN_EXPERIMENT_ABSOLUTE_FILE_PROPERTY, experimentFile);
}
}
private void setUserHome(String experimentFile) {
final String experimentName = experimentFile.substring(0, experimentFile.lastIndexOf(DOT_SYMBOL));
final String userHomeNormalized = experimentName.substring(0, experimentName.lastIndexOf(DOT_SYMBOL) + 1);
proGenProps.properties.setProperty(PROGEN_USER_HOME_PROPERTY, userHomeNormalized);
setExperimentName(experimentName);
}
private void setExperimentName(String experimentName) {
final String experimentNameNormalized = experimentName.substring(experimentName.lastIndexOf(DOT_SYMBOL) + 1, experimentName.length());
proGenProps.properties.setProperty("progen.experiment.name", experimentNameNormalized);
}
/**
* Carga las propiedades definidas en el fichero que tiene por nombre el del
* dominio del problema más un sufijo.
*
* @param sufixFile
* El sufijo que se le añade al nombre del dominio para formar el
* fichero de propiedades.
*/
private void loadOptionalFile(String sufixFile) {
final StringBuilder optionalFilesLoaded = new StringBuilder();
final String optionalFile = normalizeOptionalFileName(sufixFile, optionalFilesLoaded);
loadOptionalsProperties(optionalFile);
optionalFilesLoaded.append(optionalFile).append(", ");
ProGenContext.setProperty(PROGEN_OPTIONAL_FILES_PROPERTY, optionalFilesLoaded.toString());
}
private void closeSilently(InputStream fis) {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
// do nothing, ignore
}
}
private String normalizeOptionalFileName(String sufixFile, final StringBuilder optionalFilesLoaded) {
String optionalFile = ProGenContext.getMandatoryProperty(PROGEN_EXPERIMENT_FILE_PROPERTY);
optionalFile = optionalFile.replaceAll("\\.txt$", sufixFile).replace('.', File.separatorChar) + ".txt";
final URL optionalFileResource = ProGenContext.class.getClassLoader().getResource(optionalFile);
if( optionalFileResource == null){
optionalFile = "";
}else{
optionalFile = optionalFileResource.getFile();
}
optionalFilesLoaded.append(ProGenContext.getOptionalProperty(PROGEN_OPTIONAL_FILES_PROPERTY, ""));
return optionalFile;
}
private void loadOptionalsProperties(String optionalFile) {
InputStream fis = null;
try {
fis = new FileInputStream(new File(optionalFile));
mixProperties(fis);
} catch (IOException e) {
// do nothing, ignore
} finally {
closeSilently(fis);
}
}
private void mixProperties(InputStream fis) throws IOException {
Properties otherProperties;
Enumeration<Object> keys;
String key;
String value;
otherProperties = new Properties();
otherProperties.load(fis);
keys = otherProperties.keys();
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
value = otherProperties.getProperty(key);
proGenProps.properties.put(key, value);
}
}
}