Commits

Anonymous committed cf49a13

Initial import

Comments (0)

Files changed (41)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>ecat</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

.settings/org.eclipse.jdt.core.prefs

+#Wed Jul 22 22:47:07 CDT 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.source=1.3

.settings/org.maven.ide.eclipse.prefs

+#Wed Jul 22 15:35:17 CDT 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

ecat-io/.classpath

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>ecat-io</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

ecat-io/.settings/org.eclipse.jdt.core.prefs

+#Wed Jul 22 22:47:08 CDT 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.source=1.4

ecat-io/.settings/org.maven.ide.eclipse.prefs

+#Wed Jul 22 15:35:17 CDT 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

ecat-io/notes.txt

+Required operations:
+
+Read Main Header
+
+Summarize:
+  any Main Header value
+  size
+
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.nrg</groupId>
+  <artifactId>ecat-io</artifactId>
+  <packaging>jar</packaging>
+  <version>0.1-SNAPSHOT</version>
+  <name>ecat-io</name>
+  <parent>
+    <groupId>org.nrg</groupId>
+    <artifactId>ecat</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+	<artifactId>maven-compiler-plugin</artifactId>
+	<configuration>
+	  <source>1.4</source>
+	  <target>1.4</target>
+	</configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

ecat-io/src/main/java/org/nrg/IOUtils.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class IOUtils {
+	private IOUtils() {}
+	
+	private static final int BUF_SIZE = 512;
+	
+	/**
+	 * Copies data from in to out
+	 * @param out destination OutputStream
+	 * @param in source InputStream
+	 * @param nbytes number of bytes to copy
+	 * @throws IOException
+	 */
+	public static void copy(final OutputStream out, final InputStream in, final int nbytes)
+	throws IOException {
+		final byte[] buf = new byte[BUF_SIZE];
+		for (int remaining = nbytes; remaining > 0; ) {
+			final int chunk = Math.min(BUF_SIZE, remaining);
+			for (int buf_i = 0; buf_i < chunk; ) {
+				int nread = in.read(buf, buf_i, chunk - buf_i);
+				if (-1 == nread) {
+					throw new EOFException();
+				} else {
+					buf_i += nread;
+				}
+			}
+			out.write(buf, 0, chunk);
+			remaining -= chunk;
+		}
+	}
+	
+	
+	/**
+	 * Copies all data from in to out
+	 * @param out destination OutputStream
+	 * @param in source InputStream
+	 * @throws IOException
+	 */
+	public static void copy(final OutputStream out, final InputStream in)
+	throws IOException {
+		final byte[] buf = new byte[BUF_SIZE];
+		while (true) {
+			final int nread = in.read(buf);
+			if (-1 == nread) {
+				return;
+			} else {
+				out.write(buf, 0, nread);
+			}
+		}
+	}
+}

ecat-io/src/main/java/org/nrg/Utils.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class Utils {
+	private Utils() {}
+	
+	public static int compare(int a, int b) {
+		if (a < b) {
+			return -1;
+		} else if (a > b) {
+			return 1;
+		} else {
+			return 0;
+		}
+	}
+	
+}

