Print

Print


Author: [log in to unmask]
Date: Tue Nov  3 11:32:25 2015
New Revision: 3686

Log:
[HPSJAVA-623] Add event printing and other updates to job manager; source code formatting.

Added:
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventPrintLoopAdapter.java
Modified:
    projects/lcsim/trunk/job-manager/pom.xml
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/AidaSaveDriver.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventMarkerDriver.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/IParameterConverter.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlDriver.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlManager.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/LCSimClassLoader.java
    projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/ParameterConverters.java

Modified: projects/lcsim/trunk/job-manager/pom.xml
 =============================================================================
--- projects/lcsim/trunk/job-manager/pom.xml	(original)
+++ projects/lcsim/trunk/job-manager/pom.xml	Tue Nov  3 11:32:25 2015
@@ -1,23 +1,19 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
     <modelVersion>4.0.0</modelVersion>
     <artifactId>lcsim-job-manager</artifactId>
     <name>job-manager</name>
     <description>job manager allowing XML steering using a command line utility</description>
-    
     <parent>
         <groupId>org.lcsim</groupId>
         <artifactId>lcsim-parent</artifactId>
         <version>3.1.6-SNAPSHOT</version>
         <relativePath>../parent/pom.xml</relativePath>
     </parent>
-    
     <scm>
         <url>http://java.freehep.org/svn/repos/lcdet/list/projects/lcsim/trunk/job-manager/</url>
         <connection>scm:svn:svn://svn.freehep.org/lcdet/projects/lcsim/trunk/job-manager/</connection>
         <developerConnection>scm:svn:svn://svn.freehep.org/lcdet/projects/lcsim/trunk/job-manager/</developerConnection>
     </scm>        
-        
     <dependencies>
         <dependency>
             <groupId>org.lcsim</groupId>
@@ -30,8 +26,7 @@
         <dependency>
             <groupId>commons-cli</groupId>
             <artifactId>commons-cli</artifactId>
-            <version>1.0</version>
+            <version>1.3</version>
         </dependency>
     </dependencies>
-    
 </project>

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/AidaSaveDriver.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/AidaSaveDriver.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/AidaSaveDriver.java	Tue Nov  3 11:32:25 2015
@@ -5,34 +5,28 @@
 import org.lcsim.util.Driver;
 import org.lcsim.util.aida.AIDA;
 
