package nl.tudelft.simulation.xml.dsol; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import nl.tudelft.simulation.dsol.DSOLModel; import nl.tudelft.simulation.dsol.experiment.Experiment; import nl.tudelft.simulation.dsol.experiment.ExperimentalFrame; import nl.tudelft.simulation.dsol.experiment.Replication; import nl.tudelft.simulation.dsol.experiment.Treatment; import nl.tudelft.simulation.dsol.simtime.SimTimeDouble; import nl.tudelft.simulation.dsol.simtime.TimeUnit; import nl.tudelft.simulation.dsol.simulators.DEVSAnimator; import nl.tudelft.simulation.dsol.simulators.SimulatorInterface; import nl.tudelft.simulation.jstats.streams.MersenneTwister; import nl.tudelft.simulation.jstats.streams.StreamInterface; import nl.tudelft.simulation.language.io.URLResource; import nl.tudelft.simulation.language.reflection.ClassUtil; import nl.tudelft.simulation.logger.Logger; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; /** * The ExperimentParser parses xml-based experiments into their java objects
* (c) copyright 2002-2005 Delft University of Technology , the * Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no * warranty. * @version 2.0 21.09.2003
* @author Peter Jacobs */ public class ExperimentParser { /** builder the xerces parser with validation turned on. */ private static SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser", true); static { // turns on Schema Validation with Xerces builder.setFeature("http://xml.org/sax/features/validation", true); builder.setFeature("http://apache.org/xml/features/validation/schema", true); // Let's find the XSD file String xsd = URLResource.getResource("/xsd/experiment.xsd").toExternalForm(); builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", "http://www.simulation.tudelft.nl " + xsd); } /** * constructs a new ExperimentParser This is a Utility Class. */ protected ExperimentParser() { // A utility class should not be instantiated } /** * parses an experimentalFrame xml-file. * @param input the url of the xmlfile * @return ExperimentalFrame the experimentalFrame * @throws IOException whenever parsing fails */ public static ExperimentalFrame parseExperimentalFrame(final URL input) throws IOException { return parseExperimentalFrameTimeDouble(input); } /** * parses an experimentalFrame xml-file. * @param input the url of the xmlfile * @return ExperimentalFrame the experimentalFrame * @throws IOException whenever parsing fails */ public static ExperimentalFrame.TimeDouble parseExperimentalFrameTimeDouble(final URL input) throws IOException { if (input == null) { throw new IOException("experiment URL=null"); } try { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); String name = DateFormat.getDateTimeInstance().format(calendar.getTime()); Element rootElement = builder.build(input).getRootElement(); List> experiments = new ArrayList<>(); List experimentElements = rootElement.getChildren("experiment"); int number = 0; for (Element element : experimentElements) { Experiment.TimeDouble experiment = ExperimentParser.parseExperimentTimeDouble(element, input); experiment.setDescription("experiment " + number); experiments.add(experiment); number++; } ExperimentalFrame.TimeDouble frame = new ExperimentalFrame.TimeDouble(input); frame.setExperiments(experiments); return frame; } catch (Exception exception) { Logger.warning(ExperimentParser.class, "parseExperimentalFrame", exception); return null; } } /** * parses an experiment xml-file. * @param url the url of the experimentfile * @param rootElement the element representing the experiment * @return ExperimentalFrame the experiment * @throws IOException whenever parsing fails */ public static Experiment parseExperiment(final Element rootElement, final URL url) throws IOException { return parseExperimentTimeDouble(rootElement, url); } /** * parses an experiment xml-file. * @param url the url of the experimentfile * @param rootElement the element representing the experiment * @return ExperimentalFrame the experiment * @throws IOException whenever parsing fails */ public static Experiment.TimeDouble parseExperimentTimeDouble(final Element rootElement, final URL url) throws IOException { try { // resolve the MODEL element ClassLoader loader = ExperimentParser.resolveClassLoader(url); Element modelElement = rootElement.getChild("model"); String modelClassName = ExperimentParser.cleanName(modelElement.getChildText("model-class")); if (modelElement.getChild("class-path") != null) { @SuppressWarnings("unchecked") List jarFiles = modelElement.getChild("class-path").getChildren("jar-file"); URL[] urls = new URL[jarFiles.size()]; int nr = 0; for (Iterator i = jarFiles.iterator(); i.hasNext();) { Element child = i.next(); urls[nr] = URLResource.getResource(child.getValue()); nr++; } loader = new URLClassLoader(urls, loader); } Thread.currentThread().setContextClassLoader(loader); // XXX: START TO REMOVE ArrayList urls = new ArrayList(); for (int i = 0; i < ((URLClassLoader) loader).getURLs().length; i++) { urls.add(((URLClassLoader) loader).getURLs()[i]); } System.err.println("classloader URLs: " + urls); URLClassLoader urlLoader = (URLClassLoader) loader; System.err.println("model: " + modelClassName); // XXX: END TO REMOVE Class modelClass = Class.forName(modelClassName, true, loader); DSOLModel.TimeDouble model = (DSOLModel.TimeDouble) ClassUtil.resolveConstructor(modelClass, null).newInstance(); // resolve the SIMULATOR-CLASS element SimulatorInterface.TimeDouble simulator = null; if (modelElement.getChild("simulator-class") == null) { simulator = new DEVSAnimator.TimeDouble(); } else { Class simulatorClass = Class.forName(rootElement.getChildText("simulator-class"), true, loader); simulator = (SimulatorInterface.TimeDouble) ClassUtil.resolveConstructor(simulatorClass, null) .newInstance(); } // Define the experiment Experiment.TimeDouble experiment = new Experiment.TimeDouble(null, simulator, model); // resolve the TREATMENT element Treatment.TimeDouble treatment = ExperimentParser.parseTreatment(rootElement.getChild("treatment"), experiment); experiment.setTreatment(treatment); // resolve the ANALYST element String analyst = ""; if (rootElement.getChild("analyst") != null) { analyst = rootElement.getChild("analyst").getText(); } experiment.setAnalyst(analyst); // resolve the name element String name = null; if (rootElement.getChild("name") != null) { name = rootElement.getChild("name").getText(); experiment.setDescription(name); } // no "else" because experiment already has a default description provided in parseExperimentalFrame // resolve the REPLICATIONS element Element replicationsElement = rootElement.getChild("replications"); @SuppressWarnings("unchecked") List replicationElements = replicationsElement.getChildren("replication"); List> replicationArray = new ArrayList<>(); for (Iterator i = replicationElements.iterator(); i.hasNext();) { replicationArray.add(ExperimentParser.parseReplication(i.next(), experiment)); } experiment.setReplications(replicationArray); return experiment; } catch (Exception exception) { Logger.warning(ExperimentParser.class, "parseExperiment", exception); throw new IOException(exception.getMessage()); } } /** characters that can be used in a classpath. */ private static String legalChars = ".@$_-abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /** * clean the name of a class. */ private static String cleanName(final String string) { String out = ""; for (int i = 0; i < string.length(); i++) { if (legalChars.indexOf(string.charAt(i)) >= 0) out += string.charAt(i); } return out; } // *********************** PRIVATE PARSING FUNCTIONS *******************// /** * parses an experiment xml-file. * @param input the inputstream * @return Classloader the classLoader */ private static ClassLoader resolveClassLoader(final URL input) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (input.getProtocol().equals("file")) { try { List urls = new ArrayList(); String path = input.getPath().substring(0, input.getPath().lastIndexOf('/')) + "/"; // We add the path urls.add(new URL("file:" + path)); // If the classpath ends with src, we also add the bin if (path.endsWith("src/")) { path = path.substring(0, path.length() - 4) + "bin/"; urls.add(new URL("file:" + path)); } else if (path.endsWith("src/conf/")) // If the classpath ends with src/conf, we add target/classes { path = path.substring(0, path.length() - 9) + "target/classes/"; urls.add(new URL("file:" + path)); } else if (path.endsWith("target/classes/")) { // If the classpath ends with target/classes, leave as is } else { // We assume there might be a src & bin dir, and target, and target/classes path = path.substring(0, path.length()) + "bin/"; urls.add(new URL("file:" + path)); path = path.substring(0, path.length()) + "target/"; urls.add(new URL("file:" + path)); path = path.substring(0, path.length()) + "target/classes/"; urls.add(new URL("file:" + path)); } // XXX: START TO REMOVE System.err.println(urls); // XXX: END TO REMOVE URL[] urlArray = urls.toArray(new URL[urls.size()]); URLClassLoader urlClassLoader = new URLClassLoader(urlArray, loader); return urlClassLoader; } catch (MalformedURLException exception) { return loader; } } return loader; } /** * parses the dateTime * @param value the string value in the yyyy-mm-ddThh:mm:ss format * @return long the amount of milliseconds since 1970. */ private static long parseDateTime(final String value) { Calendar calendar = Calendar.getInstance(); String concatDate = value.split("T")[0]; String concatTime = value.split("T")[1]; String[] date = concatDate.split("-"); String[] time = concatTime.split(":"); calendar.set(new Integer(date[0]).intValue(), new Integer(date[1]).intValue() - 1, new Integer(date[2]).intValue(), new Integer(time[0]).intValue(), new Integer(time[1]).intValue(), new Integer(time[2]).intValue()); return calendar.getTimeInMillis(); } /** * parses a period * @param element the xml-element representing the period * @param treatmentTimeUnit the timeUnit of the treatment * @return double the value in units defined by the treatment * @throws Exception whenever the period. */ private static double parsePeriod(final Element element, final TimeUnit treatmentTimeUnit) throws Exception { TimeUnit timeUnit = ExperimentParser.parseTimeUnit(element.getAttribute("unit").getValue()); double value = -1; if (element.getText().equals("INF")) { value = Double.MAX_VALUE; } else { value = new Double(element.getText()).doubleValue(); } if (value < 0) { throw new JDOMException("parsePeriod: value = " + value + " <0. simulator cannot schedule in past"); } return timeUnit.getFactor() * value / treatmentTimeUnit.getFactor(); } /** * parses a replication. * @param element the JDOM element * @param parent the experiment * @return the replication * @throws Exception on failure */ private static Replication.TimeDouble parseReplication(final Element element, final Experiment.TimeDouble parent) throws Exception { Replication.TimeDouble replication = new Replication.TimeDouble(parent); if (element.getAttribute("description") != null) { replication.setDescription(element.getAttribute("description").getValue()); } Map streams = new HashMap(); @SuppressWarnings("unchecked") List streamElements = element.getChildren("stream"); for (Element streamElement : streamElements) { long seed = new Long(streamElement.getAttributeValue("seed")).longValue(); StreamInterface stream = new MersenneTwister(seed); streams.put(streamElement.getAttributeValue("name"), stream); } replication.setStreams(streams); return replication; } /** * parses properties to treatments. * @param element the element * @return Properties */ private static Properties parseProperties(final Element element) { Properties result = new Properties(); @SuppressWarnings("unchecked") List children = element.getChildren("property"); for (Iterator i = children.iterator(); i.hasNext();) { Element child = i.next(); String key = child.getAttributeValue("key"); String value = child.getAttributeValue("value"); result.put(key, value); } return result; } /** * parses a timeUnit * @param name the name * @return TimeUnitInterface result * @throws Exception on failure */ private static TimeUnit parseTimeUnit(final String name) throws Exception { if (name.equals("DAY")) { return TimeUnit.DAY; } if (name.equals("HOUR")) { return TimeUnit.HOUR; } if (name.equals("MILLISECOND")) { return TimeUnit.MILLISECOND; } if (name.equals("MINUTE")) { return TimeUnit.MINUTE; } if (name.equals("SECOND")) { return TimeUnit.SECOND; } if (name.equals("WEEK")) { return TimeUnit.WEEK; } if (name.equals("UNIT")) { return TimeUnit.UNIT; } throw new Exception("parseTimeUnit.. unknown argument: " + name); } /** * parses the treatment. * @param element the xml-element * @param parent parent * @return Treatment * @throws Exception on failure */ private static Treatment.TimeDouble parseTreatment(final Element element, final Experiment.TimeDouble parent) throws Exception { TimeUnit tu = ExperimentParser.parseTimeUnit(element.getChildText("timeUnit")); long startTime = 0L; if (element.getChild("startTime") != null) { startTime = ExperimentParser.parseDateTime(element.getChildText("startTime")); } double warmupPeriod = 0.0; if (element.getChild("warmupPeriod") != null) { warmupPeriod = ExperimentParser.parsePeriod(element.getChild("warmupPeriod"), tu); } double runLength = 0.0; if (element.getChild("runLength") != null) { runLength = ExperimentParser.parsePeriod(element.getChild("runLength"), tu); } Treatment.TimeDouble treatment = new Treatment.TimeDouble(parent, "tr", new SimTimeDouble(1.0 * startTime), warmupPeriod, runLength); if (element.getChild("properties") != null) { treatment.setProperties(ExperimentParser.parseProperties(element.getChild("properties"))); } return treatment; } }