LISTSERV mailing list manager LISTSERV 16.5

Help for HPS-SVN Archives


HPS-SVN Archives

HPS-SVN Archives


HPS-SVN@LISTSERV.SLAC.STANFORD.EDU


View:

Message:

[

First

|

Previous

|

Next

|

Last

]

By Topic:

[

First

|

Previous

|

Next

|

Last

]

By Author:

[

First

|

Previous

|

Next

|

Last

]

Font:

Proportional Font

LISTSERV Archives

LISTSERV Archives

HPS-SVN Home

HPS-SVN Home

HPS-SVN  August 2015

HPS-SVN August 2015

Subject:

r3380 - in /java/trunk/record-util/src/main/java/org/hps/record: epics/ evio/ evio/crawler/ run/ scalers/

From:

[log in to unmask]

Reply-To:

Notification of commits to the hps svn repository <[log in to unmask]>

Date:

Thu, 20 Aug 2015 19:50:16 -0000

Content-Type:

text/plain

Parts/Attachments:

Parts/Attachments

text/plain (4884 lines)

Author: [log in to unmask]
Date: Thu Aug 20 12:50:12 2015
New Revision: 3380

Log:
Major overhaul of record-util module for supporting run database.  HPSJAVA-575, HPSJAVA-576

Added:
    java/trunk/record-util/src/main/java/org/hps/record/epics/Epics20sVariables.java
    java/trunk/record-util/src/main/java/org/hps/record/epics/Epics2sVariables.java
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsRunProcessor.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EpicsLog.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EventCountProcessor.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EventCountProcessor.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileFilter.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileFilter.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaData.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaDataReader.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSequenceComparator.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileSequenceComparator.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileUtilities.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileUtilities.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoop.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoopAdapter.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunSummaryMap.java
      - copied, changed from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunLog.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDao.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDaoImpl.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDao.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDaoImpl.java
    java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDao.java
    java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDaoImpl.java
    java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDao.java
    java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDaoImpl.java
    java/trunk/record-util/src/main/java/org/hps/record/run/package-info.java
Removed:
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsCsvExporter.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EpicsLog.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EventCountProcessor.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileFilter.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileList.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileSequenceComparator.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileUtilities.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunLog.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunLogUpdater.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunSummaryUpdater.java
    java/trunk/record-util/src/main/java/org/hps/record/run/AbstractRunDatabaseReader.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataReader.java
    java/trunk/record-util/src/main/java/org/hps/record/run/EvioFileListReader.java
    java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryReader.java
    java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataReader.java
Modified:
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsData.java
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsEvioProcessor.java
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsGenericObject.java
    java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsHeader.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioEventConstants.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSource.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/Crawler.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/CrawlerConfig.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileVisitor.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/JCacheManager.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunFilter.java
    java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunProcessor.java
    java/trunk/record-util/src/main/java/org/hps/record/run/RunManager.java
    java/trunk/record-util/src/main/java/org/hps/record/run/RunSummary.java
    java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalerData.java
    java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalersEvioProcessor.java