-public class AidaSaveDriver extends Driver 
-{
+public class AidaSaveDriver extends Driver {
+
     String outputFileName = "plots.aida";
     boolean verbose = false;
 
-    public AidaSaveDriver()
-    {}
+    public AidaSaveDriver() {
+    }
 
-    public void setOutputFileName(String outputFileName)
-    {
+    public void setOutputFileName(String outputFileName) {
         this.outputFileName = outputFileName;
     }
-    
-    public void setVerbose(boolean verbose)
-    {
+
+    public void setVerbose(boolean verbose) {
         this.verbose = verbose;
     }
 
-    public void endOfData()
-    {
-        try 
-        {
+    public void endOfData() {
+        try {
             if (verbose)
                 System.out.println("Saving AIDA file to " + outputFileName + " ...");
             AIDA.defaultInstance().saveAs(outputFileName);
-        }
-        catch (IOException x)
-        {
+        } catch (IOException x) {
             throw new RuntimeException("Problem saving AIDA file to " + outputFileName + ".", x);
         }
     }

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventMarkerDriver.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventMarkerDriver.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventMarkerDriver.java	Tue Nov  3 11:32:25 2015
@@ -7,7 +7,9 @@
  * Driver to print markers during event processing.
  * 
  * @author Jeremy McCormick <[log in to unmask]>
+ * @deprecated Use built-in command line option of {@link JobManager} instead.
  */
+@Deprecated
 public class EventMarkerDriver extends Driver {
 
     private int interval = 1;
@@ -32,7 +34,7 @@
     protected void process(EventHeader event) {
         if (nEvents % interval == 0) {
             getLogger().info(marker + "Event " + event.getEventNumber() + " with sequence " + nEvents);
-        }        
+        }
         nEvents++;
     }
 

Added: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventPrintLoopAdapter.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventPrintLoopAdapter.java	(added)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/EventPrintLoopAdapter.java	Tue Nov  3 11:32:25 2015
@@ -0,0 +1,64 @@
+package org.lcsim.job;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.freehep.record.loop.RecordEvent;
+import org.freehep.record.loop.RecordListener;
+import org.lcsim.event.EventHeader;
+
+/**
+ * Prints out information from event processing such as the event number at a specified interval.
+ * 
+ * @author Jeremy McCormick, SLAC
+ */
+class EventPrintLoopAdapter implements RecordListener {
+
+    /**
+     * Setup the logger.
+     */
+    private static Logger LOGGER = Logger.getLogger(EventPrintLoopAdapter.class.getName());
+    static {
+        if (LOGGER.getLevel() == null) {
+            System.out.println("EventPrintLoopAdapter: set level to ALL");
+            LOGGER.setLevel(Level.ALL);
+        } else {
+            System.out.println("EventPrintLoopAdapter: level " + LOGGER.getLevel() + " from config");
+        }
+    }
+
+    /**
+     * Sequence number of events processed.
+     */
+    private long eventSequence = 0;
+    
+    /**
+     * Event print interval which means every Nth event will be printed.
+     */
+    private long printInterval = 1;
+
+    /**
+     * Class constructor.
+     * @param printInterval the event print interval
+     */
+    EventPrintLoopAdapter(long printInterval) {
+        this.printInterval = printInterval;
+    }
+
+    /**
+     * Process an event and print the event information.
+     */
+    @Override
+    public void recordSupplied(RecordEvent recordEvent) {
+        Object record = recordEvent.getRecord();
+        if (record instanceof EventHeader) {
+            EventHeader event = (EventHeader) recordEvent.getRecord();
+            //System.out.println("EventPrintLoopAdapter.recordSupplied - " + event.getEventNumber());
+            if (eventSequence % printInterval == 0) {
+                LOGGER.info("event: " + event.getEventNumber() + "; time: " + event.getTimeStamp() + "; seq: " 
+                        + eventSequence);
+            }
+            ++eventSequence;
+        }
+    }
+}

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/IParameterConverter.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/IParameterConverter.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/IParameterConverter.java	Tue Nov  3 11:32:25 2015
@@ -5,22 +5,25 @@
 
 /**
  * Interface for converting from XML to typed objects for input to LCSim Drivers.
+ * 
  * @author jeremym
  */
-public interface IParameterConverter 
-{
-	/**
-	 * This method returns true if the converter can handle the given type.
-	 * @param propertyType The class of the parameter.
-	 * @return True if converter handles the given type; False if no.
-	 */
-	public boolean handles(Class propertyType);	
-	
-	/**
-	 * Convert an XML element parameter to a specific type and return as an Object.
-	 * @param factory The expression factory to be used for variable evaluation.
-	 * @param parameterElement The XML parameter data.
-	 * @return Parameter converted to specific type.  Returned as generic object.
-	 */
-	public Object convert(JDOMExpressionFactory factory, Element parameterElement);
+public interface IParameterConverter {
+
+    /**
+     * This method returns true if the converter can handle the given type.
+     * 
+     * @param propertyType The class of the parameter.
+     * @return True if converter handles the given type; False if no.
+     */
+    public boolean handles(Class propertyType);
+
+    /**
+     * Convert an XML element parameter to a specific type and return as an Object.
+     * 
+     * @param factory The expression factory to be used for variable evaluation.
+     * @param parameterElement The XML parameter data.
+     * @return Parameter converted to specific type. Returned as generic object.
+     */
+    public Object convert(JDOMExpressionFactory factory, Element parameterElement);
 }

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlDriver.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlDriver.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlDriver.java	Tue Nov  3 11:32:25 2015
@@ -6,51 +6,42 @@
 import org.lcsim.util.Driver;
 
 /**
- * Extend this driver to run an xml recon job in JAS3.
- * Your driver needs to call <code>super.process()</code>
- * in its <code>process()</code> method for this to work.
+ * Extend this driver to run an xml recon job in JAS3. Your driver needs to call <code>super.process()</code> in its
+ * <code>process()</code> method for this to work. The only sections that will be used are <drivers>, <execute>,
+ * <control>, and <define>. The <code>numberOfEvents</code> parameter will be ignored. Non-standard jar files need to be
+ * added to the ~/.JAS3/extensions directory.
  * 
- * The only sections that will be used are <drivers>,
- * <execute>, <control>, and <define>.  The <code>numberOfEvents</code>
- * parameter will be ignored.  Non-standard jar files need
- * to be added to the ~/.JAS3/extensions directory.
- *  
  * @author jeremym
  */
-public class JobControlDriver extends Driver 
-{
-	JobControlManager mgr = new JobControlManager();
-	
-	private boolean wasSetup = false;
-	
-	public JobControlDriver()
-	{}
-	
-	public JobControlDriver(File steering)
-	{
-		setup(steering);
-	}
-	
-	public void setup(File steering)
-	{
-		if (wasSetup)
-			return;			
-		else
-			wasSetup = true;
-		mgr.setup(steering);
-		for (Driver driver : mgr.getDriverExecList())
-		{
-			this.add(driver);
-		}
-	}
-	
-	public JobControlManager getManager()
-	{
-		return this.mgr;
-	}
+public class JobControlDriver extends Driver {
 
-	public void process(EventHeader event)
-	{
-		super.process(event);
-	}
+    JobControlManager mgr = new JobControlManager();
+
+    private boolean wasSetup = false;
+
+    public JobControlDriver() {
+    }
+
+    public JobControlDriver(File steering) {
+        setup(steering);
+    }
+
+    public void setup(File steering) {
+        if (wasSetup)
+            return;
+        else
+            wasSetup = true;
+        mgr.setup(steering);
+        for (Driver driver : mgr.getDriverExecList()) {
+            this.add(driver);
+        }
+    }
+
+    public JobControlManager getManager() {
+        return this.mgr;
+    }
+
+    public void process(EventHeader event) {
+        super.process(event);
+    }
 }

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlManager.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlManager.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/JobControlManager.java	Tue Nov  3 11:32:25 2015
@@ -10,7 +10,6 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.PrintStream;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -38,7 +37,6 @@
 import org.apache.commons.cli.PosixParser;
 import org.freehep.record.loop.RecordEvent;
 import org.jdom.Attribute;
-import org.jdom.DataConversionException;
 import org.jdom.Document;
 import org.jdom.Element;
 import org.jdom.Text;
@@ -46,13 +44,12 @@
 import org.jdom.output.Format;
 import org.jdom.output.XMLOutputter;
 import org.lcsim.conditions.ConditionsManager;
-import org.lcsim.conditions.ConditionsManager.ConditionsSetNotFoundException;
+import org.lcsim.conditions.ConditionsManager.ConditionsNotFoundException;
 import org.lcsim.event.EventHeader;
 import org.lcsim.units.Constants;
 import org.lcsim.util.Driver;
 import org.lcsim.util.DriverAdapter;
 import org.lcsim.util.cache.FileCache;
-import org.lcsim.util.log.LogUtil;
 import org.lcsim.util.loop.LCIOEventSource;
 import org.lcsim.util.loop.LCSimConditionsManagerImplementation;
 import org.lcsim.util.loop.LCSimLoop;
@@ -61,443 +58,1021 @@
 
 /**
  * <p>
- * This class provides a frontend for running and managing LCSim jobs using an XML steering format.
- * </p>
+ * This class provides a front end for running and managing LCSim event processing jobs using XML steering files.
  * <p>
- * The command line syntax is:
- * </p>
+ * More details about this XML format can be found at the<br/>
+ * <a href="https://confluence.slac.stanford.edu/display/ilc/lcsim+xml">LCSim XML Confluence Page</a>.
  * <p>
- * <code>java -jar ./lib/lcsim-bin.jar steeringFile.xml [options]</code>
- * </p>
+ * The command line syntax is:<br/>
+ * <code>java org.lcsim.job.JobManager steeringFile.xml [options]</code>
  * <p>
  * To see the available command line options with descriptions, run with "-h" as the only option.
- * </p>
  * <p>
- * In the XML file, variables have the format <code>${variableName}</code>. No nested variable
- * definitions are allowed (e.g. no variables within variables).
- * </p>
- * <p>
- * Command-line parameters that can be defined using switches are overridden by the corresponding
- * settings in the job XML file, if they are present. This means that if these parameters are to be
- * taken from the CL, the matching settings should be left out of the XML job file. This is not the
- * case, however, for input files specified by the "-i" option, which are appended to the ones
- * listed in the steering file.
- * </p>
- * 
+ * Command-line parameters that can be defined using switches are overridden by the corresponding settings in the job
+ * XML file, if they are present. This means that if these parameters are to be taken from the CL, the matching settings
+ * should be left out of the XML job file. This is not the case, however, for input files specified by the "-i" option,
+ * which are appended to the ones listed in the steering file.
+ *
  * @version $Id: JobControlManager.java,v 1.65 2013/02/14 22:30:07 jeremy Exp $
  * @author Jeremy McCormick
  */
-// FIXME: Command line arguments should probably override the steering file when applicable.
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class JobControlManager {
 
-    protected static Logger logger = LogUtil.create(JobControlManager.class);
-    static {
-        logger.setLevel(Level.ALL);
-    }
-    
-    // The LCIO record loop.
-    protected LCSimLoop loop;
-
-    // Driver management.
-    protected Map<String, Driver> driverMap = new LinkedHashMap<String, Driver>();
-    protected List<Driver> driverExec = new ArrayList<Driver>();
-    protected Map<String, String> availableDrivers = new HashMap<String, String>();
-
-    // Run parameters.
-    protected List<File> inputFiles = new ArrayList<File>();
-    protected int numberOfEvents = -1;
-    protected int skipEvents = -1;
-    protected File rewriteFile;
-
-    // Variables and constants.
-    protected Map<String, String> variableMap = new HashMap<String, String>();
-    protected Map<String, Double> constantsMap = new HashMap<String, Double>();
-
-    // Boolean job options.
-    protected boolean performDryRun;
-    protected boolean rewrite;
-    protected boolean useSteeringResource;
-    protected boolean dummyDetector;
-
-    // Settings effecting logging verbosity.
-    protected boolean printInputFiles;
-    protected boolean printDriverStatistics;
-    protected boolean printSystemProperties;
-    protected boolean printUserClassPath;
-    protected boolean printDriversDetailed;
-    // private boolean printVersion;
-    protected boolean verbose;
-
-    // File caching.
-    protected File cacheDirectory;
-    protected FileCache fileCache; // Start with default dir.
-
-    // True once job parameters are setup from CL and XML.
-    protected boolean wasSetup;
-
-    // The manager's class loader.
-    protected ClassLoader loader;
-
-    // The log stream for messages.
-    protected PrintStream logStream = System.out;
-
-    // Root node of XML job file.
-    protected Element root;
-
-    // XML utils.
-    protected JDOMExpressionFactory factory = new JDOMExpressionFactory();
-    protected ParameterConverters paramConverter = new ParameterConverters(factory);
-
-    // Command line options.
-    protected static final Options options = createCommandLineOptions();
-
-    // Reg exp to extract variables from a string with the form "${varName}".
-    protected static final Pattern varPattern = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
-
-    protected DriverAdapter driverAdapter = null;
+    /**
+     * Initialize the logger which uses the package name.
+     */
+    protected static final Logger LOGGER = Logger.getLogger(JobControlManager.class.getPackage().getName());
+
+    /**
+     * The command line options.
+     */
+    private static final Options OPTIONS = createCommandLineOptions();
+
+    /**
+     * The regular expression for extracting the variables from an XML file.
+     */
+    private static final Pattern VARIABLE_PATTERN = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
+
+    /**
+     * Create the command line options.
+     *
+     * @return The command line options for the manager.
+     */
+    private static Options createCommandLineOptions() {
+        final Options options = new Options();
+        options.addOption(new Option("p", "properties", true, "Load a properties file containing variable definitions"));
+        options.addOption(new Option("D", "define", true, "Define a variable with form [name]=[value]"));
+        options.addOption(new Option("w", "rewrite", true, "Rewrite the XML file with variables resolved"));
+        options.addOption(new Option("s", "skip", true, "Set the number of events to skip"));
+        options.addOption(new Option("n", "nevents", true, "Set the max number of events to process"));
+        options.addOption(new Option("x", "dry-run", false, "Perform a dry run which does not process events"));
+        options.addOption(new Option("i", "input-file", true, "Add an LCIO input file to process"));
+        options.addOption(new Option("r", "resource", false, "Use a steering resource rather than a file"));
+        options.addOption(new Option("b", "batch", false, "Run in batch mode in which plots will not be shown."));
+        options.addOption(new Option("e", "event-print", true, "Event print interval"));
+        options.addOption(new Option("d", "detector", true, "user supplied detector name (careful!)"));
+        options.addOption(new Option("R", "run", true, "user supplied run number (careful!)"));
+        return options;
+    }
+
+    /**
+     * Get the Java primitive type class from a type name.
+     *
+     * @param name The name of the type.
+     * @return The primitive type class.
+     */
+    private static Class getPrimitiveType(final String name) {
+        if (name.equals("byte")) {
+            return byte.class;
+        }
+        if (name.equals("short")) {
+            return short.class;
+        }
+        if (name.equals("int")) {
+            return int.class;
+        }
+        if (name.equals("long")) {
+            return long.class;
+        }
+        if (name.equals("char")) {
+            return char.class;
+        }
+        if (name.equals("float")) {
+            return float.class;
+        }
+        if (name.equals("double")) {
+            return double.class;
+        }
+        if (name.equals("boolean")) {
+            return boolean.class;
+        }
+        if (name.equals("String")) {
+            return String.class;
+        }
+        return null;
+    }
+
+    /**
+     * Run from the command line.
+     * <p>
+     * Takes command-line options (use -h option to see them).
+     *
+     * @param args the command line arguments
+     */
+    public static void main(final String args[]) {
+        final JobControlManager mgr = new JobControlManager();
+        mgr.parse(args);
+        mgr.run();
+    }
+
+    /**
+     * Print help and exit.
+     */
+    private static void printHelp() {
+        LOGGER.info("java " + JobControlManager.class.getCanonicalName() + " [options] steeringFile.xml");
+        final HelpFormatter help = new HelpFormatter();
+        help.printHelp(" ", OPTIONS);
+        System.exit(1);
+    }
+
+    /**
+     * Root directory for file caching.
+     */
+    private File cacheDirectory;
+
+    /**
+     * The class loader that will be used for the job.
+     */
+    private ClassLoader classLoader;
+
+    /**
+     * Command line that is setup from <code>main</code> arguments.
+     */
+    CommandLine commandLine = null;
+
+    /**
+     * Map of constants definitions.
+     */
+    private final Map<String, Double> constantsMap = new HashMap<String, Double>();
+
+    /**
+     * User supplied detector name.
+     */
+    private String detectorName = null;
+
+    /**
+     * A driver adapter created on the fly in case it is needed by an external program.
+     */
+    private DriverAdapter driverAdapter = null;
+
+    /**
+     * List of drivers to execute in the job.
+     */
+    private final List<Driver> driverExec = new ArrayList<Driver>();
+
+    /**
+     * Map of driver names to objects.
+     */
+    private final Map<String, Driver> driverMap = new LinkedHashMap<String, Driver>();
+
+    /**
+     * Enable dry run so no events are processed.
+     */
+    private boolean dryRun;
+
+    /**
+     * Setup a "dummy" detector in the conditions system.
+     */
+    private boolean dummyDetector;
+
+    /**
+     * Event printing interval (null means no event printing).
+     */
+    private Long eventPrintInterval = null;
+
+    /**
+     * JDOM expression factory for variables.
+     */
+    private final JDOMExpressionFactory factory = new JDOMExpressionFactory();
+
+    /**
+     * File cache.
+     */
+    private FileCache fileCache; // Start with default dir.
+
+    /**
+     * List of input LCIO files.
+     */
+    private final List<File> inputFiles = new ArrayList<File>();
+
+    /**
+     * Flag set to <code>true</code> after setup is performed.
+     */
+    private boolean isSetup;
+
+    /**
+     * The job end timestamp in ms.
+     */
+    private long jobEnd = 0;
+
+    /**
+     * The job start timestamp in ms.
+     */
+    private long jobStart = 0;
+
+    /**
+     * The LCIO record loop.
+     */
+    private LCSimLoop loop;
+
+    /**
+     * Number of events to run before stopping job.
+     */
+    private int numberOfEvents = -1;
+
+    /**
+     * Helper for converting Driver parameters.
+     */
+    private final ParameterConverters paramConverter = new ParameterConverters(factory);
+
+    /**
+     * Set to <code>true</code> to print out driver statistics at the end of the job.
+     */
+    private boolean printDriverStatistics;
+
+    /**
+     * Path for rewriting steering file with variables resolved.
+     */
+    private File rewriteFile;
+
+    /**
+     * Enable rewriting of the steering file to a new path with variables resolved.
+     */
+    private boolean rewriteSteering;
+
+    /**
+     * The root node of the XML document providing config to the manager.
+     */
+    private Element root;
+
+    /**
+     * User supplied run number.
+     */
+    private Integer runNumber = null;
+
+    /**
+     * Number of events to skip at start of job.
+     */
+    private int skipEvents = -1;
+
+    /**
+     * Interpret steering file argument as a resource rather than file path.
+     */
+    private boolean useSteeringResource;
+
+    /**
+     * Map of variable names to their values.
+     */
+    private final Map<String, String> variableMap = new HashMap<String, String>();
 
     /**
      * The default constructor.
      */
     public JobControlManager() {
+
         try {
             fileCache = new FileCache();
-        } catch (IOException x) {
+        } catch (final IOException x) {
             throw new RuntimeException(x);
         }
+
+        // FIXME: Should this instead be done when the job is started?
         LCSimConditionsManagerImplementation.register();
     }
 
     /**
-     * Run the manager using a main. Takes command-line options (use -h option to see them).
-     * @param args The command line arguments.
-     */
-    public static void main(String args[]) {
-        JobControlManager mgr = new JobControlManager();
-        mgr.run(args);
-    }
-    
-    public void run(String args[]) {        
-        if (args.length == 0) {
-            logStream.println("java -jar lcsim-bin.jar [options] steeringFile.xml");
-            HelpFormatter help = new HelpFormatter();
-            help.printHelp(" ", options);
-            System.exit(1);
-        }
-        parseCommandLineOptions(args);
-        run();
-    }
-
-    /**
-     * Create the command line options.
-     * 
-     * @return The command line options for the manager.
-     */
-    private static Options createCommandLineOptions() {
-        Options options = new Options();
-        options.addOption(new Option("p", true, "Load a properties file containing variable definitions"));
-        options.addOption(new Option("D", true, "Define a variable with form [name]=[value]"));
-        options.addOption(new Option("w", true, "Rewrite the XML file with variables resolved"));
-        options.addOption(new Option("v", false, "Turn on verbose mode"));
-        options.addOption(new Option("s", true, "Set the number of events to skip"));
-        options.addOption(new Option("n", true, "Set the max number of events to process"));
-        options.addOption(new Option("x", false, "Perform a dry run which does not process events"));
-        options.addOption(new Option("q", false, "Turn on quiet mode"));
-        options.addOption(new Option("i", true, "Add an LCIO input file to process"));
-        options.addOption(new Option("r", false, "Use a steering resource rather than a file"));
-        options.addOption(new Option("b", false, "Run in headless mode in which plots will not be shown."));
-        return options;
-    }
-
-    /**
-     * Parse command-line options and setup job state from them. This method calls
-     * {@link #setup(File)} to load the steering paramters from an XML file, after processing other
-     * command line options. This method is private so that callers must all use the
-     * {@link #main(String[])} routine as the primary entry point.
-     * 
-     * @param args The command line arguments.
-     */
-    private void parseCommandLineOptions(String args[]) {
-        // Setup parser.
-        CommandLineParser parser = new PosixParser();
-
-        // CommandLine to be loaded.
-        CommandLine cl = null;
-
-        // Parse CL arguments.
-        try {
-            cl = parser.parse(options, args);
-        } catch (ParseException x) {
-            throw new RuntimeException("Problem parsing command line options.", x);
-        }
-
-        // Turn on verbose mode. This setting may be overridden by control section from XML.
-        if (cl.hasOption("v")) {
-            setVerbose(true);
-        }
-        // Turn on quiet mode. If "-v" is also specified, this will have no effect.
-        else if (cl.hasOption("q")) {
-            setVerbose(false);
-        }
-
-        // Load properties file containing variable definitions.
-        if (cl.hasOption("p")) {
-            String[] propValues = cl.getOptionValues("p");
-            for (String propFileName : propValues) {
-                InputStream in = null;
-                try {
-                    in = new FileInputStream(propFileName);
-                } catch (FileNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
-                Properties props = new Properties();
-                try {
-                    props.load(in);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-                for (Entry<Object, Object> entry : props.entrySet()) {
-                    String key = (String) entry.getKey();
-                    String value = (String) entry.getValue();
-                    addVariableDefinition(key, value);
-                }
-            }
-        }
-
-        // Process user variable definitions.
-        if (cl.hasOption("D")) {
-            String[] defValues = cl.getOptionValues("D");
-            for (String def : defValues) {
-                String[] s = def.split("=");
-                if (s.length != 2) {
-                    throw new RuntimeException("Bad variable format: " + def);
-                }
-                String key = s[0];
-                String value = s[1];
-                addVariableDefinition(key, value);
-            }
-        }
-
-        // Rewrite XML file with variables resolved.
-        if (cl.hasOption("w")) {
-            this.rewrite = true;
-            String rewritePath = cl.getOptionValue("w");
-            this.rewriteFile = new File(rewritePath);
-            if (this.rewriteFile.exists()) {
-                throw new RuntimeException("Rewrite file already exists: " + rewritePath);
-            }
-        }
-
-        // Set max number of events to run.
-        if (cl.hasOption("n")) {
-            this.numberOfEvents = Integer.valueOf(cl.getOptionValue("n"));
-        }
-
-        // Set number of events to skip.
-        if (cl.hasOption("s")) {
-            this.skipEvents = Integer.valueOf(cl.getOptionValue("s"));
-        }
-
-        // Perform a dry run, not processing any events but doing job setup.
-        if (cl.hasOption("x")) {
-            this.performDryRun = true;
-        }
-
-        if (cl.hasOption("r")) {
-            this.useSteeringResource = true;
-        }
-
-        // Check that there is exactly one extra argument for the XML steering file.
-        if (cl.getArgList().size() == 0) {
-            throw new RuntimeException("Missing LCSim XML file argument.");
-        } else if (cl.getArgList().size() > 1) {
-            throw new RuntimeException("Too many extra arguments.");
-        }
-
-        // Local LCIO files to process.
-        if (cl.hasOption("i")) {
-            String[] files = cl.getOptionValues("i");
-            for (String fileName : files) {
-                File file = new File(fileName);
-                if (!file.exists()) {
-                    throw new RuntimeException("File given as command line option does not exist: " + fileName);
-                }
-                inputFiles.add(new File(fileName));
-            }
-        }
-        
-        // Run in headless mode in which plots will not show.
-        if (cl.hasOption("b")) {
-            enableHeadlessMode();
-        }
-
-        // Steering argument points to either a file or embedded resource.
-        String steering = (String) cl.getArgList().get(0);
-
-        // Using an embedded resource in the jar for steering.
-        if (this.useSteeringResource) {
-            setup(steering);
-            // Steering from a local file.
-        } else {
-            File xmlRunControlFile = new File(steering);
-            if (!xmlRunControlFile.exists()) {
-                throw new RuntimeException("The steering file " + args[0] + " does not exist!");
-            }
-            setup(xmlRunControlFile);
-        }
-    }
-    
-    public void enableHeadlessMode() {
-        System.setProperty("hep.aida.IAnalysisFactory", BatchAnalysisFactory.class.getName());
-    }
-
-    /**
-     * Add a variable definition to be substituted into the job's XML file. This method is public
-     * so that caller's not using the CL can still define necessary variables for the steering
-     * file.
-     * 
+     * Add a Driver to the internal Driver map.
+     *
+     * @param name the unique name of the Driver
+     * @param driver the instance of the Driver
+     */
+    private void addDriver(final String name, final Driver driver) {
+        if (driverMap.containsKey(name)) {
+            throw new RuntimeException("Duplicate driver name: " + name);
+        }
+        driverMap.put(name, driver);
+    }
+
+    /**
+     * Add an input LCIO file to be proceesed.
+     *
+     * @param inputFile The input LCIO file.
+     */
+    public void addInputFile(final File inputFile) {
+        if (isSetup) {
+            throw new RuntimeException("Input files cannot be added when manager has already been setup.");
+        }
+        inputFiles.add(inputFile);
+    }
+
+    /**
+     * Add a variable definition to be substituted into the job's XML file. This method is public so that caller's not
+     * using the CL can still define necessary variables for the steering file.
+     *
      * @param key The variable name.
      * @param value The variable's value.
      */
-    public void addVariableDefinition(String key, String value) {
-        if (verbose) {
-            logStream.println(key + " = " + value);
-        }
+    public void addVariableDefinition(final String key, final String value) {
+        LOGGER.config(key + " = " + value);
         if (!this.variableMap.containsKey(key)) {
             variableMap.put(key, value);
         } else {
             throw new RuntimeException("Duplicate variable definition: " + key);
         }
     }
-    
-    /**
-     * Add an input LCIO file to be proceesed.
-     * @param inputFile The input LCIO file.
-     */
-    public void addInputFile(File inputFile) {
-        if (wasSetup) {
-            throw new RuntimeException("Input files cannot be added when manager has already been setup.");
-        }
-        inputFiles.add(inputFile);
+
+    /**
+     * Configure start of job (usually done automatically).
+     */
+    public void configure() {
+        this.getDriverAdapter().start(null);
+    }
+
+    /**
+     * Create a driver adapter.
+     */
+    private void createDriverAdapter() {
+        if (this.isSetup == false) {
+            throw new IllegalStateException("The job manager was never setup.");
+        }
+        final Driver topDriver = new Driver();
+        for (final Driver driver : this.getDriverExecList()) {
+            topDriver.add(driver);
+        }
+        driverAdapter = new DriverAdapter(topDriver);
+    }
+
+    /**
+     * Create the <code>Driver</code> execution list.
+     */
+    private void createDriverExecList() {
+        // Make a list of Drivers to be executed.
+        final List<Element> exec = root.getChild("execute").getChildren("driver");
+        for (final Element execDriver : exec) {
+            final String driverName = execDriver.getAttributeValue("name");
+            final Driver driverFind = driverMap.get(driverName);
+            if (driverFind != null) {
+                driverExec.add(driverFind);
+            } else {
+                throw new RuntimeException("A Driver called " + driverName + " was not found.");
+            }
+        }
+
+        // Add the drivers to the LCSimLoop.
+        for (final Driver driver : driverExec) {
+            loop.add(driver);
+        }
+    }
+
+    /**
+     * Turn on the batch analysis factory so plots are not shown on the screen even when <code>Plotter.show()</code> is
+     * called.
+     */
+    public void enableHeadlessMode() {
+        System.setProperty("hep.aida.IAnalysisFactory", BatchAnalysisFactory.class.getName());
+    }
+
+    /**
+     * Activate end of job hooks (usually done automatically).
+     */
+    public void finish() {
+        this.getDriverAdapter().finish(null);
+    }
+
+    /**
+     * Get a <code>DriverAdapter</code> from the currently configured Driver list.
+     *
+     * @return the driver adapter
+     */
+    public DriverAdapter getDriverAdapter() {
+        if (driverAdapter == null) {
+            // Driver adapter created on demand.
+            this.createDriverAdapter();
+        }
+        return driverAdapter;
+    }
+
+    /**
+     * Return a list of Drivers to be executed. This can be used from an external framework like JAS3. The list will be
+     * empty unless the <code>setup()</code> method has been called.
+     *
+     * @return A <code>List</code> of <code>Drivers</code>.
+     */
+    public List<Driver> getDriverExecList() {
+        return this.driverExec;
+    }
+
+    /**
+     * Get the <code>LCSimLoop</code> of this JobManager.
+     *
+     * @return The LCSimLoop.
+     */
+    public LCSimLoop getLCSimLoop() {
+        return loop;
+    }
+
+    /**
+     * Get a list of a class's setter methods.
+     *
+     * @param klass The class.
+     * @return A list of setter methods.
+     */
+    private List<Method> getSetterMethods(final Class klass) {
+        final List<Method> methods = new ArrayList<Method>();
+        Class currentClass = klass;
+        while (currentClass != null) {
+            for (final Method method : currentClass.getMethods()) {
+                if (method.getName().startsWith("set") && !methods.contains(method)) {
+                    methods.add(method);
+                }
+            }
+            currentClass = currentClass.getSuperclass();
+        }
+        return methods;
+    }
+
+    /**
+     * Initialize the <code>LCSimLoop</code>.
+     */
+    private void initializeLoop() {
+        LOGGER.config("initializing LCSim loop");
+        loop = new LCSimLoop();
+        if (this.eventPrintInterval != null) {
+            loop.addRecordListener(new EventPrintLoopAdapter(this.eventPrintInterval));
+            LOGGER.config("enabled event marker printing with interval " + eventPrintInterval);
+        } else {
+            LOGGER.config("no event printing enabled");
+        }
+    }
+
+    /**
+     * Parse command-line options and setup job state from them. This method calls {@link #setup(File)} to load the
+     * steering paramters from an XML file, after processing other command line options. This method is private so that
+     * callers must all use the {@link #main(String[])} routine as the primary entry point.
+     *
+     * @param args The command line arguments.
+     */
+    public void parse(final String args[]) {
+        
+        LOGGER.config("parsing command line arguments");
+
+        // Print help and exit.
+        if (args.length == 0) {
+            printHelp();
+        }
+
+        // Setup parser.
+        final CommandLineParser parser = new PosixParser();
+
+        // Parse the command line arguments.
+        try {
+            commandLine = parser.parse(OPTIONS, args);
+        } catch (final ParseException x) {
+            throw new RuntimeException("Problem parsing command line options.", x);
+        }
+
+        // Load a properties file containing variable definitions.
+        if (commandLine.hasOption("p")) {
+            final String[] propValues = commandLine.getOptionValues("p");
+            for (final String propFileName : propValues) {
+                InputStream in = null;
+                try {
+                    in = new FileInputStream(propFileName);
+                } catch (final FileNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+                final Properties props = new Properties();
+                try {
+                    props.load(in);
+                } catch (final IOException e) {
+                    throw new RuntimeException(e);
+                }
+                for (final Entry<Object, Object> entry : props.entrySet()) {
+                    final String key = (String) entry.getKey();
+                    final String value = (String) entry.getValue();
+                    this.addVariableDefinition(key, value);
+                }
+                LOGGER.config("loaded variable definitions from " + propFileName);
+            }            
+        }
+
+        // Process the user variable definitions.
+        if (commandLine.hasOption("D")) {
+            final String[] defValues = commandLine.getOptionValues("D");
+            for (final String def : defValues) {
+                final String[] s = def.split("=");
+                if (s.length != 2) {
+                    throw new RuntimeException("Bad variable format: " + def);
+                }
+                final String key = s[0];
+                final String value = s[1];
+                this.addVariableDefinition(key, value);
+                LOGGER.config("defined " + key + " = " + value);
+            }
+        }
+
+        // Rewrite XML file with variables resolved.
+        if (commandLine.hasOption("w")) {
+            this.rewriteSteering = true;
+            final String rewritePath = commandLine.getOptionValue("w");
+            this.rewriteFile = new File(rewritePath);
+            if (this.rewriteFile.exists()) {
+                throw new RuntimeException("Rewrite file already exists: " + rewritePath);
+            }
+            LOGGER.config("XML will be rewritten to " + this.rewriteFile.getPath());
+        }
+
+        // Set max number of events to run.
+        if (commandLine.hasOption("n")) {
+            this.numberOfEvents = Integer.valueOf(commandLine.getOptionValue("n"));
+            LOGGER.config("max number of events set to " + this.numberOfEvents);
+        }
+
+        // Set number of events to skip.
+        if (commandLine.hasOption("s")) {
+            this.skipEvents = Integer.valueOf(commandLine.getOptionValue("s"));
+            LOGGER.config("skip events set to " + this.skipEvents);
+        }
+
+        // Perform a dry run, not processing any events but doing job setup.
+        if (commandLine.hasOption("x")) {
+            this.dryRun = true;
+            LOGGER.config("dry run is enabled");
+        }
+
+        // Interpret steering argument as a resource rather than file path.
+        if (commandLine.hasOption("r")) {
+            this.useSteeringResource = true;
+            LOGGER.config("steering resource enabled");
+        }
+
+        // Check that there is exactly one extra argument for the XML steering file.
+        if (commandLine.getArgList().size() == 0) {
+            throw new RuntimeException("Missing LCSim XML file argument.");
+        } else if (commandLine.getArgList().size() > 1) {
+            throw new RuntimeException("Too many extra arguments.");
+        }
+
+        // Local LCIO files to process.
+        if (commandLine.hasOption("i")) {
+            final String[] files = commandLine.getOptionValues("i");
+            for (final String fileName : files) {
+                final File file = new File(fileName);
+                if (!file.exists()) {
+                    throw new RuntimeException("File given as command line option does not exist: " + fileName);
+                }
+                inputFiles.add(new File(fileName));
+                LOGGER.config("added input file " + fileName);
+            }
+        }
+
+        // Run in headless mode in which plots will not show.
+        if (commandLine.hasOption("b")) {
+            this.enableHeadlessMode();
+            LOGGER.config("headless mode enabled");
+        }
+
+        // Steering argument points to either a file or embedded resource.
+        final String steering = (String) commandLine.getArgList().get(0);
+
+        if (commandLine.hasOption("e")) {
+            this.eventPrintInterval = Long.parseLong(commandLine.getOptionValue("e"));
+            LOGGER.config("eventPrintInterval: " + this.eventPrintInterval);
+            if (this.eventPrintInterval <= 0) {
+                throw new IllegalArgumentException("The event print interval must be > 0.");
+            }
+        }
+
+        if (commandLine.hasOption("d")) {
+            this.detectorName = commandLine.getOptionValue("d");
+            LOGGER.config("detector: " + this.detectorName);
+        }
+
+        if (commandLine.hasOption("R")) {
+            this.runNumber = Integer.parseInt(commandLine.getOptionValue("R"));
+            LOGGER.config("runNumber: " + this.runNumber);
+        }
+
+        if (this.detectorName != null && this.runNumber == null || 
+                this.runNumber != null && this.detectorName == null) {
+            throw new IllegalArgumentException("The detector name and run number must be given together.");
+        }
+        
+        // This will actually initialize everything from the steering file so it must come last.
+        if (this.useSteeringResource) {
+            // Using an embedded resource in the jar for steering.
+            LOGGER.config("initializing from steering resource " + steering);
+            this.setup(steering);
+        } else {
+            // Steering from a local file.
+            final File xmlRunControlFile = new File(steering);
+            if (!xmlRunControlFile.exists()) {
+                throw new RuntimeException("The steering file " + args[0] + " does not exist!");
+            }
+            LOGGER.config("initializing from steering file " + xmlRunControlFile.getPath());
+            this.setup(xmlRunControlFile);
+        }
+    }
+
+    /**
+     * Print the list of input files.
+     */
+    private void printInputFileList() {
+        final StringBuffer sb = new StringBuffer();
+        sb.append('\n');
+        sb.append("--- Input Files ---");
+        for (final File file : inputFiles) {
+            sb.append(file.getAbsolutePath());
+            sb.append('\n');
+        }
+        LOGGER.config(sb.toString());
+    }
+
+    /**
+     * Print out extra URLs added to the classpath from the XML.
+     */
+    private void printUserClasspath() {
+        final StringBuffer sb = new StringBuffer();
+        final URL[] urls = ((URLClassLoader) classLoader).getURLs();
+        if (urls.length > 0) {
+            for (final URL url : ((URLClassLoader) classLoader).getURLs()) {
+                sb.append(url + " ");
+            }
+            sb.append('\n');
+            LOGGER.config("Extra classpath URLs:" + sb.toString());
+        }
+    }
+
+    /**
+     * Create the constants from the XML file.
+     */
+    private void processConstants() {
+        final Element define = root.getChild("define");
+        if (define != null) {
+            for (final Object o : define.getChildren()) {
+                final Element e = (Element) o;
+                final Text txt = (Text) e.getContent().get(0);
+                final double dval = factory.computeDouble(txt.getValue());
+                this.constantsMap.put(e.getName(), dval);
+                factory.addConstant(e.getName(), dval);
+            }
+        }
+    }
+
+    /**
+     * A fairly ugly method to process the provided XML parameters on a <code>Driver</code>.
+     *
+     * @param driverClass the Java class of the driver
+     * @param newDriver the instantiated Driver
+     * @param parameters the list of XML parameters
+     */
+    private void processDriverParameters(final Class driverClass, final Driver newDriver, final List<Element> parameters) {
+        // Process the parameter elements.
+        for (final Element parameterElement : parameters) {
+
+            // The parameter's setter method that we will try to find.
+            Method setter = null;
+
+            // The parameter's type that will be inferred from the method or provided by an XML attribute.
+            Class propertyType = null;
+
+            // Get the parameter's name.
+            final String pname = parameterElement.getName();
+
+            // Find setter methods that look like good matches for this parameter.
+            final List<Method> methods = this.getSetterMethods(driverClass);
+            final List<Method> methodCandidates = new ArrayList<Method>();
+            for (final Method method : methods) {
+                String propHack = method.getName().replaceFirst("set", "");
+                propHack = propHack.substring(0, 1).toLowerCase() + propHack.substring(1);
+                if (propHack.equals(pname)) {
+                    methodCandidates.add(method);
+                }
+            }
+            if (methodCandidates.size() == 1) {
+                // Found the single setter method so try to use it.
+                setter = methodCandidates.get(0);
+                if (setter.getParameterTypes().length > 1) {
+                    throw new RuntimeException("The set method has too many arguments for parameter: " + pname);
+                }
+                propertyType = setter.getParameterTypes()[0];
+            } else if (methodCandidates.size() > 1) {
+                // Found several, overloaded methods. Try to disambiguate them if possible.
+                if (parameterElement.getAttribute("type") == null) {
+                    throw new RuntimeException("Parameter " + pname + " in Driver " + driverClass.getCanonicalName()
+                            + " is overloaded, but a type field is missing from the parameter's XML element.");
+                }
+                try {
+                    // Try a primitive type first.
+                    propertyType = getPrimitiveType(parameterElement.getAttribute("type").getValue());
+
+                    // If type is null, then parameter is an Object and not a primitive, or it
+                    // is not a valid type.
+                    if (propertyType == null) {
+                        propertyType = Class.forName(parameterElement.getAttribute("type").getValue());
+                    }
+                } catch (final ClassNotFoundException x) {
+                    throw new RuntimeException("Bad type " + parameterElement.getAttribute("type").getValue()
+                            + " given for parameter " + pname + ".");
+                }
+                // Find a method that matches the user type.
+                for (final Method candidateMethod : methodCandidates) {
+                    if (candidateMethod.getParameterTypes().length == 1
+                            && candidateMethod.getParameterTypes()[0].equals(propertyType)) {
+                        setter = candidateMethod;
+                        break;
+                    }
+                }
+            } else if (methodCandidates.size() == 0) {
+                // No method found. The parameter name is probably invalid.
+                throw new RuntimeException("Set method for Driver parameter " + pname + " was not found.");
+            }
+
+            // No setter method found.
+            if (setter == null) {
+                throw new RuntimeException("Unable to find set method for parameter " + pname + ".");
+            }
+
+            // Convert the parameter to the appropriate type.
+            final IParameterConverter converter = paramConverter.getConverterForType(propertyType);
+            if (converter == null) {
+                throw new RuntimeException("No converter found for parameter " + parameterElement.getName()
+                        + " with type " + propertyType.getName() + ".");
+            }
+            final Object nextParameter = converter.convert(factory, parameterElement);
+
+            // Call the setter with the parameter as argument.
+            final Object pargs[] = new Object[1];
+            pargs[0] = nextParameter;
+            try {
+                // This invokes the setter method of the driver.
+                setter.invoke(newDriver, pargs);
+
+                // Print parameters and values as they are set.
+                LOGGER.fine("    " + pname + " = " + parameterElement.getText().trim());
+                
+            } catch (final Exception x) {
+                throw new RuntimeException("Problem processing parameter " + parameterElement.getName() + ".", x);
+            }
+        } // parameter loop
+    }
+
+    /**
+     * Process a single event.
+     *
+     * @param event
+     */
+    public void processEvent(final EventHeader event) {
+        this.getDriverAdapter().recordSupplied(new RecordEvent(loop, event));
+    }
+
+    /**
+     * Create a <code>File</code> object from the text in an XML element.
+     *
+     * @param fileElement The element containing a file path or URL.
+     * @param fileList List to append new <code>File</code>.
+     * @return The <code>File</code> object.
+     */
+    private File processFileElement(final Element fileElement, final List<File> fileList) {
+
+        final String fileLoc = this.processPath(fileElement.getText().trim());
+        File file = null;
+
+        // Try to process the file text as a URL.
+        try {
+            final URL fileURL = new URL(fileLoc);
+
+            // Local file URL.
+            if (fileLoc.startsWith("file:")) {
+                file = new File(fileURL.getPath());
+            } else {
+                // Remote file URL.
+                try {
+                    file = this.fileCache.getCachedFile(fileURL);
+                } catch (final IOException x) {
+                    throw new RuntimeException("Unable to fetch file " + fileLoc + " to the cache directory.", x);
+                }
+            }
+        } catch (final MalformedURLException x) {
+            // Interpret as local file.
+            file = new File(fileLoc);
+        }
+
+        // Add to the list.
+        if (fileList != null) {
+            fileList.add(file);
+        }
+
+        return file;
+    }
+
+    /**
+     * Cleanup file text and add to the file list.
+     *
+     * @param fileText the text containing the file's path from the XML
+     * @param fileList the list of files to which the new file will be appended
+     * @return the file that was created from the text
+     */
+    private File processFileText(final String fileText, final List<File> fileList) {
+        final Element fileElement = new Element("file");
+        fileElement.setText(fileText.trim());
+        return this.processFileElement(fileElement, fileList);
+    }
+
+    /**
+     * Process the file path string to substitute in the user's home directory for the "~" character. This is needed if
+     * running on Windows.
+     *
+     * @param path The original path.
+     * @return The path with home dir substitution.
+     */
+    private String processPath(final String path) {
+        if (path.startsWith("~")) {
+            return path.replaceFirst("~", System.getProperty("user.home"));
+        } else {
+            return path;
+        }
+    }
+
+    /**
+     * Rewrite the XML steering file after resolving variables (done externally). The output path is set by command line
+     * argument to "-w".
+     *
+     * @param doc The XML steering doc with variables substituted.
+     */
+    private void rewriteXMLSteering(final Document doc) {
+        LOGGER.info("Rewriting XML to " + this.rewriteFile);
+        final XMLOutputter outputter = new XMLOutputter();
+        outputter.setFormat(Format.getPrettyFormat());
+        try {
+            final FileOutputStream out = new FileOutputStream(this.rewriteFile);
+            outputter.output(doc, out);
+            out.close();
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
      * Execute a job using the current parameters.
      */
     public boolean run() {
-                
+
         // If setup was not called first, then abort the job.
-        if (!wasSetup) {
-            logStream.println("Aborting job!  Setup was never called.");
+        if (!isSetup) { 
+            LOGGER.info("Aborting job!  Setup was never called.");
             return false;
         }
 
-        // Dry run selected. No events will be processed here.
-        if (performDryRun) {
-            logStream.println("Executed dry run.  No events processed!");
+        // Dry run selected. No events will be processed.
+        if (dryRun) {
+            LOGGER.info("Executed dry run.  No events processed!");
             return false;
         }
-                
+
+        boolean okay = true;
+
         try {
             // Add the LCIO files to the loop.
             loop.setLCIORecordSource(new LCIOEventSource(this.getClass().getSimpleName(), inputFiles));
+
+            // Set up user supplied conditions information (already checked that these were both given if one was used).
+            initializeConditions();
             
             // Setup dummy detector if selected.
             if (dummyDetector) {
-                logStream.println("Using dummy detector for conditions system!");
+                LOGGER.info("Using dummy detector for conditions system!");
                 loop.setDummyDetector("dummy");
             }
 
-            // Driver statistics printout.
-            PrintStream statsStream = null;
-            if (printDriverStatistics) {
-                statsStream = logStream;
-            }
-
-            if (verbose) {
-                logStream.println("Start time: " + (new Date()));
-                logStream.println();
-            }
-
-            if (skipEvents > 0) {
-                if (verbose) {
-                    logStream.println("Skipping " + skipEvents + " events.");
-                }
+            this.jobStart = System.currentTimeMillis();
+
+            LOGGER.info("Job started: " + new Date(jobStart));
+
+            if (this.skipEvents > 0) {
+                LOGGER.info("Skipping " + skipEvents + " events.");
                 loop.skip(skipEvents);
             }
 
             // Execute the loop.
-            long processedEvents = loop.loop(numberOfEvents, statsStream);
-            if (processedEvents != numberOfEvents) {
-                logStream.println("End of file reached");
-            } else if (verbose) {
-                logStream.println();
-                logStream.println("End time: " + (new Date()));
-            }
-        }
-        // Catch other fatal exceptions thrown from the loop.
-        catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        // Cleanup the loop.
-        loop.dispose();
-        //loop = null;
-
-        // Job was successful.
-        return true;
-    }
-
-    /**
-     * Get the <code>LCSimLoop</code> of this JobManager.
-     * 
-     * @return The LCSimLoop.
-     */
-    public LCSimLoop getLCSimLoop() {
-        return loop;
-    }
-
-    /**
-     * Return a list of Drivers to be executed. This can be used from an external framework like
-     * JAS3. The list will be empty unless the <code>setup()</code> method has been called.
-     * 
-     * @return A <code>List</code> of <code>Drivers</code>.
-     */
-    public List<Driver> getDriverExecList() {
-        return this.driverExec;
+            final long processedEvents = loop.loop(numberOfEvents, this.printDriverStatistics ? System.out : null);
+            if (numberOfEvents != -1 && processedEvents != numberOfEvents) {
+                LOGGER.info("End of file was reached.");
+            }
+            LOGGER.info("Job processed " + processedEvents + " events.");
+            this.jobEnd = System.currentTimeMillis();
+
+            LOGGER.info("Job ended: " + new Date(this.jobEnd));
+            final long elapsed = this.jobStart - this.jobEnd;
+            LOGGER.info("Job took " + elapsed + " which is " + elapsed / processedEvents + " ms/event.");
+
+        } catch (final Exception e) {
+            LOGGER.log(Level.SEVERE, "A fatal error occurred during the job.", e);
+            okay = false;
+        } finally {
+            try {
+                // Dispose of the loop.
+                loop.dispose();
+            } catch (final Exception e) {
+                LOGGER.log(Level.WARNING, "An error occurred during job cleanup.", e);
+                okay = false;
+            }
+        }
+
+        // Return true or false depending on whether job was successfully executed.
+        return okay;
+    }
+
+    /**
+     * Set whether a dry run should be performed which will only perform setup and not process any events.
+     *
+     * @param dryRun <code>true</code> to enable a dry run
+     */
+    public void setDryRun(final boolean dryRun) {
+        this.dryRun = dryRun;
+    }
+
+    /**
+     * Set the number of events to run on the loop before ending the job. This should be called after the
+     * {@link #setup(File)} method is called or it will be overridden.
+     */
+    public void setNumberOfEvents(final int numberOfEvents) {
+        this.numberOfEvents = numberOfEvents;
+    }
+
+    /**
+     * Setup the job parameters from an XML Document.
+     * <p>
+     * This method contains the primary logic for setting up job parameters from an XML file. The other setup methods
+     * such as {@link #setup(InputStream)}, {@link #setup(String)} and {@link #setup(File)} all call this method.
+     *
+     * @param xmlDocument The lcsim recon XML document describing the job.
+     */
+    private void setup(final Document xmlDocument) {
+
+        // This method should not be called more than once.
+        if (isSetup) {
+            throw new IllegalStateException("The job manager was already setup.");
+        }
+
+        // Set the root element from the XML document.
+        root = xmlDocument.getRootElement();
+
+        // Do variable substitutions into the document first.
+        this.substituteVariables(xmlDocument);
+
+        // Rewrite XML after variable substitution.
+        if (this.rewriteSteering) {
+            this.rewriteXMLSteering(xmlDocument);
+        }
+
+        // Setup the job control parameters.
+        this.setupJobControlParameters();
+
+        // Setup the class loader.
+        this.setupClassLoader();
+
+        // Setup system of units.
+        this.setupUnits();
+
+        // Process the constant definitions.
+        this.processConstants();
+
+        // Initialize the LCSimLoop.
+        this.initializeLoop();
+
+        // Setup drivers with parameters and execution order.
+        this.setupDrivers();
+
+        // Setup the file cache.
+        this.setupFileCache();
+
+        // Setup the input files.
+        this.setupInputFiles();
+
+        // Throw an error if there were no files provided and dry run is not enabled.
+        if (inputFiles.size() == 0 && !this.dryRun) {
+            LOGGER.severe("No input files provided and dry run is not enabled.");
+            throw new IllegalStateException("No input files to process.");
+        }
+
+        // Flag JobManager as setup.
+        isSetup = true;
     }
 
     /**
      * Setup job parameters from a <code>File</code>.
-     * 
-     * @param file
-     */
-    public void setup(File file) {
+     *
+     * @param file the path to the XML file
+     */
+    public void setup(final File file) {
         try {
-            setup((new FileInputStream(file)));
-        } catch (FileNotFoundException x) {
+            this.setup(new FileInputStream(file));
+        } catch (final FileNotFoundException x) {
             throw new RuntimeException(x);
         }
     }
 
     /**
-     * Setup job parameters from an embedded resource. This method calls
-     * {@link #setup(InputStream)}.
-     */
-    public void setup(String resourceURL) {
-        setup(this.getClass().getResourceAsStream(resourceURL));
-    }
-
-    /**
-     * Setup job parameters from an <code>InputStream</code> with XML text.
-     * 
-     * @param in The XML input stream.
-     */
-    public void setup(InputStream in) {
+     * Setup job parameters from an <code>InputStream</code> that should be valid XML text.
+     *
+     * @param in the XML input stream
+     */
+    public void setup(final InputStream in) {
 
         // Make the Document builder.
-        SAXBuilder builder = new SAXBuilder();
+        final SAXBuilder builder = new SAXBuilder();
 
         // Setup XML schema validation.
         builder.setEntityResolver(new ClasspathEntityResolver());
@@ -511,429 +1086,163 @@
         Document doc = null;
         try {
             doc = builder.build(in);
-        } catch (Exception x) {
+        } catch (final Exception x) {
             throw new RuntimeException(x);
         }
 
         // Setup the JobControlManager from the XML file.
-        setup(doc);
-    }
-
-    /**
-     * Setup the job parameters from an XML Document. Though it isn't publically accessible, this
-     * method contains the primary logic for setting up job parameters from an XML file. The other
-     * setup methods, namely, {@link #setup(InputStream)}, {@link #setup(String)} and
-     * {@link #setup(File)}, all eventually call this method.
-     * 
-     * @param doc The lcsim recon XML document describing the job.
-     */
-    private void setup(Document doc) {
-        if (wasSetup) {
-            logStream.println("Ignoring call to setup as it was already called.");
+        this.setup(doc);
+    }
+
+    /**
+     * Setup job parameters from an embedded resource. This method calls {@link #setup(InputStream)}.
+     */
+    public void setup(final String resourceURL) {
+        this.setup(this.getClass().getResourceAsStream(resourceURL));
+    }
+
+    /**
+     * Setup the manager's class loader.
+     */
+    private void setupClassLoader() {
+
+        if (classLoader != null) {
+            LOGGER.info("The ClassLoader was already set externally, so custom classpaths will be ignored!");
             return;
         }
-        
-        // Set the root element.
-        root = doc.getRootElement();
-
-        // Do variable substitutions.
-        substituteVariables(doc);
-
-        // Rewrite XML after variable substitution. Correctness of values,
-        // such as existence of input file paths, is NOT checked.
-        if (this.rewrite) {
-            rewriteXMLSteering(doc);
-        }
-
-        // Setup the job control parameters.
-        setupJobControlParameters();
-
-        // Print system properties.
-        if (printSystemProperties) {
-            printSystemProperties(logStream);
-        }
-
-        // Setup the class loader.
-        setupClassLoader();
-
-        // Setup units.
-        setupUnits();
-
-        // Process the variable definitions.
-        processConstants();
-
-        // Check for required conditions.
-        checkConditions();
-
-        // Setup drivers with parameters and execution order.
-        setupDrivers();
-        
-        // Setup the file cache.
-        setupFileCache();
-
-        // Setup the input files.
-        setupInputFiles();
-
-        // Check if a dry run should be performed when no input files are present.
-        if (inputFiles.size() == 0) {
-            performDryRun = true;
-            logStream.println("No input files provided by XML or command line.  Dry run will be enabled.");
-        }
-                              
-        // Flag JobManager as setup.
-        wasSetup = true;
-    }
-
-    /**
-     * Perform variable substitution within all text data in a entire document.
-     * 
-     * @param doc The XML document.
-     */
-    private void substituteVariables(Document doc) {
-        substituteVariables(doc.getRootElement());
-    }
-
-    /**
-     * Substitute values from the <code>variableMap</code> into an XML element and all its
-     * children, recursively.
-     * 
-     * @param element The XML element.
-     * @throw RuntimeException If a variable does not exist in the <code>variableMap</code>.
-     */
-    private void substituteVariables(Element element) {
-        String text = element.getTextNormalize();
-        if (text.length() != 0) {
-            // Create a new matcher.
-            Matcher match = varPattern.matcher(text);
-
-            // No variables were used.
-            if (!match.find())
-                return;
-
-            // Text data on which to perform substitutions.
-            String newText = new String(text);
-
-            // Reset the matcher.
-            match.reset();
-
-            // Loop over the matches.
-            while (match.find()) {
-
-                // Variable string with ${...} enclosure included.
-                String var = match.group();
-
-                // The name of the variable for lookup.
-                String varName = var.substring(2, var.length() - 1);
-
-                // The value of the variable.
-                String varValue = variableMap.get(varName);
-
-                // If a variable was not defined, then the application will immediately exit here.
-                if (varValue == null) {
-                    throw new RuntimeException("Variable not defined: " + varName);
-                }
-
-                // Substitute this variable's value into the text.
-                newText = newText.replace(var, varValue);
-            }
-
-            // Set this element's new text value.
-            element.setText(newText);
-        }
-
-        // Recursively process all child elements of this one.
-        for (Iterator it = element.getChildren().iterator(); it.hasNext();) {
-            substituteVariables((Element) it.next());
-        }
-    }
-
-    /**
-     * Rewrite the XML steering file after resolving variables (done externally). The output path
-     * is set by command line argument to "-w".
-     * 
-     * @param doc The XML steering doc with variables substituted.
-     */
-    private void rewriteXMLSteering(Document doc) {
-        if (verbose) {
-            logStream.println("Rewriting XML to " + this.rewriteFile + " ...");
-        }
-        XMLOutputter outputter = new XMLOutputter();
-        outputter.setFormat(Format.getPrettyFormat());
-        try {
-            FileOutputStream out = new FileOutputStream(this.rewriteFile);
-            outputter.output(doc, out);
-            out.close();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Setup the drivers from the XML job file, adding them to the JobManager's
-     * <code>driverMap</code> .
+
+        final Element classpath = root.getChild("classpath");
+        final List<URL> urlList = new ArrayList<URL>();
+        if (classpath != null) {
+            for (final Object jarObject : classpath.getChildren("jar")) {
+                final Element jarElement = (Element) jarObject;
+                try {
+                    urlList.add(new File(this.processPath(jarElement.getText())).toURI().toURL());
+                } catch (final Exception x) {
+                    throw new RuntimeException("Bad jar location: " + jarElement.getText(), x);
+                }
+            }
+            for (final Object jarUrlObject : classpath.getChildren("jarUrl")) {
+                final Element jarUrlElement = (Element) jarUrlObject;
+                try {
+                    urlList.add(new URL(jarUrlElement.getText()));
+                } catch (final Exception x) {
+                    throw new RuntimeException("Bad jar URL: " + jarUrlElement.getText(), x);
+                }
+            }
+            for (final Object cpDirObject : classpath.getChildren("directory")) {
+                final Element cpDirElement = (Element) cpDirObject;
+                try {
+                    final File cpFile = new File(this.processPath(cpDirElement.getText()));
+                    if (!cpFile.isDirectory()) {
+                        throw new RuntimeException("The classpath component " + cpFile.getPath()
+                                + " is not a valid directory!");
+                    }
+                    urlList.add(cpFile.toURI().toURL());
+                } catch (final Exception x) {
+                    throw new RuntimeException("Bad classpath directory: " + cpDirElement.getText(), x);
+                }
+            }
+        }
+        final URL[] urls = urlList.toArray(new URL[] {});
+
+        classLoader = new LCSimClassLoader(urls);
+
+        // Print extra user classpath entries.
+        this.printUserClasspath();
+    }
+
+    /**
+     * Create the drivers from the XML.
      */
     private void setupDrivers() {
-        if (printDriversDetailed) {
-            logStream.println("--- Drivers ---");
-        }
-
-        // Instantiate the LCSimLoop here in case subsequently created Drivers want to override 
-        // its default conditions implementation.
-        loop = new LCSimLoop();
-        
+
         // Loop over the list of driver elements.
-        List<Element> drivers = root.getChild("drivers").getChildren("driver");
-        for (Element driver : drivers) {
+        final List<Element> drivers = root.getChild("drivers").getChildren("driver");
+        for (final Element driver : drivers) {
 
             // Get the name of the Driver.
-            String name = driver.getAttributeValue("name");
+            final String name = driver.getAttributeValue("name");
 
             // Get the fully qualified type of the Driver. ([packageName].[className])
-            String type = driver.getAttributeValue("type");
-
-            // Translate from a short name (optional).
-            if (availableDrivers.get(type) != null) {
-                type = availableDrivers.get(type);
-            }
-
-            //
-            // Get Class, BeanInfo, and Constructor, and setup the new Driver instance.
-            //
-
-            // Class of the Driver.
+            final String type = driver.getAttributeValue("type");
+
+            // Get the Java class of the Driver.
             Class driverClass;
             try {
-                driverClass = loader.loadClass(type);
-            } catch (ClassNotFoundException x) {
+                driverClass = classLoader.loadClass(type);
+            } catch (final ClassNotFoundException x) {
                 throw new RuntimeException("The Driver class " + type + " was not found.", x);
             }
 
-            if (printDriversDetailed)
-                logStream.println(driverClass.getCanonicalName());
-
-            // The Driver instance.
+            LOGGER.fine("adding driver " + driverClass.getCanonicalName());
+
+            // Create an instance of the driver.
             Driver newDriver;
             try {
                 newDriver = (Driver) driverClass.newInstance();
-            } catch (InstantiationException x) {
-                throw new RuntimeException("Failed to create a Driver of class " + type + ".  " + "This class might be missing a public constructor with no arguments.", x);
-            } catch (IllegalAccessException x) {
-                throw new RuntimeException("Cannot access Driver type " + type + ". " + "You may need to make this class or its constructor public.", x);
-            }
-
-            // Get a list of Driver parameters.
-            List<Element> parameters = driver.getChildren();
-
-            // Process the parameters.
-            for (Element parameterElement : parameters) {
-                // The parameter's setter method that we will find.
-                Method setter = null;
-
-                // The parameter's type that will be inferred from the method or is given by a type
-                // string in the case of an overloaded method.
-                Class propertyType = null;
-
-                // Get the parameter name.
-                String pname = parameterElement.getName();
-
-                // Find setter methods that look like good matches for this parameter.
-                List<Method> methods = getSetterMethods(driverClass);
-                List<Method> methodCandidates = new ArrayList<Method>();
-                for (Method method : methods) {
-                    String propHack = method.getName().replaceFirst("set", "");
-                    propHack = propHack.substring(0, 1).toLowerCase() + propHack.substring(1);
-                    if (propHack.equals(pname)) {
-                        methodCandidates.add(method);
-                    }
-                } if (methodCandidates.size() == 1) {
-                    // Found the single setter method.
-                    setter = methodCandidates.get(0);
-                    if (setter.getParameterTypes().length > 1) {
-                        throw new RuntimeException("The set method has too many arguments for parameter: " + pname);
-                    }
-                    propertyType = setter.getParameterTypes()[0];
-                } else if (methodCandidates.size() > 1) {
-                    // Found several, overloaded methods. Try to disambiguate them.
-                    if (parameterElement.getAttribute("type") == null)
-                        throw new RuntimeException("Parameter " + pname + " in Driver " + driverClass.getCanonicalName() + " is overloaded, but a type field is missing from the parameter's XML element.");
-                    try {
-                        // Try a primitive type first.
-                        propertyType = getPrimitiveType(parameterElement.getAttribute("type").getValue());
-
-                        // If type is null, then parameter is an Object and not a primitive, or it
-                        // is not a
-                        // valid type.
-                        if (propertyType == null)
-                            propertyType = Class.forName(parameterElement.getAttribute("type").getValue());
-                    } catch (ClassNotFoundException x) {
-                        throw new RuntimeException("Bad user type: " + parameterElement.getAttribute("type").getValue() + " for parameter " + pname + ".");
-                    }
-                    // Find a method that matches the user type.
-                    for (Method candidateMethod : methodCandidates) {
-                        if (candidateMethod.getParameterTypes().length == 1 && candidateMethod.getParameterTypes()[0].equals(propertyType)) {
-                            setter = candidateMethod;
-                            break;
-                        }
-                    }
-                } else if (methodCandidates.size() == 0) {
-                    // No method found. The parameter name is probably invalid.
-                    throw new RuntimeException("Set method was not found: " + driverClass.getSimpleName() + "." + pname);
-                }
-
-                // No setter method found.
-                if (setter == null) {
-                    throw new RuntimeException("Unable to find set method for parameter: " + pname);
-                }
-
-                // Convert the parameter to the appropriate type.
-                IParameterConverter converter = paramConverter.getConverterForType(propertyType);
-                if (converter == null) {
-                    throw new RuntimeException("No converter found for parameter " + parameterElement.getName() + " with type " + propertyType.getName() + ".");
-                }
-                Object nextParameter = converter.convert(factory, parameterElement);
-
-                // Call the setter with the parameter as argument.
-                Object pargs[] = new Object[1];
-                pargs[0] = nextParameter;
-                try {
-                    // This invokes the setter method of the driver.
-                    setter.invoke(newDriver, pargs);
-
-                    // Print parameters and values as they are set.
-                    if (printDriversDetailed)
-                        logStream.println("    " + pname + " = " + parameterElement.getText().trim());
-                } catch (Exception x) {
-                    throw new RuntimeException("Problem processing parameter " + parameterElement.getName() + ".", x);
-                }
-            } // parameter loop
-
-            // Add a driver to the manager.
-            addDriver(name, newDriver);
+            } catch (final InstantiationException x) {
+                throw new RuntimeException("Failed to create a Driver of class " + type + ".", x);
+            } catch (final IllegalAccessException x) {
+                throw new RuntimeException("Cannot access Driver type " + type + ".", x);
+            }
+
+            // Get the list of Driver parameters from the XML.
+            final List<Element> parameters = driver.getChildren();
+
+            // Process the parameters provided for the driver.
+            this.processDriverParameters(driverClass, newDriver, parameters);
+
+            // Add the driver to the manager.
+            this.addDriver(name, newDriver);
+
         } // driver loop
 
-        if (printDriversDetailed) {
-            logStream.println("--- End Drivers ---");
-        }
-
-        // Make a list of Drivers to be executed.
-        List<Element> exec = root.getChild("execute").getChildren("driver");
-        for (Element execDriver : exec) {
-            String driverName = execDriver.getAttributeValue("name");
-            Driver driverFind = driverMap.get(driverName);
-            if (driverFind != null) {
-                driverExec.add(driverFind);
-            } else {
-                throw new RuntimeException("A Driver called " + driverName + " was not found.");
-            }
-        }
-        
-        // Add the drivers to the LCSimLoop.
-        for (Driver driver : driverExec) {
-            loop.add(driver);
-        }
-    }
-
-    /**
-     * Add a Driver to the Driver map.
-     * 
-     * @param name The unique name of the Driver.
-     * @param driver The instance of the Driver.
-     */
-    private void addDriver(String name, Driver driver) {
-        if (driverMap.containsKey(name)) {
-            throw new RuntimeException("Duplicate driver name: " + name);
-        }
-        driverMap.put(name, driver);
-    }
-
-    private void printSystemProperties(PrintStream ps) {
-        logStream.println("--- System Properties ---");
-        for (Entry<Object, Object> entry : System.getProperties().entrySet()) {
-            logStream.println(entry.getKey() + " = " + entry.getValue());
-        }
-        logStream.println("-- End System Properties");
-        logStream.println();
-    }
-
-    /**
-     * Cleaup file text and add to file list.
-     * 
-     * @param fileText The file's raw text path from XML file.
-     * @param fileList The list files to which new file will be appended.
-     * @return The file that was created from the text.
-     */
-    private File processFileText(String fileText, List<File> fileList) {
-        Element fileElement = new Element("file");
-        fileElement.setText(fileText.trim());
-        return processFileElement(fileElement, fileList);
-    }
-
-    /**
-     * Create a <code>File</code> object from the text in an XML element.
-     * 
-     * @param fileElement The element containing a file path or URL.
-     * @param fileList List to append new <code>File</code>.
-     * @return The <code>File</code> object.
-     */
-    private File processFileElement(Element fileElement, List<File> fileList) {
-        String fileLoc = processPath(fileElement.getText().trim());
-        File file = null;
-
-        // Try to process the file text as a URL.
-        try {
-            URL fileURL = new URL(fileLoc);
-
-            // Local file URL.
-            if (fileLoc.startsWith("file:")) {
-                file = new File(fileURL.getPath());
-            }
-            // Remote file URL.
-            else {
-                try {
-                    file = this.fileCache.getCachedFile(fileURL);
-                } catch (IOException x) {
-                    throw new RuntimeException("Unable to fetch file " + fileLoc + " to the cache directory.", x);
-                }
-            }
-        }
-        // Text is not a URL. Attempt to process as local file.
-        catch (MalformedURLException x) {
-            file = new File(fileLoc);
-        }
-
-        // Add to list.
-        if (fileList != null) {
-            fileList.add(file);
-        }
-
-        return file;
+        // Make the list of drivers to execute.
+        this.createDriverExecList();
+    }
+
+    /**
+     * Setup the file cache.
+     */
+    private void setupFileCache() {
+        if (cacheDirectory != null) {
+            try {
+                fileCache = new FileCache();
+                fileCache.setCacheDirectory(cacheDirectory);
+                fileCache.setPrintStream(null);
+                LOGGER.config("File cache created at " + cacheDirectory);
+            } catch (final IOException x) {
+                throw new RuntimeException(x);
+            }
+        }
     }
 
     /**
      * Setup the list of input files to be processed from the XML job file.
      */
-    @SuppressWarnings("unchecked")
-    protected void setupInputFiles() {
+    private void setupInputFiles() {
+
         if (root.getChild("inputFiles") == null) {
-            logStream.println("No input files in XML file.");
-            // this.performDryRun = true;
+            // This is not a warning because input files can be provided via the command line.
+            LOGGER.config("No input files in XML file.");
             return;
         }
 
         // Process the <file> elements.
-        List<Element> files = root.getChild("inputFiles").getChildren("file");
-        for (Element fileElem : files) {
-            processFileElement(fileElem, this.inputFiles);
+        final List<Element> files = root.getChild("inputFiles").getChildren("file");
+        for (final Element fileElem : files) {
+            this.processFileElement(fileElem, this.inputFiles);
         }
 
         // Read lists of file locations given by <fileList> elements.
-        List<Element> fileLists = root.getChild("inputFiles").getChildren("fileList");
-        for (Element fileList : fileLists) {
-            String filePath = fileList.getText();
+        final List<Element> fileLists = root.getChild("inputFiles").getChildren("fileList");
+        for (final Element fileList : fileLists) {
+            final String filePath = fileList.getText();
             BufferedReader input;
             try {
                 input = new BufferedReader(new FileReader(new File(filePath)));
-            } catch (FileNotFoundException x) {
+            } catch (final FileNotFoundException x) {
                 throw new RuntimeException("File not found: " + filePath, x);
             }
             String line = null;
@@ -941,15 +1250,15 @@
                 // Read the next file, turn the text into an XML element, and process it using
                 // common method.
                 while ((line = input.readLine()) != null) {
-                    processFileText(line.trim(), inputFiles);
-                }
-            } catch (IOException x) {
+                    this.processFileText(line.trim(), inputFiles);
+                }
+            } catch (final IOException x) {
                 throw new RuntimeException(x);
             } finally {
                 if (input != null) {
                     try {
                         input.close();
-                    } catch (IOException e) {
+                    } catch (final IOException e) {
                         e.printStackTrace();
                     }
                 }
@@ -957,91 +1266,61 @@
         }
 
         // Process <fileSet> elements.
-        List<Element> fileSets = (List<Element>) root.getChild("inputFiles").getChildren("fileSet");
-        for (Element fileSet : fileSets) {
-            Attribute basedirAttrib = fileSet.getAttribute("baseDir");
+        final List<Element> fileSets = root.getChild("inputFiles").getChildren("fileSet");
+        for (final Element fileSet : fileSets) {
+            final Attribute basedirAttrib = fileSet.getAttribute("baseDir");
             String basedir = "";
             if (basedirAttrib != null) {
                 basedir = basedirAttrib.getValue();
             }
-            List<Element> fsFiles = fileSet.getChildren("file");
-            for (Element file : fsFiles) {
-                String filePath = basedir + File.separator + file.getText().trim();
-                processFileText(filePath, inputFiles);
+            final List<Element> fsFiles = fileSet.getChildren("file");
+            for (final Element file : fsFiles) {
+                final String filePath = basedir + File.separator + file.getText().trim();
+                this.processFileText(filePath, inputFiles);
             }
         }
 
         // Read <fileRegExp> elements, which may only reference local files, not URLs.
-        List<Element> fileRegExps = (List<Element>) root.getChild("inputFiles").getChildren("fileRegExp");
-        for (Element fileRegExp : fileRegExps) {
-            Pattern pattern = Pattern.compile(fileRegExp.getText());
-            String basedir = fileRegExp.getAttributeValue("baseDir");
-            File dir = new File(basedir);
-            if (!dir.isDirectory())
+        final List<Element> fileRegExps = root.getChild("inputFiles").getChildren("fileRegExp");
+        for (final Element fileRegExp : fileRegExps) {
+            final Pattern pattern = Pattern.compile(fileRegExp.getText());
+            final String basedir = fileRegExp.getAttributeValue("baseDir");
+            final File dir = new File(basedir);
+            if (!dir.isDirectory()) {
                 throw new RuntimeException(basedir + " is not a valid directory!");
-            String dirlist[] = dir.list();
-            if (verbose) {
-                logStream.println();
-            }
-            for (String file : dirlist) {
+            }
+            final String dirlist[] = dir.list();
+            for (final String file : dirlist) {
                 if (file.endsWith(".slcio")) {
-                    Matcher matcher = pattern.matcher(file);
+                    final Matcher matcher = pattern.matcher(file);
                     if (matcher.matches()) {
-                        processFileText(basedir + File.separator + file, inputFiles);
-                        if (verbose) {
-                            logStream.println("Matched file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
-                        }
+                        this.processFileText(basedir + File.separator + file, inputFiles);
+                        LOGGER.fine("Matched file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
                     } else {
-                        if (verbose) {
-                            logStream.println("Did NOT match file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
-                        }
+                        LOGGER.fine("Did NOT match file <" + file.toString() + "> to pattern <" + pattern.toString()
+                                + ">");
                     }
                 }
             }
         }
 
         // Check that all the files exist if job is not a dry run.
-        if (!performDryRun) {
-            for (File file : inputFiles) {
+        if (!dryRun) {
+            for (final File file : inputFiles) {
                 if (!file.exists()) {
-                    logStream.println("The input file " + file.getAbsolutePath() + " does not exist.");
+                    LOGGER.info("The input file " + file.getAbsolutePath() + " does not exist.");
                     throw new RuntimeException("The input file " + file.getAbsolutePath() + " does not exist!");
                 }
             }
         }
 
         // Print out the input file list.
-        if (printInputFiles) {
-            logStream.println();
-            logStream.println("--- Input Files ---");
-            int index = 1;
-            for (File file : inputFiles) {
-                logStream.println("[" + index + "] " + file.getAbsolutePath());
-                ++index;
-            }
-            logStream.println("--- End Input Files ---");
-            logStream.println();
-        }
+        this.printInputFileList();
 
         if (inputFiles.size() == 0) {
-            logStream.println("No input files were given in the steering file or command line options.  Dry run will be enabled.");
-            this.performDryRun = true;
-        }
-    }
-
-    /**
-     * Set logging verbosity.
-     * 
-     * @param verbose True to turn on verbose mode; false to turn on quiet mode.
-     */
-    private void setVerbose(boolean verbose) {
-        this.verbose = verbose;
-        this.printInputFiles = verbose;
-        this.printDriversDetailed = verbose;
-        this.printDriverStatistics = verbose;
-        this.printSystemProperties = verbose;
-        this.printUserClassPath = verbose;
-        // this.printVersion = verbose;
+            LOGGER.info("No input files were provided by the steering file or command line options.  Dry run will be enabled.");
+            this.dryRun = true;
+        }
     }
 
     /**
@@ -1049,228 +1328,59 @@
      */
     private void setupJobControlParameters() {
 
-        Element control = root.getChild("control");
-        if (control == null)
+        final Element control = root.getChild("control");
+        
+        if (control == null) {
+            // The control element is optional.
             return;
-
-        // Verbose mode.
-        Element verboseElement = control.getChild("verbose");
-        if (verboseElement != null) {
-            verbose = Boolean.valueOf(verboseElement.getText());
-            setVerbose(verbose);
-        }
-
-        // Log file setup needs to come first.
-        Element logFileElement = control.getChild("logFile");
-        String logFilePath = null;
-        if (logFileElement != null) {
-            logFilePath = logFileElement.getText();
-            File logFile = new File(logFilePath);
-            try {
-                logFile.createNewFile();
-                logStream = new PrintStream(new FileOutputStream(logFile));
-
-                // Redirect standard out and err to log file.
-                System.setOut(logStream);
-                System.setErr(logStream);
-            } catch (IOException x) {
-                throw new RuntimeException("Error creating log file: " + logFile.toString(), x);
-            }
         }
 
         // Print hello world message to appear at top of log.
-        if (verbose) {
-            logStream.println(this.getClass().getCanonicalName() + " is initialized.");
-            logStream.println();
-            logStream.println("--- Job Control Parameters ---");
-        }
-
-        // Print log file path now that PrintStream is set.
-        if (verbose)
-            logStream.println("logFile = " + logFilePath);
+        LOGGER.config(this.getClass().getCanonicalName() + " is initialized.");
 
         // Number of events to run.
-        Element controlElement = control.getChild("numberOfEvents");
+        final Element controlElement = control.getChild("numberOfEvents");
         if (controlElement != null) {
             numberOfEvents = Integer.valueOf(controlElement.getText());
-            if (verbose)
-                logStream.println("numberOfEvents = " + numberOfEvents);
-        }
-
-        Element skipElement = control.getChild("skipEvents");
+            LOGGER.config("numberOfEvents: " + numberOfEvents);
+        }
+
+        final Element skipElement = control.getChild("skipEvents");
         if (skipElement != null) {
             skipEvents = Integer.valueOf(skipElement.getText());
-            if (verbose)
-                logStream.println("skipEvents = " + skipEvents);
-        }
-
-        Element dryRunElement = control.getChild("dryRun");
+            LOGGER.config("skipEvents: " + skipEvents);
+        }
+
+        final Element dryRunElement = control.getChild("dryRun");
         if (dryRunElement != null) {
-            performDryRun = Boolean.valueOf(dryRunElement.getText());
-            if (verbose)
-                logStream.println("dryRun = " + performDryRun);
+            dryRun = Boolean.valueOf(dryRunElement.getText());
+            LOGGER.config("dryRun: " + dryRun);
         }
 
         // The cache directory. Defaults to the current directory.
-        Element cacheDirElement = control.getChild("cacheDirectory");
+        final Element cacheDirElement = control.getChild("cacheDirectory");
         if (cacheDirElement != null) {
             cacheDirectory = new File(cacheDirElement.getText());
-            if (!cacheDirectory.exists())
+            if (!cacheDirectory.exists()) {
                 throw new RuntimeException("cacheDirectory does not exist at location: " + cacheDirElement.getText());
+            }
         } else {
-            // Default to the user dir if cacheDirectory was not set explicitly.
-            // FIXME: Better default might be current directory.
+            // Cache directory defaults to user home dir.
             cacheDirectory = new File(System.getProperties().get("user.dir").toString());
         }
 
-        if (verbose)
-            logStream.println("cacheDirectory = " + cacheDirectory);
-
-        Element printInputFilesElement = control.getChild("printInputFiles");
-        if (printInputFilesElement != null) {
-            printInputFiles = Boolean.valueOf(printInputFilesElement.getText());
-        }
-
-        if (verbose)
-            logStream.println("printInputFiles = " + printInputFiles);
-
-        Element printStatisticsElement = control.getChild("printDriverStatistics");
+        LOGGER.config("cacheDirectory: " + cacheDirectory);
+
+        final Element printStatisticsElement = control.getChild("printDriverStatistics");
         if (printStatisticsElement != null) {
             printDriverStatistics = Boolean.valueOf(printStatisticsElement.getText());
-        }
-
-        if (verbose)
-            logStream.println("printDriverStatistics = " + printDriverStatistics);
-
-        Element printSystemPropertiesElement = control.getChild("printSystemProperties");
-        if (printSystemPropertiesElement != null) {
-            printSystemProperties = Boolean.valueOf(printSystemPropertiesElement.getText());
-        }
-
-        if (verbose)
-            logStream.println("printSystemProperties = " + printSystemProperties);
-
-        Element printUserClassPathElement = control.getChild("printUserClassPath");
-        if (printUserClassPathElement != null)
-            printUserClassPath = Boolean.valueOf(printUserClassPathElement.getText());
-
-        if (verbose)
-            logStream.println("printUserClassPath = " + printUserClassPath);
-
-        // Element printVersionElement = control.getChild("printVersion");
-        // if (printVersionElement != null) {
-        // printVersion = Boolean.valueOf(printVersionElement.getText());
-        // }
-
-        Element printDriversDetailedElement = control.getChild("printDriversDetailed");
-        if (printDriversDetailedElement != null)
-            printDriversDetailed = Boolean.valueOf(printDriversDetailedElement.getText());
-
-        if (verbose) {
-            logStream.println("printDriversDetailed = " + printDriversDetailed);
-            logStream.println("--- End Job Control Parameters ---");
-            logStream.println();
-        }
-        
-        Element dummyDetectorElement = control.getChild("dummyDetector");
-        if (dummyDetectorElement != null)
+            LOGGER.config("printDriverStatistics: " + printDriverStatistics);
+        }
+
+        final Element dummyDetectorElement = control.getChild("dummyDetector");
+        if (dummyDetectorElement != null) {
             dummyDetector = Boolean.valueOf(dummyDetectorElement.getText());
-    }
-
-    /**
-     * Setup the manager's class loader.
-     */
-    private void setupClassLoader() {
-        
-        if (loader != null) {
-            logStream.println("The ClassLoader was already set externally, so custom classpaths will be ignored!");
-            return;   
-        }
-        
-        Element classpath = root.getChild("classpath");
-        List<URL> urlList = new ArrayList<URL>();
-        if (classpath != null) {
-            for (Object jarObject : classpath.getChildren("jar")) {
-                Element jarElement = (Element) jarObject;
-                try {
-                    urlList.add((new File(processPath(jarElement.getText())).toURL()));
-                } catch (Exception x) {
-                    throw new RuntimeException("Bad jar location: " + jarElement.getText(), x);
-                }
-            }
-            for (Object jarUrlObject : classpath.getChildren("jarUrl")) {
-                Element jarUrlElement = (Element) jarUrlObject;
-                try {
-                    urlList.add(new URL(jarUrlElement.getText()));
-                } catch (Exception x) {
-                    throw new RuntimeException("Bad jar URL: " + jarUrlElement.getText(), x);
-                }
-            }
-            for (Object cpDirObject : classpath.getChildren("directory")) {
-                Element cpDirElement = (Element) cpDirObject;
-                try {
-                    File cpFile = new File(processPath(cpDirElement.getText()));
-                    if (!cpFile.isDirectory())
-                        throw new RuntimeException("The classpath component " + cpFile.getPath() + " is not a valid directory!");
-                    urlList.add(cpFile.toURL());
-                } catch (Exception x) {
-                    throw new RuntimeException("Bad classpath directory: " + cpDirElement.getText(), x);
-                }
-            }
-        }
-        URL[] urls = urlList.toArray(new URL[] {});
-
-        // Running in JAS. Jars need to already be on classpath.
-        // FIXME This creates a dep on freehep/JAS3 jars not used elsewhere in this package.
-        // if (Studio.getApplication() != null) {
-        // FreeHEPLookup fhl = ((Studio) Studio.getApplication()).getLookup();
-        // loader = ((DynamicClassLoader)fhl.lookup(DynamicClassLoader.class)).getClassLoader();
-        // }
-        // Running in batch.
-        // else {
-        
-        loader = new LCSimClassLoader(urls);        
-        // }
-
-        // Print user classpath entries.
-        if (printUserClassPath) {
-            logStream.println("-- Extra Classpath URLs --");
-            for (URL url : ((URLClassLoader) loader).getURLs()) {
-                logStream.println(url);
-            }
-            logStream.println("-- End Extra Classpath URLs --");
-            logStream.println();
-        }
-    }
-
-    /**
-     * Setup the file cache.
-     */
-    private void setupFileCache() {
-        if (cacheDirectory == null)
-            return;
-        try {
-            fileCache = new FileCache();
-            fileCache.setCacheDirectory(cacheDirectory);
-            fileCache.setPrintStream(null);
-        } catch (IOException x) {
-            throw new RuntimeException(x);
-        }
-    }
-
-    /**
-     * Create the constants from the XML file.
-     */
-    private void processConstants() {
-        Element define = root.getChild("define");
-        if (define != null) {
-            for (Object o : define.getChildren()) {
-                Element e = (Element) o;
-                Text txt = (Text) e.getContent().get(0);
-                double dval = factory.computeDouble(txt.getValue());
-                this.constantsMap.put(e.getName(), dval);
-                factory.addConstant(e.getName(), dval);
-            }
+            LOGGER.config("dummyDetector: " + dummyDetector);
         }
     }
 
@@ -1278,261 +1388,107 @@
      * Setup the system of units.
      */
     private void setupUnits() {
-        Constants constants = Constants.getInstance();
-        for (Entry<String, Double> unit : constants.entrySet()) {
+        final Constants constants = Constants.getInstance();
+        for (final Entry<String, Double> unit : constants.entrySet()) {
             factory.addConstant(unit.getKey(), unit.getValue());
-        }
-    }
-
-    /**
-     * Get the Java primitive type class from a type name.
+            LOGGER.finest(unit.getKey() + " = " + unit.getValue());
+        }
+    }
+
+    /**
+     * Perform variable substitution within all text data in a entire document.
+     *
+     * @param doc The XML document.
+     */
+    private void substituteVariables(final Document doc) {
+        this.substituteVariables(doc.getRootElement());
+    }
+
+    /**
+     * Substitute values from the <code>variableMap</code> into an XML element and all its children, recursively.
+     *
+     * @param element The XML element.
+     * @throw RuntimeException If a variable does not exist in the <code>variableMap</code>.
+     */
+    private void substituteVariables(final Element element) {
+
+        final String text = element.getTextNormalize();
+
+        if (text.length() != 0) {
+
+            // Create a new matcher.
+            final Matcher match = VARIABLE_PATTERN.matcher(text);
+
+            // No variables were used.
+            if (!match.find()) {
+                return;
+            }
+
+            // Text data on which to perform substitutions.
+            String newText = new String(text);
+
+            // Reset the matcher.
+            match.reset();
+
+            // Loop over the matches.
+            while (match.find()) {
+
+                // Variable string with ${...} enclosure included.
+                final String var = match.group();
+
+                // The name of the variable for lookup.
+                final String varName = var.substring(2, var.length() - 1);
+
+                // The value of the variable.
+                final String varValue = variableMap.get(varName);
+
+                // If a variable was not defined, then the application will immediately exit here.
+                if (varValue == null) {
+                    throw new RuntimeException("Required variable was not defined: " + varName);
+                }
+
+                // Substitute this variable's value into the text.
+                newText = newText.replace(var, varValue);
+            }
+
+            // Set this element's new text value.
+            element.setText(newText);
+        }
+
+        // Recursively process all child elements of this one.
+        for (final Iterator it = element.getChildren().iterator(); it.hasNext();) {
+            this.substituteVariables((Element) it.next());
+        }
+    }
+    
+    /**
+     * Initialize the conditions system before the job starts.
+     * <p>
+     * Sub-classes should override this if custom conditions system initialization is required.
      * 
-     * @param name The name of the type.
-     * @return The primitive type class.
-     */
-    private static Class getPrimitiveType(String name) {
-        if (name.equals("byte"))
-            return byte.class;
-        if (name.equals("short"))
-            return short.class;
-        if (name.equals("int"))
-            return int.class;
-        if (name.equals("long"))
-            return long.class;
-        if (name.equals("char"))
-            return char.class;
-        if (name.equals("float"))
-            return float.class;
-        if (name.equals("double"))
-            return double.class;
-        if (name.equals("boolean"))
-            return boolean.class;
-        if (name.equals("String"))
-            return String.class;
-        return null;
-    }
-
-    /**
-     * Get a list of a class's setter methods.
-     * 
-     * @param klass The class.
-     * @return A list of setter methods.
-     */
-    private List<Method> getSetterMethods(Class klass) {
-        List<Method> methods = new ArrayList<Method>();
-        Class currentClass = klass;
-        while (currentClass != null) {
-            for (Method method : currentClass.getMethods()) {
-                if (method.getName().startsWith("set") && !methods.contains(method)) {
-                    methods.add(method);
-                }
-            }
-            currentClass = currentClass.getSuperclass();
-        }
-        return methods;
-    }
-
-    /**
-     * Process the file path string to substitute in the user's home directory for the "~"
-     * character. This is needed if running on Windows.
-     * 
-     * @param path The original path.
-     * @return The path with home dir substitution.
-     */
-    private String processPath(String path) {
-        if (path.startsWith("~")) {
-            return path.replaceFirst("~", System.getProperty("user.home"));
-        } else {
-            return path;
-        }
-    }
-
-    // Wrap Drivers with a DriverAdapter. Only one will be created per job.
-    public DriverAdapter getDriverAdapter() {
-        if (driverAdapter == null) {
-            Driver topDriver = new Driver();
-            for (Driver driver : getDriverExecList()) {
-                topDriver.add(driver);
-            }
-            driverAdapter = new DriverAdapter(topDriver);
-        }
-        return driverAdapter;
-    }
-
-    // Process a single LCSim event using the DriverAdapter.
-    public void processEvent(EventHeader event) {
-        getDriverAdapter().recordSupplied(new RecordEvent(loop, event));
-    }
-
-    /**
-     * Reset the class's state so another job can be run.
-     */
-    public synchronized void reset() {
-
-        driverAdapter = null;
-        loop = null;
-
-        driverMap = new LinkedHashMap<String, Driver>();
-        driverExec = new ArrayList<Driver>();
-        availableDrivers = new HashMap<String, String>();
-
-        // Run parameters.
-        inputFiles = new ArrayList<File>();
-        numberOfEvents = -1;
-        skipEvents = -1;
-
-        rewriteFile = null;
-
-        // Variables and constants.
-        variableMap = new HashMap<String, String>();
-        constantsMap = new HashMap<String, Double>();
-
-        // Boolean job options.
-        performDryRun = false;
-        rewrite = false;
-
-        // Settings effecting logging verbosity.
-        printInputFiles = false;
-        printDriverStatistics = false;
-        printSystemProperties = false;
-        printUserClassPath = false;
-        printDriversDetailed = false;
-        // printVersion = false;
-        verbose = false;
-
-        // File caching.
-        cacheDirectory = null;
-
-        // The manager's class loader.
-        loader = null;
-
-        // The log stream for messages.
-        logStream = System.out;
-
-        // Root node of XML job file.
-        root = null;
-
-        // XML utils.
-        factory = new JDOMExpressionFactory();
-        paramConverter = new ParameterConverters(factory);
-
-        // True once job parameters are setup from CL and XML.
-        wasSetup = false;
-    }
-
-    public void configure() {
-        getDriverAdapter().start(null);
-    }
-
-    public void reconfigure() {
-        getDriverAdapter().start(null);
-    }
-
-    public void suspend() {
-        getDriverAdapter().suspend(null);
-    }
-
-    public void finish() {
-        getDriverAdapter().finish(null);
-    }
-
-    public void setPerformDryRun(boolean d) {
-        this.performDryRun = d;
+     * @throws ConditionsNotFoundException if some conditions were not found
+     */
+    protected void initializeConditions() throws ConditionsNotFoundException {
+        // Set up user supplied conditions information (we already checked that these were both given if one was provided).
+        if (this.detectorName != null) {
+            LOGGER.config("initializing conditions system with detector " + this.detectorName + " and run " + this.runNumber);
+            ConditionsManager.defaultInstance().setDetector(this.detectorName, this.runNumber);
+        }
     }
     
-    public void setClassLoader(ClassLoader loader) {
-        if (loader == null)
-            throw new IllegalArgumentException("The ClassLoader argument points to null.");
-        this.loader = loader;
-        logStream.println("Set ClassLoader to " + loader.getClass().getCanonicalName());
-    }
-
-    private void checkConditions() {
-
-        // Get conditions element if it is there; otherwise return.
-        Element conditionsElement = root.getChild("conditions");
-        if (conditionsElement == null)
-            return;
-
-        // Get the list of valid detectors for this steering file.
-        Element detectorsElement = conditionsElement.getChild("detectors");
-
-        if (detectorsElement != null) {
-            String detectors = detectorsElement.getTextNormalize();
-
-            String[] detectorNames = detectors.split(" ");
-
-            // Loop over detector names and check for required conditions in each.
-            for (String detector : detectorNames) {
-                logStream.println("Looking up required conditions for " + detector + " ...");
-                ConditionsManager mgr = ConditionsManager.defaultInstance();
-                try {
-                    mgr.setDetector(detector, 0);
-                } catch (ConditionsManager.ConditionsNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
-                for (Object condition : conditionsElement.getChildren()) {
-                    Element conditionElement = (Element) condition;
-                    boolean required = true;
-                    try {
-                        required = conditionElement.getAttribute("required").getBooleanValue();
-                    } catch (DataConversionException e) {
-                    } catch (NullPointerException e) {
-                    }
-                    try {
-                        mgr.getRawConditions(conditionElement.getTextTrim());
-                        logStream.println(conditionElement.getTextTrim() + " - OKAY");
-                    } catch (ConditionsSetNotFoundException e) {
-                        logStream.println(conditionElement.getTextTrim() + " - NOT FOUND");
-                        if (required)
-                            throw new RuntimeException("Required conditions " + conditionElement.getTextTrim() + " are missing from detector " + detector + ".");
-                    }
-                }
-            }
-        }
-
-        logStream.println("Adding ConditionsCheckDriver to front of Driver exec list.");
-        ConditionsCheckDriver d = createConditionsCheckDriver();
-        if (d != null) {
-            if (detectorsElement == null) {
-                // No detector names provided, so individual detectors will have all conditions
-                // checked.
-                d.setCheckDetector(false);
-                d.setCheckConditions(true);
-            } else {
-                // Detectors already checked by name so just check that detector names in the job
-                // are okay.
-                d.setCheckConditions(false);
-                d.setCheckDetector(true);
-            }
-            driverExec.add(d);
-        }
-    }
-
-    private ConditionsCheckDriver createConditionsCheckDriver() {
-        ConditionsCheckDriver driver = new ConditionsCheckDriver();
-        Element conditionsElement = root.getChild("conditions");
-        if (conditionsElement == null)
-            return null;
-        for (Object condition : conditionsElement.getChildren()) {
-            Element conditionElement = (Element) condition;
-            driver.setCondition(conditionElement.getTextTrim());
-        }
-        if (conditionsElement.getChild("detectors") != null) {
-            String[] detectorNames = conditionsElement.getChild("detectors").getText().split(" +");
-            for (String detector : detectorNames) {
-                driver.setDetector(detector);
-            }
-        }
-        return driver;
+    /**
+     * Get the run number set by the command line options.
+     * @return the run number or <code>null</code> if not set
+     */
+    protected Integer getRunNumber() {
+        return this.runNumber;
     }
     
     /**
-     * Set the number of events to run on the loop before ending the job.  
-     * This should be called after the {@link #setup(File)} method
-     * is called or it will be overridden.     
-     */
-    public void setNumberOfEvents(int numberOfEvents) {        
-        this.numberOfEvents = numberOfEvents;
+     * Get the detector name set by the command line options.
+     * @return the detector name or <code>null</code> if not set
+     */
+    protected String getDetectorName() {
+        return this.detectorName;
     }
 }

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/LCSimClassLoader.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/LCSimClassLoader.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/LCSimClassLoader.java	Tue Nov  3 11:32:25 2015
@@ -5,18 +5,17 @@
 
 /**
  * A simple class loader that accepts URLs and extends the system class loader.
+ * 
  * @author jeremym
  * @version $id: $
  */
-class LCSimClassLoader extends URLClassLoader
-{
-	LCSimClassLoader(URL[] urls) 
-	{
-		super(urls, ClassLoader.getSystemClassLoader());
-	}
+class LCSimClassLoader extends URLClassLoader {
 
-	public void addURL(URL url)
-	{
-		this.addURL(url);
-	}
-}	
+    LCSimClassLoader(URL[] urls) {
+        super(urls, ClassLoader.getSystemClassLoader());
+    }
+
+    public void addURL(URL url) {
+        this.addURL(url);
+    }
+}

Modified: projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/ParameterConverters.java
 =============================================================================
--- projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/ParameterConverters.java	(original)
+++ projects/lcsim/trunk/job-manager/src/main/java/org/lcsim/job/ParameterConverters.java	Tue Nov  3 11:32:25 2015
@@ -14,431 +14,360 @@
 import org.lcsim.util.xml.JDOMExpressionFactory;
 
 /**
- * Converter utilities for making Java Beans arguments from LCSim XML Driver parameters,
- * using a JDOM factory to do expression evaluation.
+ * Converter utilities for making Java Beans arguments from LCSim XML Driver parameters, using a JDOM factory to do
+ * expression evaluation.
+ * 
  * @author jeremym
  */
-public class ParameterConverters 
-{	
-	List<IParameterConverter> converters = new ArrayList<IParameterConverter>();
-	JDOMExpressionFactory factory;
-	
-	public ParameterConverters(JDOMExpressionFactory factory)	
-	{
-		this.factory = factory;
-		
-		add(new IntegerConverter());
-		add(new StringConverter());
-		add(new DoubleConverter());
-		add(new FloatConverter());
-		add(new BooleanConverter());
-		add(new Hep3VectorConverter());
-		add(new DoubleArray1DConverter());
-		add(new IntegerArray1DConverter());
-		add(new FloatArray1DConverter());
-		add(new StringArray1DConverter());
-		add(new BooleanArray1DConverter());
-		add(new FileConverter());
-		add(new URLConverter());
-		add(new IntegerArray2DConverter());
-		add(new DoubleArray2DConverter());
-	}
-	
-	public Object convert(Class propertyType, Element parameterElement)
-	{
-		IParameterConverter p = getConverterForType(propertyType);
-		if (p != null)
-		{
-			return p.convert(factory, parameterElement);
-		}
-		else 
-		{
-			return null;
-		}
-	}
-	
-	protected void add(IParameterConverter converter)
-	{		
-		converters.add(converter);
-	}
-	
-	public List<IParameterConverter> getConverters()
-	{
-		return converters;
-	}
-	
-	public IParameterConverter getConverterForType(Class propertyType)
-	{
-		for (IParameterConverter p : converters)
-		{
-			if (p.handles(propertyType))
-				return p;
-		}		
-		return null;
-	}
-	
-	public class IntegerConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(int.class);
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return Integer.valueOf((int)factory.computeDouble(parameterElement.getValue()));
-		}
-	}
-	
-	public class StringConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(String.class);
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return parameterElement.getValue();
-		}
-	}
-	
-	public class DoubleConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(double.class);			
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return Double.valueOf(factory.computeDouble(parameterElement.getValue()));
-		}
-	}
-	
-	public class FloatConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(float.class);			
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return Float.valueOf(factory.computeFloat(parameterElement.getValue()));
-		}
-	}	
-	
-	public class BooleanConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(boolean.class);			
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return Boolean.valueOf(parameterElement.getValue());
-		}
-	}	
-	
-	public class Hep3VectorConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(Hep3Vector.class);			
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
-			double x = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
-			double y = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
-			double z = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
-			return new BasicHep3Vector(x, y, z);
-		}
-	}		
-	
-	public class DoubleArray1DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[D");			
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
-			int size = tokenize.countTokens();
-			double da[] = new double[size];
-			int i = 0;
-			while (tokenize.hasMoreTokens())
-			{
-				da[i] = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
-				++i;
-			}
-			return da;
-		}
-	}
-	
-	public class IntegerArray1DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[I");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
-			int size = tokenize.countTokens();
-			int ia[] = new int[size];
-			int i = 0;
-			while (tokenize.hasMoreTokens())
-			{
-				ia[i] = Integer.valueOf((int)factory.computeDouble(tokenize.nextToken()));
-				++i;
-			}
-			return ia;
-		}
-	}
-	
-	public class FloatArray1DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[F");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());		
-			int size = tokenize.countTokens();
-			float fa[] = new float[size];
-			int i = 0;
-			while (tokenize.hasMoreTokens())
-			{
-				fa[i] = Float.valueOf(factory.computeFloat(tokenize.nextToken()));
-				++i;
-			}
-			return fa;
-		}
-	}
-	
-	public class StringArray1DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[Ljava.lang.String;");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
-			int size = tokenize.countTokens();
-			String sa[] = new String[size];
-			int i = 0;
-			while (tokenize.hasMoreTokens())
-			{
-				sa[i] = tokenize.nextToken();
-				++i;
-			}
-			return sa;
-		}
-	}
-	
-	public class BooleanArray1DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[Z");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
-			int size = tokenize.countTokens();
-			boolean ba[] = new boolean[size];
-			int i = 0;
-			while (tokenize.hasMoreTokens())
-			{
-				ba[i] = Boolean.valueOf(tokenize.nextToken());
-				++i;
-			}
-			return ba;
-		}	
-	}
-	
-	public class FileConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(File.class);
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return new File(parameterElement.getValue());
-		}
-	}
-	
-	public class URLConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(URL.class);
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			try 
-			{
-				return new URL(parameterElement.getValue());
-			}
-			catch (MalformedURLException x)
-			{
-				throw new RuntimeException("Bad URL " + parameterElement.getValue() + " in XML job description.",x);
-			}
-		}
-	}
-	public class DoubleArray2DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{	
-			return propertyType.getName().equals("[[D");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			// Parse into a list of list of doubles.
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue(), ";");
-			int length = tokenize.countTokens();
-			int ir = 0;
-			List<List<Double>> rows = new ArrayList<List<Double>>(length);
-			while (tokenize.hasMoreTokens())
-			{
-				String rowStr = tokenize.nextToken();
-				StringTokenizer tokenize2 = new StringTokenizer(rowStr);
-				rows.add(new ArrayList<Double>());
-				List<Double> row = rows.get(ir);
-				while (tokenize2.hasMoreTokens())
-				{
-					String entry = tokenize2.nextToken();
-					Double d = factory.computeDouble(entry);
-					row.add(d);
-				}
-				++ir;				
-			}		
-			
-			// Convert list of doubles into 2D array, checking for wrong sized rows.
-			double arr[][] = new double[rows.size()][rows.get(0).size()];				
-			int jcheck = rows.get(0).size() - 1;
-			int i=0;
-			int j=0;
-			for (List<Double> aa : rows)
-			{			
-				for (Double a : aa)
-				{
-					if (j > jcheck)
-					{
-						throw new RuntimeException(
-								"Row " + j + " of array " + 
-								parameterElement.getName() + 
-								"with length " + aa.size() +
-								" is too long.");
-					}
-					arr[i][j] = a;
-					++j;
-				}
-				if (j < jcheck)
-				{
-					throw new RuntimeException(
-							"Row " + j + " of array " + 
-							parameterElement.getName() + 
-							"with length " + aa.size() +
-							"is too short.");
-				}
-				j=0;
-				++i;				
-			}
-			return arr;
-		}
-	}
-	
-	public class IntegerArray2DConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.getName().equals("[[I");
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			// Parse into a list of list of doubles.
-			StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue(), ";");
-			int length = tokenize.countTokens();
-			int ir = 0;
-			List<List<Integer>> rows = new ArrayList<List<Integer>>(length);
-			while (tokenize.hasMoreTokens())
-			{
-				String rowStr = tokenize.nextToken();
-				StringTokenizer tokenize2 = new StringTokenizer(rowStr);
-				rows.add(new ArrayList<Integer>());
-				List<Integer> row = rows.get(ir);
-				while (tokenize2.hasMoreTokens())
-				{
-					String entry = tokenize2.nextToken();
-					Integer d = Integer.valueOf(entry);
-					row.add(d);
-				}
-				++ir;				
-			}		
-			
-			// Convert list of doubles into 2D array, checking for wrong sized rows.
-			int arr[][] = new int[rows.size()][rows.get(0).size()];				
-			int jcheck = rows.get(0).size() - 1;
-			int i=0;
-			int j=0;
-			for (List<Integer> aa : rows)
-			{			
-				for (Integer a : aa)
-				{
-					if (j > jcheck)
-					{
-						throw new RuntimeException(
-								"Row " + j + " of array " + 
-								parameterElement.getName() + 
-								"with length " + aa.size() +
-								" is too long.");
-					}
-					arr[i][j] = a;					
-					++j;
-				}
-				if (j < jcheck)
-				{
-					throw new RuntimeException(
-							"Row " + j + " of array " + 
-							parameterElement.getName() + 
-							"with length " + aa.size() +
-							"is too short.");
-				}
-				j=0;
-				++i;				
-			}
-			return arr;		
-		}		
-	}	
-	
-	public class ElementConverter implements IParameterConverter
-	{
-		public boolean handles(Class propertyType)
-		{
-			return propertyType.equals(Element.class);
-		}
-		
-		public Object convert(JDOMExpressionFactory factory, Element parameterElement)
-		{
-			return parameterElement;
-		}
-	}
+public class ParameterConverters {
+
+    List<IParameterConverter> converters = new ArrayList<IParameterConverter>();
+    JDOMExpressionFactory factory;
+
+    public ParameterConverters(JDOMExpressionFactory factory) {
+        this.factory = factory;
+
+        add(new IntegerConverter());
+        add(new StringConverter());
+        add(new DoubleConverter());
+        add(new FloatConverter());
+        add(new BooleanConverter());
+        add(new Hep3VectorConverter());
+        add(new DoubleArray1DConverter());
+        add(new IntegerArray1DConverter());
+        add(new FloatArray1DConverter());
+        add(new StringArray1DConverter());
+        add(new BooleanArray1DConverter());
+        add(new FileConverter());
+        add(new URLConverter());
+        add(new IntegerArray2DConverter());
+        add(new DoubleArray2DConverter());
+    }
+
+    public Object convert(Class propertyType, Element parameterElement) {
+        IParameterConverter p = getConverterForType(propertyType);
+        if (p != null) {
+            return p.convert(factory, parameterElement);
+        } else {
+            return null;
+        }
+    }
+
+    protected void add(IParameterConverter converter) {
+        converters.add(converter);
+    }
+
+    public List<IParameterConverter> getConverters() {
+        return converters;
+    }
+
+    public IParameterConverter getConverterForType(Class propertyType) {
+        for (IParameterConverter p : converters) {
+            if (p.handles(propertyType))
+                return p;
+        }
+        return null;
+    }
+
+    public class IntegerConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(int.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return Integer.valueOf((int) factory.computeDouble(parameterElement.getValue()));
+        }
+    }
+
+    public class StringConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(String.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return parameterElement.getValue();
+        }
+    }
+
+    public class DoubleConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(double.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return Double.valueOf(factory.computeDouble(parameterElement.getValue()));
+        }
+    }
+
+    public class FloatConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(float.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return Float.valueOf(factory.computeFloat(parameterElement.getValue()));
+        }
+    }
+
+    public class BooleanConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(boolean.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return Boolean.valueOf(parameterElement.getValue());
+        }
+    }
+
+    public class Hep3VectorConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(Hep3Vector.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            double x = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
+            double y = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
+            double z = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
+            return new BasicHep3Vector(x, y, z);
+        }
+    }
+
+    public class DoubleArray1DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[D");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            int size = tokenize.countTokens();
+            double da[] = new double[size];
+            int i = 0;
+            while (tokenize.hasMoreTokens()) {
+                da[i] = Double.valueOf(factory.computeDouble(tokenize.nextToken()));
+                ++i;
+            }
+            return da;
+        }
+    }
+
+    public class IntegerArray1DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[I");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            int size = tokenize.countTokens();
+            int ia[] = new int[size];
+            int i = 0;
+            while (tokenize.hasMoreTokens()) {
+                ia[i] = Integer.valueOf((int) factory.computeDouble(tokenize.nextToken()));
+                ++i;
+            }
+            return ia;
+        }
+    }
+
+    public class FloatArray1DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[F");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            int size = tokenize.countTokens();
+            float fa[] = new float[size];
+            int i = 0;
+            while (tokenize.hasMoreTokens()) {
+                fa[i] = Float.valueOf(factory.computeFloat(tokenize.nextToken()));
+                ++i;
+            }
+            return fa;
+        }
+    }
+
+    public class StringArray1DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[Ljava.lang.String;");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            int size = tokenize.countTokens();
+            String sa[] = new String[size];
+            int i = 0;
+            while (tokenize.hasMoreTokens()) {
+                sa[i] = tokenize.nextToken();
+                ++i;
+            }
+            return sa;
+        }
+    }
+
+    public class BooleanArray1DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[Z");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue());
+            int size = tokenize.countTokens();
+            boolean ba[] = new boolean[size];
+            int i = 0;
+            while (tokenize.hasMoreTokens()) {
+                ba[i] = Boolean.valueOf(tokenize.nextToken());
+                ++i;
+            }
+            return ba;
+        }
+    }
+
+    public class FileConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(File.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return new File(parameterElement.getValue());
+        }
+    }
+
+    public class URLConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(URL.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            try {
+                return new URL(parameterElement.getValue());
+            } catch (MalformedURLException x) {
+                throw new RuntimeException("Bad URL " + parameterElement.getValue() + " in XML job description.", x);
+            }
+        }
+    }
+
+    public class DoubleArray2DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[[D");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            // Parse into a list of list of doubles.
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue(), ";");
+            int length = tokenize.countTokens();
+            int ir = 0;
+            List<List<Double>> rows = new ArrayList<List<Double>>(length);
+            while (tokenize.hasMoreTokens()) {
+                String rowStr = tokenize.nextToken();
+                StringTokenizer tokenize2 = new StringTokenizer(rowStr);
+                rows.add(new ArrayList<Double>());
+                List<Double> row = rows.get(ir);
+                while (tokenize2.hasMoreTokens()) {
+                    String entry = tokenize2.nextToken();
+                    Double d = factory.computeDouble(entry);
+                    row.add(d);
+                }
+                ++ir;
+            }
+
+            // Convert list of doubles into 2D array, checking for wrong sized rows.
+            double arr[][] = new double[rows.size()][rows.get(0).size()];
+            int jcheck = rows.get(0).size() - 1;
+            int i = 0;
+            int j = 0;
+            for (List<Double> aa : rows) {
+                for (Double a : aa) {
+                    if (j > jcheck) {
+                        throw new RuntimeException("Row " + j + " of array " + parameterElement.getName()
+                                + "with length " + aa.size() + " is too long.");
+                    }
+                    arr[i][j] = a;
+                    ++j;
+                }
+                if (j < jcheck) {
+                    throw new RuntimeException("Row " + j + " of array " + parameterElement.getName() + "with length "
+                            + aa.size() + "is too short.");
+                }
+                j = 0;
+                ++i;
+            }
+            return arr;
+        }
+    }
+
+    public class IntegerArray2DConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.getName().equals("[[I");
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            // Parse into a list of list of doubles.
+            StringTokenizer tokenize = new StringTokenizer(parameterElement.getValue(), ";");
+            int length = tokenize.countTokens();
+            int ir = 0;
+            List<List<Integer>> rows = new ArrayList<List<Integer>>(length);
+            while (tokenize.hasMoreTokens()) {
+                String rowStr = tokenize.nextToken();
+                StringTokenizer tokenize2 = new StringTokenizer(rowStr);
+                rows.add(new ArrayList<Integer>());
+                List<Integer> row = rows.get(ir);
+                while (tokenize2.hasMoreTokens()) {
+                    String entry = tokenize2.nextToken();
+                    Integer d = Integer.valueOf(entry);
+                    row.add(d);
+                }
+                ++ir;
+            }
+
+            // Convert list of doubles into 2D array, checking for wrong sized rows.
+            int arr[][] = new int[rows.size()][rows.get(0).size()];
+            int jcheck = rows.get(0).size() - 1;
+            int i = 0;
+            int j = 0;
+            for (List<Integer> aa : rows) {
+                for (Integer a : aa) {
+                    if (j > jcheck) {
+                        throw new RuntimeException("Row " + j + " of array " + parameterElement.getName()
+                                + "with length " + aa.size() + " is too long.");
+                    }
+                    arr[i][j] = a;
+                    ++j;
+                }
+                if (j < jcheck) {
+                    throw new RuntimeException("Row " + j + " of array " + parameterElement.getName() + "with length "
+                            + aa.size() + "is too short.");
+                }
+                j = 0;
+                ++i;
+            }
+            return arr;
+        }
+    }
+
+    public class ElementConverter implements IParameterConverter {
+
+        public boolean handles(Class propertyType) {
+            return propertyType.equals(Element.class);
+        }
+
+        public Object convert(JDOMExpressionFactory factory, Element parameterElement) {
+            return parameterElement;
+        }
+    }
 }

########################################################################
Use REPLY-ALL to reply to list

To unsubscribe from the LCDET-SVN list, click the following link:
https://listserv.slac.stanford.edu/cgi-bin/wa?SUBED1=LCDET-SVN&A=1