public interface Transloader {
	/**
	 * The default implementation of <code>Transloader</code> which will produce {@link ObjectWrapper}s configured
	 * with {@link CloningStrategy#MAXIMAL} for <code>Object</code>s and {@link ClassWrapper}s for
	 * <code>Class</code>es.
	 */
	Transloader DEFAULT = new DefaultTransloader(CloningStrategy.MAXIMAL);

	/**
	 * Wraps the given object in an <code>ObjectWrapper</code>.
	 * 
	 * @param objectToWrap the object to wrap
	 * @return the wrapper around the given object
	 */
	ObjectWrapper wrap(Object objectToWrap);

	/**
	 * Wraps the given <code>Class</code> in a <code>ClassWrapper</code>.
	 * 
	 * @param classToWrap the <code>Class</code> to wrap
	 * @return the wrapper around the given <code>Class</code>
	 */
	ClassWrapper wrap(Class classToWrap);
}
 public abstract class BaseTestCase extends TestCase {

	protected void dump(Object object) {
		// System.out.println("[" + getName() + "] " + object.toString());
	}

	protected void assertEqualExceptForClassLoader(String originalString, Object clone) {
		String originalClassLoaderString = this.getClass().getClassLoader().toString();
		String cloneClassLoaderString = IndependentClassLoader.getInstance().toString();
		assertNotEquals(originalClassLoaderString, cloneClassLoaderString);
		String expectedCloneString =
				StringUtils.replace(originalString, originalClassLoaderString, cloneClassLoaderString);
		String cloneString = clone.toString();
		dump(originalString);
		dump(cloneString);
		assertEquals(expectedCloneString, cloneString);
	}

	protected static void assertNotEquals(Object notExpected, Object actual) {
		if (notExpected == null)
			assertNotNull(actual);
		else
			assertFalse("Expected: not '" + notExpected + "'. Actual: '" + actual + "' which is equal.",
					notExpected.equals(actual));
	}

	protected static final void assertMatches(Throwable expected, Throwable actual) {
		assertTrue(getComparisonFailedMessage("type", expected.getClass().getName(), actual),
				expected.getClass().isAssignableFrom(actual.getClass()));
		assertTrue(getComparisonFailedMessage("message", expected.getMessage(), actual),
				expected.getMessage() == null || actual.getMessage().startsWith(expected.getMessage()));
		Throwable expectedCause = ExceptionUtils.getCause(expected);
		if (expectedCause != null && expectedCause != expected) {
			assertMatches(expectedCause, ExceptionUtils.getCause(actual));
		}
	}

	private static String getComparisonFailedMessage(String attributeName, String expectedValue, Throwable actual) {
		return "Expected " + attributeName + ": '" + expectedValue + "'. Actual: '" + ExceptionUtils.getFullStackTrace(actual) + "'.";
	}

	protected static final void assertThrows(Thrower thrower, Throwable expected) {
		try {
			thrower.executeUntilThrow();
		} catch (Throwable actual) {
			assertMatches(expected, actual);
			return;
		}
		fail("Expected: '" + expected + "'. Actual: nothing thrown.");
	}

	protected static interface Thrower {
		void executeUntilThrow() throws Throwable;
	}
} public final class Triangulate {
	private static final Random RANDOM = new Random(System.currentTimeMillis());
	private static final byte[] ONE_BYTE_BUFFER = new byte[1];
	private static final Method[] MY_METHODS = Triangulate.class.getDeclaredMethods();

	private Triangulate() {
	}

	public static String anyString() {
		return anyDouble() + "";
	}

	public static String anyAlphaNumbericString() {
		return Long.toHexString(anyLong());
	}

	public static boolean eitherBoolean() {
		return RANDOM.nextBoolean();
	}

	public static byte anyByte() {
		RANDOM.nextBytes(ONE_BYTE_BUFFER);
		return ONE_BYTE_BUFFER[0];
	}

	public static char anyChar() {
		return (char) anyByte();
	}

	public static short anyShort() {
		return (short) anyByte();
	}

	public static int anyInt() {
		return RANDOM.nextInt();
	}

	public static int anyIntFromZeroTo(int upperBound) {
		return RANDOM.nextInt(upperBound);
	}

	public static Integer anyInteger() {
		return new Integer(anyInt());
	}

	public static long anyLong() {
		return RANDOM.nextLong();
	}

	public static float anyFloat() {
		return RANDOM.nextFloat();
	}

	public static double anyDouble() {
		return RANDOM.nextDouble();
	}

	public static Class anyClass() {
		return anyMethod().getReturnType();
	}

	public static Method anyMethod() {
		return MY_METHODS[anyIntFromZeroTo(MY_METHODS.length - 1)];
	}

	public static Object anyInstanceOf(Class type) {
		try {
			if (type == null || type == void.class) return null;
			if (type.isArray()) return anyArrayOf(type.getComponentType());
			Object triangulatedInstance = tryToTriangulateFromThisClass(type);
			if (triangulatedInstance != null) return triangulatedInstance;
			if (type.isInterface())
				return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {type},
						new TriangulatingInvocationHandler());
			if (Modifier.isAbstract(type.getModifiers()))
				return Enhancer.create(type, new TriangulatingInvocationHandler());
			return ObjenesisHelper.newInstance(type);
		} catch (Exception e) {
			throw new NestableRuntimeException(e);
		}
	}

	private static Object anyArrayOf(Class componentType) throws Exception {
		int length = anyIntFromZeroTo(3);
		Object array = Array.newInstance(componentType, length);
		for (int i = 0; i < length; i++) {
			Array.set(array, i, anyInstanceOf(componentType));
		}
		return array;
	}

	private static Object tryToTriangulateFromThisClass(Class type) throws Exception {
		for (int i = 0; i < MY_METHODS.length; i++) {
			Method method = MY_METHODS[i];
			Class returnType = method.getReturnType();
			boolean hasNoParameters = method.getParameterTypes() == null || method.getParameterTypes().length == 0;
			if (returnType == type && hasNoParameters) {
				return method.invoke(null, new Object[0]);
			}
		}
		return null;
	}

	private static class TriangulatingInvocationHandler implements InvocationHandler,
			net.sf.cglib.proxy.InvocationHandler {
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if (method.getReturnType() == Void.class) return null;
			if (method.getName().equals("equals") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == Object.class)
				return new Boolean(proxy == args[0]);
			if (method.getName().equals("hashCode") && method.getParameterTypes().length == 0)
				return new Integer(System.identityHashCode(proxy));
			if (method.getName().equals("toString") && method.getParameterTypes().length == 0)
				return TriangulatingInvocationHandler.class.getName() + '#' + System.identityHashCode(proxy);
			return anyInstanceOf(method.getReturnType());
		}
	}
}
 public class HiearchyWithFieldsBottom extends HiearchyWithFieldsMiddle {
	private boolean bottomBooleanField;

	public HiearchyWithFieldsBottom(NonCommonJavaObject superClassFieldValue, int intFieldValue,
			String fieldValueForWithStringField, boolean booleanFieldValue) {
		super(superClassFieldValue, intFieldValue, fieldValueForWithStringField);
		bottomBooleanField = booleanFieldValue;
	}
}
 public final class IndependentClassLoader extends URLClassLoader {
	private static final ClassLoader INSTANCE = new IndependentClassLoader();

	public static ClassLoader getInstance() {
		return INSTANCE;
	}

	private IndependentClassLoader() {
		super(getAppClassLoaderUrls(), null);
	}

	private static URL[] getAppClassLoaderUrls() {
		URLClassLoader appClassLoader = (URLClassLoader) IndependentClassLoader.class.getClassLoader();
		return appClassLoader.getURLs();
	}
}
 public class NonCommonJavaObject implements NonCommonJavaType {
	public String toString() {
		return ClassLoaderAndFieldsStringBuilder.toString(this);
	}
}
 public interface NonCommonJavaType {
}
 public class SelfAndChildReferencingParent extends WithStringField {
	private Object self = this;
	private Object child;

	public SelfAndChildReferencingParent(String stringFieldValue) {
		super(stringFieldValue);
	}

	void setChild(Object child) {
		this.child = child;
	}
}
 public class SelfAndParentReferencingChild extends WithStringField {
	private Object self = this;
	private SelfAndChildReferencingParent parent;

	public SelfAndParentReferencingChild(String stringFieldValue, SelfAndChildReferencingParent parent) {
		super(stringFieldValue);
		this.parent = parent;
		parent.setChild(this);
	}
}
 public class SerializableWithAnonymousClassFields extends SerializableWithFinalFields {
	private java.io.Serializable anonymousClassField = new Serializable() {
		private NonCommonJavaType instanceInitializerField;
		{
			instanceInitializerField = new SerializableWithFinalFields(Triangulate.anyInteger());
		}
		private int enclosingInstanceReliantField = SerializableWithAnonymousClassFields.this.hashCode();
	};

	private java.io.Serializable anonymousClassFromMethodField = getAnonymousClassInstance(Triangulate.anyString());

	public SerializableWithAnonymousClassFields(Integer integer) {
		super(integer);
	}

	private Serializable getAnonymousClassInstance(final String string) {
		return new Serializable() {
			private String setFromExternalVariableField = string;
			private String normalStringField = Triangulate.anyString();
		};
	}
}
 public class WithArrayFields extends NonCommonJavaObject {
	private int[] ints = {Triangulate.anyInt(), Triangulate.anyInt()};
	private Object[] objects = {Triangulate.anyString(), new WithPrimitiveFields(), Triangulate.anyString()};
	private boolean[] noBooleans = {};
	private NonCommonJavaObject[] nonCommonJavaObjects = {new WithStringField(Triangulate.anyString())};
	private NonCommonJavaType[] nonCommonJavaTypes = {new WithPrimitiveFields()};
}
 public class WithListFields extends NonCommonJavaObject {
	List listFromArray =
			Arrays.asList(new NonCommonJavaType[] {new WithPrimitiveFields(),
					new WithStringField(Triangulate.anyString())});
	private List list = new ArrayList();
	{
		list.add(Triangulate.anyString());
		list.add(Triangulate.anyInteger());
		list.add(Triangulate.anyString());
	}
	private List empty = Collections.EMPTY_LIST;
	private List unmodifiable = Collections.unmodifiableList(list);
	private List synchronizedList = Collections.synchronizedList(list);
	private List singelton = Collections.singletonList(Triangulate.anyInteger());
}
 public class WithNonCommonJavaFields extends NonCommonJavaObject {
	private NonCommonJavaObject object = new WithStringField(Triangulate.anyString());
	private NonCommonJavaType type;

	public WithNonCommonJavaFields(NonCommonJavaType fieldValue) {
		type = fieldValue;
	}
}
 public class WithPrimitiveFields extends NonCommonJavaObject {
	private boolean booleanField;
	private byte byteField;
	private char charField;
	private short shortField;
	private int intField;
	private long longField;
	private float floatField;
	private double doubleField;

	public WithPrimitiveFields() {
		booleanField = Triangulate.eitherBoolean();
		byteField = Triangulate.anyByte();
		charField = Triangulate.anyChar();
		shortField = Triangulate.anyShort();
		intField = Triangulate.anyInt();
		longField = Triangulate.anyLong();
		floatField = Triangulate.anyFloat();
		doubleField = Triangulate.anyDouble();
	}
}
 public class WithStringField extends NonCommonJavaObject {
	private String field;
	
	public WithStringField(String fieldValue) {
		field = fieldValue;
	}
}

