package de.fub.agg2graph.gpseval;
import de.fub.agg2graph.gpseval.data.filter.TrackFilter;
import de.fub.agg2graph.gpseval.data.filter.TrackFilterFactory;
import de.fub.agg2graph.gpseval.data.filter.WaypointFilter;
import de.fub.agg2graph.gpseval.data.filter.WaypointFilterFactory;
import de.fub.agg2graph.gpseval.features.Feature;
import de.fub.agg2graph.gpseval.features.FeatureFactory;
import de.fub.agg2graph.gpseval.utils.Parameterizable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
/**
* This Config-class reads all information for a test-case from a xml-file.
*
* <p>
* This is a simple example-config:
* </p>
*
* <pre>
* <config>
* <name>Name</name>
* <classes>
* <class>
* <name>Car</name>
* <dataFolder>/path/to/data/car</dataFolder>
* </class>
* <class>
* <name>Walking</name>
* <dataFolder>/path/to/data/walking_fast</dataFolder>
* <dataFolder>/path/to/data/walking_slow</dataFolder>
* </class>
* </classes>
* <params>
* <trackFilter type="LimitPerClass">
* <param name="limit">5</param>
* </trackFilter>
* <trainingSetSize>0.6</trainingSetSize>
* <waypointFilter type="Limit">
* <param name="limit">2</param>
* </waypointFilter>
* </params>
* <features>
* <feature type="MaxSpeed" />
* <feature type="AvgSpeed" />
* <feature type="Segments" />
* <feature type="AvgBearingChange">
* <param name="bearingChangeThreshold">0</param>
* </feature>
* <feature type="MinPrecision" />
* <feature type="MaxPrecision" />
* <feature type="AvgPrecision" />
* </features>
* </config>
* </pre>
*
* {@link de.fub.agg2graph.gpseval.features.Feature Feature}-,
* {@link de.fub.agg2graph.gpseval.data.filter.TrackFilter TrackFilter}- and
* {@link de.fub.agg2graph.gpseval.data.filter.WaypointFilter WaypointFilter}-
* instances are created based on the data in the config-file. Therefore the
* {@link de.fub.agg2graph.gpseval.features.FeatureFactory FeatureFactory},
* {@link de.fub.agg2graph.gpseval.data.filter.TrackFilterFactory
* TrackFilterFactory} and
* {@link de.fub.agg2graph.gpseval.data.filter.WaypointFilterFactory
* WaypointFilterFactory} are used which create the instances based on names
* (which are taken from the config-file).
*
* @see de.fub.agg2graph.gpseval.Config
*/
public class ConfigFile implements Config {
private Document mDoc;
private Map<String, List<String>> mClassesFolderMapping;
private List<Feature> mFeatures;
private List<TrackFilter> mTrackFilters;
private List<WaypointFilter> mWaypointFilters;
/**
* Create ConfigFile-instance based on the config-file specified by
* <code>file</code>.
*
* @param file
* The path to the (xml-)config-file.
* @throws IOException
* @throws DocumentException
*/
public ConfigFile(Path file) throws IOException, DocumentException {
SAXReader reader = new SAXReader();
try (InputStream is = new FileInputStream(file.toFile())) {
mDoc = reader.read(is);
} catch (IOException | DocumentException ex) {
throw ex;
}
}
/**
* Get a string from the config-file. If the string referenced by the
* <code>xpath</code> does not exists, the <code>defaultValue</code> is
* returned.
*
* @param xpath
* @param defaultValue
* @return
*/
public String getStr(String xpath, String defaultValue) {
return mDoc.selectSingleNode(xpath) != null ? mDoc.selectSingleNode(
xpath).getText() : defaultValue;
}
/**
* Get an integer from the config-file. If the integer referenced by the
* <code>xpath</code> does not exists, the <code>defaultValue</code> is
* returned.
*
* @param xpath
* @param defaultValue
* @return
*/
public int getInt(String xpath, int defaultValue) {
return mDoc.selectSingleNode(xpath) != null ? Integer.parseInt(mDoc
.selectSingleNode(xpath).getText()) : defaultValue;
}
/**
* Get a double from the config-file. If the double referenced by the
* <code>xpath</code> does not exists, the <code>defaultValue</code> is
* returned.
*
* @param xpath
* @param defaultValue
* @return
*/
public double getDouble(String xpath, double defaultValue) {
return mDoc.selectSingleNode(xpath) != null ? Double.parseDouble(mDoc
.selectSingleNode(xpath).getText()) : defaultValue;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, List<String>> getClassesFolderMapping() {
if (mClassesFolderMapping != null) {
return mClassesFolderMapping;
}
mClassesFolderMapping = new HashMap<>();
List<Node> classNodes = mDoc.selectNodes("/config/classes/class");
for (Node classNode : classNodes) {
String className = classNode.selectSingleNode("name").getText();
List<String> dataFolders = new LinkedList<>();
List<Node> dataFolderNodes = classNode.selectNodes("dataFolder");
for (Node dataFolder : dataFolderNodes) {
dataFolders.add(dataFolder.getText());
}
mClassesFolderMapping.put(className, dataFolders);
}
return mClassesFolderMapping;
}
@SuppressWarnings("unchecked")
@Override
public List<Feature> getFeatures() {
if (mFeatures != null) {
return mFeatures;
}
mFeatures = new ArrayList<>();
FeatureFactory featureFactory = FeatureFactory.getFactory();
List<Node> featureNodes = mDoc.selectNodes("/config/features/feature");
for (Node featureNode : featureNodes) {
Node typeNode = featureNode.selectSingleNode("@type");
if (typeNode == null) {
Logger.getLogger(ConfigFile.class.getName()).log(Level.WARNING,
"Found invalid feature-node in configuration file!");
continue;
}
String featureName = typeNode.getText();
Feature feature = featureFactory.newFeature(featureName);
if (feature == null) {
Logger.getLogger(ConfigFile.class.getName()).log(Level.SEVERE,
"Failed to create Feature-Object ({0})!", featureName);
continue;
}
decorateWithParams(feature, featureNode);
mFeatures.add(feature);
}
return mFeatures;
}
@SuppressWarnings("unchecked")
@Override
public List<TrackFilter> getTrackFilters() {
if (mTrackFilters != null) {
return mTrackFilters;
}
mTrackFilters = new ArrayList<>();
TrackFilterFactory trackFilters = TrackFilterFactory.getFactory();
List<Node> filterNodes = mDoc.selectNodes("/config/params/trackFilter");
for (Node filterNode : filterNodes) {
Node typeNode = filterNode.selectSingleNode("@type");
if (typeNode == null) {
Logger.getLogger(ConfigFile.class.getName())
.log(Level.WARNING,
"Found invalid track-filter-node in configuration file!");
continue;
}
String filterName = typeNode.getText();
TrackFilter trackFilter = trackFilters.newTrackFilter(filterName);
if (trackFilter == null) {
Logger.getLogger(ConfigFile.class.getName()).log(Level.SEVERE,
"Failed to create TrackFilter-Object ({0})!",
filterName);
continue;
}
decorateWithParams(trackFilter, filterNode);
trackFilter.init();
mTrackFilters.add(trackFilter);
}
return mTrackFilters;
}
@SuppressWarnings("unchecked")
@Override
public List<WaypointFilter> getWaypointFilters() {
if (mWaypointFilters != null) {
return mWaypointFilters;
}
mWaypointFilters = new ArrayList<>();
WaypointFilterFactory waypointFilters = WaypointFilterFactory
.getFactory();
List<Node> filterNodes = mDoc
.selectNodes("/config/params/waypointFilter");
for (Node filterNode : filterNodes) {
Node typeNode = filterNode.selectSingleNode("@type");
if (typeNode == null) {
Logger.getLogger(ConfigFile.class.getName())
.log(Level.WARNING,
"Found invalid waypoint-filter-node in configuration file!");
continue;
}
String filterName = typeNode.getText();
WaypointFilter waypointFilter = waypointFilters
.newWaypointFilter(filterName);
if (waypointFilter == null) {
Logger.getLogger(ConfigFile.class.getName()).log(Level.SEVERE,
"Failed to create WaypointFilter-Object ({0})!",
filterName);
continue;
}
decorateWithParams(waypointFilter, filterNode);
mWaypointFilters.add(waypointFilter);
}
return mWaypointFilters;
}
@Override
public double getTrainingSetSize() {
return getDouble("/config/params/trainingSetSize", 0.6);
}
@Override
public int getCrossValidationFolds() {
return getInt("/config/params/crossValidationFolds", 10);
}
/**
* Decorate a {@link de.fub.agg2graph.gpseval.utils.Parameterizable
* Parameterizable}-instance with the parameters set in the specified node.
* The node (<code>parentNode</code>) must have the following format:
*
* <pre>
* <someNode>
* <param name="myname1">myValue1</param>
* <param name="myname2">myValue2</param>
* </someNode>
* </pre>
*
* Any number of <code>param</code>-nodes are allowed.
*
* @param p
* @param parentNode
*/
@SuppressWarnings("unchecked")
private void decorateWithParams(Parameterizable p, Node parentNode) {
List<Node> paramNodes = parentNode.selectNodes("./param");
if (paramNodes == null) {
return;
}
for (Node paramNode : paramNodes) {
Node paramNameNode = paramNode.selectSingleNode("@name");
if (paramNameNode == null) {
Logger.getLogger(ConfigFile.class.getName()).log(Level.WARNING,
"Found invalid param-node in configuration file!");
continue;
}
String paramName = paramNameNode.getText();
String paramValue = paramNode.getText();
p.setParam(paramName, paramValue);
}
}
@Override
public String getName() {
return getStr("/config/name", "### Unknown ###");
}
}