commit 03a2d08fb040a31c63e390f8bf1cf9403d412c1a
Author: Michal Horký <horkmi@students.zcu.cz>
Date:   Mon Apr 6 03:24:32 2020 +0200

    #7794
    
    Vytvoření hlavního projektu.

diff --git a/demo_mh/Deserializer/src/deserialize/Deserializer.java b/demo_mh/Deserializer/src/deserialize/Deserializer.java
index b78441f..c45ee20 100644
--- a/demo_mh/Deserializer/src/deserialize/Deserializer.java
+++ b/demo_mh/Deserializer/src/deserialize/Deserializer.java
@@ -35,7 +35,7 @@ public class Deserializer extends Application {
 	@Override
 	public void init() throws Exception {
 		super.init();
-		testing = false;
+		testing = true;
 	}
 	
 	@Override
diff --git a/demo_mh/Deserializer/src/deserialize/Formatter.java b/demo_mh/Deserializer/src/deserialize/Formatter.java
deleted file mode 100644
index df910f2..0000000
--- a/demo_mh/Deserializer/src/deserialize/Formatter.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package deserialize;
-
-public class Formatter {
-
-	public static int createInt(byte first, byte second) {
-		return ((first & 0xFF) << 8) | (second & 0xFF);
-	}
-	
-}
diff --git a/project/.gitignore b/project/.gitignore
new file mode 100644
index 0000000..e10e727
--- /dev/null
+++ b/project/.gitignore
@@ -0,0 +1 @@
+/.metadata/
diff --git a/project/Deserializer/.classpath b/project/Deserializer/.classpath
new file mode 100644
index 0000000..d80b68e
--- /dev/null
+++ b/project/Deserializer/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="lib" path="D:/Michal/University/1/LS/ASWI/semestralky/Deserializer/aswi2020horky/project/Deserializer/libs/jackson-core-asl-1.9.13.jar"/>
+	<classpathentry kind="lib" path="D:/Michal/University/1/LS/ASWI/semestralky/Deserializer/aswi2020horky/project/Deserializer/libs/jackson-mapper-asl-1.9.13.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/project/Deserializer/.gitignore b/project/Deserializer/.gitignore
new file mode 100644
index 0000000..ae3c172
--- /dev/null
+++ b/project/Deserializer/.gitignore
@@ -0,0 +1 @@
+/bin/
diff --git a/project/Deserializer/.project b/project/Deserializer/.project
new file mode 100644
index 0000000..3e0ac87
--- /dev/null
+++ b/project/Deserializer/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Deserializer</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/project/Deserializer/a b/project/Deserializer/a
new file mode 100644
index 0000000..a9b4704
Binary files /dev/null and b/project/Deserializer/a differ
diff --git a/project/Deserializer/a.data b/project/Deserializer/a.data
new file mode 100644
index 0000000..53bc26d
Binary files /dev/null and b/project/Deserializer/a.data differ
diff --git a/project/Deserializer/b b/project/Deserializer/b
new file mode 100644
index 0000000..67e2784
Binary files /dev/null and b/project/Deserializer/b differ
diff --git a/project/Deserializer/b.data b/project/Deserializer/b.data
new file mode 100644
index 0000000..c3e84a1
Binary files /dev/null and b/project/Deserializer/b.data differ
diff --git a/project/Deserializer/libs/Licenses b/project/Deserializer/libs/Licenses
new file mode 100644
index 0000000..ae08f3f
--- /dev/null
+++ b/project/Deserializer/libs/Licenses
@@ -0,0 +1,29 @@
+
+JACKSON JSON
+
+This copy of Jackson JSON processor streaming parser/generator is licensed under the
+Apache (Software) License, version 2.0 ("the License").
+See the License for details about distribution rights, and the
+specific rights regarding derivate works.
+
+You may obtain a copy of the License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+************************************************************
+
+JDESERIALIZE
+
+https://code.google.com/archive/p/jdeserialize/
+https://opensource.org/licenses/BSD-3-Clause
+Copyright <YEAR> <COPYRIGHT HOLDER>
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/project/Deserializer/libs/jackson-core-asl-1.9.13.jar b/project/Deserializer/libs/jackson-core-asl-1.9.13.jar
new file mode 100644
index 0000000..bb4fe1d
Binary files /dev/null and b/project/Deserializer/libs/jackson-core-asl-1.9.13.jar differ
diff --git a/project/Deserializer/libs/jackson-mapper-asl-1.9.13.jar b/project/Deserializer/libs/jackson-mapper-asl-1.9.13.jar
new file mode 100644
index 0000000..0f2073f
Binary files /dev/null and b/project/Deserializer/libs/jackson-mapper-asl-1.9.13.jar differ
diff --git a/project/Deserializer/libs/jdeserialize-1.2.jar b/project/Deserializer/libs/jdeserialize-1.2.jar
new file mode 100644
index 0000000..1040c97
Binary files /dev/null and b/project/Deserializer/libs/jdeserialize-1.2.jar differ
diff --git a/project/Deserializer/results.json b/project/Deserializer/results.json
new file mode 100644
index 0000000..ae98006
--- /dev/null
+++ b/project/Deserializer/results.json
@@ -0,0 +1,53 @@
+{
+  "handle" : 8257537,
+  "isExceptionObject" : false,
+  "type" : "INSTANCE",
+  "fielddata" : {
+    "[cd 0x7e0000: name java.util.Hashtable uid 1421746759512286392]" : {
+      "jdeserialize.field@204b315d" : 0.75,
+      "jdeserialize.field@2e8ccb24" : 8
+    }
+  },
+  "classdesc" : {
+    "handle" : 8257536,
+    "isExceptionObject" : false,
+    "type" : "CLASSDESC",
+    "classtype" : "NORMALCLASS",
+    "name" : "java.util.Hashtable",
+    "serialVersionUID" : 1421746759512286392,
+    "descflags" : 3,
+    "fields" : [ {
+      "type" : "FLOAT",
+      "name" : "loadFactor",
+      "classname" : null,
+      "javaType" : "float",
+      "innerClassReference" : false
+    }, {
+      "type" : "INTEGER",
+      "name" : "threshold",
+      "classname" : null,
+      "javaType" : "int",
+      "innerClassReference" : false
+    } ],
+    "innerclasses" : [ ],
+    "annotations" : [ ],
+    "superclass" : null,
+    "interfaces" : null,
+    "enumconstants" : [ ],
+    "arrayClass" : false,
+    "innerClass" : false,
+    "staticMemberClass" : false,
+    "localInnerClass" : false,
+    "exceptionObject" : false
+  },
+  "annotations" : {
+    "[cd 0x7e0000: name java.util.Hashtable uid 1421746759512286392]" : [ {
+      "handle" : 0,
+      "isExceptionObject" : false,
+      "type" : "BLOCKDATA",
+      "buf" : "AAAACwAAAAA=",
+      "exceptionObject" : false
+    } ]
+  },
+  "exceptionObject" : false
+}
\ No newline at end of file
diff --git a/project/Deserializer/src/Converter.java b/project/Deserializer/src/Converter.java
new file mode 100644
index 0000000..e5dc8f6
--- /dev/null
+++ b/project/Deserializer/src/Converter.java
@@ -0,0 +1,70 @@
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.ObjectWriter;
+
+import jdeserialize.content;
+import jdeserialize.jdeserialize;
+
+public class Converter extends Thread {
+	
+	private Deserializer main;
+	
+	private File input;
+	private File output;
+	
+	public Converter(Deserializer main, File inputFilename, File outputFilename) {
+		this.main = main;
+		this.input = inputFilename;
+		this.output = outputFilename;
+	}
+	
+	@Override
+	public void run() {
+		super.run();
+		
+		try {
+			String json = convert(FileWorker.loadByteArray(input));
+			
+			String title = "Ulo�en� JSON";
+			if (FileWorker.saveJson(output, json)) {
+				System.out.println("The JSON file was saved correctly.");
+				Report.info(title, null, "Ulo�en� JSON souboru prob�hlo v po��dku.");
+			} else {
+				System.out.println("There was an error saving the JSON file.");
+				Report.error(title, null, "P�i ukl�d�n� JSON souboru nastala chyba.");
+			}
+		} catch (IOException e) {
+			System.out.println("There was an error loading the file.");
+			e.printStackTrace();
+			Report.error("Na��t�n� souboru", null, "P�i na��t�n� souboru do�lo k chyb�.");
+		}
+
+		main.convertIsComplete();
+	}
+	
+	private String convert(byte[] buffer) {
+		try {
+			System.out.println("Deserialization begins.");
+			jdeserialize deserializer = new jdeserialize(buffer);
+			
+			// gets the "contents" into an array - returnes the deserialization of all
+            // 'writes' into serialized object via objectOutputStream
+            List<content> cntnts = deserializer.getContent();
+
+            // testing with only first of the contents
+            ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
+            String json = ow.writeValueAsString(cntnts.get(0));
+            
+            System.out.println("Task completed.");
+            return json;
+		} catch (IOException e) {
+			System.out.println("An error occurred while processing!");
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+}
diff --git a/project/Deserializer/src/Deserializer.java b/project/Deserializer/src/Deserializer.java
new file mode 100644
index 0000000..306e6dc
--- /dev/null
+++ b/project/Deserializer/src/Deserializer.java
@@ -0,0 +1,121 @@
+import java.io.File;
+
+import javafx.application.Application;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+
+public class Deserializer extends Application {
+	
+	private Stage stage;
+	private File inputFile;
+	private File outputFile;
+	
+	Button convert;
+	
+	// TODO delete after deserializer debugging is complete...
+	private boolean testing;
+	
+	public static void main(String[] args) {
+		launch(args);
+	}
+
+	@Override
+	public void init() throws Exception {
+		super.init();
+		testing = true;
+	}
+	
+	@Override
+	public void start(Stage stage) throws Exception {
+		this.stage = stage;
+		stage.setTitle("Java object universal deserializer");
+		stage.setScene(createScene());
+		stage.setMinWidth(400);
+		stage.show();
+	}
+	
+	private Scene createScene() {
+		return new Scene(createLayout());
+	}
+	
+	private Parent createLayout() {
+		VBox layout = new VBox();
+		layout.setPadding(new Insets(10.0));
+		layout.setSpacing(10.0);
+		
+		Label forInputFile = new Label("Soubor k deserializaci:");
+		Label inputFile = new Label("    Je�t� nebyl vybr�n ��dn� soubor...");
+		inputFile.setStyle("-fx-font-style: italic;");
+		VBox forInput = new VBox();
+		forInput.getChildren().add(forInputFile);
+		forInput.getChildren().add(inputFile);
+		
+		Label forOutputFile = new Label("V�stupn� soubor:");
+		Label outputFile = new Label("    Je�t� nebyl vybr�n ��dn� soubor...");
+		outputFile.setStyle("-fx-font-style: italic;");
+		VBox forOutput = new VBox();
+		forOutput.getChildren().add(forOutputFile);
+		forOutput.getChildren().add(outputFile);
+		
+		convert = new Button("P�ev�st");
+		convert.setOnAction(event -> {
+			// a.data a b.data are customers test files (binary files).
+			convert.setDisable(true);
+			Converter thread = new Converter(this, testing ? new File("a.data") : this.inputFile, testing ? new File("results.json") : this.outputFile);
+			thread.start();
+		});
+		convert.setDisable(!testing);
+		Button setInputFile = new Button("Vstupn� soubor");
+		setInputFile.setOnAction(event -> {
+			FileChooser fCh = new FileChooser();
+			fCh.setInitialDirectory(new File("."));
+			// The input can be a zip file or file without a specific extension -> file chooser without extension filters...
+			// fCh.getExtensionFilters().add(new FileChooser.ExtensionFilter("Binary Files", "*.out"));
+			File file = fCh.showOpenDialog(stage);
+			if (file != null) {
+				this.inputFile = file;
+				inputFile.setText(this.inputFile == null ? "" : this.inputFile.getAbsolutePath());
+				inputFile.setStyle("-fx-font-weight: bold;");
+				convert.setDisable(this.inputFile == null || this.outputFile == null);
+			}
+		});
+		Button setOutputFile = new Button("V�stupn� soubor");
+		setOutputFile.setOnAction(event -> {
+			FileChooser fCh = new FileChooser();
+			fCh.setInitialDirectory(new File("."));
+			fCh.getExtensionFilters().add(new FileChooser.ExtensionFilter("JSON Files", "*.json"));
+			File file = fCh.showSaveDialog(stage);
+			if (file != null) {
+				this.outputFile = file;
+				outputFile.setText(this.outputFile == null ? "" : this.outputFile.getAbsolutePath());
+				outputFile.setStyle("-fx-font-weight: bold;");
+				convert.setDisable(this.inputFile == null || this.outputFile == null);
+			}
+		});
+		HBox footer = new HBox();
+		footer.setSpacing(10.0);
+		footer.setAlignment(Pos.CENTER_RIGHT);
+		footer.getChildren().add(setInputFile);
+		footer.getChildren().add(setOutputFile);
+		footer.getChildren().add(convert);
+		
+		layout.getChildren().add(forInput);
+		layout.getChildren().add(forOutput);
+		layout.getChildren().add(footer);
+		
+		return layout;
+	}
+	
+	public void convertIsComplete() {
+		convert.setDisable(false);
+	}
+	
+}
diff --git a/project/Deserializer/src/FileWorker.java b/project/Deserializer/src/FileWorker.java
new file mode 100644
index 0000000..da23158
--- /dev/null
+++ b/project/Deserializer/src/FileWorker.java
@@ -0,0 +1,82 @@
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+
+public class FileWorker {
+	
+	public static byte[] loadByteArray(File file) throws IOException {
+		byte buffer[];
+		
+		/*
+		 * Note.
+		 * For ZIP - the first four bytes should be one of the following combinations:
+		 *     50 4B 03 04
+		 *     50 4B 05 06 (empty archive)
+		 *     50 4B 07 08 (spanned archive)
+		 * Source: https://en.wikipedia.org/wiki/List_of_file_signatures
+		 */
+	    try {
+	    	
+	    	ZipInputStream zipIS = new ZipInputStream(new FileInputStream(file));
+	    	
+	        if (zipIS.getNextEntry() == null) {
+	        	zipIS.close();
+	        	throw new NoSuchElementException();
+	        }
+	        
+	        System.out.println("The file is a zip archive...");
+	        List<Byte> _buffer = new ArrayList<Byte>();
+	        while (true) {
+	        	int value = zipIS.read();
+	        	if (value < 0) {
+	        		break;
+	        	} else {
+	        		_buffer.add((byte) value);
+	        	}
+	        }
+	        
+	        buffer = new byte[_buffer.size()];
+	        for (int i = 0; i < buffer.length; i++) {
+	        	buffer[i] = _buffer.get(i);
+	        }
+	        
+	        zipIS.close();
+	        
+		} catch (NoSuchElementException | ZipException e) {
+			
+			System.out.println("The file is not a zip archive...");
+			buffer = new byte[(int) file.length()];
+	    	BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+	    	bis.read(buffer);
+			bis.close();
+			
+		}
+	    
+	    System.out.println("Bytes from the file were loaded correctly.");
+	    return buffer;
+	}
+
+	public static boolean saveJson(File file, String json) {
+        boolean retValue = false;
+		try {
+            FileWriter fw = new FileWriter(file);
+            BufferedWriter bw = new BufferedWriter(fw);
+            bw.write(json);
+            bw.flush();
+            bw.close();
+            retValue = true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+		return retValue;
+	}
+	
+}
diff --git a/project/Deserializer/src/Report.java b/project/Deserializer/src/Report.java
new file mode 100644
index 0000000..5f84ce6
--- /dev/null
+++ b/project/Deserializer/src/Report.java
@@ -0,0 +1,58 @@
+import com.sun.javafx.stage.StageHelper;
+
+import javafx.application.Platform;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.stage.Screen;
+
+public class Report {
+	
+	private static final int HEIGHT = 200;
+	private static final int WIDTH = 500;
+	
+	private static void setPosition(Alert alert) {
+		if (StageHelper.getStages() != null && StageHelper.getStages().size() != 0) {
+			if (Screen.getPrimary() != null && Screen.getPrimary().getVisualBounds() != null) {
+				Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
+				alert.setX((primScreenBounds.getWidth() - WIDTH - 20) / 2);
+		        alert.setY((primScreenBounds.getHeight() - HEIGHT - 20) / 3);
+			}
+			alert.initOwner(StageHelper.getStages().get(StageHelper.getStages().size() - 1));
+		}
+	}
+	
+	private static Alert createAlert(AlertType type, String title, String header, String content) {
+		Alert alert = new Alert(type);
+		alert.setResizable(true);
+		alert.getDialogPane().setPrefWidth(WIDTH);
+		alert.setTitle(title);
+		alert.setHeaderText(header);
+		alert.setContentText(content);
+		setPosition(alert);
+		return alert;
+	}
+	
+	public static void error(String title, String header, String content) {
+		Platform.runLater(()->{
+			createAlert(AlertType.WARNING, title, header, content).showAndWait();
+        });
+	}
+	
+	public static void info(String title, String header, String content) {
+		Platform.runLater(()->{
+			createAlert(AlertType.INFORMATION, title, header, content).showAndWait();
+        });
+	}
+	
+	public static boolean confirm(String title, String header, String content) {
+		Alert alert = createAlert(AlertType.CONFIRMATION, title, header, content);
+		alert.showAndWait();
+		if (alert.getResult().getText().equals("OK")) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+}
diff --git a/project/Deserializer/src/jdeserialize/ExceptionReadException.java b/project/Deserializer/src/jdeserialize/ExceptionReadException.java
new file mode 100644
index 0000000..dc98ef0
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/ExceptionReadException.java
@@ -0,0 +1,27 @@
+package jdeserialize;
+import java.io.*;
+
+/**
+ * Exception used to signal that an exception object was successfully read from the 
+ * stream.  This object holds a reference to the serialized exception object.
+ */
+public class ExceptionReadException extends IOException {
+    public static final long serialVersionUID = 2277356908919221L;
+    public content exceptionobj;
+    /**
+     * Constructor.
+     * @param c the serialized exception object that was read
+     */
+    public ExceptionReadException(content c) {
+        super("serialized exception read during stream");
+        this.exceptionobj = c;
+    }
+    /**
+     * Gets the Exception object that was thrown.
+     * @return the content representing the serialized exception object
+     */
+    public content getExceptionObject() {
+        return exceptionobj;
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/Getopt.java b/project/Deserializer/src/jdeserialize/Getopt.java
new file mode 100644
index 0000000..7011999
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/Getopt.java
@@ -0,0 +1,173 @@
+package jdeserialize;
+import java.util.*;
+
+/**
+ * Simple getopt()-like implementation.
+ */
+public class Getopt {
+    private Map<String, Integer> options;
+    private List<String> otherargs;
+    private Map<String, String> descriptions;
+    private Map<String, List<String>> optvals;
+
+    public class OptionParseException extends Exception {
+        public static final long serialVersionUID = 2898924890585885551L;
+        public OptionParseException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Gets the list arguments specified that were *not* options or their arguments, in
+     * the order they were specified.
+     * 
+     * @return the list of non-option String arguments
+     */
+    public List<String> getOtherArguments() {
+        return otherargs;
+    }
+
+    /**
+     * Gets the set of all options specified, as well as the list of their arguments.
+     *
+     * @return a map of all options specified; values are lists of arguments
+     */
+    public Map<String, List<String>> getOptionValues() {
+        return optvals;
+    }
+
+    /**
+     * Constructor.  
+     *
+     * @param options Map of options to parse.  The key should be an option string (including
+     * any initial dashes), and the value should be an Integer representing the number of
+     * arguments to parse following the option.
+     * @param descriptions Map of option descriptions.
+     */
+    public Getopt(Map<String, Integer> options, Map<String, String> descriptions) {
+        this.options = options;
+        this.descriptions = descriptions;
+    }
+    /**
+     * Constructor.  
+     */
+    public Getopt() {
+        this.options = new HashMap<String, Integer>();
+        this.descriptions = new HashMap<String, String>();
+    }
+
+    /**
+     * Determines whether or not the option was specified when the arguments were parsed.
+     *
+     * @return true iff the argument was specified (with the correct number of arguments).
+     */
+    public boolean hasOption(String opt) {
+        return optvals.containsKey(opt);
+    }
+
+    /**
+     * Gets the list of arguments for a given option, or null if the option wasn't
+     * specified.
+     * 
+     * @param option the option 
+     * @return the list of arguments for option
+     */
+    public List<String> getArguments(String option) {
+        return optvals.get(option);
+    }
+
+    /**
+     * Add an option to the internal set, including the number of arguments and the
+     * description. 
+     *
+     * @param option option string, including any leading dashes
+     * @param arguments number of arguments
+     * @param description description of the option
+     */
+    public void addOption(String option, int arguments, String description) {
+        options.put(option, arguments);
+        descriptions.put(option, description);
+    }
+
+    /**
+     * Do the parsing/validation.
+     * @param args arguments to parse
+     * @throws OptionParseException if a parse error occurs (the exception message will
+     * have details)
+     */
+    public void parse(String[] args) throws OptionParseException {
+        this.otherargs = new ArrayList<String>();
+        this.optvals = new HashMap<String, List<String>>();
+
+        for(int i = 0; i < args.length; i++) {
+            if(optvals != null) {
+                Integer count = options.get(args[i]);
+                if(count != null) {
+                    ArrayList<String> al = new ArrayList<String>(count.intValue());
+                    for(int j = 0; j < count; j++) {
+                        if(i+1+j >= args.length) {
+                            throw new OptionParseException("expected " + count + " arguments after " + args[i]);
+                        }
+                        al.add(args[i+1+j]);
+                    }
+                    List<String> oldal = optvals.get(args[i]);
+                    if(oldal == null) {
+                        optvals.put(args[i], al);
+                    } else {
+                        oldal.addAll(al);
+                    }
+                    i += count;
+                    continue;
+                }
+            }
+            otherargs.add(args[i]);
+        }
+    }
+
+    /**
+     * Get a tabular description of all options and their descriptions, one per line.
+     */
+    public String getDescriptionString() {
+        String linesep = System.getProperty("line.separator");
+        StringBuffer sb = new StringBuffer();
+        if(options != null && options.size() > 0) {
+            sb.append("Options:").append(linesep);
+            TreeSet<String> opts = new TreeSet<String>(this.options.keySet());
+            for(String opt: opts) {
+                sb.append("    ").append(opt);
+                for(int i = 0; i < options.get(opt); i++) {
+                    sb.append(" arg").append(i+1);
+                }
+                sb.append(": ").append(descriptions.get(opt)).append(linesep);
+            }
+        }
+        return sb.toString();
+    }
+
+    public static void main(String[] args) {
+        try {
+            Getopt go = new Getopt();
+            go.addOption("-optzero", 0, "zero-arg constructor");
+            go.addOption("-optone", 1, "one-arg constructor");
+            go.addOption("-opttwo", 2, "two-arg constructor");
+            go.parse(args);
+            System.out.println(go.getDescriptionString());
+            System.out.println("options:");
+            Map<String, List<String>> optvals = go.getOptionValues();
+            for(String opt: optvals.keySet()) {
+                System.out.print("    " + opt);
+                for(String optval: optvals.get(opt)) {
+                    System.out.print(" " + optval);
+                }
+                System.out.println("");
+            }
+            System.out.println("");
+            System.out.println("otherargs:");
+            for(String arg: go.getOtherArguments()) {
+                System.out.println("    " + arg);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/LoggerInputStream.java b/project/Deserializer/src/jdeserialize/LoggerInputStream.java
new file mode 100644
index 0000000..79a9eec
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/LoggerInputStream.java
@@ -0,0 +1,129 @@
+package jdeserialize;
+
+import java.io.*;
+
+/**
+ * <p>
+ * An InputStream designed to record data passing through the stream after a call to
+ * record() is made.  After record() is called, the results from every read will 
+ * be stored in an * internal buffer.  The contents of the buffer can be 
+ * retrieved by getRecordedData(); to stop recording and clear the internal 
+ * buffer, call stopRecording().
+ * </p>
+ *
+ * <p>
+ * <b>Note</b>: calls to mark() and reset() are merely passed through to the inner stream; if
+ * recording is active, the buffer won't be backtracked by reset().
+ * </p>
+ */
+public class LoggerInputStream extends InputStream {
+    private ByteArrayInputStream innerStream = null;
+    private ByteArrayOutputStream baos = null;
+    private boolean recording = false; 
+
+    public LoggerInputStream(byte[] buffer) {
+        super();
+        this.innerStream = new ByteArrayInputStream(buffer);
+    }
+    public synchronized int read() throws IOException {
+        int i = innerStream.read();
+        if(recording && i != -1) {
+            if(i > 255 || i < 0) {
+                throw new IOException("non-byte, non--1 value read from inner stream: " + i);
+            }
+            baos.write((byte)i);
+        }
+        return i;
+    }
+    public synchronized int read(byte[] b) throws IOException {
+        return this.read(b, 0, b.length);
+    }
+    public synchronized int read(byte[] b, int off, int len) throws IOException {
+        int retval = innerStream.read(b, off, len);
+        if(recording && retval > 0) {
+            if(retval > len) {
+                throw new IOException("inner stream read(byte[], int, int) violating contract; return value > len: " + retval);
+            }
+            baos.write(b, off, retval);
+        }
+        return retval;
+    }
+    public synchronized long skip(long n) throws IOException {
+        if(n < 0) {
+            throw new IOException("can't skip negative number of bytes");
+        }
+        if(recording == false) {
+            return innerStream.skip(n);
+        }
+        long nskipped = 0;
+        while(n > Integer.MAX_VALUE) {
+            long ret = skip(Integer.MAX_VALUE);
+            nskipped += ret;
+            if(ret < Integer.MAX_VALUE) {
+                return nskipped;
+            }
+            n -= ret;
+        }
+        int toread = (int)n, actuallyread = 0;
+        byte[] buf = new byte[10240];
+        while(toread > 0) {
+            int r = Math.min(toread, buf.length);
+            int rret = this.read(buf, 0, r);
+            actuallyread += rret;
+            toread -= rret;
+            if(rret < r) {
+                break;
+            }
+        }
+        return actuallyread;
+    }
+    public synchronized int available() throws IOException {
+        return innerStream.available();
+    }
+    public synchronized void close() throws IOException {
+        innerStream.close();
+    }
+    public synchronized void mark(int readlimit) {
+        innerStream.mark(readlimit);
+    }
+    public synchronized void reset() throws IOException {
+        innerStream.reset();
+    }
+    public boolean markSupported() {
+        return innerStream.markSupported();
+    }
+    /**
+     * If not currently recording, start recording.  If the stream is currently recording,
+     * the current buffer is cleared.
+     */
+    public synchronized void record() {
+        recording = true;
+        baos = new ByteArrayOutputStream();
+    }
+    /**
+     * Stops recording and clears the internal buffer.  If recording is not active, an
+     * IOException is thrown.
+     *
+     * @throws IOException if recording is not currently active
+     */
+    public synchronized void stopRecording() throws IOException {
+        if(recording == false) {
+            throw new IOException("recording not active");
+        }
+        try {
+            baos.close();
+        } catch (IOException ignore) {}
+        baos = null;
+        recording = false;
+    }
+    /**
+     * Returns the data recorded so far; if recording is not active, an empty buffer
+     * is returned.
+     */
+    public synchronized byte[] getRecordedData() {
+        if(recording == false) {
+            return new byte[0];
+        }
+        return baos.toByteArray();
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/ValidityException.java b/project/Deserializer/src/jdeserialize/ValidityException.java
new file mode 100644
index 0000000..901f7e4
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/ValidityException.java
@@ -0,0 +1,14 @@
+package jdeserialize;
+import java.io.*;
+
+/**
+ * Exception that denotes that data in the stream did not conform to the constraints
+ * imposed by the specification.
+ */
+public class ValidityException extends IOException {
+    public static final long serialVersionUID = 2277356908919241L;
+    public ValidityException(String msg) {
+        super(msg);
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/arraycoll.java b/project/Deserializer/src/jdeserialize/arraycoll.java
new file mode 100644
index 0000000..b610ada
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/arraycoll.java
@@ -0,0 +1,48 @@
+package jdeserialize;
+import java.util.*;
+
+/**
+ * <p>Typed collection used for storing the values of a serialized array.  </p>
+ *
+ * <p>Primitive types are stored using their corresponding objects; for instance, an int is
+ * stored as an Integer.  To determine whether or not this is an array of ints or of
+ * Integer instances, check the name in the arrayobj's class description.</p>
+ */
+public class arraycoll extends ArrayList<Object> {
+    public static final long serialVersionUID = 2277356908919248L;
+
+    private fieldtype ftype;
+
+    /**
+     * Constructor.
+     * @param ft field type of the array
+     */
+    public arraycoll(fieldtype ft) {
+        super();
+        this.ftype = ft;
+    }
+
+    /**
+     * Gets the field type of the array.
+     *
+     * @return the field type of the array
+     */
+    public fieldtype getFieldType() {
+        return ftype;
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[arraycoll sz ").append(this.size());
+        boolean first = true;
+        for(Object o: this) {
+            if(first) {
+                first = false;
+                sb.append(' ');
+            } else {
+                sb.append(", ");
+            }
+            sb.append(o.toString());
+        }
+        return sb.toString();
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/arrayobj.java b/project/Deserializer/src/jdeserialize/arrayobj.java
new file mode 100644
index 0000000..ebbccb7
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/arrayobj.java
@@ -0,0 +1,32 @@
+package jdeserialize;
+
+/**
+ * <p>Represents an array instance, including the values the comprise the array.  </p>
+ *
+ * <p>Note that in arrays of primitives, the classdesc will be named "[x", where x is the
+ * field type code representing the primitive type.  See jdeserialize.resolveJavaType()
+ * for an example of analysis/generation of human-readable names from these class names.</p>
+ */
+public class arrayobj extends contentbase {
+    /**
+     * Type of the array instance.
+     */
+    public classdesc classdesc;
+
+    /**
+     * Values of the array, in the order they were read from the stream.
+     */
+    public arraycoll data;
+
+    public arrayobj(int handle, classdesc cd, arraycoll data) {
+        super(contenttype.ARRAY);
+        this.handle = handle;
+        this.classdesc = cd;
+        this.data = data;
+    }
+    public String toString() {
+        return "[array " + jdeserialize.hex(handle) + " classdesc " + classdesc.toString() + ": " 
+            + data.toString() + "]";
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/blockdata.java b/project/Deserializer/src/jdeserialize/blockdata.java
new file mode 100644
index 0000000..4e353ce
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/blockdata.java
@@ -0,0 +1,27 @@
+package jdeserialize;
+
+/**
+ * Represents an opaque block of data written to the stream.  Primarily, these are used to
+ * write class and object annotations by ObjectOutputStream overrides; they can also occur
+ * inside an object, when the object overrides Serializable.writeObject().  Their
+ * interpretation is hereby left to users.
+ */
+public class blockdata extends contentbase {
+    /**
+     * The block data read from the stream.
+     */
+    public byte[] buf;
+
+    /**
+     * Constructor.
+     *
+     * @param buf the block data
+     */
+    public blockdata(byte[] buf) {
+        super(contenttype.BLOCKDATA);
+        this.buf = buf;
+    }
+    public String toString() {
+        return "[blockdata " + jdeserialize.hex(handle) + ": " + buf.length + " bytes]";
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/classdesc.java b/project/Deserializer/src/jdeserialize/classdesc.java
new file mode 100644
index 0000000..79aa549
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/classdesc.java
@@ -0,0 +1,227 @@
+package jdeserialize;
+import java.io.*;
+import java.util.*;
+
+/**
+ * <p>
+ * Represents the entire serialized prototype of the class, including all fields,
+ * inner classes, class annotations, and inheritance hierarchy.  This includes proxy class
+ * descriptions.
+ * </p>
+ *
+ * <p>
+ * Generally, this class is used to represent the type of an instance written to an
+ * ObjectOutputStream with its writeObject() method, or of a related array or field type.
+ * However, there's a notable exception: when instances of type java.io.ObjectStreamClass
+ * are written with writeObject(), only their class description is written (cf. Object
+ * Serialization Specification, 4.3).  They will be represented with an instance of
+ * classdesc as well.
+ * </p>
+ */
+public class classdesc extends contentbase {
+    /**
+     * Type of the class being represented; either a normal class or a proxy class.
+     */
+    public classdesctype classtype;
+
+    /**
+     * Class name.
+     */
+    public String name;
+
+    /**
+     * Serial version UID, as recorded in the stream.
+     */
+    public long serialVersionUID;
+
+    /**
+     * Description flags byte; this should be a mask of values from the ObjectStreamContants 
+     * class.  Refer to chapter 6 of the Object Stream Serialization Protocol for details.
+     */
+    public byte descflags;
+
+    /**
+     * Array of fields in the class, in the order serialized by the stream writer.
+     */
+    public field[] fields;
+
+    /**
+     * List of inner classes, in the order serialized by the stream writer.
+     */
+    public List<classdesc> innerclasses;
+    
+    /**
+     * List of annotation objects; these are *not* Java annotations, but data written by
+     * the <pre>annotateClass(Class<?>)<pre> and <pre>annotateProxyClass(Class<?>)</pre> methods of an
+     * ObjectOutputStream.  
+     */
+    public List<content> annotations;
+
+    /**
+     * The superclass of the object, if available.
+     */
+    public classdesc superclass;
+
+    /**
+     * Array of serialized interfaces, in the order serialized by the stream writer.
+     */
+    public String[] interfaces;
+
+    /**
+     * Set of enum constants, for enum classes.
+     */
+    public Set<String> enumconstants;
+
+    private boolean isInnerClass = false;
+    /**
+     * True if this class has been determined to be an inner class; this determination is
+     * generally made by connectMemberClasses().
+     * 
+     * @return true if the class is an inner class
+     */
+    public boolean isInnerClass() {
+        return isInnerClass;
+    }
+    /**
+     * Sets the value that denotes that the class is an inner class.
+     *
+     * @param nis the value to set
+     */
+    public void setIsInnerClass(boolean nis) {
+        this.isInnerClass = nis;
+    }
+
+    private boolean isLocalInnerClass = false;
+    /**
+     * True if this class has been determined to be a local inner class; this
+     * determination is generally made by connectMemberClasses().
+     *
+     * @return true if the class is a local inner class
+     */
+    public boolean isLocalInnerClass() {
+        return isLocalInnerClass;
+    }
+    /**
+     * Sets the flag that denotes whether this class is a local inner class.
+     * 
+     * @param nis the value to set
+     */
+    public void setIsLocalInnerClass(boolean nis) {
+        this.isLocalInnerClass = nis;
+    }
+
+    private boolean isStaticMemberClass = false;
+    /**
+     * <p>
+     * True if this class has been determined to be a static member class; this
+     * determination is generally made by connectMemberClasses().
+     * </p>
+     *
+     * <p>
+     * Note that in some cases, static member classes' descriptions will be serialized
+     * even though their enclosing class is not.  In these cases, this may return false.
+     * See connectMemberClasses() for details.
+     * </p>
+     *
+     * @return true if this is a static member class
+     */
+    public boolean isStaticMemberClass() {
+        return isStaticMemberClass;
+    }
+    /**
+     * Sets the flag that denotes whether this class is a static member class.
+     *
+     * @param nis the value to set
+     */
+    public void setIsStaticMemberClass(boolean nis) {
+        this.isStaticMemberClass = nis;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param classtype the type of the class
+     */
+    public classdesc(classdesctype classtype) {
+        super(contenttype.CLASSDESC);
+        this.classtype = classtype;
+        this.enumconstants = new HashSet<String>();
+        this.innerclasses = new ArrayList<classdesc>();
+    }
+
+    /**
+     * Add an inner class to the description's list.
+     * @param cd inner class to add
+     */
+    public void addInnerClass(classdesc cd) {
+        innerclasses.add(cd);
+    }
+
+    /**
+     * Add an enum constant to the description's set.
+     *
+     * @param constval enum constant string
+     */
+    public void addEnum(String constval) {
+        this.enumconstants.add(constval);
+    }
+
+    /**
+     * Determines whether this is an array type. 
+     * @return true if this is an array type.
+     */
+    public boolean isArrayClass() {
+        if(name != null && name.length() > 1 && name.charAt(0) == '[') {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[cd ").append(jdeserialize.hex(handle)).append(": name ").append(name);
+        sb.append(" uid ").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /**
+     * Generates a list of all class descriptions in this class's hierarchy, in the order
+     * described by the Object Stream Serialization Protocol.  This is the order in which
+     * fields are read from the stream.
+     * 
+     * @param classes a list to be filled in with the hierarchy
+     */
+    public void getHierarchy(ArrayList<classdesc> classes) {
+        if(superclass != null) {
+            if(superclass.classtype == classdesctype.PROXYCLASS) {
+                jdeserialize.debugerr("warning: hit a proxy class in superclass hierarchy");
+            } else {
+                superclass.getHierarchy(classes);
+            }
+        } 
+        classes.add(this);
+    }
+    public void validate() throws ValidityException {
+        // If neither SC_SERIALIZABLE nor SC_EXTERNALIZABLE is set, then the number of
+        // fields is always zero.  (spec section 4.3)
+        if((descflags & (ObjectStreamConstants.SC_SERIALIZABLE | ObjectStreamConstants.SC_EXTERNALIZABLE)) == 0 && fields != null && fields.length > 0) {
+            throw new ValidityException("non-serializable, non-externalizable class has fields!");
+        }
+        if((descflags & (ObjectStreamConstants.SC_SERIALIZABLE | ObjectStreamConstants.SC_EXTERNALIZABLE)) == (ObjectStreamConstants.SC_SERIALIZABLE | ObjectStreamConstants.SC_EXTERNALIZABLE)) {
+            throw new ValidityException("both Serializable and Externalizable are set!");
+        }
+        if((descflags & ObjectStreamConstants.SC_ENUM) != 0) {
+            // we're an enum; shouldn't have any fields/superinterfaces
+            if((fields != null && fields.length > 0) || interfaces != null) {
+                throw new ValidityException("enums shouldn't implement interfaces or have non-constant fields!");
+            }
+        } else {
+            // non-enums shouldn't have enum constant fields.  
+            if(enumconstants != null && enumconstants.size() > 0) {
+                throw new ValidityException("non-enum classes shouldn't have enum constants!");
+            }
+        }
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/classdesctype.java b/project/Deserializer/src/jdeserialize/classdesctype.java
new file mode 100644
index 0000000..a6cfe48
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/classdesctype.java
@@ -0,0 +1,8 @@
+package jdeserialize;
+
+/**
+ * Enum for class description types.
+ */
+public enum classdesctype {
+    NORMALCLASS, PROXYCLASS
+}
diff --git a/project/Deserializer/src/jdeserialize/classobj.java b/project/Deserializer/src/jdeserialize/classobj.java
new file mode 100644
index 0000000..0ee0c89
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/classobj.java
@@ -0,0 +1,28 @@
+package jdeserialize;
+
+/**
+ * This represents a Class object (i.e. an instance of type Class) serialized in the
+ * stream.
+ */
+public class classobj extends contentbase {
+    /**
+     * The class description, including its name.
+     */
+    public classdesc classdesc;
+
+    /**
+     * Constructor.
+     *
+     * @param handle the instance's handle
+     * @param cd the instance's class description
+     */
+    public classobj(int handle, classdesc cd) {
+        super(contenttype.CLASS);
+        this.handle = handle;
+        this.classdesc = cd;
+    }
+    public String toString() {
+        return "[class " + jdeserialize.hex(handle) + ": " + classdesc.toString() + "]";
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/content.java b/project/Deserializer/src/jdeserialize/content.java
new file mode 100644
index 0000000..1219de6
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/content.java
@@ -0,0 +1,68 @@
+package jdeserialize;
+
+/**
+ * <p>
+ * Generic interface for all data that may be read from the stream (except null).  
+ * </p>
+ *
+ * <p>
+ * A successful read of the stream will result in a series of content instances or null
+ * references.  For details on specific metadata, see documentation on implementing
+ * classes/subinterfaces.
+ * </p>
+ */
+public interface content {
+    /**
+     * @return the type of instance represented by this object.
+     */
+    public contenttype getType();
+
+    /**
+     * <p>
+     * Get the numeric handle by which this object was referred to in the object stream.
+     * These handles are used internally by Object{Output,Input}Stream as a mechanism to
+     * avoid costly duplication.
+     * </p>
+     *
+     * <p>
+     * CAUTION: they are *not* necessarily unique across all objects in a given stream!
+     * If an exception was thrown during serialization (which is most likely to happen
+     * during a serialized objct's writeObject() implementation), then the stream resets
+     * before and after the exception is serialized.  
+     * </p>
+     *
+     * @return the handle assigned in the stream
+     */
+    public int getHandle();
+
+    /**
+     * Performs extra object-specific validity checks.  
+     *
+     * @throws ValidityException if the object's state is invalid
+     */
+    public void validate() throws ValidityException;
+
+    /**
+     * <p>
+     * Tells whether or not this object is an exception that was caught during
+     * serialization.  
+     * </p>
+     *
+     * <p>
+     * <b>Note</b>:  Not every Throwable or Exception in the stream will have this flag set to
+     * true; only those which were thrown <i>during serialization</i> will
+     * </p>
+     * 
+     * @return true iff the object was an exception thrown during serialization
+     */
+    public boolean isExceptionObject();
+
+    /**
+     * Sets the flag that tells whether or not this object is an exception that was caught
+     * during serialization.
+     *
+     * @param value the new value to use
+     */
+    public void setIsExceptionObject(boolean value);
+}
+
diff --git a/project/Deserializer/src/jdeserialize/contentbase.java b/project/Deserializer/src/jdeserialize/contentbase.java
new file mode 100644
index 0000000..0d429c8
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/contentbase.java
@@ -0,0 +1,28 @@
+package jdeserialize;
+
+/**
+ * Provides a skeleton content implementation.
+ */
+public class contentbase implements content {
+    public int handle;
+    public boolean isExceptionObject;
+    protected contenttype type;
+    public contentbase(contenttype type) {
+        this.type = type;
+    }
+    public boolean isExceptionObject() {
+        return isExceptionObject;
+    }
+    public void setIsExceptionObject(boolean value) {
+        isExceptionObject = value;
+    }
+    public contenttype getType() {
+        return type;
+    }
+    public int getHandle() {
+        return this.handle;
+    }
+    public void validate() throws ValidityException {
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/contenttype.java b/project/Deserializer/src/jdeserialize/contenttype.java
new file mode 100644
index 0000000..24018ce
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/contenttype.java
@@ -0,0 +1,5 @@
+package jdeserialize;
+
+public enum contenttype {
+    INSTANCE, CLASS, ARRAY, STRING, ENUM, CLASSDESC, BLOCKDATA, EXCEPTIONSTATE
+}
diff --git a/project/Deserializer/src/jdeserialize/enumobj.java b/project/Deserializer/src/jdeserialize/enumobj.java
new file mode 100644
index 0000000..00a73b3
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/enumobj.java
@@ -0,0 +1,37 @@
+package jdeserialize;
+
+/**
+ * <p>
+ * Represents an enum instance.  As noted in the serialization spec, this consists of
+ * merely the class description (represented by a classdesc) and the string corresponding
+ * to the enum's value.  No other fields are ever serialized.
+ * </p>
+ */
+public class enumobj extends contentbase {
+    /**
+     * The enum's class description.
+     */
+    public classdesc classdesc;
+
+    /**
+     * The string that represents the enum's value.
+     */
+    public stringobj value;
+
+    /**
+     * Constructor.
+     *
+     * @param handle the enum's handle
+     * @param cd the enum's class description
+     * @param so the enum's value
+     */
+    public enumobj(int handle, classdesc cd, stringobj so) {
+        super(contenttype.ENUM);
+        this.handle = handle;
+        this.classdesc = cd;
+        this.value = so;
+    }
+    public String toString() {
+        return "[enum " + jdeserialize.hex(handle) + ": " + value.value + "]";
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/exceptionstate.java b/project/Deserializer/src/jdeserialize/exceptionstate.java
new file mode 100644
index 0000000..79d9e00
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/exceptionstate.java
@@ -0,0 +1,71 @@
+package jdeserialize;
+
+/**
+ * <p>
+ * This object contains embedded information about a serialization that failed, throwing
+ * an exception.  It includes the actual exception object (which was serialized by the
+ * ObjectOutputStream) and the raw bytes of the stream data that was read before the
+ * exception was recognized.
+ * </p>
+ *
+ * <p>
+ * For the mechanics of exception serialization, see the Object Serialization
+ * Specification.
+ * </p>
+ */
+public class exceptionstate extends contentbase {
+    /**
+     * The serialized exception object.
+     */
+    public content exceptionobj;
+    
+    /**
+     * <p>
+     * An array of bytes representing the data read before the exception was encountered.
+     * Generally, this starts with the first "tc" byte (cf. protocol spec), which is an
+     * ObjectStreamConstants value and ends with 0x78 (the tc byte corresponding to
+     * TC_EXCEPTION).  However, this isn't guaranteed; it may include *more* data.  
+     * </p>
+     *
+     * <p>
+     * In other words, this is the incomplete object that was being written while the
+     * exception was caught by the ObjectOutputStream.  It is not likely to be cleanly
+     * parseable.
+     * </p>
+     *
+     * <p>
+     * The uncertainty centers around the fact that this data is gathered by jdeserialize
+     * using a LoggerInputStream, and the underlying DataInputStream may have read more
+     * than is necessary.  In all tests conducted so far, the above description is
+     * accurate.
+     * </p>
+     */
+    public byte[] streamdata;
+
+    /**
+     * Consturctor.
+     * @param exobj the serialized exception object 
+     * @param data the array of stream bytes that led up to the exception
+     */
+    public exceptionstate(content exobj, byte[] data) {
+        super(contenttype.EXCEPTIONSTATE);
+        this.exceptionobj = exobj;
+        this.streamdata = data;
+        this.handle = exobj.getHandle();
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[exceptionstate object " + exceptionobj.toString() + "  buflen " + streamdata.length);
+        if(streamdata.length > 0) {
+            for(int i = 0; i < streamdata.length; i++) {
+                if((i % 16) == 0) {
+                    sb.append(jdeserialize.linesep).append(String.format("%7x: ", Integer.valueOf(i)));
+                }
+                sb.append(" ").append(jdeserialize.hexnoprefix(streamdata[i]));
+            }
+            sb.append(jdeserialize.linesep);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/field.java b/project/Deserializer/src/jdeserialize/field.java
new file mode 100644
index 0000000..d15e701
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/field.java
@@ -0,0 +1,111 @@
+package jdeserialize;
+import java.io.*;
+
+/**
+ * This class represents a field within a class description/declaration (classdesc).  It
+ * contains information about the type and name of the field.  Fields themselves don't
+ * have a handle; inside the stream, they exist only as part of a class description.
+ */
+public class field {
+    /**
+     * The type of the field.
+     */
+    public fieldtype type;
+
+    /**
+     * The name of the field.
+     */
+    public String name; 
+
+    /**
+     * The string object representing the class name.
+     */
+    public stringobj classname; 
+
+    private boolean isInnerClassReference = false;
+
+    /**
+     * Tells whether or not this class is an inner class reference.  This value is set by
+     * connectMemberClasses() -- if this hasn't been called, or if the field hasn't been
+     * otherwise set by setIsInnerClassReference(), it will be false;
+     *
+     * @return true if the class is an inner class reference
+     */
+    public boolean isInnerClassReference() {
+        return isInnerClassReference;
+    }
+
+    /**
+     * Sets the flag that denotes whether this class is an inner class reference.
+     *
+     * @param nis the value to set; true iff the class is an inner class reference.
+     */
+    public void setIsInnerClassReference(boolean nis) {
+        this.isInnerClassReference = nis;
+    }
+
+    /**
+     * Constructor.  
+     *
+     * @param type the field type
+     * @param name the field name
+     * @param classname the class name
+     */
+    public field(fieldtype type, String name, stringobj classname) throws ValidityException {
+        this.type = type;
+        this.name = name;
+        this.classname = classname;
+        if(classname != null) {
+            validate(classname.value);
+        }
+    }
+
+    /**
+     * Constructor for simple fields.
+     * 
+     * @param type the field type
+     * @param name the field name
+     */
+    public field(fieldtype type, String name) throws ValidityException {
+        this(type, name, null);
+    }
+
+    /**
+     * Get a string representing the type for this field in Java (the language)
+     * format.
+     * @return a string representing the fully-qualified type of the field
+     * @throws IOException if a validity or I/O error occurs
+     */
+    public String getJavaType() throws IOException {
+        return jdeserialize.resolveJavaType(this.type, this.classname == null ? null : this.classname.value, true, false);
+    }
+    
+    /**
+     * Changes the name of an object reference to the name specified.  This is used by
+     * the inner-class-connection code to fix up field references.
+     * @param newname the fully-qualified class 
+     * @throws ValidityException if the field isn't a reference type, or another
+     * validity error occurs
+     */
+    public void setReferenceTypeName(String newname) throws ValidityException {
+        if(this.type != fieldtype.OBJECT) {
+            throw new ValidityException("can't fix up a non-reference field!");
+        }
+        String nname = "L" + newname.replace('.', '/') + ";";
+        this.classname.value = nname;
+    }
+    public void validate(String jt) throws ValidityException {
+        if(this.type == fieldtype.OBJECT) {
+            if(jt == null) {
+                throw new ValidityException("classname can't be null");
+            }
+            if(jt.charAt(0) != 'L') {
+                throw new ValidityException("invalid object field type descriptor: " + classname.value);
+            }
+            int end = jt.indexOf(';');
+            if(end == -1 || end != (jt.length()-1)) {
+                throw new ValidityException("invalid object field type descriptor (must end with semicolon): " + classname.value);
+            }
+        }
+    }
+}
diff --git a/project/Deserializer/src/jdeserialize/fieldtype.java b/project/Deserializer/src/jdeserialize/fieldtype.java
new file mode 100644
index 0000000..0ca6990
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/fieldtype.java
@@ -0,0 +1,102 @@
+package jdeserialize;
+
+/**
+ * <p>
+ * Enum class that describes the type of a field encoded inside a classdesc description.
+ * </p>
+ *
+ * <p>
+ * This stores both information on the type (reference/array vs. primitive) and, in cases
+ * of reference or array types, the name of the class being referred to.  
+ * </p>
+ */
+public enum fieldtype {
+    BYTE ('B', "byte"),
+    CHAR ('C', "char"),
+    DOUBLE ('D', "double"), 
+    FLOAT ('F', "float"),
+    INTEGER ('I', "int"),
+    LONG ('J', "long"),
+    SHORT ('S', "String"),
+    BOOLEAN ('Z', "boolean"),
+    ARRAY ('['),
+    OBJECT ('L');
+    private final char ch;
+    private final String javatype;
+
+    /**
+     * Constructor for non-object (primitive) types.
+     *
+     * @param ch the character representing the type (must match one of those listed in
+     * prim_typecode or obj_typecode in the Object Serialization Stream Protocol)
+     */
+    fieldtype(char ch) {
+        this(ch, null);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param ch the character representing the type (must match one of those listed in
+     * prim_typecode or obj_typecode in the Object Serialization Stream Protocol)
+     * @param javatype the name of the object class, where applicable (or null if not)
+     */
+    fieldtype(char ch, String javatype) {
+        this.ch = ch;
+        this.javatype = javatype;
+    }
+
+    /**
+     * Gets the class name for a reference or array type.
+     *
+     * @return the name of the class being referred to, or null if this is not a
+     * reference/array type
+     */
+    public String getJavaType() {
+        return this.javatype;
+    }
+
+    /**
+     * Gets the type character for this field.
+     *
+     * @return the type code character for this field; values will be one of those in
+     * prim_typecode or obj_typecode in the protocol spec
+     */
+    public char ch() { return ch; }
+
+    /**
+     * Given a byte containing a type code, return the corresponding enum.
+     *
+     * @param b the type code; must be one of the charcaters in obj_typecode or
+     * prim_typecode in the protocol spec
+     * @return the corresponding fieldtype enum
+     * @throws ValidityException if the type code is invalid
+     */
+    public static fieldtype get(byte b) throws ValidityException {
+        switch(b) {
+            case 'B': 
+                return BYTE;
+            case 'C':
+                return CHAR;
+            case 'D':
+                return DOUBLE;
+            case 'F':
+                return FLOAT;
+            case 'I':
+                return INTEGER;
+            case 'J':
+                return LONG;
+            case 'S':
+                return SHORT;
+            case 'Z':
+                return BOOLEAN;
+            case '[':
+                return ARRAY;
+            case 'L':
+                return OBJECT;
+            default:
+                throw new ValidityException("invalid field type char: " + b);
+        }
+    }
+}
+
diff --git a/project/Deserializer/src/jdeserialize/instance.java b/project/Deserializer/src/jdeserialize/instance.java
new file mode 100644
index 0000000..5bf8158
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/instance.java
@@ -0,0 +1,38 @@
+package jdeserialize;
+import java.util.*;
+
+/**
+ * Represents an instance of a non-enum, non-Class, non-ObjectStreamClass, 
+ * non-array class, including the non-transient field values, for all classes in its
+ * hierarchy and inner classes.
+ */
+public class instance extends contentbase {
+    /**
+     * Collection of field data, organized by class description.  
+     */
+    public Map<classdesc, Map<field, Object>> fielddata;
+
+    /**
+     * Class description for this instance.
+     */
+    public classdesc classdesc;
+
+    /**
+     * Constructor.
+     */
+    public instance() {
+        super(contenttype.INSTANCE);
+        this.fielddata = new HashMap<classdesc, Map<field, Object>>();
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(classdesc.name).append(' ').append("_h").append(jdeserialize.hex(handle))
+            .append(" = r_").append(jdeserialize.hex(classdesc.handle)).append(";  ");
+        //sb.append("// [instance " + jdeserialize.hex(handle) + ": " + jdeserialize.hex(classdesc.handle) + "/" + classdesc.name).append("]");
+        return sb.toString();
+    }
+    /**
+     * Object annotation data.
+     */
+    public Map<classdesc, List<content>> annotations;
+}
diff --git a/project/Deserializer/src/jdeserialize/jdeserialize.java b/project/Deserializer/src/jdeserialize/jdeserialize.java
new file mode 100644
index 0000000..d4fcabd
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/jdeserialize.java
@@ -0,0 +1,1202 @@
+package jdeserialize;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * The main user-facing class for the jdeserialize library.  Also the implementation of
+ * the command-line tool.<br/>
+ * <br/>
+ * Library:<br/>
+ * <br/>
+ * The jdeserialize class parses the stream (method run()).  From there, call the
+ * getContent() method to get an itemized list of all items written to the stream, 
+ * or getHandleMaps() to get a list of all handle->content maps generated during parsing.
+ * The objects are generally instances that implement the interface "content"; see the
+ * documentation of various implementors to get more information about the inner
+ * representations.<br/>
+ * <br/>
+ * To enable debugging on stdout, use the enableDebug() or disableDebug() options.   <br/> 
+ * <br/>
+ * <br/>
+ * Command-line tool:   <br/>
+ * <br/>
+ * The tool reads in a set of files and generates configurable output on stdout.  The
+ * primary output consists of three separate stages.  The first stage is  a textual 
+ * description of every piece of content in the stream, in the order it was written.
+ * There is generally a one-to-one mapping between ObjectOutputStream.writeXXX() calls and
+ * items printed in the first stage.  The first stage may be suppressed with the
+ * -nocontent command-line option. <br/>
+ * <br/>
+ * The second stage is a list of every class declaration serialized in the file.  These
+ * are formatted as normal Java language class declarations.  Several options are
+ * available to govern this stage, including -filter, -showarrays, -noclasses, and
+ * -fixnames. <br/>
+ * <br/>
+ * The third stage is a dump of every instance embedded inside the stream, including
+ * textual descriptions of field values.  This is useful for casual viewing of class data. 
+ * To suppress this stage, use -noinstances. <br/>
+ * <br/>
+ * You can also get debugging information generated during the parse phase by supplying
+ * -debug.
+ * <br/>
+ * The data from block data objects can be extracted with the -blockdata <file> option.
+ * Additionally, a manifest describing the size of each individual block can be generated
+ * with the -blockdatamanifest <file> option.
+ * <br/>
+ * References: <br/>
+ *     - Java Object Serialization Specification ch. 6 (Object Serialization Stream
+ *       Protocol): <br/>
+ *       http://download.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html <br/>
+ *     - "Modified UTF-8 Strings" within the JNI specification: 
+ *       http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542 <br/>
+ *     - "Inner Classes Specification" within the JDK 1.1.8 docs:
+ *       http://java.sun.com/products/archive/jdk/1.1/ <br/>
+ *     - "Java Language Specification", third edition, particularly section 3:
+ *       http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html <br/>
+ *
+ * @see content
+ */
+public class jdeserialize {
+    public static final long serialVersionUID = 78790714646095L;
+    public static final String INDENT = "    ";
+    public static final int CODEWIDTH = 90;
+    public static final String linesep = System.getProperty("line.separator");
+    public static final String[] keywords = new String[] {
+        "abstract", "continue", "for", "new", "switch", "assert", "default", "if",
+        "package", "synchronized", "boolean", "do", "goto", "private", "this",
+        "break", "double", "implements", "protected", "throw", "byte", "else",
+        "import", "public", "throws", "case", "enum", "instanceof", "return",
+        "transient", "catch", "extends", "int", "short", "try", "char", "final",
+        "interface", "static", "void", "class", "finally", "long", "strictfp",
+        "volatile", "const", "float", "native", "super", "while" }; 
+    public static HashSet<String> keywordSet;
+
+    private HashMap<Integer,content> handles = new HashMap<Integer,content>();
+    private ArrayList<Map<Integer,content>> handlemaps = new ArrayList<Map<Integer,content>>();
+    private ArrayList<content> content;
+    private int curhandle;
+    private boolean debugEnabled;
+
+    static {
+        keywordSet = new HashSet<String>();
+        for(String kw: keywords) {
+            keywordSet.add(kw);
+        }
+    }
+
+    /**
+     * <p>
+     * Retrieves the list of content objects that were written to the stream.  Each item
+     * generally corresponds to an invocation of an ObjectOutputStream writeXXX() method.
+     * A notable exception is the class exceptionstate, which represents an embedded
+     * exception that was caught during serialization.
+     * </p>
+     *
+     * <p>
+     * See the various implementors of content to get information about what data is
+     * available.  
+     * </p>
+     *
+     * <p>
+     * Entries in the list may be null, because it's perfectly legitimate to write a null
+     * reference to the stream.  
+     * </p>
+     *
+     * @return a list of content objects
+     * @see content
+     * @see exceptionstate
+     */
+    public List<content> getContent() {
+        return content;
+    }
+
+    /**
+     * <p>
+     * Return a list of Maps containing every object with a handle.  The keys are integers
+     * -- the handles themselves -- and the values are instances of type content.
+     * </p>
+     *
+     * <p>
+     * Although there is only one map active at a given point, a stream may have multiple
+     * logical maps: when a reset happens (indicated by TC_RESET), the current map is
+     * cleared.  
+     * </p>
+     *
+     * <p>
+     * See the spec for details on handles.
+     * </p>
+     * @return a list of <Integer,content> maps
+     */
+    public List<Map<Integer,content>> getHandleMaps() {
+        return handlemaps;
+    }
+
+    /**
+     * Suitably escapes non-printable-ASCII characters (and doublequotes) for use 
+     * in a Java string literal.
+     *
+     * @param str string to escape
+     * @return an escaped version of the string
+     */
+    public static String unicodeEscape(String str) {
+        StringBuffer sb = new StringBuffer();
+        int cplen = str.codePointCount(0, str.length());
+        for(int i = 0; i < cplen; i++) {
+            int cp = str.codePointAt(i);
+            if(cp == '"') {
+                sb.append("\\\"");
+            }
+            if(cp < 0x20 || cp > 0x7f) {
+                sb.append("\\u" + hexnoprefix(4));
+            } else {
+                sb.appendCodePoint(cp);
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String indent(int level) {
+        StringBuffer sb = new StringBuffer("");
+        for(int i = 0; i < level; i++) {
+            sb.append(INDENT);
+        }
+        return sb.toString();
+    }
+
+    public void read_Classdata(DataInputStream dis, instance inst) throws IOException {
+        ArrayList<classdesc> classes = new ArrayList<classdesc>();
+        inst.classdesc.getHierarchy(classes);
+        Map<classdesc, Map<field, Object>> alldata = new HashMap<classdesc, Map<field, Object>>();
+        Map<classdesc, List<content>> ann = new HashMap<classdesc, List<content>>();
+        for(classdesc cd: classes) {
+            Map<field, Object> values = new HashMap<field, Object>();
+            if((cd.descflags & ObjectStreamConstants.SC_SERIALIZABLE) != 0) {
+                if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
+                    throw new IOException("SC_EXTERNALIZABLE & SC_SERIALIZABLE encountered");
+                }
+                for(field f: cd.fields) {
+                    Object o = read_FieldValue(f.type, dis);
+                    values.put(f, o);
+                }
+                alldata.put(cd, values);
+                if((cd.descflags & ObjectStreamConstants.SC_WRITE_METHOD) != 0) {
+                    if((cd.descflags & ObjectStreamConstants.SC_ENUM) != 0) {
+                        throw new IOException("SC_ENUM & SC_WRITE_METHOD encountered!");
+                    }
+                    ann.put(cd, read_classAnnotation(dis));
+                }
+            } else if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
+                if((cd.descflags & ObjectStreamConstants.SC_SERIALIZABLE) != 0) {
+                    throw new IOException("SC_SERIALIZABLE & SC_EXTERNALIZABLE encountered");
+                }
+                if((cd.descflags & ObjectStreamConstants.SC_BLOCK_DATA) != 0) {
+                    throw new EOFException("hit externalizable with nonzero SC_BLOCK_DATA; can't interpret data");
+                } else {
+                    ann.put(cd, read_classAnnotation(dis));
+                }
+            }
+        }
+        inst.annotations = ann;
+        inst.fielddata = alldata;
+    }
+
+    public Object read_FieldValue(fieldtype f, DataInputStream dis) throws IOException {
+        switch(f) {
+            case BYTE:
+                return Byte.valueOf(dis.readByte());
+            case CHAR:
+                return Character.valueOf(dis.readChar());
+            case DOUBLE:
+                return Double.valueOf(dis.readDouble());
+            case FLOAT:
+                return Float.valueOf(dis.readFloat());
+            case INTEGER:
+                return Integer.valueOf(dis.readInt());
+            case LONG:
+                return Long.valueOf(dis.readLong());
+            case SHORT:
+                return Short.valueOf(dis.readShort());
+            case BOOLEAN:
+                return Boolean.valueOf(dis.readBoolean());
+            case OBJECT:
+            case ARRAY:
+                byte stc = dis.readByte();
+                if(f == fieldtype.ARRAY && stc != ObjectStreamConstants.TC_ARRAY) {
+                    throw new IOException("array type listed, but typecode is not TC_ARRAY: " + hex(stc));
+                }
+                content c = read_Content(stc, dis, false);
+                if(c != null && c.isExceptionObject()) {
+                    throw new ExceptionReadException(c);
+                }
+                return c;
+            default:
+                throw new IOException("can't process type: " + f.toString());
+        }
+    }
+
+    private int newHandle() {
+        return curhandle++;
+    }
+
+    public static String resolveJavaType(fieldtype type, String classname, boolean convertSlashes, boolean fixname)  throws IOException {
+        if(type == fieldtype.ARRAY) {
+            StringBuffer asb = new StringBuffer("");
+            for(int i = 0; i < classname.length(); i++) {
+                char ch = classname.charAt(i);
+                switch(ch) {
+                    case '[':
+                        asb.append("[]");
+                        continue;
+                    case 'L':
+                        String cn = decodeClassName(classname.substring(i), convertSlashes);
+                        if(fixname) {
+                            cn = fixClassName(cn);
+                        }
+                        return cn + asb.toString();
+                    default:
+                        if(ch < 1 || ch > 127) {
+                            throw new ValidityException("invalid array field type descriptor character: " + classname);
+                        }
+                        fieldtype ft = fieldtype.get((byte)ch);
+                        if(i != (classname.length()-1)) {
+                            throw new ValidityException("array field type descriptor is too long: " + classname);
+                        }
+                        String ftn = ft.getJavaType();
+                        if(fixname) {
+                            ftn = fixClassName(ftn);
+                        }
+                        return ftn + asb.toString();
+                }
+            }
+            throw new ValidityException("array field type descriptor is too short: " + classname);
+        } else if(type == fieldtype.OBJECT) {
+            return decodeClassName(classname, convertSlashes);
+        } else {
+            return type.getJavaType();
+        }
+    }
+
+    public List<content> read_classAnnotation(DataInputStream dis) throws IOException {
+        List<content> list = new ArrayList<content>();
+        while(true) {
+            byte tc = dis.readByte();
+            if(tc == ObjectStreamConstants.TC_ENDBLOCKDATA) {
+                return list;
+            }
+            if(tc == ObjectStreamConstants.TC_RESET) {
+                reset();
+                continue;
+            }
+            content c = read_Content(tc, dis, true);
+            if(c != null && c.isExceptionObject()) {
+                throw new ExceptionReadException(c);
+            }
+            list.add(c);
+        }
+    }
+    public static void dump_Instance(int indentlevel, instance inst, PrintStream ps) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[instance " + hex(inst.handle) + ": " + hex(inst.classdesc.handle) + "/" + inst.classdesc.name);
+        if(inst.annotations != null && inst.annotations.size() > 0) {
+            sb.append(linesep).append("  object annotations:").append(linesep);
+            for(classdesc cd: inst.annotations.keySet()) {
+                sb.append("    ").append(cd.name).append(linesep);
+                for(content c: inst.annotations.get(cd)) {
+                    sb.append("        ").append(c.toString()).append(linesep);
+                }
+            }
+        }
+        if(inst.fielddata != null && inst.fielddata.size() > 0) {
+            sb.append(linesep).append("  field data:").append(linesep);
+            for(classdesc cd: inst.fielddata.keySet()) {
+                sb.append("    ").append(hex(cd.handle)).append("/").append(cd.name).append(":").append(linesep);
+                for(field f: inst.fielddata.get(cd).keySet()) {
+                    Object o = inst.fielddata.get(cd).get(f);
+                    sb.append("        ").append(f.name).append(": ");
+                    if(o instanceof content) {
+                        content c = (content)o;
+                        int h = c.getHandle();
+                        if(h == inst.handle) {
+                            sb.append("this");
+                        } else {
+                            sb.append("r" + hex(h));
+                        }
+                        sb.append(": ").append(c.toString());
+                        sb.append(linesep);
+                    } else {
+                        sb.append("" + o).append(linesep);
+                    }
+                }
+            }
+        }
+        sb.append("]");
+        ps.println(sb);
+    }
+
+    /**
+     * "Fix" the given name by transforming illegal characters, such that the end result
+     * is a legal Java identifier that is not a keyword.  
+     * If the string is modified at all, the result will be prepended with "$__".
+     *
+     * @param name the name to be transformed
+     * @return the unmodified string if it is legal, otherwise a legal-identifier version
+     */
+    public static String fixClassName(String name) {
+        if(name == null) {
+            return "$__null";
+        }
+        if(keywordSet.contains(name)) {
+            return "$__" + name;
+        }
+        StringBuffer sb = new StringBuffer();
+        int cplen = name.codePointCount(0, name.length());
+        if(cplen < 1) {
+            return "$__zerolen";
+        }
+        boolean modified = false;
+        int scp = name.codePointAt(0);
+        if(!Character.isJavaIdentifierStart(scp)) {
+            modified = true;
+            if(!Character.isJavaIdentifierPart(scp) || Character.isIdentifierIgnorable(scp)) {
+                sb.append("x");
+            } else {
+                sb.appendCodePoint(scp);
+            }
+        } else {
+            sb.appendCodePoint(scp);
+        }
+
+        for(int i = 1; i < cplen; i++) {
+            int cp = name.codePointAt(i);
+            if(!Character.isJavaIdentifierPart(cp) || Character.isIdentifierIgnorable(cp)) {
+                modified = true;
+                sb.append("x");
+            } else {
+                sb.appendCodePoint(cp);
+            }
+        }
+        if(modified) {
+            return "$__" + sb.toString();
+        } else {
+            return name;
+        }
+    }
+
+    public static void dump_ClassDesc(int indentlevel, classdesc cd, PrintStream ps, boolean fixname) throws IOException {
+        String classname = cd.name;
+        if(fixname) {
+            classname = fixClassName(classname);
+        }
+        if(cd.annotations != null && cd.annotations.size() > 0) {
+            ps.println(indent(indentlevel) + "// annotations: ");
+            for(content c: cd.annotations) {
+                ps.print(indent(indentlevel) + "// " + indent(1));
+                ps.println(c.toString());
+            }
+        }
+        if(cd.classtype == classdesctype.NORMALCLASS) {
+            if((cd.descflags & ObjectStreamConstants.SC_ENUM) != 0) {
+                ps.print(indent(indentlevel) + "enum " + classname + " {");
+                boolean shouldindent = true;
+                int len = indent(indentlevel+1).length();
+                for(String econst: cd.enumconstants) {
+                    if(shouldindent) {
+                        ps.println("");
+                        ps.print(indent(indentlevel+1));
+                        shouldindent = false;
+                    }
+                    len += econst.length();
+                    ps.print(econst + ", ");
+                    if(len >= CODEWIDTH) {
+                        len = indent(indentlevel+1).length();
+                        shouldindent = true;
+                    }
+                }
+                ps.println("");
+                ps.println(indent(indentlevel) + "}");
+                return;
+            } 
+            ps.print(indent(indentlevel));
+            if(cd.isStaticMemberClass()) {
+                ps.print("static ");
+            }
+            ps.print("class " + (classname.charAt(0) == '[' ? resolveJavaType(fieldtype.ARRAY, cd.name, false, fixname) : classname));
+            if(cd.superclass != null) {
+                ps.print(" extends " + cd.superclass.name);
+            }
+            ps.print(" implements ");
+            if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
+                ps.print("java.io.Externalizable");
+            } else {
+                ps.print("java.io.Serializable");
+            }
+            if(cd.interfaces != null) {
+                for(String intf: cd.interfaces) {
+                    ps.print(", " + intf);
+                }
+            }
+            ps.println(" {");
+            for(field f: cd.fields) {
+                if(f.isInnerClassReference()) {
+                    continue;
+                }
+                ps.print(indent(indentlevel+1) + f.getJavaType());
+                ps.println(" " + f.name + ";");
+            }
+            for(classdesc icd: cd.innerclasses) {
+                dump_ClassDesc(indentlevel+1, icd, ps, fixname);
+            }
+            ps.println(indent(indentlevel)+"}");
+        } else if(cd.classtype == classdesctype.PROXYCLASS) {
+            ps.print(indent(indentlevel) + "// proxy class " + hex(cd.handle));
+            if(cd.superclass != null) {
+                ps.print(" extends " + cd.superclass.name);
+            }
+            ps.println(" implements ");
+            for(String intf: cd.interfaces) {
+                ps.println(indent(indentlevel) + "//    " + intf + ", ");
+            }
+            if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
+                ps.println(indent(indentlevel) + "//    java.io.Externalizable");
+            } else {
+                ps.println(indent(indentlevel) + "//    java.io.Serializable");
+            }
+        } else {
+            throw new ValidityException("encountered invalid classdesc type!");
+        }
+    }
+
+    public void setHandle(int handle, content c) throws IOException {
+        if(handles.containsKey(handle)) {
+            throw new IOException("trying to reset handle " + hex(handle));
+        }
+        handles.put(handle, c);
+    }
+    public void reset() {
+        debug("reset ordered!");
+        if(handles != null && handles.size() > 0) {
+            HashMap<Integer,content> hm = new HashMap<Integer,content>();
+            hm.putAll(handles);
+            handlemaps.add(hm);
+        }
+        handles.clear();
+        curhandle = ObjectStreamConstants.baseWireHandle;  // 0x7e0000
+    }
+    /**
+     * Read the content of a thrown exception object.  According to the spec, this must be
+     * an object of type Throwable.  Although the Sun JDK always appears to provide enough
+     * information about the hierarchy to reach all the way back to java.lang.Throwable,
+     * it's unclear whether this is actually a requirement.  From my reading, it's
+     * possible that some other ObjectOutputStream implementations may leave some gaps in
+     * the hierarchy, forcing this app to hit the classloader.  To avoid this, we merely
+     * ensure that the written object is indeed an instance; ensuring that the object is
+     * indeed a Throwable is an exercise left to the user.
+     */
+    public content read_Exception(DataInputStream dis) throws IOException {
+        reset();
+        byte tc = dis.readByte();
+        if(tc == ObjectStreamConstants.TC_RESET) {
+            throw new ValidityException("TC_RESET for object while reading exception: what should we do?");
+        }
+        content c = read_Content(tc, dis, false);
+        if(c == null) {
+            throw new ValidityException("stream signaled for an exception, but exception object was null!");
+        }
+        if(!(c instanceof instance)) { 
+            throw new ValidityException("stream signaled for an exception, but content is not an object!");
+        }
+        if(c.isExceptionObject()) {
+            throw new ExceptionReadException(c);
+        }
+        c.setIsExceptionObject(true);
+        reset();
+        return c;
+    }
+
+    public classdesc read_classDesc(DataInputStream dis) throws IOException {
+        byte tc = dis.readByte();
+        classdesc cd = handle_classDesc(tc, dis, false);
+        return cd;
+    }
+    public classdesc read_newClassDesc(DataInputStream dis) throws IOException {
+        byte tc = dis.readByte();
+        classdesc cd = handle_newClassDesc(tc, dis);
+        return cd;
+    }
+    public content read_prevObject(DataInputStream dis) throws IOException {
+            int handle = dis.readInt();
+            if(!handles.containsKey(Integer.valueOf(handle))) {
+                throw new ValidityException("can't find an entry for handle " + hex(handle));
+            }
+            content c = handles.get(handle);
+            debug("prevObject: handle " + hex(c.getHandle()) + " classdesc " + c.toString());
+            return c;
+    }
+
+    public classdesc handle_newClassDesc(byte tc, DataInputStream dis) throws IOException {
+        return handle_classDesc(tc, dis, true);
+    }
+    public classdesc handle_classDesc(byte tc, DataInputStream dis, boolean mustBeNew) throws IOException {
+        if(tc == ObjectStreamConstants.TC_CLASSDESC) {
+            String name = dis.readUTF();
+            long serialVersionUID = dis.readLong();
+            int handle = newHandle();
+            byte descflags = dis.readByte();
+            short nfields = dis.readShort();
+            if(nfields < 0) {
+                throw new IOException("invalid field count: " + nfields);
+            }
+            field[] fields = new field[nfields];
+            for(short s = 0; s < nfields; s++) {
+                byte ftype = dis.readByte();
+                if(ftype == 'B' || ftype == 'C' || ftype == 'D' 
+                        || ftype == 'F' || ftype == 'I' || ftype == 'J'
+                        || ftype == 'S' || ftype == 'Z') {
+                    String fieldname = dis.readUTF();
+                    fields[s] = new field(fieldtype.get(ftype), fieldname);
+                } else if(ftype == '[' || ftype == 'L') {
+                    String fieldname = dis.readUTF();
+                    byte stc = dis.readByte();
+                    stringobj classname = read_newString(stc, dis);
+                    //String classname = dis.readUTF();
+                    fields[s] = new field(fieldtype.get(ftype), fieldname, classname);
+                } else {
+                    throw new IOException("invalid field type char: " + hex(ftype));
+                }
+            }
+            classdesc cd = new classdesc(classdesctype.NORMALCLASS);
+            cd.name = name;
+            cd.serialVersionUID = serialVersionUID;
+            cd.handle = handle;
+            cd.descflags = descflags;
+            cd.fields = fields;
+            cd.annotations = read_classAnnotation(dis);
+            cd.superclass = read_classDesc(dis);
+            setHandle(handle, cd);
+            debug("read new classdesc: handle " + hex(handle) + " name " + name);
+            return cd;
+        } else if(tc == ObjectStreamConstants.TC_NULL) {
+            if(mustBeNew) {
+                throw new ValidityException("expected new class description -- got null!");
+            }
+            debug("read null classdesc");
+            return null;
+        } else if(tc == ObjectStreamConstants.TC_REFERENCE) {
+            if(mustBeNew) {
+                throw new ValidityException("expected new class description -- got a reference!");
+            }
+            content c = read_prevObject(dis);
+            if(!(c instanceof classdesc)) {
+                throw new IOException("referenced object not a class description!");
+            }
+            classdesc cd = (classdesc)c;
+            return cd;
+        } else if(tc == ObjectStreamConstants.TC_PROXYCLASSDESC) {
+            int handle = newHandle();
+            int icount = dis.readInt();
+            if(icount < 0) {
+                throw new IOException("invalid proxy interface count: " + hex(icount));
+            }
+            String interfaces[] = new String[icount];
+            for(int i = 0; i < icount; i++) {
+                interfaces[i] = dis.readUTF();
+            }
+            classdesc cd = new classdesc(classdesctype.PROXYCLASS);
+            cd.handle = handle;
+            cd.interfaces = interfaces;
+            cd.annotations = read_classAnnotation(dis);
+            cd.superclass = read_classDesc(dis);
+            setHandle(handle, cd);
+            cd.name = "(proxy class; no name)";
+            debug("read new proxy classdesc: handle " + hex(handle) + " names [" + Arrays.toString(interfaces) + "]");
+            return cd;
+        } else {
+            throw new ValidityException("expected a valid class description starter got " + hex(tc));
+        }
+    }
+    public arrayobj read_newArray(DataInputStream dis) throws IOException {
+        classdesc cd = read_classDesc(dis);
+        int handle = newHandle();
+        debug("reading new array: handle " + hex(handle) + " classdesc " + cd.toString());
+        if(cd.name.length() < 2) {
+            throw new IOException("invalid name in array classdesc: " + cd.name);
+        }
+        arraycoll ac = read_arrayValues(cd.name.substring(1), dis);
+        return new arrayobj(handle, cd, ac);
+    }
+    public arraycoll read_arrayValues(String str, DataInputStream dis) throws IOException {
+        byte b = str.getBytes("UTF-8")[0];
+        fieldtype ft = fieldtype.get(b);
+        int size = dis.readInt();
+        if(size < 0) {
+            throw new IOException("invalid array size: " + size);
+        }
+
+        arraycoll ac = new arraycoll(ft);
+        for(int i = 0; i < size; i++) {
+            ac.add(read_FieldValue(ft, dis));
+            continue;
+        }
+        return ac;
+    }
+    public classobj read_newClass(DataInputStream dis) throws IOException {
+        classdesc cd = read_classDesc(dis);
+        int handle = newHandle();
+        debug("reading new class: handle " + hex(handle) + " classdesc " + cd.toString());
+        classobj c = new classobj(handle, cd);
+        setHandle(handle, c);
+        return c;
+    }
+    public enumobj read_newEnum(DataInputStream dis) throws IOException {
+        classdesc cd = read_classDesc(dis);
+        if(cd == null) {
+            throw new IOException("enum classdesc can't be null!");
+        }
+        int handle = newHandle();
+        debug("reading new enum: handle " + hex(handle) + " classdesc " + cd.toString());
+        byte tc = dis.readByte();
+        stringobj so = read_newString(tc, dis);
+        cd.addEnum(so.value);
+        setHandle(handle, so);
+        return new enumobj(handle, cd, so);
+    }
+    public stringobj read_newString(byte tc, DataInputStream dis) throws IOException {
+        byte[] data;
+        if(tc == ObjectStreamConstants.TC_REFERENCE) {
+                content c = read_prevObject(dis);
+                if(!(c instanceof stringobj)) {
+                    throw new IOException("got reference for a string, but referenced value was something else!");
+                }
+                return (stringobj)c;
+        }
+        int handle = newHandle();
+        if(tc == ObjectStreamConstants.TC_STRING) {
+            int len = dis.readUnsignedShort();
+            data = new byte[len];
+        } else if(tc == ObjectStreamConstants.TC_LONGSTRING) {
+            long len = dis.readLong();
+            if(len < 0) {
+                throw new IOException("invalid long string length: " + len);
+            }
+            if(len > 2147483647) {
+                throw new IOException("long string is too long: " + len);
+            }
+            if(len < 65536) {
+                debugerr("warning: small string length encoded as TC_LONGSTRING: " + len);
+            }
+            data = new byte[(int)len];
+        } else if(tc == ObjectStreamConstants.TC_NULL) {
+            throw new ValidityException("stream signaled TC_NULL when string type expected!");
+        } else {
+            throw new IOException("invalid tc byte in string: " + hex(tc));
+        }
+        dis.readFully(data);
+        debug("reading new string: handle " + hex(handle) + " bufsz " + data.length);
+        stringobj sobj = new stringobj(handle, data);
+        setHandle(handle, sobj);
+        return sobj;
+    }
+    public blockdata read_blockdata(byte tc, DataInputStream dis) throws IOException {
+        int size;
+        if(tc == ObjectStreamConstants.TC_BLOCKDATA) {
+            size = dis.readUnsignedByte();
+        } else if(tc == ObjectStreamConstants.TC_BLOCKDATALONG) {
+            size = dis.readInt();
+        } else {
+            throw new IOException("invalid tc value for blockdata: " + hex(tc));
+        }
+        if(size < 0) {
+            throw new IOException("invalid value for blockdata size: " + size);
+        }
+        byte[] b = new byte[size];
+        dis.readFully(b);
+        debug("read blockdata of size " + size);
+        return new blockdata(b);
+    }
+    public instance read_newObject(DataInputStream dis) throws IOException {
+        classdesc cd = read_classDesc(dis);
+        int handle = newHandle();
+        debug("reading new object: handle " + hex(handle) + " classdesc " + cd.toString());
+        instance i = new instance();
+        i.classdesc = cd;
+        i.handle = handle;
+        setHandle(handle, i);
+        read_Classdata(dis, i);
+        debug("done reading object for handle " + hex(handle));
+        return i;
+    }
+
+    /**
+     * <p>
+     * Read the next object corresponding to the spec grammar rule "content", and return
+     * an object of type content.
+     * </p>
+     *
+     * <p>
+     * Usually, there is a 1:1 mapping of content items and returned instances.  The
+     * one case where this isn't true is when an exception is embedded inside another
+     * object.  When this is encountered, only the serialized exception object is
+     * returned; it's up to the caller to backtrack in order to gather any data from the
+     * object that was being serialized when the exception was thrown.
+     * </p>
+     *
+     * @param tc the last byte read from the stream; it must be one of the TC_* values
+     * within ObjectStreamConstants.*
+     * @param dis the DataInputStream to read from
+     * @param blockdata whether or not to read TC_BLOCKDATA (this is the difference
+     * between spec rules "object" and "content").
+     * @return an object representing the last read item from the stream 
+     * @throws IOException when a validity or I/O error occurs while reading
+     */
+    public content read_Content(byte tc, DataInputStream dis, boolean blockdata) throws IOException {
+        try {
+            switch(tc) {
+                case ObjectStreamConstants.TC_OBJECT:
+                    return read_newObject(dis);
+                case ObjectStreamConstants.TC_CLASS:
+                    return read_newClass(dis);
+                case ObjectStreamConstants.TC_ARRAY:
+                    return read_newArray(dis);
+                case ObjectStreamConstants.TC_STRING:
+                case ObjectStreamConstants.TC_LONGSTRING:
+                    return read_newString(tc, dis);
+                case ObjectStreamConstants.TC_ENUM:
+                    return read_newEnum(dis);
+                case ObjectStreamConstants.TC_CLASSDESC:
+                case ObjectStreamConstants.TC_PROXYCLASSDESC:
+                    return handle_newClassDesc(tc, dis);
+                case ObjectStreamConstants.TC_REFERENCE:
+                    return read_prevObject(dis);
+                case ObjectStreamConstants.TC_NULL:
+                    return null;
+                case ObjectStreamConstants.TC_EXCEPTION:
+                    return read_Exception(dis);
+                case ObjectStreamConstants.TC_BLOCKDATA:
+                case ObjectStreamConstants.TC_BLOCKDATALONG:
+                    if(blockdata == false) {
+                        throw new IOException("got a blockdata TC_*, but not allowed here: " + hex(tc));
+                    }
+                    return read_blockdata(tc, dis);
+                default:
+                    throw new IOException("unknown content tc byte in stream: " + hex(tc));
+            }
+        } catch (ExceptionReadException ere) {
+            return ere.getExceptionObject();
+        }
+    }
+
+    /**
+     * <p>
+     * Reads in an entire ObjectOutputStream output on the given stream, filing 
+     * this object's content and handle maps with data about the objects in the stream.  
+     * </p>
+     *
+     * <p>
+     * If shouldConnect is true, then jdeserialize will attempt to identify member classes
+     * by their names according to the details laid out in the Inner Classes
+     * Specification.  If it finds one, it will set the classdesc's flag indicating that
+     * it is an member class, and it will create a reference in its enclosing class.
+     * </p>
+     *
+     * @param is an open InputStream on a serialized stream of data
+     * @param shouldConnect true if jdeserialize should attempt to identify and connect
+     * member classes with their enclosing classes
+     * 
+     * Also see the <pre>connectMemberClasses</pre> method for more information on the 
+     * member-class-detection algorithm.
+     */
+    public jdeserialize(byte[] input/*, boolean shouldConnect*/) throws IOException {
+        LoggerInputStream lis = null;
+        DataInputStream dis = null;
+        try {
+            lis = new LoggerInputStream(input);
+            dis = new DataInputStream(lis);
+
+            short magic = dis.readShort();
+            if(magic != ObjectStreamConstants.STREAM_MAGIC) {
+                throw new ValidityException("file magic mismatch!  expected " + ObjectStreamConstants.STREAM_MAGIC + ", got " + magic);
+            }
+            short streamversion = dis.readShort();
+            if(streamversion != ObjectStreamConstants.STREAM_VERSION) {
+                throw new ValidityException("file version mismatch!  expected " + ObjectStreamConstants.STREAM_VERSION + ", got " + streamversion);
+            }
+            reset();
+            content = new ArrayList<content>();
+            while(true) {
+                byte tc;
+                try { 
+                    lis.record();
+                    tc = dis.readByte();
+                    if(tc == ObjectStreamConstants.TC_RESET) {
+                        reset();
+                        continue;
+                    }
+                } catch (EOFException eoe) {
+                    break;
+                }
+                content c = read_Content(tc, dis, true);
+                System.out.println("read: " + c.toString());
+                if(c != null && c.isExceptionObject()) {
+                    c = new exceptionstate(c, lis.getRecordedData());
+                }
+                content.add(c);
+            }
+        } finally {
+            if(dis != null) {
+                try {
+                    dis.close();
+                } catch (Exception ignore) { }
+            }
+            if(lis != null) {
+                try {
+                    lis.close();
+                } catch (Exception ignore) {}
+            }
+        }
+        for(content c: handles.values()) {
+            c.validate();
+        }
+        /*if(shouldConnect) {
+            connectMemberClasses();
+            for(content c: handles.values()) {
+                c.validate();
+            }
+        }*/
+        if(handles != null && handles.size() > 0) {
+            HashMap<Integer,content> hm = new HashMap<Integer,content>();
+            hm.putAll(handles);
+            handlemaps.add(hm);
+        }
+    }
+    public void dump(Getopt go) throws IOException {
+        if(go.hasOption("-blockdata") || go.hasOption("-blockdatamanifest")) {
+            List<String> bout = go.getArguments("-blockdata");
+            List<String> mout = go.getArguments("-blockdatamanifest");
+            FileOutputStream bos = null, mos = null;
+            PrintWriter pw = null;
+            try {
+                if(bout != null && bout.size() > 0) {
+                    bos = new FileOutputStream(bout.get(0));
+                }
+                if(mout != null && bout.size() > 0) {
+                    mos = new FileOutputStream(mout.get(0));
+                    pw = new PrintWriter(mos);
+                    pw.println("# Each line in this file that doesn't begin with a '#' contains the size of");
+                    pw.println("# an individual blockdata block written to the stream.");
+                }
+                for(content c: content) {
+                    System.out.println(c.toString());
+                    if(c instanceof blockdata) {
+                        blockdata bd = (blockdata)c;
+                        if(mos != null) {
+                            pw.println(bd.buf.length);
+                        }
+                        if(bos != null) {
+                            bos.write(bd.buf);
+                        }
+                    }
+                }
+            } finally {
+                if(bos != null) {
+                    try {
+                        bos.close();
+                    } catch (IOException ignore) { }
+                }
+                if(mos != null) {
+                    try {
+                        pw.close();
+                        mos.close();
+                    } catch (IOException ignore) { }
+                }
+            }
+        }
+        if(!go.hasOption("-nocontent")) {
+            System.out.println("//// BEGIN stream content output");
+            for(content c: content) {
+                System.out.println(c.toString());
+            }
+            System.out.println("//// END stream content output");
+            System.out.println("");
+        }
+
+        if(!go.hasOption("-noclasses")) {
+            boolean showarray = go.hasOption("-showarrays");
+            List<String> fpat = go.getArguments("-filter");
+            System.out.println("//// BEGIN class declarations"
+                    + (showarray? "" : " (excluding array classes)")
+                    + ((fpat != null && fpat.size() > 0) 
+                        ? " (exclusion filter " + fpat.get(0) + ")"
+                        : ""));
+            for(content c: handles.values()) {
+                if(c instanceof classdesc) {
+                    classdesc cl = (classdesc)c;
+                    if(showarray == false && cl.isArrayClass()) {
+                        continue;
+                    }
+                    // Member classes will be displayed as part of their enclosing
+                    // classes.
+                    if(cl.isStaticMemberClass() || cl.isInnerClass()) {
+                        continue;
+                    }
+                    if(fpat != null && fpat.size() > 0 && cl.name.matches(fpat.get(0))) {
+                        continue;
+                    }
+                    dump_ClassDesc(0, cl, System.out, go.hasOption("-fixnames"));
+                    System.out.println("");
+                }
+            }
+            System.out.println("//// END class declarations");
+            System.out.println("");
+        }
+        if(!go.hasOption("-noinstances")) {
+            System.out.println("//// BEGIN instance dump");
+            for(content c: handles.values()) {
+                if(c instanceof instance) {
+                    instance i = (instance)c;
+                    dump_Instance(0, i, System.out);
+                }
+            }
+            System.out.println("//// END instance dump");
+            System.out.println("");
+        }
+    }
+
+
+    /**
+     * <p>
+     * Connects member classes according to the rules specified by the JDK 1.1 Inner
+     * Classes Specification.  
+     * </p>
+     *
+     * <pre>
+     * Inner classes:
+     * for each class C containing an object reference member R named this$N, do:
+     *     if the name of C matches the pattern O$I
+     *     AND the name O matches the name of an existing type T
+     *     AND T is the exact type referred to by R, then:
+     *         don't display the declaration of R in normal dumping,
+     *         consider C to be an inner class of O named I
+     *
+     * Static member classes (after):
+     * for each class C matching the pattern O$I, 
+     * where O is the name of a class in the same package
+     * AND C is not an inner class according to the above algorithm:
+     *     consider C to be an inner class of O named I
+     * </pre>
+     *
+     * <p>
+     * This functions fills in the isInnerClass value in classdesc, the
+     * isInnerClassReference value in field, the isLocalInnerClass value in 
+     * classdesc, and the isStaticMemberClass value in classdesc where necessary.
+     * </p>
+     *
+     * <p>
+     * A word on static classes: serializing a static member class S doesn't inherently
+     * require serialization of its parent class P.  Unlike inner classes, S doesn't
+     * retain an instance of P, and therefore P's class description doesn't need to be
+     * written.  In these cases, if parent classes can be found, their static member
+     * classes will be connected; but if they can't be found, the names will not be
+     * changed and no ValidityException will be thrown.
+     * </p>
+     *
+     * @throws ValidityException if the found values don't correspond to spec
+     */
+    public void connectMemberClasses() throws IOException {
+        HashMap<classdesc, String> newnames = new HashMap<classdesc, String>();
+        HashMap<String, classdesc> classes = new HashMap<String, classdesc>();
+        HashSet<String> classnames = new HashSet<String>();
+        for(content c: handles.values()) {
+            if(!(c instanceof classdesc)) {
+                continue;
+            }
+            classdesc cd = (classdesc)c;
+            classes.put(cd.name, cd);
+            classnames.add(cd.name);
+        }
+        Pattern fpat = Pattern.compile("^this\\$(\\d+)$");
+        Pattern clpat = Pattern.compile("^((?:[^\\$]+\\$)*[^\\$]+)\\$([^\\$]+)$");
+        for(classdesc cd: classes.values()) {
+            if(cd.classtype == classdesctype.PROXYCLASS) {
+                continue;
+            }
+            for(field f: cd.fields) {
+                if(f.type != fieldtype.OBJECT) {
+                    continue;
+                }
+                Matcher m = fpat.matcher(f.name);
+                if(!m.matches()) {
+                    continue;
+                }
+                boolean islocal = false;
+                Matcher clmat = clpat.matcher(cd.name);
+                if(!clmat.matches()) {
+                    throw new ValidityException("inner class enclosing-class reference field exists, but class name doesn't match expected pattern: class " + cd.name + " field " + f.name);
+                }
+                String outer = clmat.group(1), inner = clmat.group(2);
+                classdesc outercd = classes.get(outer);
+                if(outercd == null) {
+                    throw new ValidityException("couldn't connect inner classes: outer class not found for field name " + f.name);
+                }
+                if(!outercd.name.equals(f.getJavaType())) {
+                    throw new ValidityException("outer class field type doesn't match field type name: " + f.classname.value + " outer class name " + outercd.name);
+                }
+                outercd.addInnerClass(cd);
+                cd.setIsLocalInnerClass(islocal);
+                cd.setIsInnerClass(true);
+                f.setIsInnerClassReference(true);
+                newnames.put(cd, inner);
+            }
+        }
+        for(classdesc cd: classes.values()) {
+            if(cd.classtype == classdesctype.PROXYCLASS) {
+                continue;
+            }
+            if(cd.isInnerClass()) {
+                continue;
+            }
+            Matcher clmat = clpat.matcher(cd.name);
+            if(!clmat.matches()) {
+                continue;
+            }
+            String outer = clmat.group(1), inner = clmat.group(2);
+            classdesc outercd = classes.get(outer);
+            if(outercd != null) {
+                outercd.addInnerClass(cd);
+                cd.setIsStaticMemberClass(true);
+                newnames.put(cd, inner);
+            }
+        }
+        for(classdesc ncd: newnames.keySet()) {
+            String newname = newnames.get(ncd);
+            if(classnames.contains(newname)) {
+                throw new ValidityException("can't rename class from " + ncd.name + " to " + newname + " -- class already exists!");
+            }
+            for(classdesc cd: classes.values()) {
+                if(cd.classtype == classdesctype.PROXYCLASS) {
+                    continue;
+                }
+                for(field f: cd.fields) {
+                    if(f.getJavaType().equals(ncd.name)) {
+                        f.setReferenceTypeName(newname);
+                    }
+                }
+            }
+            if(classnames.remove(ncd.name) == false) {
+                throw new ValidityException("tried to remove " + ncd.name + " from classnames cache, but couldn't find it!");
+            }
+            ncd.name = newname;
+            if(classnames.add(newname) == false) {
+                throw new ValidityException("can't rename class to " + newname + " -- class already exists!");
+            }
+        }
+    }
+
+    /**
+     * Decodes a class name according to the field-descriptor format in the jvm spec,
+     * section 4.3.2.
+     * @param fdesc name in field-descriptor format (Lfoo/bar/baz;)
+     * @param convertSlashes true iff slashes should be replaced with periods (true for
+     * "real" field-descriptor format; false for names in classdesc)
+     * @return a fully-qualified class name
+     * @throws ValidityException if the name isn't valid
+     */
+    public static String decodeClassName(String fdesc, boolean convertSlashes) throws ValidityException {
+        if(fdesc.charAt(0) != 'L' || fdesc.charAt(fdesc.length()-1) != ';' || fdesc.length() < 3) {
+            throw new ValidityException("invalid name (not in field-descriptor format): " + fdesc);
+        }
+        String subs = fdesc.substring(1, fdesc.length()-1);
+        if(convertSlashes) {
+            return subs.replace('/', '.');
+        } 
+        return subs;
+    }
+
+    public static String hexnoprefix(long value) {
+        return hexnoprefix(value, 2);
+    }
+    public static String hexnoprefix(long value, int len) {
+        if(value < 0) {
+            value = 256 + value;
+        }
+        String s = Long.toString(value, 16);
+        while(s.length() < len) {
+            s = "0" + s;
+        }
+        return s;
+    }
+    public static String hex(long value) {
+        return "0x" + hexnoprefix(value);
+    }
+    public static void debugerr(String message) {
+        System.err.println(message);
+    }
+    public void debug(String message) {
+        if(debugEnabled) {
+            System.out.println(message);
+        }
+    }
+    
+    /*
+    public static void main(String[] args) {
+        HashMap<String, Integer> options = new HashMap<String, Integer>();
+        Getopt go = new Getopt();
+        go.addOption("-help", 0, "Show this list.");
+        go.addOption("-debug", 0, "Write debug info generated during parsing to stdout.");
+        go.addOption("-filter", 1, "Exclude classes that match the given String.matches() regex from class output.");
+        go.addOption("-nocontent", 0, "Don't output descriptions of the content in the stream.");
+        go.addOption("-noinstances", 0, "Don't output descriptions of every instance.");
+        go.addOption("-showarrays", 0, "Show array class declarations (e.g. int[]).");
+        go.addOption("-noconnect", 0, "Don't attempt to connect member classes to their enclosing classes.");
+        go.addOption("-fixnames", 0, "In class names, replace illegal Java identifier characters with legal ones.");
+        go.addOption("-noclasses", 0, "Don't output class declarations.");
+        go.addOption("-blockdata", 1, "Write raw blockdata out to the specified file.");
+        go.addOption("-blockdatamanifest", 1, "Write blockdata manifest out to the specified file.");
+        try {
+            go.parse(args);
+        } catch (Getopt.OptionParseException ope) {
+            System.err.println("argument error: " + ope.getMessage());
+            System.out.println(go.getDescriptionString());
+            System.exit(1);
+        }
+        if(go.hasOption("-help")) {
+            System.out.println(go.getDescriptionString());
+            System.exit(1);
+        }
+        List<String> fargs = go.getOtherArguments();
+        if(fargs.size() < 1) {
+            debugerr("args: [options] file1 [file2 .. fileN]");
+            System.err.println("");
+            System.err.println(go.getDescriptionString());
+            System.exit(1);
+        }
+        for(String filename: fargs) {
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(filename);
+                jdeserialize jd = new jdeserialize(fis);
+                if(go.hasOption("-debug")) {
+                    jd.debugEnabled = true;
+                } else {
+                    jd.debugEnabled = false;
+                }
+                jd.run(fis, !go.hasOption("-noconnect"));
+                jd.dump(go);
+            } catch(EOFException eoe) {
+                debugerr("EOF error while attempting to decode file " + filename + ": " + eoe.getMessage());
+                eoe.printStackTrace();
+            } catch(IOException ioe) {
+                debugerr("error while attempting to decode file " + filename + ": " + ioe.getMessage());
+                ioe.printStackTrace();
+            } finally {
+                if(fis != null) {
+                    try {
+                        fis.close();
+                    } catch (Exception ignore) { }
+                }
+            }
+        }
+    }
+    */
+}
diff --git a/project/Deserializer/src/jdeserialize/stringobj.java b/project/Deserializer/src/jdeserialize/stringobj.java
new file mode 100644
index 0000000..6f08c86
--- /dev/null
+++ b/project/Deserializer/src/jdeserialize/stringobj.java
@@ -0,0 +1,70 @@
+package jdeserialize;
+import java.io.*;
+
+/**
+ * Represents a serialized string object.  This is primarily used in serialized streams;
+ * however, it is also used internally by other objects, since string objects have
+ * handles as well.
+ */
+public class stringobj extends contentbase {
+    public String value;
+    private int readorthrow(ByteArrayInputStream bais) throws EOFException {
+        int x = bais.read();
+        if(x == -1) {
+            throw new EOFException("unexpected eof in modified utf-8 string");
+        }
+        return x;
+    }
+    public String toString() {
+        return "[String " + jdeserialize.hex(handle) + ": \"" + value + "\"]";
+    }
+    /**
+     * Constructor.
+     *
+     * @param handle the string object's handle
+     * @param data the bytes corresponding to the string 
+     * @throws IOException if an I/O or validity error occurs
+     */
+    public stringobj(int handle, byte[] data) throws IOException {
+        super(contenttype.STRING);
+        this.handle = handle;
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        StringBuffer sb = new StringBuffer();
+        while(true) {
+            int ba = bais.read();
+            if(ba == -1) {
+                break;
+            }
+            if((ba & 0x80) == 0) {                  /* U+0001..U+007F */
+                if(ba == 0) {
+                    throw new IOException("improperly-encoded null in modified UTF8 string!");
+                }
+                sb.append((char)ba);
+            } else if((ba & 0xf0) == 0xe0) {        /* U+0800..U+FFFF */
+                int bb = readorthrow(bais);
+                if((bb & 0xc0) != 0x80) {
+                    throw new IOException("byte b in 0800-FFFF seq doesn't begin with correct prefix");
+                }
+                int bc = readorthrow(bais);
+                if((bc & 0xc0) != 0x80) {
+                    throw new IOException("byte c in 0800-FFFF seq doesn't begin with correct prefix");
+                }
+                int cp = 
+                    ((ba & 0xf) << 12)
+                    | ((bb & 0x3f) << 6)
+                    | (bc & 0x3f);
+                sb.append((char)cp);
+            } else if((ba & 0xe0) == 0xc0) {        /* U+0080..U+07FF */
+                int bb = readorthrow(bais);
+                if((bb & 0xc0) != 0x80) {
+                    throw new IOException("byte b in 0080-07FF seq doesn't begin with correct prefix");
+                }
+                int cp = ((ba & 0x1f) << 6) | (bb & 0x3f);
+                sb.append((char)cp);
+            } else {
+                throw new IOException("invalid byte in modified utf-8 string: " + jdeserialize.hex(ba));
+            }
+        }
+        this.value = sb.toString();
+    }
+}
diff --git a/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1164.data b/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1164.data
new file mode 100644
index 0000000..53bc26d
Binary files /dev/null and b/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1164.data differ
diff --git a/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1165.data b/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1165.data
new file mode 100644
index 0000000..c3e84a1
Binary files /dev/null and b/project/Deserializer/test_data/GK_REPORT_ARCHIVE_202004031533.md-1165.data differ
diff --git a/project/JOU_Deserializer.jar b/project/JOU_Deserializer.jar
new file mode 100644
index 0000000..846d336
Binary files /dev/null and b/project/JOU_Deserializer.jar differ
diff --git a/project/a.data b/project/a.data
new file mode 100644
index 0000000..53bc26d
Binary files /dev/null and b/project/a.data differ
diff --git a/project/a.json b/project/a.json
new file mode 100644
index 0000000..948bafd
--- /dev/null
+++ b/project/a.json
@@ -0,0 +1,53 @@
+{
+  "handle" : 8257537,
+  "isExceptionObject" : false,
+  "type" : "INSTANCE",
+  "fielddata" : {
+    "[cd 0x7e0000: name java.util.Hashtable uid 1421746759512286392]" : {
+      "jdeserialize.field@6d03831a" : 8,
+      "jdeserialize.field@563e22e0" : 0.75
+    }
+  },
+  "classdesc" : {
+    "handle" : 8257536,
+    "isExceptionObject" : false,
+    "type" : "CLASSDESC",
+    "classtype" : "NORMALCLASS",
+    "name" : "java.util.Hashtable",
+    "serialVersionUID" : 1421746759512286392,
+    "descflags" : 3,
+    "fields" : [ {
+      "type" : "FLOAT",
+      "name" : "loadFactor",
+      "classname" : null,
+      "javaType" : "float",
+      "innerClassReference" : false
+    }, {
+      "type" : "INTEGER",
+      "name" : "threshold",
+      "classname" : null,
+      "javaType" : "int",
+      "innerClassReference" : false
+    } ],
+    "innerclasses" : [ ],
+    "annotations" : [ ],
+    "superclass" : null,
+    "interfaces" : null,
+    "enumconstants" : [ ],
+    "staticMemberClass" : false,
+    "localInnerClass" : false,
+    "innerClass" : false,
+    "arrayClass" : false,
+    "exceptionObject" : false
+  },
+  "annotations" : {
+    "[cd 0x7e0000: name java.util.Hashtable uid 1421746759512286392]" : [ {
+      "handle" : 0,
+      "isExceptionObject" : false,
+      "type" : "BLOCKDATA",
+      "buf" : "AAAACwAAAAA=",
+      "exceptionObject" : false
+    } ]
+  },
+  "exceptionObject" : false
+}
\ No newline at end of file
