diff --git a/pom.xml b/pom.xml
index 6d5072d..c4cf3eb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,11 +73,6 @@
scala-library
2.12.6
-
- xmlunit
- xmlunit
- 1.6
-
edu.illinois.cs
testrunner-running
@@ -98,6 +93,16 @@
junit-platform-launcher
1.1.0
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.19
+
+
+ org.mockito
+ mockito-core
+ 3.11.2
+
diff --git a/testrunner-running/MANIFEST.MF b/testrunner-running/MANIFEST.MF
new file mode 100644
index 0000000..135fece
--- /dev/null
+++ b/testrunner-running/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Premain-Class: edu.illinois.cs.statecapture.agent.MainAgent
diff --git a/testrunner-running/pom.xml b/testrunner-running/pom.xml
index 3c92d87..1ee6e05 100644
--- a/testrunner-running/pom.xml
+++ b/testrunner-running/pom.xml
@@ -37,14 +37,18 @@
org.scala-lang
scala-library
-
- xmlunit
- xmlunit
-
org.junit.platform
junit-platform-launcher
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ org.mockito
+ mockito-core
+
@@ -120,6 +124,16 @@
+
+ maven-jar-plugin
+ 2.4
+
+
+ false
+ MANIFEST.MF
+
+
+
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/IStateCapture.java b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/IStateCapture.java
new file mode 100644
index 0000000..3897ff2
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/IStateCapture.java
@@ -0,0 +1,6 @@
+package edu.illinois.cs.statecapture;
+
+public interface IStateCapture {
+
+ public void capture();
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/StateCapture.java b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/StateCapture.java
new file mode 100644
index 0000000..f45ef8e
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/StateCapture.java
@@ -0,0 +1,489 @@
+package edu.illinois.cs.statecapture;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
+import com.thoughtworks.xstream.converters.reflection.FieldKey;
+import com.thoughtworks.xstream.converters.reflection.FieldKeySorter;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+import com.thoughtworks.xstream.mapper.Mapper;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+
+import edu.illinois.cs.statecapture.agent.MainAgent;
+import edu.illinois.cs.testrunner.configuration.Configuration;
+import edu.illinois.cs.xstream.CustomElementIgnoringMapper;
+import edu.illinois.cs.xstream.EnumMapConverter;
+import edu.illinois.cs.xstream.LambdaConverter;
+import edu.illinois.cs.xstream.LookAndFeelConverter;
+import edu.illinois.cs.xstream.MapConverter;
+import edu.illinois.cs.xstream.ReflectionConverter;
+import edu.illinois.cs.xstream.SerializableConverter;
+import edu.illinois.cs.xstream.TreeMapConverter;
+import edu.illinois.cs.xstream.UnmarshalChain;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.lang.Object;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.mockito.Mockito;
+
+import static com.thoughtworks.xstream.XStream.PRIORITY_LOW;
+import static com.thoughtworks.xstream.XStream.PRIORITY_NORMAL;
+import static com.thoughtworks.xstream.XStream.PRIORITY_VERY_LOW;
+
+public class StateCapture implements IStateCapture {
+
+ protected final String testName;
+ private boolean dirty;
+
+ // The State, field name of static root to object pointed to
+ private static final LinkedHashMap nameToInstance = new LinkedHashMap();
+
+ //for reflection and deserialization
+ private String xmlDir;
+ private String rootFile;
+ private String reflectionFile;
+
+ public StateCapture(String testName) {
+ this.testName = testName;
+ setup();
+ }
+
+ static Set readFileContentsAsSet(String path) {
+ File file = new File(path);
+ Set keys = new HashSet<>();
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ keys.add(line);
+ }
+ } catch (FileNotFoundException fnfe) {
+ fnfe.printStackTrace();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ return keys;
+ }
+
+ private void setup() {
+ xmlDir = Configuration.config().getProperty("statecapture.xmlDir");
+ rootFile = Configuration.config().getProperty("statecapture.rootFile");
+ reflectionFile = Configuration.config().getProperty("statecapture.reflectionFile");
+ }
+
+ public void load(String fieldName) throws IOException {
+ if (xmlDir.isEmpty() || reflectionFile.isEmpty()) {
+ System.out.println("WARNING: The subxml folder or reflection file are not provided, thus it will not do loading.");
+ return;
+ }
+ try {
+ String path0 = xmlDir + File.separator + fieldName + ".xml";
+ String state0 = readFile(path0);
+ String className = fieldName.substring(0, fieldName.lastIndexOf("."));
+ String subFieldName = fieldName.substring(fieldName.lastIndexOf(".") + 1, fieldName.length());
+
+ Object obj;
+ XStream xstream = getXStreamInstance();
+
+ try {
+ Class c = Class.forName(className);
+ Field[] fieldList = c.getDeclaredFields();
+ for (int i = 0; i < fieldList.length; i++) {
+ if (!fieldList[i].getName().equals(subFieldName)) {
+ continue;
+ }
+ try {
+ fieldList[i].setAccessible(true);
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(fieldList[i], fieldList[i].getModifiers() & ~Modifier.FINAL);
+ } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+ String outputPrivateError = fieldName + " reflectionError: " + e + "\n";
+ Files.write(Paths.get(reflectionFile), outputPrivateError.getBytes(),
+ StandardOpenOption.APPEND);
+ }
+ try {
+ obj = fieldList[i].get(null);
+ boolean threadLocal = false;
+ if (obj instanceof ThreadLocal) {
+ threadLocal = true;
+ }
+ try {
+ // directly invoke reset when dealing with a Mockito mock object
+ if (Mockito.mockingDetails(obj).isMock()) {
+ Method m = Mockito.class.getDeclaredMethod("reset", Object[].class);
+ m.invoke(null, new Object[]{new Object[]{obj}});
+ } else {
+ UnmarshalChain.reset();
+ UnmarshalChain.initializeChain(fieldList[i].getDeclaringClass().getName(), fieldList[i].getName());
+ obj = xstream.fromXML(state0);
+ }
+ } catch (NoSuchMethodError nsme) { // In case the Mockito reset does not apply, still try to load from XML
+ UnmarshalChain.reset();
+ UnmarshalChain.initializeChain(fieldList[i].getDeclaringClass().getName(), fieldList[i].getName());
+ obj = xstream.fromXML(state0);
+ }
+ if (obj == null) {
+ System.out.println("Unable to construct the relevant object during load");
+ return;
+ }
+ if (AccessibleObject.class.isAssignableFrom(obj.getClass())) {
+ ((AccessibleObject) obj).setAccessible(true);
+ }
+ // the purpose is to serialize/deserialize the object wrapped within a ThreadLocal.
+ if (threadLocal) {
+ Object tmp = fieldList[i].get(null);
+ ((ThreadLocal)tmp).set(obj);
+ obj = tmp;
+ }
+
+ if (!Mockito.mockingDetails(obj).isMock()) {
+ FieldUtils.writeField(fieldList[i], (Object) null, obj, true);
+ }
+
+ String output = fieldName + " set\n";
+ Files.write(Paths.get(reflectionFile), output.getBytes(),
+ StandardOpenOption.APPEND);
+ } catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException
+ | SecurityException | InvocationTargetException e) {
+ e.printStackTrace();
+ String outputNormalError = fieldName + " reflectionError: " + e + "\n";
+ Files.write(Paths.get(reflectionFile), outputNormalError.getBytes(),
+ StandardOpenOption.APPEND);
+ }
+ }
+ } catch (ClassNotFoundException | SecurityException e) {
+ e.printStackTrace();
+ String output = fieldName + " deserializeError: " + e + "\n";
+ Files.write(Paths.get(reflectionFile), output.getBytes(),
+ StandardOpenOption.APPEND);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ String output = fieldName + " deserializeError: " + e + "\n";
+ Files.write(Paths.get(reflectionFile), output.getBytes(),
+ StandardOpenOption.APPEND);
+ }
+ }
+
+ @Override
+ public void capture() {
+ try {
+ if (xmlDir.isEmpty() || rootFile.isEmpty()) {
+ System.out.println("WARNING: The xml directory or root file are not provided, thus it will not do capturing.");
+ return;
+ }
+ String eagerload = Configuration.config().getProperty("statecapture.eagerload", "");
+ if (!eagerload.equals("true")) {
+ capture_real();
+ } else {
+ capture_class();
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Adds the current serialized reachable state to the currentTestStates list
+ * and the current roots to the currentRoots list.
+ * @throws IOException
+ */
+ private void capture_real() throws IOException {
+ PrintWriter writer;
+
+ createXmlDir();
+
+ Set allFieldName = new HashSet();
+ Class[] loadedClasses = MainAgent.getInstrumentation().getAllLoadedClasses();
+ File eagerLoadFile = new File(Configuration.config().getProperty("statecapture.eagerloadfile", ""));
+ if (eagerLoadFile.exists()) {
+ List loadedClassesList = new ArrayList(Arrays.asList(loadedClasses));
+ Set eagerLoadedClasses = readFileContentsAsSet(eagerLoadFile.toPath().toString());
+ for (String clz : eagerLoadedClasses) {
+ try {
+ Class tmp = Class.forName(clz);
+ loadedClassesList.add(tmp);
+ } catch (ClassNotFoundException cnfe) {
+ cnfe.printStackTrace();
+ continue;
+ } catch (NoClassDefFoundError ncdfe) {
+ ncdfe.printStackTrace();
+ continue;
+ }
+ }
+ Class[] arrayClasses = new Class[loadedClassesList.size()];
+ loadedClassesList.toArray(arrayClasses);
+ loadedClasses = arrayClasses;
+ }
+
+ for (Class c : loadedClasses) {
+ // Ignore classes in standard java to get top-level
+ // TODO(gyori): make this read from file or config option
+ String clz = c.getName();
+ if (!shouldCaptureClass(clz)) {
+ continue;
+ }
+
+ Set allFields = new HashSet();
+ try {
+ Field[] declaredFields = c.getDeclaredFields();
+ Field[] fields = c.getFields();
+ allFields.addAll(Arrays.asList(declaredFields));
+ allFields.addAll(Arrays.asList(fields));
+ } catch (NoClassDefFoundError e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ for (Field f : allFields) {
+ String fieldName = getFieldFQN(f);
+
+ // if a field is final and has a primitive type there's no point to capture it.
+ if (Modifier.isStatic(f.getModifiers())
+ && !(Modifier.isFinal(f.getModifiers()) && f.getType().isPrimitive())) {
+ try {
+ allFieldName.add(fieldName);
+ f.setAccessible(true);
+
+ Object instance;
+ try {
+ instance = f.get(null);
+ } catch (NoClassDefFoundError ncdfe) {
+ instance = null;
+ ncdfe.printStackTrace();
+ }
+ // If it is actually of ThreadLocal type, we want the contents inside
+ if (instance instanceof ThreadLocal) {
+ instance = ((ThreadLocal)instance).get();
+ }
+ dirty = false;
+ String obj4field = serializeObj(instance);
+ if (!dirty) {
+ nameToInstance.put(fieldName, instance);
+ writer = new PrintWriter(xmlDir + File.separator + fieldName + ".xml", "UTF-8");
+ writer.println(obj4field);
+ writer.close();
+ }
+ } catch (OutOfMemoryError ofme) {
+ ofme.printStackTrace();
+ continue;
+ } catch (NoClassDefFoundError ncdfe) {
+ ncdfe.printStackTrace();
+ continue;
+ } catch (IllegalAccessException iae) {
+ iae.printStackTrace();
+ continue;
+ }
+ }
+ }
+ }
+
+ String allFieldsFile = Configuration.config().getProperty("statecapture.allFieldsFile");
+ if (allFieldsFile.isEmpty()) {
+ System.out.println("WARNING: The allFieldsFile file are not provided, thus it can not create a writer to " +
+ "write all fields to this field.");
+ return;
+ }
+ writer = new PrintWriter(allFieldsFile, "UTF-8");
+ for (String ff : allFieldName) {
+ writer.println(ff);
+ }
+ writer.close();
+
+ writer = new PrintWriter(rootFile, "UTF-8");
+ for (String key : nameToInstance.keySet()) {
+ writer.println(key);
+ }
+ writer.close();
+ }
+
+ /**
+ * Adds the current loadable classes to the current class list in eagerLoadingFields.txt file in the phase 2tmp.
+ * @throws IOException
+ */
+ private void capture_class() throws IOException {
+ String eagerLoadFileName = Configuration.config().getProperty("statecapture.eagerloadfile", "");
+ if (eagerLoadFileName.isEmpty()) {
+ System.out.println("Need to provide name of file to write what classes are loaded");
+ return;
+ }
+ // get all loadable classes
+ Class[] loadedClasses = MainAgent.getInstrumentation().getAllLoadedClasses();
+ Set classes = new HashSet<>();
+ for (Class c : loadedClasses) {
+ if (shouldCaptureClass(c.getName())) {
+ classes.add(c.getName());
+ }
+ }
+
+ File eagerLoadFile = new File(eagerLoadFileName);
+ PrintWriter writer = new PrintWriter(eagerLoadFile, "UTF-8");
+ for (String str : classes) {
+ writer.println(str);
+ }
+ writer.close();
+ }
+
+ private boolean shouldCaptureClass(String clz) {
+ if ((clz.contains("java.") && !clz.startsWith("java.lang.System"))
+ || (clz.contains("javax.") && !clz.startsWith("javax.cache.Caching"))
+ || clz.contains("javafx.")
+ || clz.contains("jdk.")
+ || clz.contains("scala.")
+ || clz.contains("sun.")
+ || clz.contains("edu.illinois.cs")
+ || clz.contains("org.custommonkey.xmlunit")
+ || clz.contains("org.junit")
+ || clz.contains("statecapture.com.")) {
+ return false;
+ }
+ return true;
+ }
+
+ private void createXmlDir() {
+ File theDir = new File(xmlDir);
+ if (!theDir.exists()) {
+ theDir.mkdirs();
+ }
+ }
+
+ /**
+ * Takes in a string and removes problematic characters.
+ *
+ * @param in the input string to be filtered
+ * @return the input string with the unparsable characters removed
+ */
+ private static String sanitizeXmlChars(String in) {
+ in = in.replaceAll("", "&#");
+ StringBuilder out = new StringBuilder();
+ char current;
+
+ if (in == null || ("".equals(in)))
+ return "";
+ for (int i = 0; i < in.length(); i++) {
+ current = in.charAt(i);
+ if ((current == 0x9) ||
+ (current == 0xA) ||
+ (current == 0xD) ||
+ ((current >= 0x20) && (current <= 0xD7FF)) ||
+ ((current >= 0xE000) && (current <= 0xFFFD)) ||
+ ((current >= 0x10000) && (current <= 0x10FFFF)))
+ out.append(current);
+ }
+ return out.toString();
+ }
+
+ /**
+ * This is the method that calls XStream to serialize the object into a string.
+ *
+ * @param ob the object that need to be serialized
+ * @return string representing the serialized input object
+ */
+ private String serializeObj(Object ob) {
+ XStream xstream = getXStreamInstance();
+ String s = "";
+
+ try {
+ s = xstream.toXML(ob);
+ s = sanitizeXmlChars(s);
+ } catch (Exception e) {
+ // In case serialization fails, mark the StateCapture for this test
+ // as dirty, meaning it should be ignored
+ dirty = true;
+ // throw e;
+ }
+ return s;
+ }
+
+ private static class AlphabeticalFieldkeySorter implements FieldKeySorter {
+ @Override
+ public Map sort(Class type, Map keyedByFieldKey) {
+ final Map map = new TreeMap<>(new Comparator() {
+
+ @Override
+ public int compare(final FieldKey fieldKey1, final FieldKey fieldKey2) {
+ return fieldKey1.getFieldName().compareTo(fieldKey2.getFieldName());
+ }
+ });
+ map.putAll(keyedByFieldKey);
+ return map;
+ }
+ }
+
+ private XStream getXStreamInstance() {
+ XStream xstream = new XStream(JVM.newReflectionProvider(new FieldDictionary(
+ new AlphabeticalFieldkeySorter())),new DomDriver());
+ Mapper mapper = xstream.getMapper();
+ mapper = new CustomElementIgnoringMapper(mapper);
+ XStream newXStream = new XStream(JVM.newReflectionProvider(new FieldDictionary(
+ new AlphabeticalFieldkeySorter())),new DomDriver(),xstream.getClassLoader(),mapper);
+ // Set fields to be omitted during serialization
+
+ xstream = newXStream;
+ xstream.setMode(XStream.XPATH_ABSOLUTE_REFERENCES);
+ xstream.addPermission(AnyTypePermission.ANY);
+ xstream.omitField(Thread.class, "contextClassLoader");
+ xstream.omitField(java.security.ProtectionDomain.class, "classloader");
+ xstream.omitField(java.security.ProtectionDomain.class, "codesource");
+ xstream.omitField(ClassLoader.class, "defaultDomain");
+ xstream.omitField(ClassLoader.class, "classes");
+
+ xstream.omitField(java.lang.ref.SoftReference.class, "timestamp");
+ xstream.omitField(java.lang.ref.SoftReference.class, "referent");
+ xstream.omitField(java.lang.ref.Reference.class, "referent");
+
+ // Register all our custom converters that override the defaults, similar to how XStream registers its default converters
+ xstream.registerConverter(new MapConverter(xstream.getMapper()), PRIORITY_NORMAL + 1);
+ xstream.registerConverter(new TreeMapConverter(xstream.getMapper()), PRIORITY_NORMAL + 1);
+ xstream.registerConverter(new EnumMapConverter(xstream.getMapper()), PRIORITY_NORMAL + 1);
+
+ xstream.registerConverter(new ReflectionConverter(xstream.getMapper(), xstream.getReflectionProvider()), PRIORITY_VERY_LOW + 1);
+ xstream.registerConverter(new SerializableConverter(xstream.getMapper(), xstream.getReflectionProvider(), xstream.getClassLoaderReference()), PRIORITY_LOW + 1);
+ xstream.registerConverter(new LambdaConverter(xstream.getMapper(), xstream.getReflectionProvider(), xstream.getClassLoaderReference()), PRIORITY_NORMAL + 1);
+
+ if (JVM.isSwingAvailable()) {
+ xstream.registerConverter(new LookAndFeelConverter(xstream.getMapper(), xstream.getReflectionProvider()), PRIORITY_NORMAL + 1);
+ }
+
+ return xstream;
+ }
+
+ protected String getFieldFQN(Field f) {
+ String clz = f.getDeclaringClass().getName();
+ String fld = f.getName();
+ return clz + "." + fld;
+ }
+
+ private String readFile(String path) throws IOException {
+ File file = new File(path);
+ return FileUtils.readFileToString(file, "UTF-8");
+ }
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/agent/MainAgent.java b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/agent/MainAgent.java
new file mode 100644
index 0000000..e27edbd
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/statecapture/agent/MainAgent.java
@@ -0,0 +1,13 @@
+package edu.illinois.cs.statecapture.agent;
+
+import java.lang.instrument.Instrumentation;
+
+public class MainAgent {
+ private static Instrumentation inst;
+
+ public static Instrumentation getInstrumentation() { return inst; }
+
+ public static void premain(String agentArgs, Instrumentation inst) {
+ MainAgent.inst = inst;
+ }
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/testrunner/execution/TestListener.java b/testrunner-running/src/main/scala/edu/illinois/cs/testrunner/execution/TestListener.java
index 1e5b6b3..d2e94b2 100644
--- a/testrunner-running/src/main/scala/edu/illinois/cs/testrunner/execution/TestListener.java
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/testrunner/execution/TestListener.java
@@ -1,5 +1,7 @@
package edu.illinois.cs.testrunner.execution;
+import edu.illinois.cs.statecapture.StateCapture;
+import edu.illinois.cs.testrunner.configuration.Configuration;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
@@ -35,7 +37,16 @@ public void testIgnored(Description description) throws Exception {
@Override
public void testStarted(Description description) throws Exception {
- times.put(JUnitTestRunner.fullName(description), System.nanoTime());
+ String fullTestName = JUnitTestRunner.fullName(description);
+ times.put(fullTestName, System.nanoTime());
+
+ String phase = Configuration.config().getProperty("statecapture.phase", "");
+ if (Configuration.config().getProperty("statecapture.testname").equals(fullTestName)) {
+ if (phase.equals("capture_before")) {
+ StateCapture sc = new StateCapture(fullTestName);
+ sc.capture();
+ }
+ }
}
@Override
@@ -58,5 +69,23 @@ public void testFinished(Description description) throws Exception {
} else {
System.out.println("Test finished but did not start: " + fullTestName);
}
+
+ String phase = Configuration.config().getProperty("statecapture.phase", "");
+ if (Configuration.config().getProperty("statecapture.testname").equals(fullTestName)) {
+ if (phase.equals("capture_after")) {
+ StateCapture sc = new StateCapture(fullTestName);
+ sc.capture();
+ }
+ else if (phase.equals("load")) {
+ // load one field each time
+ String fieldName = Configuration.config().getProperty("statecapture.fieldName", "");
+
+ if (!fieldName.isEmpty()) {
+ StateCapture sc = new StateCapture(fullTestName);
+ String diffField = fieldName.split(",")[0];
+ sc.load(diffField);
+ }
+ }
+ }
}
}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/CustomElementIgnoringMapper.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/CustomElementIgnoringMapper.java
new file mode 100644
index 0000000..3d19662
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/CustomElementIgnoringMapper.java
@@ -0,0 +1,65 @@
+package edu.illinois.cs.xstream;
+
+import com.thoughtworks.xstream.core.util.FastField;
+import com.thoughtworks.xstream.mapper.ElementIgnoringMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CustomElementIgnoringMapper extends ElementIgnoringMapper {
+ public CustomElementIgnoringMapper(Mapper mapper) {
+ super(mapper);
+ }
+
+ private static Set ignoreList;
+
+ private static Set getIgnoreList() {
+ if (ignoreList == null) {
+ ignoreList = new HashSet<>();
+
+ ignoreList.add("java.security.CodeSource");
+ ignoreList.add("sun.nio.cs.UTF_8$Decoder");
+ ignoreList.add("java.nio.charset.CharsetEncoder");
+ ignoreList.add("java.nio.charset.CharsetDecoder");
+ ignoreList.add("sun.nio.cs.StreamEncoder");
+ ignoreList.add("java.util.zip.ZipCoder");
+ ignoreList.add("com.sun.crypto.provider.SunJCE");
+ ignoreList.add("java.lang.ClassLoader");
+ ignoreList.add("java.security.SecureClassLoader");
+ ignoreList.add("java.security.Provider");
+ ignoreList.add("javax.security.auth.Subject");
+ }
+ return ignoreList;
+ }
+
+ {
+ getIgnoreList();
+ }
+ @Override
+ public boolean shouldSerializeMember(final Class definedIn, final String fieldName) {
+ if (fieldsToOmit.contains(customKey(definedIn, fieldName))) {
+ return false;
+ } else if (definedIn == Object.class && isIgnoredElement(fieldName)) {
+ return false;
+ }
+ try {
+ // Hack to ignore field of type CodeSource
+ for(String ignoreItem : ignoreList) {
+ if (definedIn.getDeclaredField(fieldName).getType().equals(Class.forName(ignoreItem))) {
+ return false;
+ }
+ }
+ if (Class.forName("java.lang.ClassLoader").isAssignableFrom(definedIn.getDeclaredField(fieldName).getType())) {
+ return false;
+ }
+ } catch (Exception exception) {
+ // ignore
+ }
+ return super.shouldSerializeMember(definedIn, fieldName);
+ }
+
+ private FastField customKey(final Class> type, final String name) {
+ return new FastField(type, name);
+ }
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/EnumMapConverter.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/EnumMapConverter.java
new file mode 100644
index 0000000..2c53ba4
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/EnumMapConverter.java
@@ -0,0 +1,29 @@
+package edu.illinois.cs.xstream;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+public class EnumMapConverter extends com.thoughtworks.xstream.converters.enums.EnumMapConverter {
+
+ public EnumMapConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ @Override
+ protected void putCurrentEntryIntoMap(final HierarchicalStreamReader reader, final UnmarshallingContext context,
+ final Map map, final Map target) {
+ try {
+ MapConverterHelper.putCurrentEntryIntoMap(reader, context, map, target, this);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LambdaConverter.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LambdaConverter.java
new file mode 100644
index 0000000..c91127e
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LambdaConverter.java
@@ -0,0 +1,22 @@
+package edu.illinois.cs.xstream;
+
+import java.lang.reflect.Field;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+public class LambdaConverter extends com.thoughtworks.xstream.converters.reflection.LambdaConverter {
+
+ public LambdaConverter(final Mapper mapper, final ReflectionProvider reflectionProvider, final ClassLoaderReference classLoaderReference) {
+ super(mapper, reflectionProvider, classLoaderReference);
+ }
+
+ @Override
+ protected Object unmarshallField(final UnmarshallingContext context, final Object result, final Class type,
+ final Field field) {
+ return ReflectionConverterHelper.unmarshallField(context, result, type, field, mapper);
+ }
+}
\ No newline at end of file
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LookAndFeelConverter.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LookAndFeelConverter.java
new file mode 100644
index 0000000..bd4113b
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/LookAndFeelConverter.java
@@ -0,0 +1,21 @@
+package edu.illinois.cs.xstream;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Field;
+
+public class LookAndFeelConverter extends com.thoughtworks.xstream.converters.extended.LookAndFeelConverter {
+
+ public LookAndFeelConverter(final Mapper mapper, final ReflectionProvider reflectionProvider) {
+ super(mapper, reflectionProvider);
+ }
+
+ @Override
+ protected Object unmarshallField(final UnmarshallingContext context, final Object result, final Class type,
+ final Field field) {
+ return ReflectionConverterHelper.unmarshallField(context, result, type, field, mapper);
+ }
+}
\ No newline at end of file
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverter.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverter.java
new file mode 100644
index 0000000..b631e8e
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverter.java
@@ -0,0 +1,62 @@
+package edu.illinois.cs.xstream;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
+import java.util.Map;
+
+public class MapConverter extends com.thoughtworks.xstream.converters.collections.MapConverter {
+
+ public MapConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ // Logic mostly copied from writeItem
+ protected void writeItemWithName(String name, Object item, MarshallingContext context, HierarchicalStreamWriter writer) {
+ if (item == null) {
+ writeNullItem(context, writer);
+ } else {
+ String clazz = mapper().serializedClass(item.getClass());
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, item.getClass());
+ writer.addAttribute("class", clazz); // Map the class as an attribute to the node
+ writeBareItem(item, context, writer);
+ writer.endNode();
+ }
+ }
+
+ @Override
+ public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ Map map = (Map) source;
+ String entryName = mapper().serializedClass(Map.Entry.class);
+ for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, entryName, entry.getClass());
+
+ // Give consistent name to elements (their types will be encoded as attribute)
+ writeItemWithName("key", entry.getKey(), context, writer);
+ writeItemWithName("value", entry.getValue(), context, writer);
+
+ writer.endNode();
+ }
+ }
+
+ @Override
+ protected void putCurrentEntryIntoMap(final HierarchicalStreamReader reader, final UnmarshallingContext context,
+ final Map map, final Map target) {
+ try {
+ MapConverterHelper.putCurrentEntryIntoMap(reader, context, map, target, this);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverterHelper.java b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverterHelper.java
new file mode 100644
index 0000000..9da86ac
--- /dev/null
+++ b/testrunner-running/src/main/scala/edu/illinois/cs/xstream/MapConverterHelper.java
@@ -0,0 +1,63 @@
+package edu.illinois.cs.xstream;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.collections.MapConverter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+import edu.illinois.cs.xstream.UnmarshalChain;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Map;
+
+public class MapConverterHelper {
+
+ private static Method findMethod(String methodName, Class clz) {
+ if (clz == null) {
+ return null; // Should never happen, must have found this method
+ }
+ for (Method meth : clz.getDeclaredMethods()) {
+ if (meth.getName().equals(methodName)) {
+ return meth;
+ }
+ }
+ return findMethod(methodName, clz.getSuperclass());
+ }
+
+ protected static void putCurrentEntryIntoMap(final HierarchicalStreamReader reader, final UnmarshallingContext context,
+ final Map, ?> map, final Map, ?> target, MapConverter converter) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method readCompleteItem = findMethod("readCompleteItem", converter.getClass());
+ readCompleteItem.setAccessible(true);
+ final Object key = readCompleteItem.invoke(converter, reader, context, map);
+ UnmarshalChain.pushNode(UnmarshalChain.makeUnmarshalMapEntryNode(key)); // Try getting the key and putting it in the chain to map to the value
+ Object value = null;
+ String nodeName = reader.getNodeName();
+ try {
+ value = readCompleteItem.invoke(converter, reader, context, map);
+ } catch (ConversionException ce) {
+ // If there is a problem getting the value from this map entry, use the value in the current heap
+ try {
+ value = UnmarshalChain.getCurrObject();
+ } catch (UnmarshalChain.MapEntryMissingException e) { // If map entry is simply missing in current heap, then return, don't update map
+ return;
+ } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ throw new ConversionException(e);
+ }
+ } finally {
+ // Make sure level moves up to the proper location after this kind of exception
+ while (!reader.getNodeName().equals(nodeName)) {
+ reader.moveUp();
+ }
+ UnmarshalChain.popNode();
+ }
+ @SuppressWarnings("unchecked")
+ final Map