Added: java/trunk/record-util/src/main/java/org/hps/record/epics/Epics20sVariables.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/Epics20sVariables.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/Epics20sVariables.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,54 @@
+package org.hps.record.epics;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Static list of variable names contained in 20s EPICS data block from Eng Run 2015 data along
+ * with a brief description (if known).
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public final class Epics20sVariables {
+
+    /**
+     * Map with variable names and description.
+     */
+    static final Map<String, String> VARIABLES = new HashMap<String, String>();
+
+    /**
+     * Variable definitions.
+     */
+    static {
+        VARIABLES.put("beam_stop.RBV", "beam stop motor position");
+        VARIABLES.put("hps:svt_bot:motor.RBV", "SVT bottom motor position");
+        VARIABLES.put("hps:svt_top:motor.RBV", "SVT top motor position");
+        VARIABLES.put("hps:target:motor.RBV", "target motor position");
+        VARIABLES.put("hps_collimator.RBV", "collimator motor position");
+        VARIABLES.put("scalerS10b", "DWN-B beamline counter");
+        VARIABLES.put("scalerS11b", "DWN-R beamline counter");
+        VARIABLES.put("scalerS8b", "DWN-T beamline counter");
+        VARIABLES.put("scalerS9b", "DWN-L beamline counter");
+        VARIABLES.put("scaler_cS3b", "UPS-L  beamline counter");
+        VARIABLES.put("scaler_cS4b", "UPS-R beamline counter");
+        VARIABLES.put("scaler_cS5b", "TAG-L beamline counter");
+        VARIABLES.put("scaler_cS6b", "TAG-T beamline counter");
+        VARIABLES.put("scaler_cS7b", "TAG-T2 beamline counter");
+        VARIABLES.put("SMRPOSB", "");
+    }
+
+    /**
+     * Get the variable map with the names and descriptions.
+     * 
+     * @return the variable map
+     */
+    public static Map<String, String> getVariables() {
+        return VARIABLES;
+    }
+
+    /**
+     * Do not allow class instantiation.
+     */
+    private Epics20sVariables() {
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/epics/Epics2sVariables.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/Epics2sVariables.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/Epics2sVariables.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,65 @@
+package org.hps.record.epics;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * List of EPICS variables and their descriptions contained in the 2s data bank from Eng Run 2015 data.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public final class Epics2sVariables {
+
+    /**
+     * Map with variable names and description.
+     */
+    static final Map<String, String> VARIABLES = new HashMap<String, String>();
+
+    /**
+     * Variable definitions.
+     */
+    static {
+        VARIABLES.put("MBSY2C_energy", "Beam energy according to Hall B BSY dipole string");
+        VARIABLES.put("PSPECIRBCK", "Pair Spectrometer Current Readback");
+        VARIABLES.put("HPS:LS450_2:FIELD", "Frascati probe field");
+        VARIABLES.put("HPS:LS450_1:FIELD", "Pair Spectrometer probe field");
+        VARIABLES.put("MTIRBCK", "Frascati Current Readback");
+        VARIABLES.put("VCG2C21 2C21", "Vacuum gauge pressure");
+        VARIABLES.put("VCG2C21A", "2C21A Vacuum gauge pressure");
+        VARIABLES.put("VCG2C24A", "2C24A Vacuum gauge pressure");
+        VARIABLES.put("VCG2H00A", "2H00 Vacuum gauge pressure");
+        VARIABLES.put("VCG2H01A", "2H01 Vacuum gauge pressure");
+        VARIABLES.put("VCG2H02A", "2H02 Vacuum gauge pressure");
+        VARIABLES.put("scaler_calc1", "Faraday cup current");
+        VARIABLES.put("scalerS12b", "HPS-Left beam halo count");
+        VARIABLES.put("scalerS13b", "HPS-Right beam halo count");
+        VARIABLES.put("scalerS14b", "HPS-Top beam halo count");
+        VARIABLES.put("scalerS15b", "HPS-SC beam halo count");
+        VARIABLES.put("hallb_IPM2C21A_XPOS", "Beam position X at 2C21");
+        VARIABLES.put("hallb_IPM2C21A_YPOS", "Beam position Y at 2C21");
+        VARIABLES.put("hallb_IPM2C21A_CUR", "Current at 2C21");
+        VARIABLES.put("hallb_IPM2C24A_XPOS", "Beam position X at 2C24");
+        VARIABLES.put("hallb_IPM2C24A_YPOS", "Beam position Y at 2C24");
+        VARIABLES.put("hallb_IPM2C24A_CUR", "Current at 2C24");
+        VARIABLES.put("hallb_IPM2H00_XPOS", "Beam position X at 2H00");
+        VARIABLES.put("hallb_IPM2H00_YPOS", "Beam position Y at 2H00");
+        VARIABLES.put("hallb_IPM2H00_CUR", "Current at 2H00");
+        VARIABLES.put("hallb_IPM2H02_YPOS", "Beam position X at 2H02");
+        VARIABLES.put("hallb_IPM2H02_XPOS", "Beam position Y at 2H02");
+    }
+
+    /**
+     * Get the variable map.
+     * 
+     * @return the variable map
+     */
+    public static Map<String, String> getVariables() {
+        return VARIABLES;
+    }
+
+    /**
+     * Do not allow class instantiation.
+     */
+    private Epics2sVariables() {
+    }
+}

Modified: java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsData.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsData.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsData.java	Thu Aug 20 12:50:12 2015
@@ -42,66 +42,6 @@
     private static final String EPICS_VARIABLE_NAMES = "EPICS_VARIABLE_NAMES";
 
     /**
-     * This map contains the list of EPICS keys and their descriptions from the<br/>
-     * <a href="https://confluence.slac.stanford.edu/display/hpsg/EVIO+Data+Format">EVIO Data Format Confluence Page</a>
-     */
-    private final static Map<String, String> VARIABLES = new HashMap<String, String>();
-
-    /**
-     * List of possible EPICS keys.
-     */
-    static {
-        VARIABLES.put("MBSY2C_energy", "Beam energy according to Hall B BSY dipole string");
-        VARIABLES.put("PSPECIRBCK", "Pair Spectrometer Current Readback");
-        VARIABLES.put("HPS:LS450_2:FIELD", "Frascati probe field");
-        VARIABLES.put("HPS:LS450_1:FIELD", "Pair Spectrometer probe field");
-        VARIABLES.put("MTIRBCK", "Frascati Current Readback");
-        VARIABLES.put("VCG2C21 2C21", "Vacuum gauge pressure");
-        VARIABLES.put("VCG2C21A", "2C21A Vacuum gauge pressure");
-        VARIABLES.put("VCG2C24A", "2C24A Vacuum gauge pressure");
-        VARIABLES.put("VCG2H00A", "2H00 Vacuum gauge pressure");
-        VARIABLES.put("VCG2H01A", "2H01 Vacuum gauge pressure");
-        VARIABLES.put("VCG2H02A", "2H02 Vacuum gauge pressure");
-        VARIABLES.put("scaler_calc1", "Faraday cup current");
-        VARIABLES.put("scalerS12b", "HPS-Left beam halo count");
-        VARIABLES.put("scalerS13b", "HPS-Right beam halo count");
-        VARIABLES.put("scalerS14b", "HPS-Top beam halo count");
-        VARIABLES.put("scalerS15b", "HPS-SC beam halo count");
-        VARIABLES.put("hallb_IPM2C21A_XPOS", "Beam position X at 2C21");
-        VARIABLES.put("hallb_IPM2C21A_YPOS", "Beam position Y at 2C21");
-        VARIABLES.put("hallb_IPM2C21A_CUR", "Current at 2C21");
-        VARIABLES.put("hallb_IPM2C24A_XPOS", "Beam position X at 2C24");
-        VARIABLES.put("hallb_IPM2C24A_YPOS", "Beam position Y at 2C24");
-        VARIABLES.put("hallb_IPM2C24A_CUR", "Current at 2C24");
-        VARIABLES.put("hallb_IPM2H00_XPOS", "Beam position X at 2H00");
-        VARIABLES.put("hallb_IPM2H00_YPOS", "Beam position Y at 2H00");
-        VARIABLES.put("hallb_IPM2H00_CUR", "Current at 2H00");
-        VARIABLES.put("hallb_IPM2H02_YPOS", "Beam position X at 2H02");
-        VARIABLES.put("hallb_IPM2H02_XPOS", "Beam position Y at 2H02");
-    }
-
-    /**
-     * Get the description of a named EPICS variable.
-     *
-     * @param name the name of the variable
-     */
-    public static String getVariableDescription(final String name) {
-        return VARIABLES.get(name);
-    }
-
-    /**
-     * Get the static list of all available EPICs variable names.
-     * <p>
-     * This could be different than the variable names which were actually written into the collection header. For this,
-     * instead use the method {@link #getKeys()} method.
-     *
-     * @return the set of default EPICS variable names
-     */
-    public static Set<String> getVariableNames() {
-        return VARIABLES.keySet();
-    };
-
-    /**
      * <p>
      * Read data into this object from an LCIO event using the default collection name.
      * <p>
@@ -109,7 +49,7 @@
      * {@link org.lcsim.util.Driver#process(EventHeader)} method.
      *
      * @param event the LCIO event
-     * @return the EPICS data from the event
+     * @return the EPICS data from the event or null if none exists
      */
     public static EpicsData read(final EventHeader event) {
         if (event.hasCollection(GenericObject.class, EpicsData.DEFAULT_COLLECTION_NAME)) {
@@ -210,9 +150,6 @@
 
     /**
      * Get the list of EPICS variables used by this object.
-     * <p>
-     * This could potentially be different than the list of default names from {@link #getVariableNames()} as not all
-     * variables are included in every EPICS event.
      *
      * @return the list of used EPICS variable names
      */
@@ -243,14 +180,15 @@
      *
      * @param epicsHeader the {@link EpicsHeader} object
      */
-    void setEpicsHeader(final EpicsHeader epicsHeader) {
+    public void setEpicsHeader(final EpicsHeader epicsHeader) {
         this.epicsHeader = epicsHeader;
     }
 
     /**
-     * Set a double value by name.
-     *
-     * @return the value from the key
+     * Set a variable's value.
+     *
+     * @param name the name of the variable
+     * @param value the new value of the variable
      */
     public void setValue(final String name, final double value) {
         this.dataMap.put(name, value);
@@ -268,7 +206,7 @@
 
         newObject.setKeys(new String[this.dataMap.size()]);
         newObject.setValues(new double[this.dataMap.size()]);
-        
+
         int index = 0;
         for (final String key : this.dataMap.keySet()) {
             newObject.setKey(index, key);
@@ -278,10 +216,8 @@
 
         // Write header information into the object's int array.
         if (epicsHeader != null) {
-            final int[] headerData = new int[] {
-                    epicsHeader.getRun(), 
-                    epicsHeader.getSequence(),
-                    epicsHeader.getTimeStamp()};
+            final int[] headerData = new int[] {epicsHeader.getRun(), epicsHeader.getSequence(),
+                    epicsHeader.getTimestamp()};
             newObject.setHeaderData(headerData);
         }
         return newObject;

Modified: java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsEvioProcessor.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsEvioProcessor.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsEvioProcessor.java	Thu Aug 20 12:50:12 2015
@@ -50,7 +50,7 @@
         // Is this an EPICS event?
         if (EventTagConstant.EPICS.isEventTag(evio)) {
 
-            LOGGER.info("processing EPICS event " + evio.getEventNumber());
+            LOGGER.fine("processing EPICS event " + evio.getEventNumber());
 
             // Find the bank with the EPICS data string.
             final BaseStructure epicsBank = EvioBankTag.EPICS_STRING.findBank(evio);
@@ -73,7 +73,7 @@
 
             } else {
                 // This is an error because the string data bank should always be present in EPICS events.
-                final RuntimeException x = new RuntimeException("No EPICS data bank found in EPICS event.");
+                final RuntimeException x = new RuntimeException("No data bank found in EPICS event.");
                 LOGGER.log(Level.SEVERE, x.getMessage(), x);
                 throw x;
             }

Modified: java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsGenericObject.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsGenericObject.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsGenericObject.java	Thu Aug 20 12:50:12 2015
@@ -36,11 +36,23 @@
         return this.values[index];
     }
 
+    /**
+     * Dummy implementation.
+     *
+     * @param index the array index
+     * @return always returns 0
+     */
     @Override
     public float getFloatVal(final int index) {
         return 0;
     }
 
+    /**
+     * Get an int value which is used to store the EPICS header information.
+     *
+     * @param index the array index
+     * @return the int value at <code>index</code>
+     */
     @Override
     public int getIntVal(final int index) {
         return headerData[index];
@@ -75,11 +87,21 @@
         return this.values.length;
     }
 
+    /**
+     * Dummy implementation.
+     *
+     * @return always returns 0
+     */
     @Override
     public int getNFloat() {
         return 0;
     }
 
+    /**
+     * Get the number of int values which is the length of the data header.
+     *
+     * @return the number of int values
+     */
     @Override
     public int getNInt() {
         return this.headerData.length;

Modified: java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsHeader.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsHeader.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsHeader.java	Thu Aug 20 12:50:12 2015
@@ -1,7 +1,7 @@
 package org.hps.record.epics;
 
 /**
- * Representation of EPICs header data (run, sequence, time stamp).
+ * Representation of EPICs header data from the EVIO bank (run, sequence, time stamp).
  *
  * @author Jeremy McCormick, SLAC
  */
@@ -31,14 +31,15 @@
     private final int sequence;
 
     /**
-     * The time stamp in seconds (Unix).
+     * The unix time in seconds.
      */
     private final int timestamp;
 
     /**
      * Class constructor.
      * <p>
-     * The data array should be length 3 and usually will come from the int data of a <code>GenericObject</code>.
+     * The data array should be length 3 and usually will come from the int data of a <code>GenericObject</code>. In
+     * order, it should contain the run, sequence and timestamp values.
      *
      * @param data the header data with length 3
      */
@@ -74,7 +75,7 @@
      *
      * @return the time stamp
      */
-    public int getTimeStamp() {
+    public int getTimestamp() {
         return timestamp;
     }
 }

Copied: java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsRunProcessor.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EpicsLog.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EpicsLog.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/epics/EpicsRunProcessor.java	Thu Aug 20 12:50:12 2015
@@ -1,25 +1,19 @@
-package org.hps.record.evio.crawler;
+package org.hps.record.epics;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
-import org.hps.record.epics.EpicsData;
-import org.hps.record.epics.EpicsEvioProcessor;
 import org.hps.record.evio.EvioEventProcessor;
 import org.jlab.coda.jevio.EvioEvent;
 
 /**
- * Create a summary log of EPICS information found in EVIO events.
+ * Creates a list of EPICS data found in EVIO events across an entire job.
  *
  * @author Jeremy McCormick, SLAC
  */
-final class EpicsLog extends EvioEventProcessor {
-
-    /**
-     * A count of how many times a given EPICS variable is found in the input for computing the mean value across the
-     * entire run.
-     */
-    private final Map<String, Integer> counts = new HashMap<String, Integer>();
+public final class EpicsRunProcessor extends EvioEventProcessor {
 
     /**
      * The current EPICS data block from the EVIO events (last one that was found).
@@ -27,9 +21,11 @@
     private EpicsData currentEpicsData;
 
     /**
-     * The summary information for the variables computed from the mean values across the whole run.
+     * Collection of the EPICS data accumulated during the job.
+     * <p>
+     * A set is used here to avoid adding duplicate objects.
      */
-    private final EpicsData logData = new EpicsData();
+    private Set<EpicsData> epicsDataSet;
 
     /**
      * The processor for extracting the EPICS information from EVIO events.
@@ -39,31 +35,16 @@
     /**
      * Create an EPICs log.
      */
-    EpicsLog() {
+    public EpicsRunProcessor() {
     }
 
     /**
-     * End of job hook which computes the mean values for all EPICS variables found in the run.
+     * Get the EPICS data from the job.
+     *
+     * @return the EPICS data from the job
      */
-    @Override
-    public void endJob() {
-        System.out.println(this.logData);
-
-        // Compute means for all EPICS variables.
-        for (final String name : this.logData.getKeys()) {
-            final double total = this.logData.getValue(name);
-            final double mean = total / this.counts.get(name);
-            this.logData.setValue(name, mean);
-        }
-    }
-
-    /**
-     * Get the {@link org.hps.record.epics.EpicsData} which contains mean values for the run.
-     *
-     * @return the {@link org.hps.record.epics.EpicsData} for the run
-     */
-    EpicsData getEpicsData() {
-        return this.logData;
+    public List<EpicsData> getEpicsData() {
+        return new ArrayList<EpicsData>(this.epicsDataSet);
     }
 
     /**
@@ -71,33 +52,22 @@
      */
     @Override
     public void process(final EvioEvent evioEvent) {
+
+        // Call the processor that will load EPICS data if it exists in the event.
         this.processor.process(evioEvent);
         this.currentEpicsData = this.processor.getEpicsData();
-        this.update();
+
+        // Add EPICS data to the collection.
+        if (this.currentEpicsData != null) {
+            this.epicsDataSet.add(this.currentEpicsData);
+        }
     }
 
     /**
-     * Update state from the current EPICS data.
-     * <p>
-     * If the current data is <code>null</code>, this method does nothing.
+     * Start of job hook (reset the list of EPICS data).
      */
-    private void update() {
-        if (this.currentEpicsData != null) {
-            for (final String name : this.currentEpicsData.getKeys()) {
-                if (!this.logData.getKeys().contains(name)) {
-                    this.logData.setValue(name, 0.);
-                }
-                if (!this.counts.keySet().contains(name)) {
-                    this.counts.put(name, 0);
-                }
-                int count = this.counts.get(name);
-                count += 1;
-                this.counts.put(name, count);
-                final double value = this.logData.getValue(name) + this.currentEpicsData.getValue(name);
-                this.logData.setValue(name, value);
-                // System.out.println(name + " => added " + this.currentData.getValue(name) + "; total = " + value +
-                // "; mean = " + value / count);
-            }
-        }
+    @Override
+    public void startJob() {
+        epicsDataSet = new HashSet<EpicsData>();
     }
 }

Copied: java/trunk/record-util/src/main/java/org/hps/record/evio/EventCountProcessor.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EventCountProcessor.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EventCountProcessor.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EventCountProcessor.java	Thu Aug 20 12:50:12 2015
@@ -1,12 +1,8 @@
-package org.hps.record.evio.crawler;
+package org.hps.record.evio;
 
 import java.util.HashMap;
 import java.util.Map;
 
-import org.hps.record.evio.EventTagBitMask;
-import org.hps.record.evio.EventTagConstant;
-import org.hps.record.evio.EvioEventProcessor;
-import org.hps.record.evio.EvioEventUtilities;
 import org.jlab.coda.jevio.EvioEvent;
 
 /**

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioEventConstants.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioEventConstants.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioEventConstants.java	Thu Aug 20 12:50:12 2015
@@ -16,29 +16,16 @@
      * EPICS bank tag.
      */
     public static final int EPICS_BANK_TAG = 57620;
-    
-    
-    /**
-     * Tag of bank containing the EPICS data bank.
-     */
-    public static final int EPICS_PARENT_BANK_TAG = 129;
-
-    /**
-     * EPICS 20 second event tag.
-     */
-    // FIXME: This is unused and not handled in event processing.
-    public static final int EPICS_BANK_TAG_20s = -1;
-
-    /**
-     * EPICS 2 second event tag.
-     */
-    // FIXME: This is unused and not handled in event processing.
-    public static final int EPICS_BANK_TAG_2s = -1;
 
     /**
      * EPICS event tag.
      */
     public static final int EPICS_EVENT_TAG = 31;
+
+    /**
+     * Tag of bank containing the EPICS data bank.
+     */
+    public static final int EPICS_PARENT_BANK_TAG = 129;
 
     /**
      * Event ID bank tag.
@@ -58,7 +45,6 @@
     /**
      * Pause event tag.
      */
-    // FIXME: Not generally handled or used in event processing.
     public static final int PAUSE_EVENT_TAG = 19;
 
     /**

Copied: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileFilter.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileFilter.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileFilter.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileFilter.java	Thu Aug 20 12:50:12 2015
@@ -1,27 +1,29 @@
-package org.hps.record.evio.crawler;
+package org.hps.record.evio;
 
 import java.io.File;
 import java.io.FileFilter;
 
 /**
- * This is a simple file filter that will accept EVIO files with a certain convention to their naming which looks like
- * <i>FILENAME.evio.SEQUENCE</i>. This matches the convention used by the CODA DAQ software.
+ * This is a simple file filter that will accept EVIO files with a certain pattern to their file names:<br/>
+ * <i>FILENAME.evio.SEQUENCE</i>.
+ * <p>
+ * This matches the convention used by the CODA DAQ software.
  *
  * @author Jeremy McCormick, SLAC
  */
-final class EvioFileFilter implements FileFilter {
+public final class EvioFileFilter implements FileFilter {
 
     /**
-     * Return <code>true</code> if file is an EVIO file with correct file name convention.
+     * Return <code>true</code> if file is an EVIO file with the correct file naming convention.
      *
-     * @return <code>true</code> if file is an EVIO file with correct file name convention
+     * @return <code>true</code> if file is an EVIO file with correct file naming convention
      */
     @Override
     public boolean accept(final File pathname) {
         final boolean isEvio = pathname.getName().contains(".evio");
         boolean hasSeqNum = false;
         try {
-            EvioFileUtilities.getSequence(pathname);
+            EvioFileUtilities.getSequenceFromName(pathname);
             hasSeqNum = true;
         } catch (final Exception e) {
         }

Added: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaData.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaData.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaData.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,315 @@
+package org.hps.record.evio;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * Meta data that can be extracted from EVIO files.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public final class EvioFileMetaData {
+
+    /**
+     * The number of bad events in the file that are unreadable.
+     */
+    private int badEventCount;
+
+    /**
+     * The total number of bytes in the file.
+     */
+    private long byteCount;
+
+    /**
+     * The end date of the file which will be taken from the <i>END</i> event or the last physics event.
+     */
+    private Date endDate;
+
+    /**
+     * The last event number in the file.
+     */
+    private int endEvent;
+
+    /**
+     * The number of events in the file.
+     */
+    private int eventCount;
+
+    /**
+     * The EVIO file which is set at class construction time and cannot be changed.
+     */
+    private final File evioFile;
+
+    /**
+     * <code>true</code> if there is an <i>END</i> event in this file.
+     */
+    private boolean hasEnd;
+
+    /**
+     * <code>true</code> if there is a <i>PRESTART</i> event in this file.
+     */
+    private boolean hasPrestart;
+
+    /**
+     * The run number.
+     */
+    private int run;
+
+    /**
+     * The file sequence number.
+     */
+    private int sequence;
+
+    /**
+     * The start date which comes from the <i>PRESTART</i> event or the first physics event.
+     */
+    private Date startDate;
+
+    /**
+     * The first event number in the file.
+     */
+    private int startEvent;
+
+    /**
+     * Create a meta data object.
+     *
+     * @param evioFile the EVIO file to which the meta data applies
+     */
+    public EvioFileMetaData(final File evioFile) {
+        if (evioFile == null) {
+            throw new IllegalArgumentException("The EVIO file argument is null.");
+        }
+        if (!evioFile.exists()) {
+            throw new IllegalArgumentException("The file " + evioFile.getPath()
+                    + " does not exist or it is inaccessible.");
+        }
+        this.evioFile = evioFile;
+    }
+
+    /**
+     * Get the bad event count.
+     *
+     * @return the bad event count
+     */
+    public int getBadEventCount() {
+        return badEventCount;
+    }
+
+    /**
+     * Get the byte count.
+     *
+     * @return the byte count
+     */
+    public long getByteCount() {
+        return byteCount;
+    }
+
+    /**
+     * Get the end date.
+     *
+     * @return the end date
+     */
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    /**
+     * Get the end event number.
+     *
+     * @return the end event number
+     */
+    public int getEndEvent() {
+        return endEvent;
+    }
+
+    /**
+     * Get the number of events in the file (all types).
+     *
+     * @return the number of events in the file
+     */
+    public int getEventCount() {
+        return eventCount;
+    }
+
+    /**
+     * Get the EVIO file.
+     *
+     * @return the EVIO file
+     */
+    public File getEvioFile() {
+        return evioFile;
+    }
+
+    /**
+     * Get the run number.
+     *
+     * @return the run number
+     */
+    public Integer getRun() {
+        return run;
+    }
+
+    /**
+     * Get the file sequence number, numbered from 0.
+     *
+     * @return the file sequence number
+     */
+    public int getSequence() {
+        return sequence;
+    }
+
+    /**
+     * Get the start date.
+     *
+     * @return the start date
+     */
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    /**
+     * Get the first event number in the file.
+     *
+     * @return the first event number in the file
+     */
+    public int getStartEvent() {
+        return startEvent;
+    }
+
+    /**
+     * Return <code>true</code> if the file has an EVIO <i>END</i> event.
+     *
+     * @return <code>true</code> if the file has an EVIO <i>END</i> event
+     */
+    public boolean hasEnd() {
+        return hasEnd;
+    }
+
+    /**
+     * Return <code>true</code> if the file has an EVIO <i>PRESTART</i> event.
+     *
+     * @return <code>true</code> if the file has an EVIO <i>PRESTART</i> event
+     */
+    public boolean hasPrestart() {
+        return hasPrestart;
+    }
+
+    /**
+     * Set the bad event count.
+     *
+     * @param badEventCount the bad event count
+     */
+    void setBadEventCount(final int badEventCount) {
+        if (badEventCount < 0) {
+            throw new IllegalArgumentException("badEventCount");
+        }
+        this.badEventCount = badEventCount;
+    }
+
+    /**
+     * Set the byte count.
+     *
+     * @param byteCount the byte count
+     */
+    void setByteCount(final long byteCount) {
+        if (byteCount < 0) {
+            throw new IllegalArgumentException("byteCount");
+        }
+        this.byteCount = byteCount;
+    }
+
+    /**
+     * Set the end date.
+     *
+     * @param endDate the end date
+     */
+    void setEndDate(final Date endDate) {
+        this.endDate = endDate;
+    }
+
+    /**
+     * Set the end event number.
+     *
+     * @param endEvent the end event number
+     */
+    void setEndEvent(final int endEvent) {
+        this.endEvent = endEvent;
+    }
+
+    /**
+     * Set the event count.
+     *
+     * @param eventCount the event count
+     */
+    void setEventCount(final int eventCount) {
+        this.eventCount = eventCount;
+    }
+
+    /**
+     * Set whether the file has an EVIO <i>END</i> event.
+     *
+     * @param hasEnd <code>true</code> if file has an EVIO <i>END</i> event
+     */
+    void setHasEnd(final boolean hasEnd) {
+        this.hasEnd = hasEnd;
+    }
+
+    /**
+     * Set whether the file has an EVIO <i>PRESTART</i> event.
+     *
+     * @param hasPrestart <code>true</code> if file has an EVIO <i>PRESTART</i> event
+     */
+    void setHasPrestart(final boolean hasPrestart) {
+        this.hasPrestart = hasPrestart;
+    }
+
+    /**
+     * Set the run number.
+     *
+     * @param run the run number
+     */
+    void setRun(final int run) {
+        this.run = run;
+    }
+
+    /**
+     * Set the sequence number
+     *
+     * @param sequence the sequence number
+     */
+    void setSequence(final int sequence) {
+        this.sequence = sequence;
+    }
+
+    /**
+     * Set the start date.
+     *
+     * @param startDate the start date
+     */
+    void setStartDate(final Date startDate) {
+        this.startDate = startDate;
+    }
+
+    /**
+     * Set the start event number.
+     *
+     * @param startEvent the start event number
+     */
+    void setStartEvent(final int startEvent) {
+        this.startEvent = startEvent;
+    }
+
+    /**
+     * Convert this object to a human readable string.
+     *
+     * @return this object converted to a string
+     */
+    @Override
+    public String toString() {
+        return "EvioFileMetaData { evioFile: " + this.evioFile + ", startDate: " + this.startDate + "endDate: "
+                + this.endDate + "badEventCount: " + this.badEventCount + ", byteCount: " + this.byteCount
+                + ", eventCount: " + this.eventCount + ", hasPrestart: " + this.hasPrestart + ", hasEnd: "
+                + this.hasEnd + ", run: " + this.run + ", fileNumber: " + sequence + ", startEvent:  "
+                + this.startEvent + ", endEvent: " + endEvent + " }";
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaDataReader.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaDataReader.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileMetaDataReader.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,151 @@
+package org.hps.record.evio;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jlab.coda.jevio.BaseStructure;
+import org.jlab.coda.jevio.EvioEvent;
+import org.jlab.coda.jevio.EvioException;
+import org.jlab.coda.jevio.EvioReader;
+import org.lcsim.util.log.DefaultLogFormatter;
+import org.lcsim.util.log.LogUtil;
+
+/**
+ * Read {@link EvioFileMetaData} from an EVIO file.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class EvioFileMetaDataReader {
+
+    /**
+     * The class logger.
+     */
+    private static Logger LOGGER = LogUtil.create(EvioFileMetaDataReader.class, new DefaultLogFormatter(), Level.ALL);
+
+    /**
+     * Get meta data from the EVIO file.
+     *
+     * @param evioFile the EVIO file
+     * @return the file's meta data
+     */
+    public EvioFileMetaData getMetaData(final File evioFile) {
+
+        LOGGER.info("getting meta data for " + evioFile.getPath());
+
+        final EvioFileMetaData metaData = new EvioFileMetaData(evioFile);
+
+        EvioEvent evioEvent = null;
+        Date startDate = null;
+        Date endDate = null;
+        int badEventCount = 0;
+        int eventCount = 0;
+        int byteCount = 0;
+        boolean hasPrestart = false;
+        boolean hasEnd = false;
+        int[] headBankData = null;
+        int[] eventIdData = null;
+        Integer run = null;
+        Integer endEvent = null;
+        Integer startEvent = null;
+
+        final int fileNumber = EvioFileUtilities.getSequenceFromName(evioFile);
+        LOGGER.info("set file number to " + fileNumber);
+
+        try {
+            final EvioReader evioReader = EvioFileUtilities.open(evioFile, false);
+            LOGGER.getHandlers()[0].flush();
+            eventCount = evioReader.getEventCount();
+            while (true) {
+                try {
+                    evioEvent = evioReader.parseNextEvent();
+                    if (evioEvent == null) {
+                        break;
+                    }
+                } catch (final Exception e) {
+                    badEventCount++;
+                    LOGGER.log(Level.WARNING, "got bad EVIO event", e);
+                    continue;
+                }
+                byteCount += evioEvent.getTotalBytes();
+                if (EventTagConstant.PRESTART.isEventTag(evioEvent)) {
+                    LOGGER.info("found PRESTART");
+                    hasPrestart = true;
+                    final int[] controlEventData = EvioEventUtilities.getControlEventData(evioEvent);
+                    final long timestamp = controlEventData[0] * 1000L;
+                    startDate = new Date(timestamp);
+                    LOGGER.info("set start date to " + startDate);
+                    if (run == null) {
+                        run = controlEventData[1];
+                        LOGGER.info("set run to " + run);
+                    }
+                } else if (EventTagConstant.END.isEventTag(evioEvent)) {
+                    LOGGER.info("found END");
+                    hasEnd = true;
+                    final int[] controlEventData = EvioEventUtilities.getControlEventData(evioEvent);
+                    final long timestamp = controlEventData[0] * 1000L;
+                    endDate = new Date(timestamp);
+                    LOGGER.info("set end date to " + endDate);
+                    if (run == null) {
+                        run = controlEventData[1];
+                        LOGGER.info("set run to " + run);
+                    }
+                } else if (EventTagBitMask.PHYSICS.isEventTag(evioEvent)) {
+                    final BaseStructure headBank = EvioEventUtilities.getHeadBank(evioEvent);
+                    if (headBank != null) {
+                        headBankData = headBank.getIntData();
+                        if (startDate == null) {
+                            startDate = new Date(headBankData[3] * 1000);
+                            LOGGER.info("set start date to " + endDate + " from physics event");
+                        }
+                        if (run == null) {
+                            run = headBankData[1];
+                            LOGGER.info("set run to " + run + " from physics event");
+                        }
+                    }
+                    eventIdData = EvioEventUtilities.getEventIdData(evioEvent);
+                    if (eventIdData != null) {
+                        if (startEvent == null) {
+                            startEvent = eventIdData[0];
+                            LOGGER.info("set start event " + startEvent);
+                        }
+                    }
+                }
+                LOGGER.getHandlers()[0].flush();
+            }
+
+            if (endDate == null) {
+                if (headBankData != null) {
+                    endDate = new Date(headBankData[3] * 1000);
+                    LOGGER.info("set end date to " + endDate + " from last head bank");
+                }
+            }
+            if (eventIdData != null) {
+                endEvent = eventIdData[0];
+                LOGGER.info("set end event " + endEvent);
+            }
+        } catch (EvioException | IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        metaData.setStartDate(startDate);
+        metaData.setEndDate(endDate);
+        metaData.setBadEventCount(badEventCount);
+        metaData.setByteCount(byteCount);
+        metaData.setEventCount(eventCount);
+        metaData.setHasPrestart(hasPrestart);
+        metaData.setHasEnd(hasEnd);
+        metaData.setRun(run);
+        metaData.setSequence(fileNumber);
+        if (endEvent != null) {
+            metaData.setEndEvent(endEvent);
+        }
+        if (startEvent != null) {
+            metaData.setStartEvent(startEvent);
+        }
+
+        return metaData;
+    }
+}

Copied: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSequenceComparator.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileSequenceComparator.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileSequenceComparator.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSequenceComparator.java	Thu Aug 20 12:50:12 2015
@@ -1,4 +1,4 @@
-package org.hps.record.evio.crawler;
+package org.hps.record.evio;
 
 import java.io.File;
 import java.util.Comparator;
@@ -8,7 +8,7 @@
  *
  * @author Jeremy McCormick, SLAC
  */
-final class EvioFileSequenceComparator implements Comparator<File> {
+public final class EvioFileSequenceComparator implements Comparator<File> {
 
     /**
      * Compare two EVIO files by their sequence numbers.
@@ -17,8 +17,8 @@
      */
     @Override
     public int compare(final File o1, final File o2) {
-        final Integer sequenceNumber1 = EvioFileUtilities.getSequence(o1);
-        final Integer sequenceNumber2 = EvioFileUtilities.getSequence(o2);
+        final Integer sequenceNumber1 = EvioFileUtilities.getSequenceFromName(o1);
+        final Integer sequenceNumber2 = EvioFileUtilities.getSequenceFromName(o2);
         return sequenceNumber1.compareTo(sequenceNumber2);
     }
 }

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSource.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSource.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileSource.java	Thu Aug 20 12:50:12 2015
@@ -12,12 +12,12 @@
 import org.jlab.coda.jevio.EvioReader;
 
 /**
- * A basic implementation of an <tt>AbstractRecordSource</tt> for supplying <tt>EvioEvent</tt> objects to a loop from
- * EVIO files.
+ * A basic implementation of an <tt>AbstractRecordSource</tt> for supplying <tt>EvioEvent</tt> objects to a loop from a
+ * list of EVIO files.
  * <p>
  * Unlike the LCIO record source, it has no rewind or indexing capabilities.
  *
- * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
+ * @author Jeremy McCormick, SLAC
  */
 public final class EvioFileSource extends AbstractRecordSource {
 
@@ -48,7 +48,7 @@
      */
     public EvioFileSource(final File file) {
         this.files.add(file);
-        openReader();
+        this.openReader();
     }
 
     /**
@@ -58,7 +58,7 @@
      */
     public EvioFileSource(final List<File> files) {
         this.files.addAll(files);
-        openReader();
+        this.openReader();
     }
 
     /**
@@ -89,6 +89,15 @@
     @Override
     public Object getCurrentRecord() throws IOException {
         return this.currentEvent;
+    }
+
+    /**
+     * Get the list of files.
+     *
+     * @return the list of files
+     */
+    public List<File> getFiles() {
+        return this.files;
     }
 
     /**
@@ -130,10 +139,10 @@
                 throw new IOException(e);
             }
             if (this.currentEvent == null) {
-                closeReader();
+                this.closeReader();
                 this.fileIndex++;
-                if (!endOfFiles()) {
-                    openReader();
+                if (!this.endOfFiles()) {
+                    this.openReader();
                     continue;
                 } else {
                     throw new NoSuchRecordException();
@@ -151,7 +160,7 @@
     private void openReader() {
         try {
             System.out.println("Opening reader for file " + this.files.get(this.fileIndex) + " ...");
-            this.reader = new EvioReader(this.files.get(this.fileIndex), false,true);
+            this.reader = new EvioReader(this.files.get(this.fileIndex), false, true);
             System.out.println("Done opening file.");
         } catch (EvioException | IOException e) {
             throw new RuntimeException(e);
@@ -167,4 +176,5 @@
     public boolean supportsNext() {
         return true;
     }
+
 }

Copied: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileUtilities.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileUtilities.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileUtilities.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioFileUtilities.java	Thu Aug 20 12:50:12 2015
@@ -1,26 +1,24 @@
-package org.hps.record.evio.crawler;
+package org.hps.record.evio;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 import java.util.logging.Logger;
 
-import org.hps.record.evio.EvioEventConstants;
-import org.hps.record.evio.EvioEventUtilities;
-import org.jlab.coda.jevio.BaseStructure;
-import org.jlab.coda.jevio.EvioEvent;
 import org.jlab.coda.jevio.EvioException;
 import org.jlab.coda.jevio.EvioReader;
 import org.lcsim.util.log.LogUtil;
 
 /**
- * A miscellaneous collection of EVIO file utility methods used by the crawler package.
+ * A miscellaneous collection of EVIO file utility methods.
  *
  * @author Jeremy McCormick, SLAC
  */
-final class EvioFileUtilities {
+public final class EvioFileUtilities {
 
     /**
-     * Setup logger.
+     * Setup class logger.
      */
     private static final Logger LOGGER = LogUtil.create(EvioFileUtilities.class);
 
@@ -36,7 +34,7 @@
      * @return the cached file path (prepends "/cache" to the path)
      * @throws IllegalArgumentException if the file is not on the MSS (e.g. path does not start with "/mss")
      */
-    static File getCachedFile(final File file) {
+    public static File getCachedFile(final File file) {
         if (!isMssFile(file)) {
             throw new IllegalArgumentException("File " + file.getPath() + " is not on the JLab MSS.");
         }
@@ -47,80 +45,13 @@
     }
 
     /**
-     * Get the end date
-     *
-     * @param evioReader the <code>EvioReader</code>
-     * @return the run end date
-     */
-    static Long getEndTimestamp(final EvioReader evioReader) {
-
-        // Date endDate = null;
-        Long timestamp = null;
-
-        try {
-            // Search for the last physics event in the last 5 events of the file.
-            System.out.println("going to event " + (evioReader.getEventCount() - 5) + " / "
-                    + evioReader.getEventCount() + " to find end date");
-            evioReader.gotoEventNumber(evioReader.getEventCount() - 5);
-            EvioEvent evioEvent = null;
-            EvioEvent lastPhysicsEvent = null;
-
-            // Find last physics event.
-            while ((evioEvent = evioReader.parseNextEvent()) != null) {
-                if (EvioEventUtilities.isPhysicsEvent(evioEvent)) {
-                    lastPhysicsEvent = evioEvent;
-                }
-            }
-
-            // If there is no physics event found this is an error.
-            if (lastPhysicsEvent == null) {
-                throw new RuntimeException("No physics event found.");
-            }
-
-            // Get the timestamp from the head bank of the physics event.
-            LOGGER.info("getting head bank date from " + lastPhysicsEvent.getEventNumber());
-            final Long eventTimestamp = getHeadBankTimestamp(lastPhysicsEvent);
-            if (eventTimestamp != null) {
-                LOGGER.info("found end timestamp " + eventTimestamp);
-                timestamp = eventTimestamp;
-            } else {
-                throw new RuntimeException("No timestamp found in head bank.");
-            }
-
-        } catch (EvioException | IOException e) {
-            throw new RuntimeException(e);
-        }
-        return timestamp;
-    }
-
-    /**
-     * Get the date from the head bank.
-     *
-     * @param event the EVIO file
-     * @return the date from the head bank or null if not found
-     */
-    static Long getHeadBankTimestamp(final EvioEvent event) {
-        // Date date = null;
-        Long timestamp = null;
-        final BaseStructure headBank = EvioEventUtilities.getHeadBank(event);
-        if (headBank != null) {
-            final int[] data = headBank.getIntData();
-            final long time = data[3];
-            if (time != 0L) {
-                timestamp = time * MILLISECONDS;
-            }
-        }
-        return timestamp;
-    }
-
-    /**
      * Get the run number from the file name.
      *
      * @param file the EVIO file
      * @return the run number
      * @throws Exception if there is a problem parsing out the run number
      */
-    static Integer getRun(final File file) {
+    public static Integer getRunFromName(final File file) {
         final String name = file.getName();
         final int startIndex = name.lastIndexOf("_") + 1;
         final int endIndex = name.indexOf(".");
@@ -134,65 +65,9 @@
      * @return the file's sequence number
      * @throws Exception if there is an error parsing out the sequence number
      */
-    static Integer getSequence(final File file) {
+    public static Integer getSequenceFromName(final File file) {
         final String name = file.getName();
         return Integer.parseInt(name.substring(name.lastIndexOf(".") + 1));
-    }
-
-    /**
-     * Get the start date from the first physics event.
-     *
-     * @param evioReader the <code>EvioReader</code>
-     * @return the run start date
-     */
-    static Long getStartTimestamp(final EvioReader evioReader) {
-
-        Long timestamp = null;
-
-        // Read events until there is a physics event and return its timestamp.
-        try {
-            EvioEvent event = null;
-            while ((event = evioReader.parseNextEvent()) != null) {
-                if (EvioEventUtilities.isPhysicsEvent(event)) {
-                    if ((timestamp = getHeadBankTimestamp(event)) != null) {
-                        break;
-                    }
-                }
-            }
-        } catch (EvioException | IOException e) {
-            throw new RuntimeException(e);
-        }
-        return timestamp;
-    }
-
-    /**
-     * Get the date from the control bank of an EVIO event.
-     *
-     * @param file the EVIO file
-     * @param eventTag the event tag on the bank
-     * @param gotoEvent an event to start the scanning
-     * @return the control bank date or null if not found
-     */
-    static Long getTimestamp(final EvioReader evioReader, final int eventTag, final int gotoEvent) {
-        Long timestamp = null;
-        try {
-            EvioEvent evioEvent;
-            if (gotoEvent > 0) {
-                evioReader.gotoEventNumber(gotoEvent);
-            } else if (gotoEvent < 0) {
-                evioReader.gotoEventNumber(evioReader.getEventCount() + gotoEvent);
-            }
-            while ((evioEvent = evioReader.parseNextEvent()) != null) {
-                if (evioEvent.getHeader().getTag() == eventTag) {
-                    final int[] data = EvioEventUtilities.getControlEventData(evioEvent);
-                    timestamp = (long) (data[0] * MILLISECONDS);
-                    break;
-                }
-            }
-        } catch (EvioException | IOException e) {
-            throw new RuntimeException(e);
-        }
-        return timestamp;
     }
 
     /**
@@ -201,35 +76,8 @@
      * @param file the file
      * @return <code>true</code> if the file is a cached file
      */
-    static boolean isCachedFile(final File file) {
+    public static boolean isCachedFile(final File file) {
         return file.getPath().startsWith("/cache");
-    }
-
-    /**
-     * Return <code>true</code> if a valid CODA <i>END</i> event can be located in the <code>EvioReader</code>'s current
-     * file.
-     *
-     * @param reader the EVIO reader
-     * @return <code>true</code> if valid END event is located
-     * @throws Exception if there are IO problems using the reader
-     */
-    static boolean isEndOkay(final EvioReader reader) throws Exception {
-        LOGGER.info("checking is END okay ...");
-
-        boolean endOkay = false;
-
-        // Go to second to last event for searching.
-        reader.gotoEventNumber(reader.getEventCount() - 2);
-
-        // Look for END event.
-        EvioEvent event = null;
-        while ((event = reader.parseNextEvent()) != null) {
-            if (event.getHeader().getTag() == EvioEventConstants.END_EVENT_TAG) {
-                endOkay = true;
-                break;
-            }
-        }
-        return endOkay;
     }
 
     /**
@@ -238,7 +86,7 @@
      * @param file the file
      * @return <code>true</code> if the file is on the MSS
      */
-    static boolean isMssFile(final File file) {
+    public static boolean isMssFile(final File file) {
         return file.getPath().startsWith("/mss");
     }
 
@@ -250,7 +98,7 @@
      * @throws IOException if there is an IO problem
      * @throws EvioException if there is an error reading the EVIO data
      */
-    static EvioReader open(final File file) throws IOException, EvioException {
+    public static EvioReader open(final File file) throws IOException, EvioException {
         return open(file, false);
     }
 
@@ -263,7 +111,7 @@
      * @throws IOException if there is an IO problem
      * @throws EvioException if there is an error reading the EVIO data
      */
-    static EvioReader open(final File file, final boolean sequential) throws IOException, EvioException {
+    public static EvioReader open(final File file, final boolean sequential) throws IOException, EvioException {
         File openFile = file;
         if (isMssFile(file)) {
             openFile = getCachedFile(file);
@@ -284,12 +132,21 @@
      * @throws IOException if there is an IO problem
      * @throws EvioException if there is an error reading the EVIO data
      */
-    static EvioReader open(final String path) throws IOException, EvioException {
+    public static EvioReader open(final String path) throws IOException, EvioException {
         return open(new File(path), false);
     }
 
     /**
-     * Present class instantiation.
+     * Sort a list of EVIO files by their sequence numbers.
+     *
+     * @param evioFileList the list of files to sort
+     */
+    public static void sortBySequence(final List<File> evioFileList) {
+        Collections.sort(evioFileList, new EvioFileSequenceComparator());
+    }
+
+    /**
+     * Prevent class instantiation.
      */
     private EvioFileUtilities() {
     }

Added: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoop.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoop.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoop.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,58 @@
+package org.hps.record.evio;
+
+import org.freehep.record.loop.DefaultRecordLoop;
+
+/**
+ * Implementation of a Freehep <code>RecordLoop</code> for EVIO data.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class EvioLoop extends DefaultRecordLoop {
+
+    /**
+     * The record adapter.
+     */
+    private final EvioLoopAdapter adapter = new EvioLoopAdapter();
+
+    /**
+     * Create a new record loop.
+     */
+    public EvioLoop() {
+        this.addLoopListener(adapter);
+        this.addRecordListener(adapter);
+    }
+
+    /**
+     * Add an EVIO event processor to the adapter which will be activated for every EVIO event that is processed.
+     *
+     * @param evioEventProcessor the EVIO processor to add
+     */
+    public void addEvioEventProcessor(final EvioEventProcessor evioEventProcessor) {
+        adapter.addEvioEventProcessor(evioEventProcessor);
+    }
+
+    /**
+     * Loop over events from the source.
+     *
+     * @param number the number of events to process or -1L for all events from the source
+     * @return the number of records that were processed
+     */
+    public long loop(final long number) {
+        if (number < 0L) {
+            this.execute(Command.GO, true);
+        } else {
+            this.execute(Command.GO_N, number, true);
+            this.execute(Command.STOP);
+        }
+        return this.getSupplied();
+    }
+
+    /**
+     * Set the EVIO data source.
+     *
+     * @param evioFileSource the EVIO data source
+     */
+    public void setEvioFileSource(final EvioFileSource evioFileSource) {
+        this.setRecordSource(evioFileSource);
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoopAdapter.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoopAdapter.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/EvioLoopAdapter.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,96 @@
+package org.hps.record.evio;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.freehep.record.loop.AbstractLoopListener;
+import org.freehep.record.loop.LoopEvent;
+import org.freehep.record.loop.RecordEvent;
+import org.freehep.record.loop.RecordListener;
+import org.jlab.coda.jevio.EvioEvent;
+import org.lcsim.util.log.DefaultLogFormatter;
+import org.lcsim.util.log.LogUtil;
+
+/**
+ * A loop adapter for the {@link EvioLoop} which manages and activates a list of {@link EvioEventProcessor} objects.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public final class EvioLoopAdapter extends AbstractLoopListener implements RecordListener {
+
+    /**
+     * Setup class logger.
+     */
+    private final Logger LOGGER = LogUtil.create(EvioLoopAdapter.class, new DefaultLogFormatter(), Level.ALL);
+
+    /**
+     * List of event processors to activate.
+     */
+    private final List<EvioEventProcessor> processors = new ArrayList<EvioEventProcessor>();
+
+    /**
+     * Create a new loop adapter.
+     */
+    EvioLoopAdapter() {
+    }
+
+    /**
+     * Add an EVIO processor to the adapter.
+     *
+     * @param processor the EVIO processor to add to the adapter
+     */
+    void addEvioEventProcessor(final EvioEventProcessor processor) {
+        LOGGER.info("adding " + processor.getClass().getName() + " to EVIO processors");
+        this.processors.add(processor);
+    }
+
+    /**
+     * Implementation of the finish hook which activates the {@link EvioEventProcessor#endJob()} method of all
+     * registered processors.
+     */
+    @Override
+    protected void finish(final LoopEvent event) {
+        LOGGER.info("executing finish hook");
+        for (final EvioEventProcessor processor : processors) {
+            processor.endJob();
+        }
+    }
+
+    /**
+     * Primary event processing method that activates the {@link EvioEventProcessor#process(EvioEvent)} method of all
+     * registered processors.
+     *
+     * @param recordEvent the record event to process which should have an EVIO event
+     * @throws IllegalArgumentException if the record is the wrong type
+     */
+    @Override
+    public void recordSupplied(final RecordEvent recordEvent) {
+        final Object record = recordEvent.getRecord();
+        if (record instanceof EvioEvent) {
+            final EvioEvent evioEvent = EvioEvent.class.cast(record);
+            for (final EvioEventProcessor processor : processors) {
+                try {
+                    processor.process(evioEvent);
+                } catch (final Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        } else {
+            throw new IllegalArgumentException("The supplied record has the wrong type: " + record.getClass());
+        }
+    }
+
+    /**
+     * Implementation of the start hook which activates the {@link EvioEventProcessor#startJob()} method of all
+     * registered processors.
+     */
+    @Override
+    protected void start(final LoopEvent event) {
+        LOGGER.info("executing start hook");
+        for (final EvioEventProcessor processor : processors) {
+            processor.startJob();
+        }
+    }
+}

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/Crawler.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/Crawler.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/Crawler.java	Thu Aug 20 12:50:12 2015
@@ -7,6 +7,7 @@
 import java.nio.file.attribute.BasicFileAttributes;
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.HashSet;
@@ -20,6 +21,8 @@
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.cli.PosixParser;
 import org.hps.conditions.database.ConnectionParameters;
+import org.hps.record.run.RunSummary;
+import org.hps.record.run.RunSummaryDaoImpl;
 import org.lcsim.util.log.DefaultLogFormatter;
 import org.lcsim.util.log.LogUtil;
 
@@ -50,20 +53,20 @@
      * Statically define the command options.
      */
     static {
-        OPTIONS.addOption("a", "runs", true, "list of run numbers to accept (others will be excluded)");
+        // TODO: add -f argument with file name to include; others would be excluded if they do not match
         OPTIONS.addOption("b", "min-date", true, "min date for a file (example \"2015-03-26 11:28:59\")");
         OPTIONS.addOption("c", "cache", false, "automatically cache files from MSS to cache disk (JLAB only)");
         OPTIONS.addOption("C", "connection-properties", true, "database connection properties file (required)");
         OPTIONS.addOption("d", "directory", true, "root directory to start crawling (default is current dir)");
         OPTIONS.addOption("E", "evio-processor", true, "class name of an EvioEventProcessor to execute");
         OPTIONS.addOption("h", "help", false, "print help and exit (overrides all other arguments)");
-        OPTIONS.addOption("m", "max-files", true, "max number of files to process per run (mostly for debugging)");
-        OPTIONS.addOption("p", "print", true, "set event printing interval during EVIO processing");
-        OPTIONS.addOption("r", "insert", false, "insert information into the run database (not done by default)");
+        OPTIONS.addOption("i", "insert", false, "insert information into the run database (not done by default)");
+        OPTIONS.addOption("L", "log-level", true, "set the log level (INFO, FINE, etc.)");
+        OPTIONS.addOption("r", "run", true, "add a run number to accept (when used others will be excluded)");
         OPTIONS.addOption("t", "timestamp-file", true, "existing or new timestamp file name");
         OPTIONS.addOption("w", "max-cache-wait", true, "total time to allow for file caching (seconds)");
-        OPTIONS.addOption("L", "log-level", true, "set the log level (INFO, FINE, etc.)");
-        OPTIONS.addOption("u", "update", false, "allow overriding existing data in the run db (not allowed by default)");
+        OPTIONS.addOption("u", "update", false,
+                "allow replacement of existing data in the run db (not allowed by default)");
         OPTIONS.addOption("x", "max-depth", true, "max depth to crawl in the directory tree");
     }
 
@@ -179,9 +182,9 @@
             }
 
             // List of one or more runs to accept in the job.
-            if (cl.hasOption("a")) {
+            if (cl.hasOption("r")) {
                 final Set<Integer> acceptRuns = new HashSet<Integer>();
-                for (final String runString : cl.getOptionValues("a")) {
+                for (final String runString : cl.getOptionValues("r")) {
                     final Integer acceptRun = Integer.parseInt(runString);
                     acceptRuns.add(acceptRun);
                     LOGGER.config("added run filter " + acceptRun);
@@ -189,7 +192,7 @@
                 config.setAcceptRuns(acceptRuns);
             }
 
-            // Enable run log updating (off by default).
+            // Enable updating of run database.
             if (cl.hasOption("r")) {
                 config.setUpdateRunLog(true);
                 LOGGER.config("inserting into run database is enabled");
@@ -208,21 +211,7 @@
                 LOGGER.config("max time for file caching set to " + config.waitTime());
             }
 
-            // Max files to process per run; mostly just here for debugging purposes.
-            if (cl.hasOption("m")) {
-                final int maxFiles = Integer.parseInt(cl.getOptionValue("m"));
-                config.setMaxFiles(maxFiles);
-                LOGGER.config("max files set to " + maxFiles);
-            }
-
-            // Event printing interval when doing EVIO event processing.
-            if (cl.hasOption("p")) {
-                final int eventPrintInterval = Integer.parseInt(cl.getOptionValue("p"));
-                config.setEventPrintInterval(eventPrintInterval);
-                LOGGER.config("event print interval set to " + eventPrintInterval);
-            }
-
-            // Flag to allow replacement of existing records in the database; not allowed by default.
+            // Allow deletion and replacement of records in run database.
             if (cl.hasOption("u")) {
                 config.setAllowUpdates(true);
                 LOGGER.config("deletion and replacement of existing runs in the database is enabled");
@@ -267,7 +256,13 @@
             throw new RuntimeException("Error parsing options.", e);
         }
 
+        // Configure the max wait time for file caching operations.
+        if (config.waitTime() != null && config.waitTime() > 0L) {
+            cacheManager.setWaitTime(config.waitTime());
+        }
+
         LOGGER.info("done parsing command line options");
+        LOGGER.getHandlers()[0].flush();
 
         return this;
     }
@@ -293,31 +288,32 @@
         LOGGER.info("running Crawler job");
 
         // Create the file visitor for crawling the root directory with the given date filter.
+        LOGGER.info("creating file visitor");
+        LOGGER.getHandlers()[0].flush();
         final EvioFileVisitor visitor = new EvioFileVisitor(config.timestamp());
 
         // Walk the file tree using the visitor.
+        LOGGER.info("walking the dir tree");
+        LOGGER.getHandlers()[0].flush();
         this.walk(visitor);
 
         // Get the list of run data created by the visitor.
-        final RunLog runs = visitor.getRunLog();
-
-        // Print the run numbers that were found.
-        runs.printRunNumbers();
-
-        // Sort the files on their sequence numbers.
-        runs.sortFiles();
+        final RunSummaryMap runs = visitor.getRunMap();
 
         // Process all the files, performing caching from the MSS if necessary.
+        LOGGER.info("processing all runs");
         RunProcessor.processAllRuns(this.cacheManager, runs, config);
-
-        // Print the summary information after the run processing is done.
-        runs.printRunSummaries();
+        LOGGER.getHandlers()[0].flush();
 
         // Execute the run database update.
+        LOGGER.info("updating run database");
         this.updateRunDatabase(runs);
+        LOGGER.getHandlers()[0].flush();
 
         // Update the timestamp output file.
+        LOGGER.info("updating the timestamp");
         this.updateTimestamp();
+        LOGGER.getHandlers()[0].flush();
 
         LOGGER.info("Crawler job is done!");
     }
@@ -328,21 +324,26 @@
      * @param runs the list of runs to update
      * @throws SQLException if there is a database query error
      */
-    private void updateRunDatabase(final RunLog runs) throws SQLException {
+    private void updateRunDatabase(final RunSummaryMap runs) throws SQLException {
         // Insert the run information into the database.
         if (config.updateRunLog()) {
 
+            LOGGER.info("updating run database");
+
             // Open a DB connection.
             final Connection connection = config.connectionParameters().createConnection();
 
-            // Create and configure RunLogUpdater which updates the run log for all runs found in the crawl job.
-            final RunLogUpdater runUpdater = new RunLogUpdater(connection, runs, config.allowUpdates());
-
-            // Update the DB.
-            runUpdater.insert();
+            // Insert all run summaries into the database.
+            new RunSummaryDaoImpl(connection).insertFullRunSummaries(new ArrayList<RunSummary>(runs.getRunSummaries()),
+                    config.allowUpdates());
 
             // Close the DB connection.
             connection.close();
+
+            LOGGER.info("done updating run database");
+
+        } else {
+            LOGGER.info("run database will not be updated");
         }
     }
 

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/CrawlerConfig.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/CrawlerConfig.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/CrawlerConfig.java	Thu Aug 20 12:50:12 2015
@@ -13,7 +13,7 @@
 import org.hps.record.evio.EvioEventProcessor;
 
 /**
- * Full configuration information for the {@link Crawler class}.
+ * Full configuration information for the {@link Crawler} class.
  * <p>
  * Method chaining of setters is supported.
  *
@@ -38,19 +38,9 @@
     private boolean allowUpdates = false;
 
     /**
-     * The database connection parameters which must be provided by command line argument.
+     * The database connection parameters which must be provided by a command line argument.
      */
     private ConnectionParameters connectionParameters;
-
-    /**
-     * Default event print interval.
-     */
-    private final int DEFAULT_EVENT_PRINT_INTERVAL = 1000;
-
-    /**
-     * Interval for printing out event number while running EVIO processors.
-     */
-    private int eventPrintInterval = DEFAULT_EVENT_PRINT_INTERVAL;
 
     /**
      * The maximum depth to crawl.
@@ -151,15 +141,6 @@
     }
 
     /**
-     * Get the event print interval.
-     *
-     * @return the event print interval
-     */
-    int eventPrintInterval() {
-        return this.eventPrintInterval;
-    }
-
-    /**
      * Get the max depth in the directory tree to crawl.
      *
      * @return the max depth
@@ -231,17 +212,6 @@
     }
 
     /**
-     * Set the interval for printing the EVIO event numbers during processing.
-     *
-     * @param eventPrintInterval the event print interval
-     * @return this object
-     */
-    CrawlerConfig setEventPrintInterval(final int eventPrintInterval) {
-        this.eventPrintInterval = eventPrintInterval;
-        return this;
-    }
-
-    /**
      * Set the max depth.
      *
      * @param maxDepth the max depth

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileVisitor.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileVisitor.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/EvioFileVisitor.java	Thu Aug 20 12:50:12 2015
@@ -12,11 +12,13 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.hps.record.evio.EvioFileFilter;
+import org.hps.record.evio.EvioFileUtilities;
 import org.lcsim.util.log.DefaultLogFormatter;
 import org.lcsim.util.log.LogUtil;
 
 /**
- * A file visitor that crawls directories for EVIO files and returns the information as a {@link RunLog}.
+ * A file visitor that crawls directories for EVIO files and returns the information as a {@link RunSummaryMap}.
  *
  * @author Jeremy McCormick, SLAC
  */
@@ -35,8 +37,8 @@
     /**
      * The run log containing information about files from each run.
      */
-    private final RunLog runs = new RunLog();
-
+    private final RunSummaryMap runs = new RunSummaryMap();
+    
     /**
      * Create a new file visitor.
      *
@@ -83,7 +85,7 @@
      *
      * @return the run log
      */
-    RunLog getRunLog() {
+    RunSummaryMap getRunMap() {
         return this.runs;
     }
 
@@ -99,10 +101,10 @@
         if (this.accept(file)) {
 
             // Get the run number from the file name.
-            final Integer run = EvioFileUtilities.getRun(file);
+            final Integer run = EvioFileUtilities.getRunFromName(file);
 
             // Get the sequence number from the file name.
-            final Integer seq = EvioFileUtilities.getSequence(file);
+            final Integer seq = EvioFileUtilities.getSequenceFromName(file);
 
             LOGGER.info("accepted file " + file.getPath() + " with run " + run + " and seq " + seq);
 

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/JCacheManager.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/JCacheManager.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/JCacheManager.java	Thu Aug 20 12:50:12 2015
@@ -13,6 +13,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.hps.record.evio.EvioFileUtilities;
 import org.jdom.Document;
 import org.jdom.Element;
 import org.jdom.input.SAXBuilder;

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunFilter.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunFilter.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunFilter.java	Thu Aug 20 12:50:12 2015
@@ -3,6 +3,8 @@
 import java.io.File;
 import java.io.FileFilter;
 import java.util.Set;
+
+import org.hps.record.evio.EvioFileUtilities;
 
 /**
  * A filter which rejects files with run numbers not in a specified set.
@@ -36,6 +38,6 @@
      */
     @Override
     public boolean accept(final File file) {
-        return this.acceptRuns.contains(EvioFileUtilities.getRun(file));
+        return this.acceptRuns.contains(EvioFileUtilities.getRunFromName(file));
     }
 }

Modified: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunProcessor.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunProcessor.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunProcessor.java	Thu Aug 20 12:50:12 2015
@@ -1,19 +1,16 @@
 package org.hps.record.evio.crawler;
 
 import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.hps.record.evio.EvioEventConstants;
-import org.hps.record.evio.EvioEventProcessor;
+import org.hps.record.epics.EpicsRunProcessor;
+import org.hps.record.evio.EvioFileMetaData;
+import org.hps.record.evio.EvioFileMetaDataReader;
+import org.hps.record.evio.EvioFileSource;
+import org.hps.record.evio.EvioLoop;
 import org.hps.record.run.RunSummary;
 import org.hps.record.scalers.ScalersEvioProcessor;
-import org.jlab.coda.jevio.EvioEvent;
-import org.jlab.coda.jevio.EvioException;
-import org.jlab.coda.jevio.EvioReader;
 import org.lcsim.util.log.DefaultLogFormatter;
 import org.lcsim.util.log.LogUtil;
 
@@ -40,35 +37,24 @@
      * @param runs the run log containing the list of run summaries
      * @throws Exception if there is an error processing one of the runs
      */
-    static void processAllRuns(final JCacheManager cacheManager, final RunLog runs, final CrawlerConfig config)
+    static void processAllRuns(final JCacheManager cacheManager, final RunSummaryMap runs, final CrawlerConfig config)
             throws Exception {
 
-        // Configure the max wait time for file caching operations.
-        if (config.waitTime() != null && config.waitTime() > 0L) {
-            cacheManager.setWaitTime(config.waitTime());
-        }
-
         // Process all of the runs that were found.
-        for (final int run : runs.getSortedRunNumbers()) {
-
-            // Get the run summary.
-            final RunSummary runSummary = runs.getRunSummary(run);
+        for (final RunSummary runSummary : runs.getRunSummaries()) {
 
             // Clear the cache manager.
             if (config.useFileCache()) {
+                LOGGER.info("clearing file cache");
                 cacheManager.clear();
             }
 
             // Create a processor to process all the EVIO events in the run.
+            LOGGER.info("creating run processor for " + runSummary.getRun());
             final RunProcessor runProcessor = new RunProcessor(cacheManager, runSummary, config);
 
-            // Add extra processors.
-            for (final EvioEventProcessor processor : config.processors()) {
-                runProcessor.addProcessor(processor);
-                LOGGER.config("added extra EVIO processor " + processor.getClass().getName());
-            }
-
             // Process all of the run's files.
+            LOGGER.info("processing run " + runSummary.getRun());
             runProcessor.processRun();
         }
     }
@@ -81,27 +67,17 @@
     /**
      * Processor for extracting EPICS information.
      */
-    private final EpicsLog epicsLog;
-
-    /**
-     * Processor for extracting event type counts (sync, physics, trigger types, etc.).
-     */
-    private final EventCountProcessor eventCountProcessor;
-
-    /**
-     * The event printing interval when processing EVIO files.
-     */
-    private int eventPrintInterval = 1000;
-
-    /**
-     * Max total files to read (default is unlimited).
-     */
-    private int maxFiles = -1;
-
-    /**
-     * The list of EVIO processors to run on the files that are found.
-     */
-    private final List<EvioEventProcessor> processors = new ArrayList<EvioEventProcessor>();
+    private final EpicsRunProcessor epicsLog;
+
+    /**
+     * The data source with the list of EVIO files to process.
+     */
+    private final EvioFileSource evioFileSource;
+
+    /**
+     * The EVIO event processing loop.
+     */
+    private final EvioLoop evioLoop = new EvioLoop();
 
     /**
      * The run summary information updated by running this processor.
@@ -119,9 +95,9 @@
     private boolean useFileCache;
 
     /**
-     * Create the processor for a single run.
-     *
-     * @param runSummary the run summary for the run
+     * Create a run processor.
+     *
+     * @param runSummary the run summary object for the run
      * @return the run processor
      */
     RunProcessor(final JCacheManager cacheManager, final RunSummary runSummary, final CrawlerConfig config) {
@@ -129,39 +105,22 @@
         this.runSummary = runSummary;
         this.cacheManager = cacheManager;
 
-        // EPICS processor.
-        epicsLog = new EpicsLog();
-        this.addProcessor(epicsLog);
-
-        // Scaler data processor.
+        // Setup record loop.
+        runSummary.sortFiles();
+        evioFileSource = new EvioFileSource(runSummary.getEvioFileList());
+        evioLoop.setEvioFileSource(evioFileSource);
+
+        // Add EPICS processor.
+        epicsLog = new EpicsRunProcessor();
+        evioLoop.addEvioEventProcessor(epicsLog);
+
+        // Add Scaler data processor.
         scalersProcessor = new ScalersEvioProcessor();
         scalersProcessor.setResetEveryEvent(false);
-        this.addProcessor(scalersProcessor);
-
-        // Event log processor.
-        eventCountProcessor = new EventCountProcessor();
-        this.addProcessor(eventCountProcessor);
-
-        // Max files.
-        if (config.maxFiles() != -1) {
-            this.setMaxFiles(config.maxFiles());
-        }
-
-        // Enable file caching.
+        evioLoop.addEvioEventProcessor(scalersProcessor);
+
+        // Set whether file caching from MSS is enabled.
         this.useFileCache(config.useFileCache());
-
-        // Set event printing interval.
-        this.setEventPrintInterval(config.eventPrintInterval());
-    }
-
-    /**
-     * Add a processor of EVIO events.
-     *
-     * @param processor the EVIO event processor
-     */
-    void addProcessor(final EvioEventProcessor processor) {
-        this.processors.add(processor);
-        LOGGER.config("added processor " + processor.getClass().getSimpleName());
     }
 
     /**
@@ -172,10 +131,10 @@
      */
     private void cacheFiles() {
 
-        LOGGER.info("caching files from run " + this.runSummary.getRun() + " ...");
+        LOGGER.info("caching files from run " + this.runSummary.getRun());
 
         // Cache all the files and wait for the operation to complete (it will take awhile!).
-        this.cacheManager.cache(this.getFiles());
+        this.cacheManager.cache(this.runSummary.getEvioFileList());
         final boolean cached = this.cacheManager.waitForCache();
 
         // If the files weren't cached then die.
@@ -187,245 +146,82 @@
     }
 
     /**
-     * Find the end date in the EVIO events.
-     *
-     * @param evioReader the open <code>EvioReader</code>
-     */
-    private void findEndDate(final EvioReader evioReader) {
-
-        // Try to get end date from END event.
-        Long endTimestamp = EvioFileUtilities.getTimestamp(evioReader, EvioEventConstants.END_EVENT_TAG, -5);
-
-        if (endTimestamp != null) {
-            // Flag end okay for the run.
-            this.runSummary.setEndOkay(true);
-        } else {
-            // Try to find the end date from the last physics event.
-            endTimestamp = EvioFileUtilities.getEndTimestamp(evioReader);
-            this.runSummary.setEndOkay(false);
-        }
-
-        if (endTimestamp == null) {
-            // Not finding the end date is a fatal error.
-            throw new RuntimeException("Failed to find end date.");
-        }
-
-        LOGGER.info("found end timestamp " + endTimestamp);
-        this.runSummary.setEndTimeUtc(endTimestamp);
-    }
-
-    /**
-     * Find the start date in the EVIO events.
-     *
-     * @param evioReader the open <code>EvioReader</code>
-     */
-    private void findStartDate(final EvioReader evioReader) {
-
-        // First try to find the start date in the PRESTART event.
-        Long startTimestamp = EvioFileUtilities.getTimestamp(evioReader, EvioEventConstants.PRESTART_EVENT_TAG, 0);
-
-        if (startTimestamp == null) {
-            // Search for start date in first physics event.
-            startTimestamp = EvioFileUtilities.getStartTimestamp(evioReader);
-        }
-
-        if (startTimestamp == null) {
-            // Not finding the start date is a fatal error.
-            throw new RuntimeException("Failed to find start date.");
-        }
-
-        LOGGER.fine("got run start " + startTimestamp);
-        this.runSummary.setStartTimeUtc(startTimestamp);
-    }
-
-    /**
-     * Get the list of files to process, which will be limited by the {@link #maxFiles} value if it is set.
-     *
-     * @return the files to process
-     */
-    private List<File> getFiles() {
-        // Get the list of files to process, taking into account the max files setting.
-        List<File> files = this.runSummary.getEvioFileList();
-        if (this.maxFiles != -1) {
-            int toIndex = this.maxFiles;
-            if (toIndex > files.size()) {
-                toIndex = files.size();
-            }
-            files = files.subList(0, toIndex);
-        }
-        return files;
-    }
-
-    /**
-     * Get the list of EVIO processors.
-     *
-     * @return the list of EVIO processors
-     */
-    List<EvioEventProcessor> getProcessors() {
-        return this.processors;
-    }
-
-    /**
-     * Return <code>true</code> if the file is the first one in the list for the run.
-     *
-     * @param file the EVIO <code>File</code>
-     * @return <code>true</code> if the file is the first one in the list for the run
-     */
-    private boolean isFirstFile(final File file) {
-        return file.equals(this.runSummary.getEvioFileList().first());
-    }
-
-    /**
-     * Return <code>true</code> if the file is the last one in the list for the run.
-     *
-     * @param file the EVIO <code>File</code>
-     * @return <code>true</code> if the file is the last one in the list for the run
-     */
-    private boolean isLastFile(final File file) {
-        return file.equals(this.getFiles().get(this.getFiles().size() - 1));
-    }
-
-    /**
-     * Process events using the list of EVIO processors.
-     *
-     * @param evioReader the open <code>EvioReader</code>
-     * @throws IOException if there is a file IO error
-     * @throws EvioException if there is an EVIO error
-     * @throws Exception if there is some other error
-     */
-    private void processEvents(final EvioReader evioReader) throws IOException, EvioException, Exception {
-        LOGGER.finer("running EVIO processors ...");
-        evioReader.gotoEventNumber(0);
-        int nProcessed = 0;
-        if (!this.processors.isEmpty()) {
-            EvioEvent event = null;
-            while ((event = evioReader.parseNextEvent()) != null) {
-                for (final EvioEventProcessor processor : this.processors) {
-                    processor.process(event);
-                }
-                ++nProcessed;
-                if (nProcessed % this.eventPrintInterval == 0) {
-                    LOGGER.finer("processed " + nProcessed + " EVIO events");
-                }
-            }
-            LOGGER.info("done running EVIO processors");
-        }
-    }
-
-    /**
-     * Process a single EVIO file from the run.
-     *
-     * @param file the EVIO file
-     * @throws EvioException if there is an EVIO error
-     * @throws IOException if there is some kind of IO error
-     * @throws Exception if there is a generic error thrown by event processing
-     */
-    private void processFile(final File file) throws EvioException, IOException, Exception {
-
-        LOGGER.fine("processing file " + file.getPath() + " ...");
-
-        EvioReader evioReader = null;
-        try {
-
-            // Open file for reading (flag should be true for sequential or false for mem map).
-            evioReader = EvioFileUtilities.open(file, true);
-
-            // If this is the first file then get the start date.
-            if (this.isFirstFile(file)) {
-                LOGGER.fine("getting run start from first file " + file.getPath() + " ...");
-                this.findStartDate(evioReader);
-            }
-
-            // Go back to the first event and process the events using the list of EVIO processors.
-            this.processEvents(evioReader);
-
-            // Find end date from last file in the run.
-            if (this.isLastFile(file)) {
-                LOGGER.fine("getting run end from last file " + file.getPath() + " ...");
-                this.findEndDate(evioReader);
-            }
-
-        } finally {
-            // Close the EvioReader for the current file.
-            if (evioReader != null) {
-                evioReader.close();
-            }
-        }
-        LOGGER.fine("done processing " + file.getPath());
-    }
-
-    /**
-     * Process the run by executing the registered {@link org.hps.record.evio.EvioEventProcessor}s extracting the start
-     * and end dates.
+     * Process the run by executing the registered {@link org.hps.record.evio.EvioEventProcessor}s and extracting the
+     * start and end dates.
      * <p>
-     * This method will also activate file caching, if enabled by the {@link #useFileCache} option.
+     * This method will also execute file caching from MSS, if enabled by the {@link #useFileCache} option.
      *
      * @throws Exception if there is an error processing a file
      */
     void processRun() throws Exception {
 
-        LOGGER.info("processing run " + this.runSummary.getRun() + " ...");
-
-        // First cache all the files we will process, if necessary.
+        LOGGER.info("processing " + this.runSummary.getEvioFileList().size() + " files from run "
+                + this.runSummary.getRun());
+
+        // Cache files from MSS if this is enabled.
         if (this.useFileCache) {
+            LOGGER.info("caching files from MSS");
             this.cacheFiles();
         }
 
-        // Run the start of job hooks.
-        for (final EvioEventProcessor processor : this.processors) {
-            processor.startJob();
-        }
-
-        // Get the list of files, limited by max files setting.
-        final List<File> files = this.getFiles();
-
-        LOGGER.info("processing " + files.size() + " from run " + this.runSummary.getRun());
-
-        // Process all the files.
-        for (final File file : files) {
-            this.processFile(file);
-        }
-
-        // Run the end job hooks.
-        LOGGER.info("running end of job hooks on EVIO processors ...");
-        for (final EvioEventProcessor processor : this.processors) {
-            processor.endJob();
-        }
-
+        // Run processors over all files.
+        LOGGER.info("looping over all events");
+        evioLoop.loop(-1);
+
+        // Get run start date.
+        LOGGER.info("setting run start date");
+        this.setRunStartDate();
+
+        // Get run end date.
+        LOGGER.info("setting run end date");
+        this.setRunEndDate();
+
+        // Update run summary from processors.
+        LOGGER.info("updating run summary");
+        this.updateRunSummary();
+
+        LOGGER.info("done processing run " + this.runSummary.getRun());
+    }
+
+    /**
+     * Set the run end date by getting meta data from the last file and copying it to the run summary.
+     */
+    private void setRunEndDate() {
+        final File lastEvioFile = runSummary.getEvioFileList().get(runSummary.getEvioFileList().size() - 1);
+        LOGGER.info("getting meta data for " + lastEvioFile.getPath());
+        final EvioFileMetaDataReader metaDataReader = new EvioFileMetaDataReader();
+        final EvioFileMetaData metaData = metaDataReader.getMetaData(lastEvioFile);
+        LOGGER.info(metaData.toString());
+        LOGGER.info("setting unix end time to " + metaData.getEndDate().getTime() + " from meta data");
+        runSummary.setEndTimeUtc(metaData.getEndDate().getTime());
+        runSummary.setEndOkay(metaData.hasEnd());
+    }
+
+    /**
+     * Set the run start date by getting meta data from the first file and copying it to the run summary.
+     */
+    private void setRunStartDate() {
+        final File firstEvioFile = runSummary.getEvioFileList().get(0);
+        LOGGER.info("getting meta data for " + firstEvioFile.getPath());
+        final EvioFileMetaDataReader metaDataReader = new EvioFileMetaDataReader();
+        final EvioFileMetaData metaData = metaDataReader.getMetaData(firstEvioFile);
+        LOGGER.info(metaData.toString());
+        LOGGER.info("setting unix start time to " + metaData.getStartDate().getTime() + " from meta data");
+        runSummary.setStartTimeUtc(metaData.getStartDate().getTime());
+    }
+
+    /**
+     * Update the current run summary by copying data to it from the EVIO processors.
+     */
+    private void updateRunSummary() {
         // Put scaler data from EVIO processor into run summary.
         runSummary.setScalerData(this.scalersProcessor.getScalerData());
 
-        // Set the counts of event types on the run summary.
-        runSummary.setEventTypeCounts(eventCountProcessor.getEventCounts());
-
         // Set total number of events on the run summary from the event counter.
-        runSummary.setTotalEvents(this.eventCountProcessor.getTotalEventCount());
+        runSummary.setTotalEvents((int) evioLoop.getTotalCountableConsumed());
 
         // Set EpicsData for the run.
         runSummary.setEpicsData(this.epicsLog.getEpicsData());
-
-        LOGGER.info("done processing run " + this.runSummary.getRun());
-    }
-
-    /**
-     * Set the event print interval when running the EVIO processors.
-     *
-     * @param eventPrintInterval the event print interval when running the EVIO processors
-     */
-    void setEventPrintInterval(final int eventPrintInterval) {
-        this.eventPrintInterval = eventPrintInterval;
-    }
-
-    /**
-     * Set the maximum number of files to process.
-     * <p>
-     * This is intended primarily for debugging.
-     *
-     * @param maxFiles the maximum number of files to process
-     */
-    void setMaxFiles(final int maxFiles) {
-        this.maxFiles = maxFiles;
-        LOGGER.config("max files set to " + maxFiles);
     }
 
     /**

Copied: java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunSummaryMap.java (from r3364, java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunLog.java)
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunLog.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/evio/crawler/RunSummaryMap.java	Thu Aug 20 12:50:12 2015
@@ -1,32 +1,33 @@
 package org.hps.record.evio.crawler;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.HashMap;
 import java.util.logging.Logger;
 
 import org.hps.record.run.RunSummary;
 import org.lcsim.util.log.LogUtil;
 
 /**
- * This class contains information about a series of runs which each have a {@link RunSummary} object.
+ * This class maps run numbers to {@link RunSummary} objects.
  *
  * @author Jeremy McCormick, SLAC
  */
-final class RunLog {
+@SuppressWarnings("serial")
+final class RunSummaryMap extends HashMap<Integer, RunSummary> {
 
     /**
      * Setup logging.
      */
-    private static final Logger LOGGER = LogUtil.create(RunLog.class);
+    private static final Logger LOGGER = LogUtil.create(RunSummaryMap.class);
 
     /**
-     * A map between run numbers and the run summary information.
+     * Get the collection of {@link RunSummary} objects.
+     *
+     * @return the collection of {@link RunSummary} objects
      */
-    private final Map<Integer, RunSummary> runs = new LinkedHashMap<Integer, RunSummary>();
+    public Collection<RunSummary> getRunSummaries() {
+        return this.values();
+    }
 
     /**
      * Get a {@link RunSummary} by its run number.
@@ -37,63 +38,10 @@
      * @return the <code>RunSummary</code> for the run number
      */
     public RunSummary getRunSummary(final int run) {
-        if (!this.runs.containsKey(run)) {
+        if (!this.containsKey(run)) {
             LOGGER.info("creating new RunSummary for run " + run);
-            this.runs.put(run, new RunSummary(run));
+            this.put(run, new RunSummary(run));
         }
-        return this.runs.get(run);
+        return this.get(run);
     }
-
-    /**
-     * Get the collection of {@link RunSummary} objects.
-     * 
-     * @return the collection of {@link RunSummary} objects
-     */
-    public Collection<RunSummary> getRunSummaries() {
-        return this.runs.values();
-    }
-
-    /**
-     * Get a list of sorted run numbers from this run log.
-     * <p>
-     * This is a copy of the keys from the map, so modifying it will have no effect on the original.
-     *
-     * @return the list of sorted run numbers
-     */
-    List<Integer> getSortedRunNumbers() {
-        final List<Integer> runList = new ArrayList<Integer>(this.runs.keySet());
-        Collections.sort(runList);
-        return runList;
-    }
-
-    /**
-     * Print out each {@link RunSummary} to <code>System.out</code>.
-     */
-    void printRunSummaries() {
-        for (final int run : this.runs.keySet()) {
-            this.runs.get(run).printOut(System.out);
-        }
-    }
-
-    /**
-     * Sort the file list for each run in place by EVIO sequence numbers.
-     */
-    void sortFiles() {
-        for (final Integer run : this.runs.keySet()) {
-            this.runs.get(run).sortFiles();
-        }
-    }
-
-    /**
-     * Print the run numbers to the log.
-     */
-    void printRunNumbers() {
-        // Print the list of runs that were found.
-        final StringBuffer sb = new StringBuffer();
-        for (final Integer run : getSortedRunNumbers()) {
-            sb.append(run + " ");
-        }
-        LOGGER.info("found EVIO files from runs: " + sb.toString());
-    }
-
 }

Added: java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDao.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDao.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDao.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,65 @@
+package org.hps.record.run;
+
+import java.util.List;
+
+import org.hps.record.epics.EpicsData;
+
+/**
+ * Database Access Object (DAO) API for EPICS data from the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public interface EpicsDataDao {
+
+    /**
+     * Delete EPICS data from the database.
+     *
+     * @param epicsData the EPICS data to delete
+     */
+    void deleteEpicsData(EpicsData epicsData);
+
+    /**
+     * Delete all EPICS data for a run from the database.
+     *
+     * @param run the run number
+     */
+    void deleteEpicsData(int run);
+
+    /**
+     * Get all the EPICS data in the database.
+     *
+     * @return the list of EPICS data
+     */
+    List<EpicsData> getAllEpicsData();
+
+    /**
+     * Get EPICS data by run.
+     *
+     * @param run the run number
+     * @return the EPICS data
+     */
+    List<EpicsData> getEpicsData(int run);
+
+    /**
+     * Get the list of unique variables names used in the database records.
+     *
+     * @return the list of unique variable names
+     */
+    List<String> getVariableNames();
+
+    /**
+     * Insert a list of EPICS data into the database.
+     * <p>
+     * The run number comes from the header information.
+     *
+     * @param epicsDataList the list of EPICS data
+     */
+    void insertEpicsData(List<EpicsData> epicsDataList);
+
+    /**
+     * Updates EPICS data in the database.
+     *
+     * @param epicsData the EPICS data to update
+     */
+    void updateEpicsData(EpicsData epicsData);
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDaoImpl.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDaoImpl.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/EpicsDataDaoImpl.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,362 @@
+package org.hps.record.run;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.record.epics.EpicsData;
+import org.hps.record.epics.EpicsHeader;
+
+/**
+ * Implementation of database operations for EPICS data.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class EpicsDataDaoImpl implements EpicsDataDao {
+
+    /**
+     * SQL data query strings.
+     */
+    private static class EpicsDataQuery {
+
+        /**
+         * Delete by run number.
+         */
+        private static final String DELETE_BY_RUN = "DELETE FROM run_epics WHERE run = ?";
+        /**
+         * Delete by run and sequence number.
+         */
+        private static final String DELETE_RUN_AND_SEQUENCE = "DELETE FROM run_epics WHERE run = ? and sequence = ?";
+        /**
+         * Insert a record.
+         */
+        private static final String INSERT = "INSERT INTO run_epics (run, sequence, timestamp, variable_name, value) VALUES (?, ?, ?, ?, ?)";
+        /**
+         * Select all records.
+         */
+        private static final String SELECT_ALL = "SELECT * FROM run_epics ORDER BY run, sequence";
+        /**
+         * Select by run number.
+         */
+        private static final String SELECT_RUN = "SELECT * FROM run_epics WHERE run = ? ORDER BY `sequence`";
+        /**
+         * Select unique variable names.
+         */
+        private static final String SELECT_VARIABLE_NAMES = "SELECT DISTINCT(variable_name) FROM run_epics ORDER BY variable_name";
+        /**
+         * Update a record.
+         */
+        private static final String UPDATE = "UPDATE run_epics SET run = ?, sequence = ?, timestamp = ?, variable_name = ?, value = ? WHERE run = ? and sequence = ? and variable_name = ?";
+    }
+
+    /**
+     * The database connection.
+     */
+    private final Connection connection;
+
+    /**
+     * Create a new DAO implementation for EPICS data.
+     *
+     * @param connection the database connection
+     */
+    public EpicsDataDaoImpl(final Connection connection) {
+        if (connection == null) {
+            throw new IllegalArgumentException("The connection is null.");
+        }
+        this.connection = connection;
+    }
+
+    /**
+     * Delete the record for this EPICS data object using its run and sequence number.
+     *
+     * @param run the run number
+     * @throws IllegalArgumentException if the EPICS data is missing a header object
+     */
+    @Override
+    public void deleteEpicsData(final EpicsData epicsData) {
+        PreparedStatement preparedStatement = null;
+        try {
+            final EpicsHeader epicsHeader = epicsData.getEpicsHeader();
+            if (epicsHeader == null) {
+                throw new IllegalArgumentException("The EPICS data is missing header information.");
+            }
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.DELETE_RUN_AND_SEQUENCE);
+            preparedStatement.setInt(1, epicsHeader.getRun());
+            preparedStatement.setInt(2, epicsHeader.getSequence());
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete all EPICS data for a run from the database.
+     *
+     * @param run the run number
+     */
+    @Override
+    public void deleteEpicsData(final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.DELETE_BY_RUN);
+            preparedStatement.setInt(1, run);
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Get all the EPICS data in the database.
+     *
+     * @return the list of EPICS data
+     */
+    @Override
+    public List<EpicsData> getAllEpicsData() {
+        PreparedStatement preparedStatement = null;
+        final List<EpicsData> epicsDataList = new ArrayList<EpicsData>();
+        try {
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.SELECT_ALL);
+            final ResultSet resultSet = preparedStatement.executeQuery();
+            Integer currentRun = null;
+            Integer currentSequence = null;
+            EpicsData epicsData = new EpicsData();
+            while (resultSet.next()) {
+                if (currentRun == null) {
+                    currentRun = resultSet.getInt("run");
+                }
+                if (currentSequence == null) {
+                    currentSequence = resultSet.getInt("sequence");
+                }
+                final int run = resultSet.getInt("run");
+                final int sequence = resultSet.getInt("sequence");
+                final int timestamp = resultSet.getInt("timestamp");
+                final String variableName = resultSet.getString("variable_name");
+                final double value = resultSet.getDouble("value");
+                if (currentRun != run || currentSequence != sequence) {
+                    epicsDataList.add(epicsData);
+                    epicsData = new EpicsData();
+                    final EpicsHeader epicsHeader = new EpicsHeader(new int[] {run, sequence, timestamp});
+                    epicsData.setEpicsHeader(epicsHeader);
+                }
+                epicsData.setValue(variableName, value);
+            }
+            epicsDataList.add(epicsData);
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            try {
+                preparedStatement.close();
+            } catch (final SQLException e) {
+                e.printStackTrace();
+            }
+        }
+        return epicsDataList;
+    }
+
+    /**
+     * Get EPICS data by run.
+     *
+     * @param run the run number
+     * @return the EPICS data
+     */
+    @Override
+    public List<EpicsData> getEpicsData(final int run) {
+        PreparedStatement preparedStatement = null;
+        final List<EpicsData> epicsDataList = new ArrayList<EpicsData>();
+        try {
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.SELECT_RUN);
+            preparedStatement.setInt(1, run);
+            final ResultSet resultSet = preparedStatement.executeQuery();
+            Integer currentSequence = null;
+            EpicsData epicsData = new EpicsData();
+            EpicsHeader epicsHeader = null;
+            while (resultSet.next()) {
+
+                // Get record data.
+                final int sequence = resultSet.getInt("sequence");
+                final int timestamp = resultSet.getInt("timestamp");
+                final String variableName = resultSet.getString("variable_name");
+                final double value = resultSet.getDouble("value");
+
+                // Get sequence first time.
+                if (currentSequence == null) {
+                    currentSequence = resultSet.getInt("sequence");
+                }
+
+                // Create EPICS header.
+                epicsHeader = new EpicsHeader(new int[] {run, sequence, timestamp});
+
+                // First time need to set header here.
+                if (epicsData.getEpicsHeader() == null) {
+                    epicsData.setEpicsHeader(epicsHeader);
+                }
+
+                // New sequence number occurred.
+                if (currentSequence != sequence) {
+
+                    // Add the EPICS data to the list.
+                    epicsDataList.add(epicsData);
+
+                    // Use the new sequence number.
+                    currentSequence = sequence;
+
+                    // Create new EPICS data.
+                    epicsData = new EpicsData();
+
+                    // Set header from current record.
+                    epicsData.setEpicsHeader(epicsHeader);
+                }
+
+                // Set the value of the variable from the current record.
+                epicsData.setValue(variableName, value);
+            }
+
+            // Add the last object which will not happen inside the loop.
+            epicsDataList.add(epicsData);
+
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            try {
+                preparedStatement.close();
+            } catch (final SQLException e) {
+                e.printStackTrace();
+            }
+        }
+        return epicsDataList;
+    }
+
+    /**
+     * Get the list of unique variables names used in the database records.
+     *
+     * @return the list of unique variable names
+     */
+    @Override
+    public List<String> getVariableNames() {
+        final List<String> variableNames = new ArrayList<String>();
+        Statement statement = null;
+        try {
+            statement = connection.createStatement();
+            final ResultSet resultSet = statement.executeQuery(EpicsDataQuery.SELECT_VARIABLE_NAMES);
+            while (resultSet.next()) {
+                variableNames.add(resultSet.getString(1));
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return variableNames;
+    }
+
+    /**
+     * Insert a list of EPICS data into the database.
+     * <p>
+     * The run number comes from the header information.
+     *
+     * @param epicsDataList the list of EPICS data
+     */
+    @Override
+    public void insertEpicsData(final List<EpicsData> epicsDataList) {
+        if (epicsDataList.isEmpty()) {
+            throw new IllegalStateException("The EPICS data list is empty.");
+        }
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.INSERT);
+            for (final EpicsData epicsData : epicsDataList) {
+                final EpicsHeader epicsHeader = epicsData.getEpicsHeader();
+                if (epicsHeader == null) {
+                    throw new IllegalArgumentException("The EPICS data is missing header information.");
+                }
+                for (final String variableName : epicsData.getKeys()) {
+                    preparedStatement.setInt(1, epicsData.getEpicsHeader().getRun());
+                    preparedStatement.setInt(2, epicsData.getEpicsHeader().getSequence());
+                    preparedStatement.setInt(3, epicsData.getEpicsHeader().getTimestamp());
+                    preparedStatement.setString(4, variableName);
+                    preparedStatement.setDouble(5, epicsData.getValue(variableName));
+                    preparedStatement.executeUpdate();
+                }
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates EPICS data in the database.
+     *
+     * @param epicsData the EPICS data to update
+     */
+    @Override
+    public void updateEpicsData(final EpicsData epicsData) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(EpicsDataQuery.UPDATE);
+            final int run = epicsData.getEpicsHeader().getRun();
+            final int sequence = epicsData.getEpicsHeader().getSequence();
+            final int timestamp = epicsData.getEpicsHeader().getTimestamp();
+            for (final String variableName : epicsData.getKeys()) {
+                preparedStatement.setInt(1, run);
+                preparedStatement.setInt(2, sequence);
+                preparedStatement.setInt(3, timestamp);
+                preparedStatement.setString(4, variableName);
+                preparedStatement.setDouble(5, epicsData.getValue(variableName));
+                preparedStatement.setInt(6, run);
+                preparedStatement.setInt(7, sequence);
+                preparedStatement.setString(8, variableName);
+                preparedStatement.executeUpdate();
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            try {
+                if (preparedStatement != null) {
+                    preparedStatement.close();
+                }
+            } catch (final SQLException e) {
+                e.printStackTrace();
+            }
+            try {
+                connection.setAutoCommit(true);
+            } catch (final SQLException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDao.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDao.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDao.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,43 @@
+package org.hps.record.run;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Database Access Object (DAO) interface to EVIO files in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public interface EvioFilesDao {
+
+    /**
+     * Delete the EVIO file records for a run.
+     *
+     * @param run the run number
+     */
+    void deleteEvioFiles(int run);
+
+    /**
+     * Get all EVIO files from the database.
+     *
+     * @return all EVIO files from the database
+     */
+    List<File> getAllEvioFiles();
+
+    /**
+     * Get a list of EVIO files by run number.
+     *
+     * @param run the run number
+     * @return the list of EVIO files for the run
+     */
+    List<File> getEvioFiles(int run);
+
+    /**
+     * Insert the list of files for a run.
+     *
+     * @param fileList the list of files
+     * @param run the run number
+     */
+    void insertEvioFiles(List<File> fileList, int run);
+
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDaoImpl.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDaoImpl.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/EvioFilesDaoImpl.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,168 @@
+package org.hps.record.run;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of database operations for EVIO files in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class EvioFilesDaoImpl implements EvioFilesDao {
+
+    /**
+     * SQL query strings.
+     */
+    private static final class EvioFilesQuery {
+
+        /**
+         * Delete files by run number.
+         */
+        private static final String DELETE_RUN = "DELETE FROM run_files where run = ?";
+        /**
+         * Insert files by run number.
+         */
+        private static final String INSERT_RUN = "INSERT INTO run_files (run, directory, name) VALUES(?, ?, ?)";
+        /**
+         * Select all records.
+         */
+        private static final String SELECT_ALL = "SELECT directory, name FROM run_files";
+        /**
+         * Select records by run number.
+         */
+        private static final String SELECT_RUN = "SELECT directory, name FROM run_files WHERE run = ?";
+    }
+
+    /**
+     * The database connection.
+     */
+    private final Connection connection;
+
+    public EvioFilesDaoImpl(final Connection connection) {
+        this.connection = connection;
+    }
+
+    /**
+     * Delete the EVIO file records for a run.
+     *
+     * @param run the run number
+     */
+    @Override
+    public void deleteEvioFiles(final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(EvioFilesQuery.DELETE_RUN);
+            preparedStatement.setInt(1, run);
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Get all EVIO files from the database.
+     *
+     * @return all EVIO files from the database
+     */
+    @Override
+    public List<File> getAllEvioFiles() {
+        Statement statement = null;
+        final List<File> evioFileList = new ArrayList<File>();
+        try {
+            statement = this.connection.createStatement();
+            final ResultSet resultSet = statement.executeQuery(EvioFilesQuery.SELECT_ALL);
+            while (resultSet.next()) {
+                evioFileList.add(new File(resultSet.getString("directory") + File.separator
+                        + resultSet.getString("name")));
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return evioFileList;
+    }
+
+    /**
+     * Get a list of EVIO files by run number.
+     *
+     * @param run the run number
+     * @return the list of EVIO files for the run
+     */
+    @Override
+    public List<File> getEvioFiles(final int run) {
+        PreparedStatement preparedStatement = null;
+        final List<File> evioFileList = new ArrayList<File>();
+        try {
+            preparedStatement = this.connection.prepareStatement(EvioFilesQuery.SELECT_RUN);
+            preparedStatement.setInt(1, run);
+            final ResultSet resultSet = preparedStatement.executeQuery();
+            while (resultSet.next()) {
+                evioFileList.add(new File(resultSet.getString("directory") + File.separator
+                        + resultSet.getString("name")));
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return evioFileList;
+    }
+
+    /**
+     * Insert the list of files for a run.
+     *
+     * @param fileList the list of files
+     * @param run the run number
+     */
+    @Override
+    public void insertEvioFiles(final List<File> fileList, final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(EvioFilesQuery.INSERT_RUN);
+            for (final File file : fileList) {
+                preparedStatement.setInt(1, run);
+                preparedStatement.setString(2, file.getParentFile().getPath());
+                preparedStatement.setString(3, file.getName());
+                preparedStatement.executeUpdate();
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

Modified: java/trunk/record-util/src/main/java/org/hps/record/run/RunManager.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/RunManager.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/RunManager.java	Thu Aug 20 12:50:12 2015
@@ -1,9 +1,8 @@
 package org.hps.record.run;
 
 import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -14,19 +13,14 @@
 import org.lcsim.util.log.LogUtil;
 
 /**
- * Manages read-only access to the run database and creates a {@link RunSummary} object from the data for a specific
- * run.
- * <p>
- * This class converts database records into {@link RunSummary}, {@link org.hps.record.epics.EpicsData},
- * {@link org.hps.record.scalers.ScalerData}, and {@link org.hps.record.evio.crawler.EvioFileList} objects using their
- * corresponding {@link AbstractRunDatabaseReader} implementation classes.
+ * Manages read-only access to the run database and creates a {@link RunSummary} for a specific run.
  *
  * @author Jeremy McCormick, SLAC
  */
 public final class RunManager implements ConditionsListener {
 
     /**
-     * The default connection parameters for read-only access to the run database using the standard 'hpsuser' account.
+     * The default connection parameters for read-only access to the run database.
      */
     private static ConnectionParameters DEFAULT_CONNECTION_PARAMETERS = new ConnectionParameters("hpsuser",
             "darkphoton", "hps_run_db", "hpsdb.jlab.org");
@@ -61,7 +55,7 @@
     /**
      * The database connection parameters, initially set to the default parameters.
      */
-    private ConnectionParameters connectionParameters = DEFAULT_CONNECTION_PARAMETERS;
+    private final ConnectionParameters connectionParameters = DEFAULT_CONNECTION_PARAMETERS;
 
     /**
      * The run number; the -1 value indicates that this has not been set externally yet.
@@ -73,11 +67,18 @@
      */
     private RunSummary runSummary = null;
 
-    void closeConnection() {
-        try {
-            this.connection.close();
-        } catch (final SQLException e) {
-            e.printStackTrace();
+    /**
+     * Close the database connection.
+     */
+    private void closeConnection() {
+        if (!(this.connection == null)) {
+            try {
+                if (!this.connection.isClosed()) {
+                    this.connection.close();
+                }
+            } catch (final SQLException e) {
+                e.printStackTrace();
+            }
         }
     }
 
@@ -90,21 +91,21 @@
     }
 
     /**
-     * Get the database connection.
-     *
-     * @return the database connection or <code>null</code> if it is not set
-     */
-    Connection getConnection() {
-        return this.connection;
-    }
-
-    /**
-     * Get the run number.
+     * Get the current run number.
      *
      * @return the run number
      */
     public int getRun() {
         return run;
+    }
+
+    /**
+     * Get the complete list of run numbers from the database.
+     *
+     * @return the complete list of run numbers
+     */
+    List<Integer> getRuns() {
+        return new RunSummaryDaoImpl(this.connection).getRuns();
     }
 
     /**
@@ -117,122 +118,70 @@
     }
 
     /**
-     * Read information from the run database and create a {@link RunSummary} from it.
+     * Open a new database connection from the connection parameters if the current one is closed or <code>null</code>.
+     * <p>
+     * This method does nothing if the connection is already open.
      */
-    private void readRun() {
-
-        // Read main RunSummary object but not objects that it references.
-        final RunSummaryReader runSummaryReader = new RunSummaryReader();
-        runSummaryReader.setRun(this.getRun());
-        runSummaryReader.setConnection(this.getConnection());
-        runSummaryReader.read();
-        this.setRunSummary(runSummaryReader.getData());
-
-        // Read EpicsData and set on RunSummary.
-        final EpicsDataReader epicsDataReader = new EpicsDataReader();
-        epicsDataReader.setRun(this.getRun());
-        epicsDataReader.setConnection(this.getConnection());
-        epicsDataReader.read();
-        this.getRunSummary().setEpicsData(epicsDataReader.getData());
-
-        // Read ScalerData and set on RunSummary.
-        final ScalerDataReader scalerDataReader = new ScalerDataReader();
-        scalerDataReader.setRun(this.getRun());
-        scalerDataReader.setConnection(this.getConnection());
-        scalerDataReader.read();
-        this.getRunSummary().setScalerData(scalerDataReader.getData());
-
-        // Read ScalerData and set on RunSummary.
-        final EvioFileListReader evioFileListReader = new EvioFileListReader();
-        evioFileListReader.setRun(this.getRun());
-        evioFileListReader.setConnection(this.getConnection());
-        evioFileListReader.read();
-        this.getRunSummary().setEvioFileList(evioFileListReader.getData());
+    private void openConnection() {
+        try {
+            if (this.connection == null || this.connection.isClosed()) {
+                LOGGER.info("creating database connection");
+                this.connection = connectionParameters.createConnection();
+            } else {
+                LOGGER.warning("connection already open");
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException("Error opening database connection.", e);
+        }
     }
 
     /**
-     * Check if the current run number exists in the run database.
+     * Set the connection externally if using a database other than the default one at JLAB.
      *
-     * @return <code>true</code> if run exists
+     * @param connection the database connection
      */
-    private boolean runExists() throws SQLException {
-        PreparedStatement statement = null;
-        boolean exists = false;
-        try {
-            statement = connection.prepareStatement("SELECT run FROM runs where run = ?");
-            statement.setInt(1, this.run);
-            final ResultSet resultSet = statement.executeQuery();
-            exists = resultSet.next();
-        } finally {
-            if (statement != null) {
-                try {
-                    statement.close();
-                } catch (final SQLException e) {
-                    e.printStackTrace();
-                }
-            }
-        }
-        return exists;
+    public void setConnection(final Connection connection) {
+        this.connection = connection;
     }
 
     /**
-     * Set the database connection parameters.
-     */
-    public void setConnectionParameters(final ConnectionParameters connectionParameters) {
-        this.connectionParameters = connectionParameters;
-    }
-
-    /**
-     * Set the run number and load the applicable {@link RunSummary} from the db.
+     * Set the run number and then load the applicable {@link RunSummary} from the database.
      *
      * @param run the run number
      */
-    synchronized void setRun(final int run) {
+    public synchronized void setRun(final int run) {
 
+        // Check if run number is valid.
         if (run < 0) {
             throw new IllegalArgumentException("invalid run number: " + run);
         }
 
-        try {
+        // Setup the database connection.
+        this.openConnection();
 
-            // Setup the database connection.
-            if (this.connection == null || this.connection.isClosed()) {
-                this.connection = connectionParameters.createConnection();
+        // Initialize database interface.
+        final RunSummaryDao runSummaryDao = new RunSummaryDaoImpl(this.connection);
+
+        // Set the current run number.
+        this.run = run;
+
+        // Does the current run exist in the database?
+        if (runSummaryDao.runSummaryExists(this.getRun())) {
+            LOGGER.info("run " + run + " found in database");
+            try {
+                // Read the records from the database and convert into complex Java object.
+                this.runSummary = runSummaryDao.readFullRunSummary(this.getRun());
+            } catch (final Exception e) {
+                // There was some unknown error when reading in the run records.
+                LOGGER.log(Level.SEVERE, "Error reading from run database.", e);
+                throw new RuntimeException(e);
             }
+        } else {
+            // Run is not in the database.
+            LOGGER.warning("run database record does not exist for run " + run);
+        }
 
-            // Set the current run number.
-            this.run = run;
-
-            // Does the current run exist in the database?
-            if (this.runExists()) {
-                LOGGER.info("run record found in hps_run_db for " + run);
-                try {
-                    // Read the records from the database and convert into Java objects.
-                    this.readRun();
-                } catch (final Exception e) {
-                    // There was some unknown error when reading in the run records.
-                    LOGGER.log(Level.SEVERE, "Error reading from run database for run: " + run, e);
-                    throw new RuntimeException(e);
-                }
-            } else {
-                // Run is not in the database.
-                LOGGER.warning("run database record does not exist for run " + run);
-            }
-
-            // Close the database connection.
-            this.connection.close();
-
-        } catch (final SQLException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Set the current {@link RunSummary}.
-     *
-     * @param runSummary the current {@link RunSummary}
-     */
-    void setRunSummary(final RunSummary runSummary) {
-        this.runSummary = runSummary;
+        // Close the database connection.
+        this.closeConnection();
     }
 }

Modified: java/trunk/record-util/src/main/java/org/hps/record/run/RunSummary.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/RunSummary.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/RunSummary.java	Thu Aug 20 12:50:12 2015
@@ -4,13 +4,16 @@
 import java.io.PrintStream;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 
 import org.hps.record.epics.EpicsData;
-import org.hps.record.evio.crawler.EvioFileList;
+import org.hps.record.evio.EvioFileSequenceComparator;
 import org.hps.record.scalers.ScalerData;
 
 /**
@@ -26,11 +29,10 @@
  * <li>number of EVIO files in the run</li>
  * <li>whether the END event was found indicating that the DAQ did not crash</li>
  * <li>whether the run is considered good (all <code>true</code> for now)</li>
- * <li>list of EVIO files in the run</li>
  * </ul>
- * There are also associated {@link org.hps.record.epics.EpicsData} and {@link org.hps.record.scalers.ScalerData}
- * objects representing the EPICS and scaler data summaries for the run. The EPICS data is averaged over the whole run
- * while the scalers are from the last scaler data bank that was found.
+ * <p>
+ * It also references several complex objects including lists of {@link org.hps.record.epics.EpicsData} and
+ * {@link org.hps.record.scalers.ScalerData} for the run, as well as a list of EVIO files.
  *
  * @author Jeremy McCormick, SLAC
  */
@@ -48,6 +50,9 @@
         DATE_DISPLAY.setCalendar(new GregorianCalendar(TimeZone.getTimeZone("America/New_York")));
     }
 
+    /**
+     * Date this record was created.
+     */
     private Date created;
 
     /**
@@ -61,9 +66,9 @@
     private long endTimeUtc;
 
     /**
-     * The combined EPICS information for the run (uses the mean values for each variable).
-     */
-    private EpicsData epics;
+     * The EPICS data from the run.
+     */
+    private List<EpicsData> epicsDataList;
 
     /**
      * The counts of different types of events that were found.
@@ -73,7 +78,7 @@
     /**
      * The list of EVIO files in the run.
      */
-    private EvioFileList evioFileList = new EvioFileList();
+    private List<File> evioFileList = new ArrayList<File>();
 
     /**
      * The run number.
@@ -86,9 +91,9 @@
     private boolean runOkay = true;
 
     /**
-     * The scaler data from the last physics event in the run.
-     */
-    private ScalerData scalerData;
+     * The scaler data for the run.
+     */
+    private List<ScalerData> scalerDataList;
 
     /**
      * The run start time in UTC (milliseconds).
@@ -165,14 +170,12 @@
     }
 
     /**
-     * Get the EPICS data summary.
-     * <p>
-     * This is computed by taking the mean of each variable for the run.
-     *
-     * @return the EPICS data summary
-     */
-    public EpicsData getEpicsData() {
-        return this.epics;
+     * Get the EPICS data from the run.
+     *
+     * @return the EPICS data from the run
+     */
+    public List<EpicsData> getEpicsDataSet() {
+        return this.epicsDataList;
     }
 
     /**
@@ -202,7 +205,7 @@
      *
      * @return the list of EVIO files in this run
      */
-    public EvioFileList getEvioFileList() {
+    public List<File> getEvioFileList() {
         return this.evioFileList;
     }
 
@@ -225,12 +228,12 @@
     }
 
     /**
-     * Get the scaler data of this run (last event only).
-     *
-     * @return the scaler data of this run from the last event
-     */
-    public ScalerData getScalerData() {
-        return this.scalerData;
+     * Get the scaler data of this run.
+     *
+     * @return the scaler data of this run
+     */
+    public List<ScalerData> getScalerData() {
+        return this.scalerDataList;
     }
 
     /**
@@ -295,8 +298,8 @@
     public void printOut(final PrintStream ps) {
         ps.println("--------------------------------------------");
         ps.println("run: " + this.run);
-        ps.println("first file: " + this.evioFileList.first());
-        ps.println("last file: " + this.evioFileList.last());
+        ps.println("first file: " + this.evioFileList.get(0));
+        ps.println("last file: " + this.evioFileList.get(evioFileList.size() - 1));
         ps.println("started: " + DATE_DISPLAY.format(this.getStartDate()));
         ps.println("ended: " + DATE_DISPLAY.format(this.getEndDate()));
         ps.println("total events: " + this.getTotalEvents());
@@ -333,7 +336,7 @@
     /**
      * Set the end date.
      *
-     * @param endDate the end date
+     * @param endTimeUtc the end date
      */
     public void setEndTimeUtc(final long endTimeUtc) {
         this.endTimeUtc = endTimeUtc;
@@ -344,8 +347,8 @@
      *
      * @param epics the EPICS data for the run
      */
-    public void setEpicsData(final EpicsData epics) {
-        this.epics = epics;
+    public void setEpicsData(final List<EpicsData> epicsDataList) {
+        this.epicsDataList = epicsDataList;
     }
 
     /**
@@ -362,7 +365,7 @@
      *
      * @param evioFileList the list of EVIO files for the run
      */
-    public void setEvioFileList(final EvioFileList evioFileList) {
+    public void setEvioFileList(final List<File> evioFileList) {
         this.evioFileList = evioFileList;
     }
 
@@ -380,14 +383,14 @@
      *
      * @param scalerData the scaler data
      */
-    public void setScalerData(final ScalerData scalerData) {
-        this.scalerData = scalerData;
+    public void setScalerData(final List<ScalerData> scalerDataList) {
+        this.scalerDataList = scalerDataList;
     }
 
     /**
      * Set the start date of the run.
      *
-     * @param startDate the start date of the run
+     * @param startTimeUtc the start date of the run
      */
     public void setStartTimeUtc(final long startTimeUtc) {
         this.startTimeUtc = startTimeUtc;
@@ -424,7 +427,7 @@
      * Sort the files in the run by sequence number in place.
      */
     public void sortFiles() {
-        this.evioFileList.sort();
+        Collections.sort(this.evioFileList, new EvioFileSequenceComparator());
     }
 
     /**

Added: java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDao.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDao.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDao.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,99 @@
+package org.hps.record.run;
+
+import java.util.List;
+
+/**
+ * Data Access Object (DAO) API for managing run summary information in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public interface RunSummaryDao {
+
+    /**
+     * Delete a run summary from the database including its referenced objects such as EPICS data.
+     *
+     * @param runSummary the run summary to delete
+     */
+    void deleteFullRunSummary(RunSummary runSummary);
+
+    /**
+     * Delete a run summary by run number.
+     *
+     * @param run the run number
+     */
+    void deleteRunSummary(int run);
+
+    /**
+     * Delete a run summary but not its objects.
+     *
+     * @param runSummary the run summary object
+     */
+    void deleteRunSummary(RunSummary runSummary);
+
+    /**
+     * Get the list of run numbers.
+     *
+     * @return the list of run numbers
+     */
+    List<Integer> getRuns();
+
+    /**
+     * Get a list of run summaries without loading their objects such as EPICS data.
+     *
+     * @return the list of run summaries
+     */
+    List<RunSummary> getRunSummaries();
+
+    /**
+     * Get a run summary by run number without loading object state.
+     *
+     * @param run the run number
+     * @return the run summary object
+     */
+    RunSummary getRunSummary(int run);
+
+    /**
+     * Insert a list of run summaries along with its referenced objects such as scaler and EPICS data.
+     *
+     * @param runSummaryList the list of run summaries
+     * @param deleteExisting <code>true</code> to allow deletion and replacement of existing run summaries
+     */
+    void insertFullRunSummaries(List<RunSummary> runSummaryList, boolean deleteExisting);
+
+    /**
+     * Insert a run summary including all its objects.
+     *
+     * @param runSummary the run summary object
+     */
+    void insertFullRunSummary(RunSummary runSummary);
+
+    /**
+     * Insert a run summary but not its objects.
+     *
+     * @param runSummary the run summary object
+     */
+    void insertRunSummary(RunSummary runSummary);
+
+    /**
+     * Read a run summary and its objects such as scaler data.
+     *
+     * @param run the run number
+     * @return the full run summary
+     */
+    RunSummary readFullRunSummary(int run);
+
+    /**
+     * Return <code>true</code> if a run summary exists in the database.
+     *
+     * @param run the run number
+     * @return <code>true</code> if <code>run</code> exists in the database
+     */
+    boolean runSummaryExists(int run);
+
+    /**
+     * Update a run summary but not its objects.
+     *
+     * @param runSummary the run summary to update
+     */
+    void updateRunSummary(RunSummary runSummary);
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDaoImpl.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDaoImpl.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/RunSummaryDaoImpl.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,488 @@
+package org.hps.record.run;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.lcsim.util.log.DefaultLogFormatter;
+import org.lcsim.util.log.LogUtil;
+
+/**
+ * Implementation of database operations for {@link RunSummary} objects in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class RunSummaryDaoImpl implements RunSummaryDao {
+
+    /**
+     * SQL query strings.
+     */
+    private static final class RunSummaryQuery {
+
+        /**
+         * Delete by run number.
+         */
+        private static final String DELETE_RUN = "DELETE FROM runs WHERE run = ?";
+        /**
+         * Insert a record for a run.
+         */
+        private static final String INSERT = "INSERT INTO runs (run, start_time_utc, end_time_utc, nevents, nfiles, end_ok, created) VALUES(?, ?, ?, ?, ?, ?, NOW())";
+        /**
+         * Select all records.
+         */
+        private static final String SELECT_ALL = "SELECT * from runs";
+        /**
+         * Select record by run number.
+         */
+        private static final String SELECT_RUN = "SELECT run, start_time_utc, end_time_utc, nevents, nfiles, end_ok, run_ok, updated, created FROM runs WHERE run = ?";
+        /**
+         * Update information for a run.
+         */
+        private static final String UPDATE_RUN = "UPDATE runs SET start_time_utc, end_time_utc, nevents, nfiles, end_ok, run_ok WHERE run = ?";
+    }
+
+    /**
+     * Setup class logging.
+     */
+    private static final Logger LOGGER = LogUtil.create(RunSummaryDaoImpl.class, new DefaultLogFormatter(), Level.ALL);
+
+    /**
+     * The database connection.
+     */
+    private final Connection connection;
+
+    /**
+     * The database API for EPICS data.
+     */
+    private EpicsDataDao epicsDataDao = null;
+
+    /**
+     * The database API for EVIO file information.
+     */
+    private EvioFilesDao evioFilesDao = null;
+
+    /**
+     * The database API for scaler data.
+     */
+    private ScalerDataDao scalerDataDao = null;
+
+    /**
+     * Create a new DAO object for run summary information.
+     *
+     * @param connection the database connection
+     */
+    public RunSummaryDaoImpl(final Connection connection) {
+        // Set the connection.
+        if (connection == null) {
+            throw new IllegalArgumentException("The connection is null.");
+        }
+        this.connection = connection;
+
+        // Setup DAO API objects for managing complex object state.
+        epicsDataDao = new EpicsDataDaoImpl(this.connection);
+        scalerDataDao = new ScalerDataDaoImpl(this.connection);
+        evioFilesDao = new EvioFilesDaoImpl(this.connection);
+    }
+
+    /**
+     * Delete a run summary from the database including its referenced objects such as EPICS data.
+     *
+     * @param runSummary the run summary to delete
+     */
+    @Override
+    public void deleteFullRunSummary(final RunSummary runSummary) {
+        // Delete EPICS log.
+        epicsDataDao.deleteEpicsData(runSummary.getRun());
+
+        // Delete scaler data.
+        scalerDataDao.deleteScalerData(runSummary.getRun());
+
+        // Delete file list.
+        evioFilesDao.deleteEvioFiles(runSummary.getRun());
+
+        // Finally delete the run summary information.
+        this.deleteRunSummary(runSummary.getRun());
+    }
+
+    /**
+     * Delete a run summary by run number.
+     *
+     * @param run the run number
+     */
+    @Override
+    public void deleteRunSummary(final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(RunSummaryQuery.DELETE_RUN);
+            preparedStatement.setInt(1, run);
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete a run summary but not its objects.
+     *
+     * @param runSummary the run summary object
+     */
+    @Override
+    public void deleteRunSummary(final RunSummary runSummary) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(RunSummaryQuery.DELETE_RUN);
+            preparedStatement.setInt(1, runSummary.getRun());
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the list of run numbers.
+     *
+     * @return the list of run numbers
+     */
+    @Override
+    public List<Integer> getRuns() {
+        final List<Integer> runs = new ArrayList<Integer>();
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = this.connection.prepareStatement("SELECT distinct(run) FROM runs ORDER BY run");
+            final ResultSet resultSet = preparedStatement.executeQuery();
+            while (resultSet.next()) {
+                final Integer run = resultSet.getInt(1);
+                runs.add(run);
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return runs;
+    }
+
+    /**
+     * Get a list of run summaries without loading their objects such as EPICS data.
+     *
+     * @return the list of run summaries
+     */
+    @Override
+    public List<RunSummary> getRunSummaries() {
+        PreparedStatement statement = null;
+        final List<RunSummary> runSummaries = new ArrayList<RunSummary>();
+        try {
+            statement = this.connection.prepareStatement(RunSummaryQuery.SELECT_ALL);
+            final ResultSet resultSet = statement.executeQuery();
+            while (resultSet.next()) {
+                final RunSummary runSummary = new RunSummary(resultSet.getInt("run"));
+                runSummary.setStartTimeUtc(resultSet.getLong("start_time_utc"));
+                runSummary.setEndTimeUtc(resultSet.getLong("end_time_utc"));
+                runSummary.setTotalEvents(resultSet.getInt("nevents"));
+                runSummary.setTotalFiles(resultSet.getInt("nfiles"));
+                runSummary.setEndOkay(resultSet.getBoolean("end_ok"));
+                runSummary.setRunOkay(resultSet.getBoolean("run_ok"));
+                runSummary.setUpdated(resultSet.getTimestamp("updated"));
+                runSummary.setCreated(resultSet.getTimestamp("created"));
+                runSummaries.add(runSummary);
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return runSummaries;
+    }
+
+    /**
+     * Get a run summary by run number without loading object state.
+     *
+     * @param run the run number
+     * @return the run summary object
+     */
+    @Override
+    public RunSummary getRunSummary(final int run) {
+        PreparedStatement statement = null;
+        RunSummary runSummary = null;
+        try {
+            statement = this.connection.prepareStatement(RunSummaryQuery.SELECT_RUN);
+            statement.setInt(1, run);
+            final ResultSet resultSet = statement.executeQuery();
+            if (!resultSet.next()) {
+                throw new IllegalArgumentException("No record exists for run " + run + " in database.");
+            }
+
+            runSummary = new RunSummary(run);
+            runSummary.setStartTimeUtc(resultSet.getLong("start_time_utc"));
+            runSummary.setEndTimeUtc(resultSet.getLong("end_time_utc"));
+            runSummary.setTotalEvents(resultSet.getInt("nevents"));
+            runSummary.setTotalFiles(resultSet.getInt("nfiles"));
+            runSummary.setEndOkay(resultSet.getBoolean("end_ok"));
+            runSummary.setRunOkay(resultSet.getBoolean("run_ok"));
+            runSummary.setUpdated(resultSet.getTimestamp("updated"));
+            runSummary.setCreated(resultSet.getTimestamp("created"));
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return runSummary;
+    }
+
+    /**
+     * Insert a list of run summaries along with their complex state such as referenced scaler and EPICS data.
+     *
+     * @param runSummaryList the list of run summaries
+     * @param deleteExisting <code>true</code> to allow deletion and replacement of existing run summaries
+     */
+    @Override
+    public void insertFullRunSummaries(final List<RunSummary> runSummaryList, final boolean deleteExisting) {
+
+        if (runSummaryList == null) {
+            throw new IllegalArgumentException("The run summary list is null.");
+        }
+        if (runSummaryList.isEmpty()) {
+            throw new IllegalArgumentException("The run summary list is empty.");
+        }
+
+        LOGGER.info("inserting " + runSummaryList.size() + " run summaries into database");
+
+        // Turn off auto commit.
+        try {
+            LOGGER.info("turning off auto commit");
+            this.connection.setAutoCommit(false);
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Loop over all runs found while crawling.
+        for (final RunSummary runSummary : runSummaryList) {
+
+            final int run = runSummary.getRun();
+
+            LOGGER.info("inserting run summary for run " + run + " into database");
+
+            // Does the run exist in the database already?
+            if (this.runSummaryExists(run)) {
+                // Is deleting existing rows allowed?
+                if (deleteExisting) {
+                    LOGGER.info("deleting existing run summary");
+                    // Delete the existing rows.
+                    this.deleteFullRunSummary(runSummary);
+                } else {
+                    // Rows exist but updating is disallowed which is a fatal error.
+                    throw new IllegalStateException("Run " + runSummary.getRun()
+                            + " already exists and updates are disallowed.");
+                }
+            }
+
+            // Insert full run summary information including sub-objects.
+            LOGGER.info("inserting run summary");
+            this.insertFullRunSummary(runSummary);
+            LOGGER.info("run summary for " + run + " inserted successfully");
+
+            try {
+                // Commit the transaction for the run.
+                LOGGER.info("committing transaction");
+                this.connection.commit();
+            } catch (final SQLException e1) {
+                try {
+                    LOGGER.severe("rolling back transaction");
+                    // Rollback the transaction if there was an error.
+                    this.connection.rollback();
+                } catch (final SQLException e2) {
+                    throw new RuntimeException(e2);
+                }
+            }
+
+            LOGGER.info("done inserting run summary " + run);
+
+            LOGGER.getHandlers()[0].flush();
+        }
+
+        try {
+            LOGGER.info("turning auto commit on");
+            // Turn auto commit back on.
+            this.connection.setAutoCommit(true);
+        } catch (final SQLException e) {
+            e.printStackTrace();
+        }
+
+        LOGGER.info("done inserting run summaries");
+    }
+
+    /**
+     * Insert a run summary including all its objects.
+     *
+     * @param runSummary the run summary object to insert
+     */
+    @Override
+    public void insertFullRunSummary(final RunSummary runSummary) {
+
+        // Insert basic run log info.
+        this.insertRunSummary(runSummary);
+
+        // Insert list of files.
+        LOGGER.info("inserting EVIO " + runSummary.getEvioFileList().size() + " files");
+        evioFilesDao.insertEvioFiles(runSummary.getEvioFileList(), runSummary.getRun());
+
+        // Insert EPICS data.
+        LOGGER.info("inserting " + runSummary.getEpicsDataSet().size() + " EPICS records");
+        epicsDataDao.insertEpicsData(runSummary.getEpicsDataSet());
+
+        // Insert scaler data.
+        LOGGER.info("inserting " + runSummary.getScalerData().size() + " scaler data records");
+        scalerDataDao.insertScalerData(runSummary.getScalerData(), runSummary.getRun());
+    }
+
+    /**
+     * Insert a run summary but not its objects.
+     *
+     * @param runSummary the run summary object
+     */
+    @Override
+    public void insertRunSummary(final RunSummary runSummary) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(RunSummaryQuery.INSERT);
+            preparedStatement.setInt(1, runSummary.getRun());
+            preparedStatement.setLong(2, runSummary.getStartTimeUtc());
+            preparedStatement.setLong(3, runSummary.getEndTimeUtc());
+            preparedStatement.setInt(4, runSummary.getTotalEvents());
+            preparedStatement.setInt(5, runSummary.getEvioFileList().size());
+            preparedStatement.setBoolean(6, runSummary.getEndOkay());
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Read a run summary and its objects such as scaler data.
+     *
+     * @param run the run number
+     * @return the full run summary
+     */
+    @Override
+    public RunSummary readFullRunSummary(final int run) {
+
+        // Read main run summary but not referenced objects.
+        final RunSummary runSummary = this.getRunSummary(run);
+
+        // Read EPICS data and set on RunSummary.
+        runSummary.setEpicsData(epicsDataDao.getEpicsData(run));
+
+        // Read scaler data and set on RunSummary.
+        runSummary.setScalerData(scalerDataDao.getScalerData(run));
+
+        // Read EVIO file list and set on RunSummary.
+        runSummary.setEvioFileList(evioFilesDao.getEvioFiles(run));
+
+        return runSummary;
+    }
+
+    /**
+     * Return <code>true</code> if a run summary exists in the database for the run number.
+     *
+     * @param run the run number
+     * @return <code>true</code> if run exists in the database
+     */
+    @Override
+    public boolean runSummaryExists(final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement("SELECT run FROM runs where run = ?");
+            preparedStatement.setInt(1, run);
+            final ResultSet rs = preparedStatement.executeQuery();
+            return rs.first();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Update a run summary but not its complex state.
+     *
+     * @param runSummary the run summary to update
+     */
+    @Override
+    public void updateRunSummary(final RunSummary runSummary) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(RunSummaryQuery.UPDATE_RUN);
+            preparedStatement.setLong(1, runSummary.getStartTimeUtc());
+            preparedStatement.setLong(2, runSummary.getEndTimeUtc());
+            preparedStatement.setInt(3, runSummary.getTotalEvents());
+            preparedStatement.setInt(4, runSummary.getEvioFileList().size());
+            preparedStatement.setBoolean(5, runSummary.getEndOkay());
+            preparedStatement.setBoolean(6, runSummary.getRunOkay());
+            preparedStatement.setInt(7, runSummary.getRun());
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDao.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDao.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDao.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,36 @@
+package org.hps.record.run;
+
+import java.util.List;
+
+import org.hps.record.scalers.ScalerData;
+
+/**
+ * Database Access Object (DAO) for scaler data in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public interface ScalerDataDao {
+
+    /**
+     * Delete scaler data for the run.
+     *
+     * @param run the run number
+     */
+    void deleteScalerData(int run);
+
+    /**
+     * Get scaler data for a run.
+     *
+     * @param run the run number
+     * @return the scaler data for the run
+     */
+    List<ScalerData> getScalerData(int run);
+
+    /**
+     * Insert scaler data for a run.
+     *
+     * @param scalerData the list of scaler data
+     * @param run the run number
+     */
+    void insertScalerData(List<ScalerData> scalerData, int run);
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDaoImpl.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDaoImpl.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/ScalerDataDaoImpl.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,175 @@
+package org.hps.record.run;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.record.scalers.ScalerData;
+
+/**
+ * Implementation of database API for {@link org.hps.record.scalers.ScalerData} in the run database.
+ *
+ * @author Jeremy McCormick, SLAC
+ */
+public class ScalerDataDaoImpl implements ScalerDataDao {
+
+    /**
+     * SQL query strings.
+     */
+    private static final class ScalerDataQuery {
+
+        /**
+         * Delete by run.
+         */
+        private static final String DELETE_RUN = "DELETE FROM run_scalers WHERE run = ?";
+        /**
+         * Insert a record.
+         */
+        private static final String INSERT = "INSERT INTO run_scalers (run, event, idx, value) VALUES (?, ?, ?, ?)";
+        /**
+         * Select by run.
+         */
+        private static final String SELECT_RUN = "SELECT event, idx, value FROM run_scalers WHERE run = ? ORDER BY event, idx";
+    }
+
+    /**
+     * The database connection.
+     */
+    private final Connection connection;
+
+    /**
+     * Create object for managing scaler data in the run database.
+     *
+     * @param connection the database connection
+     */
+    public ScalerDataDaoImpl(final Connection connection) {
+        if (connection == null) {
+            throw new IllegalArgumentException("The connection is null.");
+        }
+        this.connection = connection;
+    }
+
+    /**
+     * Delete scaler data for the run.
+     *
+     * @param run the run number
+     */
+    @Override
+    public void deleteScalerData(final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = connection.prepareStatement(ScalerDataQuery.DELETE_RUN);
+            preparedStatement.setInt(1, run);
+            preparedStatement.executeUpdate();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Get scaler data for a run.
+     *
+     * @param run the run number
+     * @return the scaler data for the run
+     */
+    @Override
+    public List<ScalerData> getScalerData(final int run) {
+        PreparedStatement preparedStatement = null;
+        final List<ScalerData> scalerDataList = new ArrayList<ScalerData>();
+        try {
+            preparedStatement = this.connection.prepareStatement(ScalerDataQuery.SELECT_RUN);
+            preparedStatement.setInt(1, run);
+            final ResultSet resultSet = preparedStatement.executeQuery();
+
+            int[] scalerArray = new int[ScalerData.ARRAY_SIZE];
+            int event = 0;
+
+            while (resultSet.next()) {
+
+                // Get record data.
+                event = resultSet.getInt("event");
+                final int idx = resultSet.getInt("idx");
+                final int value = resultSet.getInt("value");
+
+                // Is this the start of a new scaler data set and not the first one?
+                if (idx == 0 && resultSet.getRow() > 1) {
+                    // Create new scaler data object and add to list.
+                    final ScalerData scalerData = new ScalerData(scalerArray, event);
+                    scalerDataList.add(scalerData);
+
+                    // Reset the data array for next object.
+                    scalerArray = new int[ScalerData.ARRAY_SIZE];
+                }
+
+                // Set value by index.
+                scalerArray[idx] = value;
+            }
+
+            // Add the last object which will not happen inside the loop.
+            if (scalerArray != null) {
+                scalerDataList.add(new ScalerData(scalerArray, event));
+            }
+
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return scalerDataList;
+    }
+
+    /**
+     * Insert scaler data for a run.
+     *
+     * @param scalerData the list of scaler data
+     * @param run the run number
+     */
+    @Override
+    public void insertScalerData(final List<ScalerData> scalerDataList, final int run) {
+        PreparedStatement preparedStatement = null;
+        try {
+            preparedStatement = this.connection.prepareStatement(ScalerDataQuery.INSERT);
+            for (final ScalerData scalerData : scalerDataList) {
+                final int size = scalerData.size();
+                final Integer event = scalerData.getEventId();
+                if (event == null) {
+                    throw new IllegalStateException("The scaler data is missing the event ID.");
+                }
+                for (int i = 0; i < size; i++) {
+                    preparedStatement.setInt(1, run);
+                    preparedStatement.setInt(2, event);
+                    preparedStatement.setInt(3, i);
+                    preparedStatement.setInt(4, scalerData.getValue(i));
+                    preparedStatement.executeUpdate();
+                }
+            }
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                } catch (final SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

Added: java/trunk/record-util/src/main/java/org/hps/record/run/package-info.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/run/package-info.java	(added)
+++ java/trunk/record-util/src/main/java/org/hps/record/run/package-info.java	Thu Aug 20 12:50:12 2015
@@ -0,0 +1,4 @@
+/**
+ * API for accessing the HPS run database with run summary information.
+ */
+package org.hps.record.run;

Modified: java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalerData.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalerData.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalerData.java	Thu Aug 20 12:50:12 2015
@@ -13,6 +13,11 @@
  * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
  */
 public final class ScalerData {
+
+    /**
+     * Fixed array size of scaler data in the EVIO bank.
+     */
+    public static final int ARRAY_SIZE = 72;
 
     /**
      * Default name of scaler data collection in LCSim events.
@@ -38,10 +43,11 @@
     public static ScalerData read(final EventHeader event, final String collectionName) {
         ScalerData data = null;
         if (event.hasCollection(GenericObject.class, collectionName)) {
-            //System.out.println("ScalerData - found collection");
+            // System.out.println("ScalerData - found collection");
             final List<GenericObject> objects = event.get(GenericObject.class, collectionName);
             data = new ScalerData();
             data.fromGenericObject(objects.get(0));
+            data.setEventId(event.getEventNumber());
         }
         return data;
     }
@@ -50,6 +56,11 @@
      * The scaler data values.
      */
     private int[] data;
+
+    /**
+     * The event ID of the data.
+     */
+    private Integer eventId;
 
     /**
      * This is the no argument constructor which is for package internal use only.
@@ -62,9 +73,10 @@
      *
      * @param data the scaler data
      */
-    public ScalerData(final int[] data) {
+    public ScalerData(final int[] data, final int eventId) {
         this.data = new int[data.length];
         System.arraycopy(data, 0, this.data, 0, data.length);
+        this.eventId = eventId;
     }
 
     /**
@@ -80,6 +92,18 @@
     }
 
     /**
+     * Get the event ID of the scaler data.
+     * <p>
+     * This information is not persisted to the LCIO.
+     *
+     * @return the event ID of the scaler data
+     */
+    public Integer getEventId() {
+        // Null value will be returned here to indicate not set.
+        return this.eventId;
+    }
+
+    /**
      * Get the scaler data value at the index.
      *
      * @param index the scaler data index
@@ -88,14 +112,23 @@
     public Integer getValue(final int index) {
         return this.data[index];
     }
-    
+
     /**
      * Get the value using a {@link ScalerDataIndex} enum.
-     * 
+     *
      * @return the value at the index
      */
-    public Integer getValue(ScalerDataIndex scalarDataIndex) {
+    public Integer getValue(final ScalerDataIndex scalarDataIndex) {
         return this.data[scalarDataIndex.index()];
+    }
+
+    /**
+     * Set the event ID of the scaler data.
+     *
+     * @param eventId the event ID of the scaler data
+     */
+    void setEventId(final int eventId) {
+        this.eventId = eventId;
     }
 
     /**
@@ -154,5 +187,5 @@
         final List<GenericObject> collection = new ArrayList<GenericObject>();
         collection.add(this.toGenericObject());
         event.put(collectionName, collection, GenericObject.class, 0);
-    }
+    }    
 }

Modified: java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalersEvioProcessor.java
 =============================================================================
--- java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalersEvioProcessor.java	(original)
+++ java/trunk/record-util/src/main/java/org/hps/record/scalers/ScalersEvioProcessor.java	Thu Aug 20 12:50:12 2015
@@ -1,10 +1,15 @@
 package org.hps.record.scalers;
 
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import org.hps.record.evio.EvioEventConstants;
 import org.hps.record.evio.EvioEventProcessor;
+import org.hps.record.evio.EvioEventUtilities;
 import org.jlab.coda.jevio.BaseStructure;
 import org.jlab.coda.jevio.EvioEvent;
 import org.lcsim.util.log.DefaultLogFormatter;
@@ -15,38 +20,36 @@
  *
  * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
  */
-public final class ScalersEvioProcessor extends EvioEventProcessor {
+public class ScalersEvioProcessor extends EvioEventProcessor {
 
-    private static final Logger LOGGER = LogUtil.create(ScalersEvioProcessor.class, new DefaultLogFormatter(), Level.INFO);
+    private static final Logger LOGGER = LogUtil.create(ScalersEvioProcessor.class, new DefaultLogFormatter(),
+            Level.ALL);
 
     /**
      * Currently cached ScalerData object which was created by the process method.
      */
-    private ScalerData data;
+    private ScalerData currentScalerData;
+    private boolean resetEveryEvent = true;
 
-    boolean resetEveryEvent = true;
+    private Set<ScalerData> scalerDataSet = new LinkedHashSet<ScalerData>();
+
+    public ScalerData getCurrentScalerData() {
+        return this.currentScalerData;
+    }
 
     /**
      * Get the current scaler data or null if there was none in the last event processed.
      *
      * @return the current scaler data or <code>null</code> if none exists
      */
-    public ScalerData getScalerData() {
-        return this.data;
+    public List<ScalerData> getScalerData() {
+        return new ArrayList<ScalerData>(this.scalerDataSet);
     }
 
-    /**
-     * This method will create a <code>ScalerData</code> object and cache it. The current object is first reset to <code>null</code> every time this
-     * method is called.
-     *
-     * @param evio the EVIO event data
-     */
-    @Override
-    public void process(final EvioEvent evio) {
-        if (resetEveryEvent) {
-            this.data = null;
-        }
-        for (final BaseStructure bank : evio.getChildrenList()) {
+    private ScalerData getScalerData(final EvioEvent evioEvent) {
+        ScalerData scalerData = null;
+        // Proceed if sync bit checking is not enabled or sync bit is on.
+        outerBankLoop: for (final BaseStructure bank : evioEvent.getChildrenList()) {
             // Does the crate tag match?
             if (bank.getHeader().getTag() == EvioEventConstants.SCALERS_CRATE_TAG) {
                 if (bank.getChildrenList() != null) {
@@ -54,19 +57,48 @@
                         // Does the bank tag match?
                         if (subBank.getHeader().getTag() == EvioEventConstants.SCALERS_BANK_TAG) {
 
-                            LOGGER.fine("found scaler data in bank " + subBank.getHeader().getTag() + " and EVIO event " + evio.getEventNumber());
+                            LOGGER.fine("found scaler data in bank " + subBank.getHeader().getTag()
+                                    + " and EVIO event " + evioEvent.getEventNumber());
 
                             // Scaler data exists in event so create object and stop processing.
-                            this.data = new ScalerData(subBank.getIntData());
-                            break;
+                            scalerData = new ScalerData(subBank.getIntData(),
+                                    EvioEventUtilities.getEventIdData(evioEvent)[0]);
+
+                            break outerBankLoop;
                         }
                     }
                 }
             }
+        }
+        return scalerData;
+    }
+
+    /**
+     * This method will create a <code>ScalerData</code> object and cache it. The current object is first reset to
+     * <code>null</code> every time this method is called.
+     *
+     * @param evioEvent the EVIO event data
+     */
+    @Override
+    public void process(final EvioEvent evioEvent) {
+        if (resetEveryEvent) {
+            // Reset the cached data object.
+            this.currentScalerData = null;
+        }
+
+        final ScalerData scalerData = this.getScalerData(evioEvent);
+        if (scalerData != null) {
+            this.currentScalerData = scalerData;
+            this.scalerDataSet.add(this.currentScalerData);
         }
     }
 
     public void setResetEveryEvent(final boolean resetEveryEvent) {
         this.resetEveryEvent = resetEveryEvent;
     }
+
+    @Override
+    public void startJob() {
+        this.scalerDataSet = new LinkedHashSet<ScalerData>();
+    }
 }

Top of Message | Previous Page | Permalink

Advanced Options


Options

Log In

Log In

Get Password

Get Password


Search Archives

Search Archives


Subscribe or Unsubscribe

Subscribe or Unsubscribe


Archives

November 2017
August 2017
July 2017
January 2017
December 2016
November 2016
October 2016
September 2016
August 2016
July 2016
June 2016
May 2016
April 2016
March 2016
February 2016
January 2016
December 2015
November 2015
October 2015
September 2015
August 2015
July 2015
June 2015
May 2015
April 2015
March 2015
February 2015
January 2015
December 2014
November 2014
October 2014
September 2014
August 2014
July 2014
June 2014
May 2014
April 2014
March 2014
February 2014
January 2014
December 2013
November 2013

ATOM RSS1 RSS2



LISTSERV.SLAC.STANFORD.EDU

Secured by F-Secure Anti-Virus CataList Email List Search Powered by the LISTSERV Email List Manager

Privacy Notice, Security Notice and Terms of Use