import com.googlecode.transloader.Transloader;
import com.googlecode.transloader.test.BaseTestCase;
import com.googlecode.transloader.test.Triangulate;
import com.googlecode.transloader.test.fixture.HiearchyWithFieldsBottom;
import com.googlecode.transloader.test.fixture.IndependentClassLoader;
import com.googlecode.transloader.test.fixture.NonCommonJavaObject;
import com.googlecode.transloader.test.fixture.NonCommonJavaType;
import com.googlecode.transloader.test.fixture.SelfAndChildReferencingParent;
import com.googlecode.transloader.test.fixture.SelfAndParentReferencingChild;
import com.googlecode.transloader.test.fixture.SerializableWithAnonymousClassFields;
import com.googlecode.transloader.test.fixture.WithArrayFields;
import com.googlecode.transloader.test.fixture.WithListFields;
import com.googlecode.transloader.test.fixture.WithNonCommonJavaFields;
import com.googlecode.transloader.test.fixture.WithPrimitiveFields;
import com.googlecode.transloader.test.fixture.WithStringField;

package com.googlecode.transloader.test.function;


public abstract class CloningTestCase extends BaseTestCase {

	protected Object assertDeeplyClonedToOtherClassLoader(NonCommonJavaType original) throws Exception {
		String originalString = original.toString();
		Object clone = getTransloader().wrap(original).cloneWith(IndependentClassLoader.getInstance());
		assertNotSame(original, clone);
		assertEqualExceptForClassLoader(originalString, clone);
		return clone;
	}


	protected abstract Transloader getTransloader();