ecat-io/src/main/java/org/nrg/ecat/AbstractHeaderModification.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat;
+
+import org.nrg.Utils;
+
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public abstract class AbstractHeaderModification implements HeaderModification {
+	private final Header.Type type;
+	private final int offset;
+	
+	protected AbstractHeaderModification(final Header.Type type, final int offset) {
+		this.type = type;
+		this.offset = offset;
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.HeaderModification#getHeaderType()
+	 */
+	public Header.Type getHeaderType() { return type; }
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.HeaderModification#getOffset()
+	 */
+	public int getOffset() { return offset; }
+
+	/* (non-Javadoc)
+	 * @see java.lang.Comparable#compareTo(java.lang.Object)
+	 */
+	public int compareTo(Object o) {
+		final int headerc = type.compareTo(((HeaderModification)o).getHeaderType());
+		return 0 == headerc ? Utils.compare(offset, ((HeaderModification)o).getOffset()) : headerc;
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/Header.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat;
+
+import java.util.Collection;
+import java.util.SortedMap;
+
+import org.nrg.ecat.var.Variable;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface Header {
+	Type getType();
+	Object getValue(Variable variable);
+	SortedMap getVariableValues();
+	Collection getVariables();
+	
+	public final static class Type implements Comparable {
+		private final String name;
+		private final Integer code;
+		
+		private Type(final String name, final int code) {
+			this.name = name;
+			this.code = new Integer(code);
+		}
+		
+		public String getName() { return name; }
+		
+		/*
+		 * (non-Javadoc)
+		 * @see java.lang.Comparable#compareTo(java.lang.Object)
+		 */
+		public int compareTo(final Object o) {
+			return code.compareTo(((Type)o).code);
+		}
+		
+		/*
+		 * (non-Javadoc)
+		 * @see java.lang.Object#toString()
+		 */
+		public String toString() {
+			return super.toString() + " " + name;
+		}
+	}
+	
+	public final static Type MAIN = new Type("Main", 0);
+}

ecat-io/src/main/java/org/nrg/ecat/HeaderModification.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface HeaderModification extends Comparable {
+	Header.Type getHeaderType();
+	int getOffset();
+	int modify(OutputStream to, InputStream from) throws IOException;
+}

ecat-io/src/main/java/org/nrg/ecat/MainHeader.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.nrg.ecat.var.CharacterVariable;
+import org.nrg.ecat.var.DateTimeVariable;
+import org.nrg.ecat.var.RealListVariable;
+import org.nrg.ecat.var.RealVariable;
+import org.nrg.ecat.var.ShortListVariable;
+import org.nrg.ecat.var.ShortVariable;
+import org.nrg.ecat.var.Variable;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class MainHeader implements Header {
+	private final SortedMap values;
+	
+	public MainHeader(final InputStream in) throws IOException {
+		final SortedMap m = new TreeMap();
+		for (final Iterator vi = allVariables.iterator(); vi.hasNext(); ) {
+			final Variable v = (Variable)vi.next();
+			m.put(v, v.readValue(in));
+		}
+		values = Collections.unmodifiableSortedMap(m);
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.Header#getType()
+	 */
+	public Type getType() { return MAIN; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.Header#getValue(org.nrg.ecat.var.Variable)
+	 */
+	public Object getValue(Variable variable) {
+		return values.get(variable);
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.Header#getVariableValues()
+	 */
+	public SortedMap getVariableValues() {
+		return values;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.Header#getVariables()
+	 */
+	public Collection getVariables() {
+		return values.keySet();
+	}
+	
+	public String toString() {
+		return values.toString();
+	}
+
+	// main header
+	public static final Variable MAGIC_NUMBER = new CharacterVariable(MAIN,"MAGIC_NUMBER",0,14);
+	public static final Variable ORIGINAL_FILE_NAME = new CharacterVariable(MAIN,"ORIGINAL_FILE_NAME",14,32);
+	public static final Variable SW_VERSION = new ShortVariable(MAIN,"SW_VERSION",46);
+	public static final Variable SYSTEM_TYPE = new ShortVariable(MAIN,"SYSTEM_TYPE",48);
+	public static final Variable FILE_TYPE = new ShortVariable(MAIN,"FILE_TYPE",50);
+	public static final Variable SERIAL_NUMBER = new CharacterVariable(MAIN,"SERIAL_NUMBER",52,10);
+	public static final Variable SCAN_START_TIME = new DateTimeVariable(MAIN,"SCAN_START_TIME",62); 
+	public static final Variable ISOTOPE_NAME = new CharacterVariable(MAIN,"ISOTOPE_NAME",66,8);
+	public static final Variable ISOTOPE_HALFLIFE = new RealVariable(MAIN,"ISOTOPE_HALFLIFE",74);
+	public static final Variable RADIOPHARMACEUTICAL = new CharacterVariable(MAIN,"RADIOPHARMACEUTICAL",78,32);
+	public static final Variable GANTRY_TILT = new RealVariable(MAIN,"GANTRY_TILT",110);
+	public static final Variable GANTRY_ROTATION = new RealVariable(MAIN,"GANTRY_ROTATION",114);
+	public static final Variable BED_ELEVATION = new RealVariable(MAIN,"BED_ELEVATION",118);
+	public static final Variable INTRINSIC_TILT = new RealVariable(MAIN,"INTRINSIC_TILT",122);
+	public static final Variable WOBBLE_SPEED = new ShortVariable(MAIN,"WOBBLE_SPEED",126);
+	public static final Variable TRANSM_SOURCE_TYPE = new ShortVariable(MAIN,"TRANSM_SOURCE_TYPE",128);
+	public static final Variable DISTANCE_SCANNED = new RealVariable(MAIN,"DISTANCE_SCANNED",130);
+	public static final Variable TRANSAXIAL_FOV = new RealVariable(MAIN,"TRANSAXIAL_FOV",134);
+	public static final Variable ANGULAR_COMPRESSION = new ShortVariable(MAIN,"ANGULAR_COMPRESSION",138);
+	public static final Variable COIN_SAMP_MODE = new ShortVariable(MAIN,"COIN_SAMP_MODE",140);
+	public static final Variable AXIAL_SAMP_MODE = new ShortVariable(MAIN,"AXIAL_SAMP_MODE",142);
+	public static final Variable ECAT_CALIBRATION_FACTOR = new RealVariable(MAIN,"ECAT_CALIBRATION_FACTOR",144);
+	public static final Variable CALIBRATION_UNITS = new ShortVariable(MAIN,"CALIBRATION_UNITS",148);
+	public static final Variable CALIBRATION_UNITS_LABEL = new ShortVariable(MAIN,"CALIBRATION_UNITS_LABEL",150);
+	public static final Variable COMPRESSION_CODE = new ShortVariable(MAIN,"COMPRESSION_CODE",152);
+	public static final Variable STUDY_TYPE = new CharacterVariable(MAIN,"STUDY_TYPE",154,12);
+	public static final Variable PATIENT_ID = new CharacterVariable(MAIN,"PATIENT_ID",166,16);
+	public static final Variable PATIENT_NAME = new CharacterVariable(MAIN,"PATIENT_NAME",182,32);
+	public static final Variable PATIENT_SEX = new CharacterVariable(MAIN,"PATIENT_SEX",214,1);
+	public static final Variable PATIENT_DEXTERITY = new CharacterVariable(MAIN,"PATIENT_DEXTERITY",215,1);
+	public static final Variable PATIENT_AGE = new RealVariable(MAIN,"PATIENT_AGE",216);
+	public static final Variable PATIENT_HEIGHT = new RealVariable(MAIN,"PATIENT_HEIGHT",220);
+	public static final Variable PATIENT_WEIGHT = new RealVariable(MAIN,"PATIENT_WEIGHT",224);
+	public static final Variable PATIENT_BIRTH_DATE = new DateTimeVariable(MAIN,"PATIENT_BIRTH_DATE",228);	// TODO: format?
+	public static final Variable PHYSICIAN_NAME = new CharacterVariable(MAIN,"PHYSICIAN_NAME",232,32);
+	public static final Variable OPERATOR_NAME = new CharacterVariable(MAIN,"OPERATOR_NAME",264,32);
+	public static final Variable STUDY_DESCRIPTION = new CharacterVariable(MAIN,"STUDY_DESCRIPTION",296,32);
+	public static final Variable ACQUISITION_TYPE = new ShortVariable(MAIN,"ACQUISITION_TYPE",328);
+	public static final Variable PATIENT_ORIENTATION = new ShortVariable(MAIN,"PATIENT_ORIENTATION",330);
+	public static final Variable FACILITY_NAME = new CharacterVariable(MAIN,"FACILITY_NAME",332,20);
+	public static final Variable NUM_PLANES = new ShortVariable(MAIN,"NUM_PLANES",352);
+	public static final Variable NUM_FRAMES = new ShortVariable(MAIN,"NUM_FRAMES",354);
+	public static final Variable NUM_GATES = new ShortVariable(MAIN,"NUM_GATES",356);
+	public static final Variable NUM_BED_POS = new ShortVariable(MAIN,"NUM_BED_POS",358);
+	public static final Variable INIT_BED_POSITION = new RealVariable(MAIN,"INIT_BED_POSITION",360);
+	public static final Variable BED_POSITION = new RealListVariable(MAIN,"BED_POSITION",364,15);
+	public static final Variable PLANE_SEPARATION = new RealVariable(MAIN,"PLANE_SEPARATION",424);
+	public static final Variable LWR_SCTR_THRES = new ShortVariable(MAIN,"LWR_SCTR_THRES",428);
+	public static final Variable LWR_TRUE_THRES = new ShortVariable(MAIN,"LWR_TRUE_THRES",430);
+	public static final Variable UPR_TRUE_THRES = new ShortVariable(MAIN,"UPR_TRUE_THRES",432);
+	public static final Variable USER_PROCESS_CODE = new CharacterVariable(MAIN,"USER_PROCESS_CODE",434,10);
+	public static final Variable ACQUISITION_MODE = new ShortVariable(MAIN,"ACQUISITION_MODE",444);
+	public static final Variable BIN_SIZE = new RealVariable(MAIN,"BIN_SIZE",446);
+	public static final Variable BRANCHING_FRACTION = new RealVariable(MAIN,"BRANCHING_FRACTION",450);
+	public static final Variable DOSE_START_TIME = new DateTimeVariable(MAIN,"DOSE_START_TIME",454);
+	public static final Variable DOSAGE = new RealVariable(MAIN,"DOSAGE",458);
+	public static final Variable WELL_COUNTER_CORR_FACTOR = new RealVariable(MAIN,"WELL_COUNTER_CORR_FACTOR",462);
+	public static final Variable DATA_UNITS = new CharacterVariable(MAIN,"DATA_UNITS",466,32);
+	public static final Variable SEPTA_STATE = new ShortVariable(MAIN,"SEPTA_STATE",498);
+	public static final Variable MAIN_CTI_FILL = new ShortListVariable(MAIN,"FILL",500,6);
+
+	private static final List allVariables =
+		Collections.unmodifiableList(Arrays.asList(new Variable[] {
+				MAGIC_NUMBER,
+				ORIGINAL_FILE_NAME,
+				SW_VERSION,
+				SYSTEM_TYPE,
+				FILE_TYPE,
+				SERIAL_NUMBER,
+				SCAN_START_TIME,
+				ISOTOPE_NAME,
+				ISOTOPE_HALFLIFE,
+				RADIOPHARMACEUTICAL,
+				GANTRY_TILT,
+				GANTRY_ROTATION,
+				BED_ELEVATION,
+				INTRINSIC_TILT,
+				WOBBLE_SPEED,
+				TRANSM_SOURCE_TYPE,
+				DISTANCE_SCANNED,
+				TRANSAXIAL_FOV,
+				ANGULAR_COMPRESSION,
+				COIN_SAMP_MODE,
+				AXIAL_SAMP_MODE,
+				ECAT_CALIBRATION_FACTOR,
+				CALIBRATION_UNITS,
+				CALIBRATION_UNITS_LABEL,
+				COMPRESSION_CODE,
+				STUDY_TYPE,
+				PATIENT_ID,
+				PATIENT_NAME,
+				PATIENT_SEX,
+				PATIENT_DEXTERITY,
+				PATIENT_AGE,
+				PATIENT_HEIGHT,
+				PATIENT_WEIGHT,
+				PATIENT_BIRTH_DATE,
+				PHYSICIAN_NAME,
+				OPERATOR_NAME,
+				STUDY_DESCRIPTION,
+				ACQUISITION_TYPE,
+				PATIENT_ORIENTATION,
+				FACILITY_NAME,
+				NUM_PLANES,
+				NUM_FRAMES,
+				NUM_GATES,
+				NUM_BED_POS,
+				INIT_BED_POSITION,
+				BED_POSITION,
+				PLANE_SEPARATION,
+				LWR_SCTR_THRES,
+				LWR_TRUE_THRES,
+				UPR_TRUE_THRES,
+				USER_PROCESS_CODE,
+				ACQUISITION_MODE,
+				BIN_SIZE,
+				BRANCHING_FRACTION,
+				DOSE_START_TIME,
+				DOSAGE,
+				WELL_COUNTER_CORR_FACTOR,
+				DATA_UNITS,
+				SEPTA_STATE,
+				MAIN_CTI_FILL,
+		}));
+}

ecat-io/src/main/java/org/nrg/ecat/MatrixData.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+
+import org.nrg.IOUtils;
+import org.nrg.ecat.var.Variable;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class MatrixData {	// TODO: implements Comparable
+	private final static int BLOCK_SIZE = 512;
+	private final Map values;
+	private final MainHeader mainHeader;
+	
+	private MatrixData(final InputStream in, final Map requestedVars) throws IOException {
+		values = new LinkedHashMap();
+		
+		mainHeader = new MainHeader(in);
+
+		// First, extract the requested main header variables
+		if (requestedVars.containsKey(Header.MAIN)) {
+			for (final Iterator i = ((Collection)requestedVars.get(Header.MAIN)).iterator(); i.hasNext(); ) {
+				final Variable v = (Variable)i.next();
+				values.put(v, mainHeader.getValue(v));
+			}
+		}
+	}
+	
+	public MatrixData(final InputStream in, final Collection requestedVars)
+	throws IOException {
+		this(in, arrangeRequestedVars(requestedVars));
+	}
+	
+	public MatrixData(final InputStream in) throws IOException {
+		this(in, Collections.EMPTY_MAP);
+	}
+	
+
+	private static Map arrangeRequestedVars(final Collection requestedVars) {
+		final Map m = new HashMap();
+		for (final Iterator i = requestedVars.iterator(); i.hasNext(); ) {
+			final Variable v = (Variable)i.next();
+			if (!m.containsKey(v)) {
+				m.put(v, new TreeSet());
+			}
+			((Collection)m.get(v)).add(v);
+		}
+		return m;
+	}
+	
+	
+	public MainHeader getMainHeader() { return mainHeader; }
+	
+	
+	
+	private static void copyOneHeaderWithModifications(final OutputStream out, final InputStream in,
+			final List modifications)
+	throws IOException {
+		// Make any requested modifications, copying data to get to the variables.
+		int hpos = 0;
+		for (final Iterator i = modifications.iterator(); i.hasNext(); i.remove()) {
+			final HeaderModification mod = (HeaderModification)i.next();
+			final int offset = mod.getOffset();
+			if (offset > hpos) {
+				IOUtils.copy(out, in, offset - hpos);
+			}
+			assert offset == hpos;
+			hpos += mod.modify(out, in);
+		}
+		
+		// Copy the rest of the record.
+		if (hpos < BLOCK_SIZE) {
+			IOUtils.copy(out, in, BLOCK_SIZE - hpos);
+		}
+	}
+	
+	/**
+	 * Sorts a single, not-necessarily-ordered Collection of header modifications
+	 * into a Map from header types to ordered Lists of modifications.
+	 * @param modifications Collection of HeaderModifications
+	 * @return Map : Header.Type -> Collection[HeaderModification]
+	 */
+	private static Map sortModifications(final Collection modifications) {
+		final Map m = new HashMap();
+		for (final Iterator i = modifications.iterator(); i.hasNext(); ) {
+			final HeaderModification hm = (HeaderModification)i.next();
+			final Header.Type type = hm.getHeaderType();
+			if (!m.containsKey(type)) {
+				m.put(type, new ArrayList());
+			}
+			((Collection)m.get(type)).add(hm);
+		}
+		for (final Iterator i = m.values().iterator(); i.hasNext(); ) {
+			Collections.sort((List)i.next());
+		}
+		return m;
+	}
+	
+	
+	public static void copyWithModifications(final OutputStream out, final InputStream in,
+			final Collection modifications)
+			throws IOException {
+		final Map m = sortModifications(modifications);
+		
+		// Record 1: main header
+		copyOneHeaderWithModifications(out, in, (List)m.get(Header.MAIN));
+		
+		// TODO: directory list
+		// TODO: matrices
+		IOUtils.copy(out, in);
+	}
+	
+	
+	public static void copyClearing(final OutputStream out, final InputStream in,
+			final Collection variables) throws IOException {
+		final Collection mods = new ArrayList(variables.size());
+		for (final Iterator i = variables.iterator(); i.hasNext(); ) {
+			mods.add(((Variable)i.next()).createClearModification());
+		}
+		copyWithModifications(out, in, mods);
+	}
+	
+	
+	public static void main(final String[] args) throws IOException {
+		for (int i = 0; i < args.length; i++) {
+			final FileInputStream in = new FileInputStream(args[i]);
+			try {
+				final MatrixData m = new MatrixData(in);
+				final MainHeader mainHeader = m.getMainHeader();
+				System.out.println("Main header:");
+				System.out.println(mainHeader);
+			} finally {
+				in.close();
+			}
+		}
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/AbstractVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.nrg.Utils;
+import org.nrg.ecat.AbstractHeaderModification;
+import org.nrg.ecat.Header;
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * 
+ * Note: this class has a natural ordering that is inconsistent with equals.
+ * 
+ * In particular: equals has not been overridden, and compareTo compares only
+ * the header type and offset.  Two variables with the same header type and
+ * offset will compare as 0, but not be equal (since the test is object
+ * identity).  However, two variables with the same header type and offset
+ * is pathological.  Don't do that.
+ *
+ */
+public abstract class AbstractVariable implements Variable {
+	private Header.Type type;
+	private String name;
+	private int offset;
+	
+	protected AbstractVariable(final Header.Type type,
+			final String name, final int offset) {
+		this.type = type;
+		this.name = name;
+		this.offset = offset;
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#getHeaderType()
+	 */
+	public final Type getHeaderType() { return type; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#getName()
+	 */
+	public final String getName() { return name; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#getOffset()
+	 */
+	public final int getOffset() { return offset; }
+	
+	/*
+	 * (non-Javadoc)
+	 * @see java.lang.Comparable#compareTo(java.lang.Object)
+	 */
+	public final int compareTo(final Object o) {
+		final Variable other = (Variable)o;
+		final int hc = type.compareTo(other.getHeaderType());
+		return 0 == hc ? Utils.compare(offset, other.getOffset()) : hc;
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer();
+		sb.append(offset).append(" ").append(name);
+		return sb.toString();
+	}
+	
+	protected static byte[] readFull(final InputStream in, final int size)
+	throws IOException {
+		final byte[] bytes = new byte[size];
+		int n = 0;
+		while (n < size) {
+			final int nr = in.read(bytes, n, size - n);
+			if (nr < 0) {
+				throw new EOFException("End of file reached where " + size + " byte field was expected");
+			} else {
+				assert nr > 0;
+				n += nr;
+			}
+		}
+		return bytes;
+	}
+	
+//	public static String showByteArray(final byte[] bytes) {
+//		final StringBuffer sb = new StringBuffer("bytes: ");
+//		if (0 == bytes.length) {
+//			return sb.append("(empty)").toString();
+//		} else {
+//			sb.append(Integer.toHexString(0xff & bytes[0]));
+//		}
+//		for (int i = 1; i < bytes.length; i++) {
+//			sb.append(", ");
+//			sb.append(Integer.toHexString(0xff & bytes[i]));
+//		}
+//		return sb.toString();
+//	}
+	
+	protected static int read4ByteInt(final InputStream in) throws IOException {
+		final byte[] bytes = readFull(in, 4);
+		int ib3 = bytes[0] & 0xff;
+		int ib2 = bytes[1] & 0xff;
+		int ib1 = bytes[2] & 0xff;
+		int ib0 = bytes[3] & 0xff;
+		return (((((ib3 << 8) | ib2) << 8) | ib1) << 8) | ib0;
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification(final int size) {
+		return new AbstractHeaderModification(getHeaderType(), getOffset()) {		
+			public int modify(OutputStream to, InputStream from) throws IOException {
+				readFull(from, size);
+				to.write(new byte[size]);
+				return size;
+			}
+		};
+	}
+
+}

ecat-io/src/main/java/org/nrg/ecat/var/CharacterVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class CharacterVariable extends AbstractVariable {
+	private static final String CHARSET_NAME = "US-ASCII";
+	private final int size;
+	
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 */
+	public CharacterVariable(final Type type, final String name, final int offset, final int size) {
+		super(type, name, offset);
+		this.size = size;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		sb.append(" Character*").append(size);
+		return sb.toString();
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		final byte[] bytes = readFull(in, size);
+		int len = 0;
+		while (len < size && 0 != bytes[len]) {
+			len++;	// ECAT strings are NUL-terminated
+		}
+		return new String(bytes, 0, len, CHARSET_NAME);
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(size);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/DateTimeVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class DateTimeVariable extends AbstractVariable {
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 */
+	public DateTimeVariable(final Type type, final String name, final int offset) {
+		super(type, name, offset);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append(" Integer*4").toString();
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		return new Date((0xffffffffl & read4ByteInt(in)) * 1000L);
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(4);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/RealListVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class RealListVariable extends AbstractVariable {
+	private final Variable realVar;
+	private final int length;
+	
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 * @param length
+	 */
+	public RealListVariable(final Type type, final String name, final int offset, final int length) {
+		super(type, name, offset);
+		this.realVar = new RealVariable(type, name + " component", offset);
+		this.length = length;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append("(").append(length).append(") Real*4").toString();
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		final List list = new ArrayList(length);
+		for (int i = 0; i < length; i++) {
+			list.add(realVar.readValue(in));
+		}
+		return list;
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(4*length);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/RealVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class RealVariable extends AbstractVariable {
+
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 */
+	public RealVariable(final Type type, final String name, final int offset) {
+		super(type, name, offset);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append(" Real*4").toString();
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		return new Float(Float.intBitsToFloat(read4ByteInt(in)));
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(4);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/ShortListVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class ShortListVariable extends AbstractVariable {
+	private final Variable shortVar;
+	private final int length;
+	
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 * @param length
+	 */
+	public ShortListVariable(final Type type, final String name, final int offset, final int length) {
+		super(type, name, offset);
+		this.shortVar = new ShortVariable(type, name + " component", offset);
+		this.length = length;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append("(").append(length).append(") Integer*2").toString();
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		final List list = new ArrayList(length);
+		for (int i = 0; i < length; i++) {
+			list.add(shortVar.readValue(in));
+		}
+		return list;
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(2*length);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/ShortVariable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class ShortVariable extends AbstractVariable {
+	/**
+	 * @param type
+	 * @param name
+	 * @param offset
+	 */
+	public ShortVariable(Type type, String name, int offset) {
+		super(type, name, offset);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.AbstractVariable#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append(" Integer*2").toString();
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#readValue(java.io.InputStream)
+	 */
+	public Object readValue(final InputStream in) throws IOException {
+		final byte[] bytes = readFull(in, 2);
+		final int v = ((int)bytes[0] << 8) | (int)bytes[1];
+		return new Short((short)v);
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.nrg.ecat.var.Variable#createClearModification()
+	 */
+	public HeaderModification createClearModification() {
+		return createClearModification(2);
+	}
+}

ecat-io/src/main/java/org/nrg/ecat/var/Variable.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.Header;
+import org.nrg.ecat.HeaderModification;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface Variable extends Comparable {
+	Header.Type getHeaderType();
+	int getOffset();
+	String getName();
+	Object readValue(InputStream in) throws IOException;
+	HeaderModification createClearModification();
+}

ecat-io/src/test/java/org/nrg/IOUtilsTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class IOUtilsTest extends TestCase {
+	private static final byte[] b1 = new byte[]{0, 1, 2, 3};
+	private static final byte[] big = new byte[5124];
+
+	/**
+	 * Test method for {@link org.nrg.IOUtils#copy(java.io.OutputStream, java.io.InputStream, int)}.
+	 */
+	public void testCopyN() throws IOException {
+		final ByteArrayInputStream in1 = new ByteArrayInputStream(b1);
+		in1.mark(b1.length);
+		try {
+			final ByteArrayOutputStream out1 = new ByteArrayOutputStream(b1.length + 2);
+			try {
+				IOUtils.copy(out1, in1, 4);
+				assertTrue(Arrays.equals(out1.toByteArray(), b1));
+				try {
+					IOUtils.copy(out1, in1, 1);
+					fail("expected IOException on EOF");
+				} catch (IOException ok) {}
+			} finally {
+				out1.close();
+			}
+			
+			in1.reset();
+			final ByteArrayOutputStream out2 = new ByteArrayOutputStream(6);
+			try {
+				IOUtils.copy(out2, in1, 6);
+				fail("expected IOException on EOF");
+			} catch (IOException ok) {
+			} finally {
+				out2.close();
+			}
+		} finally {
+			in1.close();
+		}
+		
+		final ByteArrayInputStream in2 = new ByteArrayInputStream(big);
+		in2.mark(big.length);
+		try {
+			final ByteArrayOutputStream out = new ByteArrayOutputStream(big.length);
+			try {
+				IOUtils.copy(out, in2, big.length);
+				assertTrue(Arrays.equals(big, out.toByteArray()));
+			} finally {
+				out.close();
+			}
+		} finally {
+			in2.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.IOUtils#copy(java.io.OutputStream, java.io.InputStream)}.
+	 */
+	public void testCopy() throws IOException {
+		final InputStream in = new ByteArrayInputStream(big);
+		try {
+			final ByteArrayOutputStream out = new ByteArrayOutputStream(big.length + 1);
+			try {
+				IOUtils.copy(out, in);
+				assertTrue(Arrays.equals(big, out.toByteArray()));
+			} finally {
+				out.close();
+			}
+		} finally {
+			in.close();
+		}
+	}
+}

ecat-io/src/test/java/org/nrg/UtilsTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class UtilsTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.Utils#compare(int, int)}.
+	 */
+	public void testCompare() {
+		final Integer ZERO = new Integer(0);
+		final Integer ONE = new Integer(1);
+		final Integer MONE = new Integer(-1);
+		
+		assertEquals(ONE.compareTo(ZERO), Utils.compare(1, 0));
+		assertEquals(ZERO.compareTo(ONE), Utils.compare(0, 1));
+		assertEquals(MONE.compareTo(ZERO), Utils.compare(-1, 0));
+		assertEquals(ZERO.compareTo(MONE), Utils.compare(0, -1));
+		assertEquals(ONE.compareTo(MONE), Utils.compare(1, -1));
+		assertEquals(MONE.compareTo(ONE), Utils.compare(-1, 1));
+		assertEquals(ZERO.compareTo(ZERO), Utils.compare(0, 0));
+		assertEquals(ONE.compareTo(ONE), Utils.compare(1, 1));
+		assertEquals(MONE.compareTo(MONE), Utils.compare(-1, -1));
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/AbstractVariableTest.java

+/**
+ * Copyright (c) 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.nrg.ecat.Header;
+import org.nrg.ecat.HeaderModification;
+import org.nrg.ecat.Header.Type;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class AbstractVariableTest extends TestCase {
+	private final class ConcreteVariable extends AbstractVariable {
+		public ConcreteVariable(final Type type, final String name, final int offset) {
+			super(type, name, offset);
+		}
+		
+		public Object readValue(final InputStream in) {
+			throw new UnsupportedOperationException();
+		}
+		
+		public HeaderModification createClearModification() {
+			return createClearModification(4);
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#AbstractVariable(org.nrg.ecat.Header.Type, java.lang.String, long)}.
+	 */
+	public void testAbstractVariable() {
+		final Variable v = new ConcreteVariable(Header.MAIN, "TEST_VAR", 16);
+		assertEquals(Header.MAIN, v.getHeaderType());
+		assertEquals("TEST_VAR", v.getName());
+		assertTrue(16 == v.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#getHeaderType()}.
+	 */
+	public void testGetHeaderType() {
+		final Variable v = new ConcreteVariable(Header.MAIN, "TEST_VAR", 16);
+		assertEquals(Header.MAIN, v.getHeaderType());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#getName()}.
+	 */
+	public void testGetName() {
+		final Variable v = new ConcreteVariable(Header.MAIN, "TEST_VAR", 16);
+		assertEquals("TEST_VAR", v.getName());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#getOffset()}.
+	 */
+	public void testGetOffset() {
+		final Variable v = new ConcreteVariable(Header.MAIN, "TEST_VAR", 16);
+		assertTrue(16 == v.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#compareTo(java.lang.Object)}.
+	 */
+	public void testCompareTo() {
+		final Integer EQ = new Integer(0);
+		final Integer LT = new Integer(-1);
+		final Integer GT = new Integer(1);
+
+		final Variable v1 = new ConcreteVariable(Header.MAIN, "V1", 8);
+		final Variable v1a = new ConcreteVariable(Header.MAIN, "V1a", 8);
+		final Variable v2 = new ConcreteVariable(Header.MAIN, "V2", 12);
+		assertEquals(EQ, new Integer(v1.compareTo(v1a)));
+		assertEquals(EQ, new Integer(v1a.compareTo(v1)));
+		assertEquals(LT, new Integer(v1.compareTo(v2)));
+		assertEquals(GT, new Integer(v2.compareTo(v1)));
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#createClearModification(int)}.
+	 */
+	public void testCreateClearModification() throws IOException {
+		final Variable v = new ConcreteVariable(Header.MAIN, "V", 2);
+		final HeaderModification hm = v.createClearModification();
+		assertEquals(Header.MAIN, hm.getHeaderType());
+		assertTrue(2 == hm.getOffset());
+		
+		final InputStream in = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6});
+		try {
+			final ByteArrayOutputStream out = new ByteArrayOutputStream(4);
+			try {
+				hm.modify(out, in);
+				assertTrue(Arrays.equals(new byte[]{0, 0, 0, 0}, out.toByteArray()));
+				
+				try {
+					hm.modify(out, in);
+					fail("expected IOException on EOF");
+				} catch (IOException ok) {}
+			} finally {
+				out.close();
+			}
+		} finally {
+			in.close();
+		}
+		
+		final InputStream bad = new ByteArrayInputStream(new byte[]{1, 2, 3});
+		try {
+			final ByteArrayOutputStream out = new ByteArrayOutputStream(4);
+			try {
+				hm.modify(out, in);
+				fail("expected IOException on EOF");
+			} catch (IOException ok) {
+			} finally {
+				out.close();
+			}
+		} finally {
+			bad.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.AbstractVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable v1 = new ConcreteVariable(Header.MAIN, "V", 0);
+		assertEquals("0 V", v1.toString());
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/CharacterVariableTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.Header;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class CharacterVariableTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.CharacterVariable#CharacterVariable(org.nrg.ecat.Header.Type, java.lang.String, long, int)}.
+	 */
+	public void testCharacterVariable() {
+		final Variable cv = new CharacterVariable(Header.MAIN, "TEST_VAR", 32, 16);
+		assertEquals(Header.MAIN, cv.getHeaderType());
+		assertEquals("TEST_VAR", cv.getName());
+		assertTrue(32 == cv.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.CharacterVariable#readValue(java.io.InputStream)}.
+	 */
+	public void testReadValue() throws IOException {
+		final byte[] bytes = {
+				't', 'e', 's', 't', 0, 0, 0, 0,
+				'a', 'n', 'o', 't', 'h', 'e', 'r', '1'
+		};
+		final Variable cv = new CharacterVariable(Header.MAIN, "TEST_VAR", 2, 8);
+		final InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			assertEquals("test", cv.readValue(in));
+			assertEquals("another1", cv.readValue(in));
+			try {
+				cv.readValue(in);
+			} catch (IOException ok) {}
+		} finally {
+			in.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.CharacterVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable cv = new CharacterVariable(Header.MAIN, "V", 18, 32);
+		assertEquals("18 V Character*32", cv.toString());
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/DateTimeVariableTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.nrg.ecat.Header;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class DateTimeVariableTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.DateTimeVariable#DateTimeVariable(org.nrg.ecat.Header.Type, java.lang.String, long)}.
+	 */
+	public void testDateTimeVariable() {
+		final Variable dtv = new DateTimeVariable(Header.MAIN, "TEST_VAR", 32);
+		assertEquals(Header.MAIN, dtv.getHeaderType());
+		assertEquals("TEST_VAR", dtv.getName());
+		assertTrue(32 == dtv.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.DateTimeVariable#readValue(java.io.InputStream)}.
+	 */
+	public void testReadValue() throws IOException {
+		final Date now = Calendar.getInstance().getTime();
+		final long lnow = now.getTime() / 1000L;
+		final byte[] bytes = new byte[] {
+			(byte)((lnow & 0xff000000) >> 24),
+			(byte)((lnow & 0x00ff0000) >> 16),
+			(byte)((lnow & 0x0000ff00) >> 8),
+			(byte)(lnow & 0x000000ff),
+		};
+		final Variable dtv = new DateTimeVariable(Header.MAIN, "TEST_VAR", 16);
+		final InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			// Can't do simple data conversion, because the ECAT representation
+			// truncates out millseconds.
+			final Date then = (Date)dtv.readValue(in);
+			assertTrue(lnow == then.getTime() / 1000L);
+			try {
+				dtv.readValue(in);
+				fail("Expected EOF");
+			} catch (IOException ok) {}
+		} finally {
+			in.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.DateTimeVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable dtv = new DateTimeVariable(Header.MAIN, "V", 24);
+		assertEquals("24 V Integer*4", dtv.toString());
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/RealListVariableTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.nrg.ecat.Header;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class RealListVariableTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealListVariable#RealListVariable(org.nrg.ecat.Header.Type, java.lang.String, long, int)}.
+	 */
+	public void testRealListVariable() {
+		final Variable rlv = new RealListVariable(Header.MAIN, "TEST_VAR", 16, 3);
+		assertEquals(Header.MAIN, rlv.getHeaderType());
+		assertEquals("TEST_VAR", rlv.getName());
+		assertTrue(16 == rlv.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealListVariable#readValue(java.io.InputStream)}.
+	 */
+	public void testReadValue() throws IOException {
+		final byte[] bytes = RealVariableTest.toBytes(new int[] {
+				Float.floatToRawIntBits(-1.1f),
+				Float.floatToRawIntBits(0.0f),
+				Float.floatToRawIntBits(1.1f),
+				Float.floatToRawIntBits(2.2f),
+				Float.floatToRawIntBits(3.3f),
+				Float.floatToRawIntBits(4.4f),
+				Float.floatToRawIntBits(5.5f),
+		});
+		
+		final Variable rlv = new RealListVariable(Header.MAIN, "TEST_VAR", 32, 3);
+		final InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			final Iterator i1 = ((Collection)rlv.readValue(in)).iterator();
+			assertEquals(new Float(-1.1), i1.next());
+			assertEquals(new Float(0.0), i1.next());
+			assertEquals(new Float(1.1), i1.next());
+			assertFalse(i1.hasNext());
+			
+			final Iterator i2 = ((Collection)rlv.readValue(in)).iterator();
+			assertEquals(new Float(2.2), i2.next());
+			assertEquals(new Float(3.3), i2.next());
+			assertEquals(new Float(4.4), i2.next());
+			assertFalse(i2.hasNext());
+			try {
+				rlv.readValue(in);
+				fail("expected IOException on EOF");
+			} catch (IOException ok) {}
+		} finally {
+			in.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealListVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable v = new RealListVariable(Header.MAIN, "V", 8, 4);
+		assertEquals("8 V(4) Real*4", v.toString());
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/RealVariableTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.nrg.ecat.Header;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class RealVariableTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealVariable#RealVariable(org.nrg.ecat.Header.Type, java.lang.String, long)}.
+	 */
+	public void testRealVariable() {
+		final Variable rv = new RealVariable(Header.MAIN, "TEST_VAR", 32);
+		assertEquals(Header.MAIN, rv.getHeaderType());
+		assertEquals("TEST_VAR", rv.getName());
+		assertTrue(32 == rv.getOffset());
+	}
+
+	static byte[] toBytes(final int[] ints) {
+		final byte[] bytes = new byte[4*ints.length];
+		for (int ii = 0; ii < ints.length; ii++) {
+			final int bi = ii * 4;
+			bytes[bi + 0] = (byte)((ints[ii] & 0xff000000) >> 24); 
+			bytes[bi + 1] = (byte)((ints[ii] & 0x00ff0000) >> 16);
+			bytes[bi + 2] = (byte)((ints[ii] & 0x0000ff00) >> 8);
+			bytes[bi + 3] = (byte)(ints[ii] & 0x000000ff);
+		}
+		return bytes;
+	}
+		
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealVariable#readValue(java.io.InputStream)}.
+	 */
+	public void testReadValue() throws IOException {
+		final byte[] bytes = toBytes(new int[] {
+				Float.floatToRawIntBits(2.1f),
+				Float.floatToRawIntBits(-13.4f),
+				Float.floatToRawIntBits(2.2f),
+				Float.floatToRawIntBits(0.0f),
+		});
+
+		final Variable rv = new RealVariable(Header.MAIN, "TEST_VAR", 32);
+		final InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			assertEquals(new Float(2.1), rv.readValue(in));
+			assertEquals(new Float(-13.4), rv.readValue(in));
+			assertEquals(new Float(2.2), rv.readValue(in));
+			assertEquals(new Float(0.0), rv.readValue(in));
+			try {
+				rv.readValue(in);
+				fail("expected IOException on EOF");
+			} catch (IOException ok) {}
+		} finally {
+			in.close();
+		}
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.RealVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable v = new RealVariable(Header.MAIN, "V", 0);
+		assertEquals("0 V Real*4", v.toString());
+	}
+}

ecat-io/src/test/java/org/nrg/ecat/var/ShortListVariableTest.java

+/**
+ * Copyright 2009 Washington University
+ */
+package org.nrg.ecat.var;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.nrg.ecat.Header;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class ShortListVariableTest extends TestCase {
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.ShortListVariable#ShortListVariable(org.nrg.ecat.Header.Type, java.lang.String, long, int)}.
+	 */
+	public void testShortListVariable() {
+		final Variable sv = new ShortListVariable(Header.MAIN, "TEST_VAR", 32, 4);
+		assertEquals(Header.MAIN, sv.getHeaderType());
+		assertEquals("TEST_VAR", sv.getName());
+		assertTrue(32 == sv.getOffset());
+	}
+
+	/**
+	 * Test method for {@link org.nrg.ecat.var.ShortListVariable#readValue(java.io.InputStream)}.
+	 */
+	public void testReadValue() throws IOException {
+		final Variable sv = new ShortListVariable(Header.MAIN, "TEST_VAR", 16, 3);
+		final InputStream in = new ByteArrayInputStream(new byte[]{1,1,0,16,0,9,0,1});
+		try {
+			final Collection r = (Collection)sv.readValue(in);
+			final Iterator i = r.iterator();
+			assertEquals(new Short((short)257), i.next());
+			assertEquals(new Short((short)16), i.next());
+			assertEquals(new Short((short)9), i.next());
+			assertFalse(i.hasNext());
+			try {
+				sv.readValue(in);
+				fail("expected IOException on EOF");
+			} catch (IOException ok) {}
+		} finally {
+			in.close();
+		}
+	}
+	
+	/**
+	 * Test method for {@link org.nrg.ecat.var.ShortListVariable#toString()}.
+	 */
+	public void testToString() {
+		final Variable v = new ShortListVariable(Header.MAIN, "V", 30, 4);
+		assertEquals("30 V(4) Integer*2", v.toString());
+	}
+}