package org.mvel2.marshalling;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import junit.framework.TestCase;
import org.mvel2.MVEL;
import org.mvel2.integration.impl.MapVariableResolverFactory;
import org.mvel2.util.StringAppender;
// import com.thoughtworks.xstream.XStream;
/**
* Generates templates to marshaller classes.
* TODO
* -Currently uses BeanInfo, needs to handle all MVEL getter/setter types
* -Use objenesis or equivalent to always be able to handle no-arg constructor
* -handle special immutable classes like BigInteger, BigDecimal(which are already done, but are there others?)
* -As well as allowing users to register custom templates, maybe also custom built in marshallers (i.e. how map, collection, array currently works)
* -Support optional generated imports, to reduce verbosity
* -some issue related to values allowed in a Map
*/
public class MarshallingTest extends TestCase {
public static enum Type {
PRIMITIVE, CHAR, STRING, DATE, CALENDAR, BIG_INTEGER, BIG_DECIMAL, ARRAY, MAP, COLLECTION, OBJECT;
}
public static class ObjectConverter {
private Class type;
private ObjectConverterEntry[] fields;
public ObjectConverter(Class type,
ObjectConverterEntry[] fields) {
this.type = type;
this.fields = fields;
}
public Class getType() {
return this.type;
}
public ObjectConverterEntry[] getFields() {
return fields;
}
}
public static class ObjectConverterEntry {
private String name;
private Type type;
private Method method;
public ObjectConverterEntry(String name,
Method method,
Type type) {
this.name = name;
this.type = type;
this.method = method;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
public Method getMethod() {
return this.method;
}
}
public static class MarshallerContext {
private Marshaller marshaller;
private StringAppender appender = new StringAppender();
public MarshallerContext(Marshaller marshaller) {
this.marshaller = marshaller;
this.appender = new StringAppender();
}
public void marshall(Object object) {
marshaller.marshall(object,
this);
}
public StringAppender getAppender() {
return appender;
}
}
public static interface CustomMarshaller {
public void marshall(Object object,
MarshallerContext ctx);
}
public static class EpocDateMarshaller
implements
CustomMarshaller {
public void marshall(Object object,
MarshallerContext ctx) {
ctx.getAppender().append("new java.util.Date(" + ((Date) object).getTime() + ")");
}
}
public static class EpocDefaultCalendarMarshaller
implements
CustomMarshaller {
private CustomMarshaller dateMarshaller;
public EpocDefaultCalendarMarshaller() {
this(new EpocDateMarshaller());
}
public EpocDefaultCalendarMarshaller(CustomMarshaller dateMarshaller) {
this.dateMarshaller = dateMarshaller;
}
public void marshall(Object object,
MarshallerContext ctx) {
ctx.getAppender().append("with ( java.util.Calendar.getInstance() ) { time = ");
this.dateMarshaller.marshall(((Calendar) object).getTime(),
ctx);
ctx.getAppender().append("} ");
}
}
public static class Marshaller {
private Map<Class, ObjectConverter> converters;
private CustomMarshaller dateMarshaller;
private CustomMarshaller calendarMarshaller;
public Marshaller() {
this(new HashMap<Type, CustomMarshaller>());
}
public Marshaller(Map<Type, CustomMarshaller> custom) {
this.converters = new HashMap<Class, ObjectConverter>();
this.dateMarshaller = custom.get(Type.DATE);
if (this.dateMarshaller == null) {
this.dateMarshaller = new EpocDateMarshaller();
}
this.calendarMarshaller = custom.get(Type.CALENDAR);
if (this.calendarMarshaller == null) {
this.calendarMarshaller = new EpocDefaultCalendarMarshaller();
}
}
public void marshall(Object object,
MarshallerContext ctx) {
marshall(object,
getType(object.getClass()),
ctx);
}
public void marshall(Object object,
Type type,
MarshallerContext ctx) {
if (object == null) {
ctx.getAppender().append("null");
return;
}
if (type != Type.OBJECT) {
marshallValue(object,
type,
ctx);
}
else {
Class cls = object.getClass();
ObjectConverter converter = this.converters.get(cls);
if (converter == null) {
converter = generateConverter(cls);
this.converters.put(cls,
converter);
}
try {
int i = 0;
ctx.getAppender().append("new " + cls.getName() + "().{ ");
for (ObjectConverterEntry entry : converter.getFields()) {
if (i++ != 0) {
ctx.getAppender().append(", ");
}
ctx.getAppender().append(entry.getName());
ctx.getAppender().append(" = ");
marshallValue(entry.getMethod().invoke(object,
null),
entry.getType(),
ctx);
}
}
catch (Exception e) {
throw new IllegalStateException("Unable to marshall object " + object,
e);
}
ctx.getAppender().append(" }");
}
}
private void marshallValue(Object object,
Type type,
MarshallerContext ctx) {
if (object == null) {
ctx.getAppender().append("null");
return;
}
switch (type) {
case PRIMITIVE: {
ctx.getAppender().append(object);
break;
}
case CHAR: {
ctx.getAppender().append("'");
ctx.getAppender().append(object);
ctx.getAppender().append("'");
break;
}
case STRING: {
ctx.getAppender().append("'");
ctx.getAppender().append(object);
ctx.getAppender().append("'");
break;
}
case DATE: {
dateMarshaller.marshall(object,
ctx);
break;
}
case CALENDAR: {
calendarMarshaller.marshall(object,
ctx);
break;
}
case BIG_INTEGER: {
ctx.getAppender().append(object);
break;
}
case BIG_DECIMAL: {
ctx.getAppender().append(object);
break;
}
case ARRAY: {
marshallArray(object,
ctx);
break;
}
case MAP: {
marshallMap((Map) object,
ctx);
break;
}
case COLLECTION: {
marshallCollection((Collection) object,
ctx);
break;
}
case OBJECT: {
marshall(object,
type,
ctx);
break;
}
}
}
private ObjectConverter generateConverter(Class cls) {
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(cls);
}
catch (IntrospectionException e) {
throw new RuntimeException(e);
}
PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
List<ObjectConverterEntry> list = new ArrayList<ObjectConverterEntry>();
for (int i = 0, length = props.length; i < length; i++) {
PropertyDescriptor prop = props[i];
if ("class".equals(prop.getName())) {
continue;
}
list.add(new ObjectConverterEntry(prop.getName(),
prop.getReadMethod(),
getType(prop.getPropertyType())));
}
return new ObjectConverter(cls,
list.toArray(new ObjectConverterEntry[list.size()]));
}
private Type getType(Class cls) {
Type type = null;
if (cls.isPrimitive() || Number.class.isAssignableFrom(cls)) {
type = Type.PRIMITIVE;
}
else if (Character.class.isAssignableFrom(cls)) {
type = Type.CHAR;
}
else if (String.class.isAssignableFrom(cls)) {
type = Type.STRING;
}
else if (Date.class.isAssignableFrom(cls)) {
type = Type.DATE;
}
else if (Calendar.class.isAssignableFrom(cls)) {
type = Type.CALENDAR;
}
else if (BigInteger.class.isAssignableFrom(cls)) {
type = Type.BIG_INTEGER;
}
else if (BigDecimal.class.isAssignableFrom(cls)) {
type = Type.BIG_DECIMAL;
}
else if (cls.isArray()) {
type = Type.ARRAY;
}
else if (Map.class.isAssignableFrom(cls)) {
type = Type.MAP;
}
else if (Collection.class.isAssignableFrom(cls)) {
type = Type.COLLECTION;
}
else {
type = Type.OBJECT;
}
return type;
}
private void marshallMap(Map map,
MarshallerContext ctx) {
ctx.getAppender().append(" [ ");
int i = 0;
for (Iterator<Entry> it = map.entrySet().iterator(); it.hasNext(); i++) {
if (i != 0) {
ctx.getAppender().append(", ");
}
Entry entry = it.next();
marshall(entry.getKey(),
ctx);
ctx.getAppender().append(':');
marshall(entry.getValue(),
ctx);
}
ctx.getAppender().append(" ] ");
}
private void marshallCollection(Collection collection,
MarshallerContext ctx) {
ctx.getAppender().append(" [ ");
int i = 0;
for (Iterator it = collection.iterator(); it.hasNext(); i++) {
if (i != 0) {
ctx.getAppender().append(", ");
}
marshall(it.next(),
ctx);
}
ctx.getAppender().append(" ] ");
}
private void marshallArray(Object array,
MarshallerContext ctx) {
ctx.getAppender().append(" { ");
for (int i = 0, length = Array.getLength(array); i < length; i++) {
if (i != 0) {
ctx.getAppender().append(", ");
}
marshall(Array.get(array,
i),
ctx);
}
ctx.getAppender().append(" } ");
}
public String marshallToString(Object object) {
MarshallerContext ctx = new MarshallerContext(this);
marshall(object,
ctx);
return ctx.getAppender().toString();
}
}
private Object getData() {
Pet pet = new Pet();
pet.setName("rover");
pet.setAge(7);
List list = new ArrayList();
list.add("a");
list.add(12);
list.add(new SomeNumers(10.02f,
22.02,
5,
100l,
new BigDecimal(23.0234d,
MathContext.DECIMAL128),
new BigInteger("1001")));
list.add(new Date());
//list.add( 'b' ); // generates ok but breaks round trip equals
list.add(new Cheese("cheddar",
6));
pet.setList(list);
pet.setArray(new int[]{1, 2, 3});
Map map = new HashMap();
//map.put( new Date(), new Cheese( "stilton", 11) ); // TODO why doesn't this work
map.put("key1",
13);
map.put("key3",
"value3");
map.put("key2",
15);
map.put("key4",
new Cheese("stilton",
11));
Calendar cal = Calendar.getInstance();
// cal.setTime( new Date() );
// map.put( "key5",
// cal ); // TODO why doesn't this work.
//map.put( "key4", new String[] { "a", "b" } ); // TODO why doesn't this work
Person person = new Person();
person.setName("mark");
person.setAge(33);
person.setPet(pet);
person.setSomeDate(new Date());
person.setMap(map);
cal = Calendar.getInstance();
cal.setTime(new Date());
person.setCal(cal);
return person;
}
private static final int COUNT = 0;
// public void testXStream() {
// XStream xstream = new XStream();
//
// // run once to allow for caching
// Object data1 = getData();
// String str = xstream.toXML( data1 );
// System.out.println( str );
// Object data2 = xstream.fromXML( str );
// assertNotSame( data1,
// data2 );
// assertEquals( data1,
// data2 );
//
// long start = System.currentTimeMillis();
// for ( int i = 0; i < COUNT; i++ ) {
// data1 = getData();
// str = xstream.toXML( data1 );
// data2 = xstream.fromXML( str );
// assertNotSame( data1,
// data2 );
// assertEquals( data1,
// data2 );
// }
// long end = System.currentTimeMillis();
//
// System.out.println( "xstream : " + (end - start) );
// }
public void testMVEL() throws Exception {
Marshaller marshaller = new Marshaller();
// run once to generate templates
Object data1 = getData();
String str = marshaller.marshallToString(data1);
System.out.println(str);
Object data2 = MVEL.eval(str);
assertNotSame(data1,
data2);
assertEquals(data1,
data2);
long start = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
data1 = getData();
str = marshaller.marshallToString(data1);
data2 = MVEL.eval(str);
assertNotSame(data1,
data2);
assertEquals(data1,
data2);
}
long end = System.currentTimeMillis();
System.out.println("mvel : " + (end - start));
}
public static class SomeNumers {
private float aFloat;
private double aDouble;
private int aInt;
private long aLong;
private BigDecimal aBigDecimal;
private BigInteger aBigInteger;
public SomeNumers() {
}
public SomeNumers(float float1,
double double1,
int int1,
long long1,
BigDecimal bigDecimal,
BigInteger bigInteger) {
super();
aFloat = float1;
aDouble = double1;
aInt = int1;
aLong = long1;
aBigDecimal = bigDecimal;
aBigInteger = bigInteger;
}
public float getAFloat() {
return aFloat;
}
public void setAFloat(float float1) {
aFloat = float1;
}
public double getADouble() {
return aDouble;
}
public void setADouble(double double1) {
aDouble = double1;
}
public int getAInt() {
return aInt;
}
public void setAInt(int int1) {
aInt = int1;
}
public long getALong() {
return aLong;
}
public void setALong(long long1) {
aLong = long1;
}
public BigDecimal getABigDecimal() {
return aBigDecimal;
}
public void setABigDecimal(BigDecimal bigDecimal) {
aBigDecimal = bigDecimal;
}
public BigInteger getABigInteger() {
return aBigInteger;
}
public void setABigInteger(BigInteger bigInteger) {
aBigInteger = bigInteger;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((aBigDecimal == null) ? 0 : aBigDecimal.hashCode());
result = prime * result + ((aBigInteger == null) ? 0 : aBigInteger.hashCode());
long temp;
temp = Double.doubleToLongBits(aDouble);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + Float.floatToIntBits(aFloat);
result = prime * result + aInt;
result = prime * result + (int) (aLong ^ (aLong >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
SomeNumers other = (SomeNumers) obj;
if (aBigDecimal == null) {
if (other.aBigDecimal != null) return false;
}
else if (!aBigDecimal.equals(other.aBigDecimal)) return false;
if (aBigInteger == null) {
if (other.aBigInteger != null) return false;
}
else if (!aBigInteger.equals(other.aBigInteger)) return false;
if (Double.doubleToLongBits(aDouble) != Double.doubleToLongBits(other.aDouble)) return false;
if (Float.floatToIntBits(aFloat) != Float.floatToIntBits(other.aFloat)) return false;
if (aInt != other.aInt) return false;
if (aLong != other.aLong) return false;
return true;
}
}
public static class Person {
private String name;
private int age;
private Date someDate;
private Pet pet;
private Object nullTest;
private Map map;
private Calendar cal;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Pet getPet() {
return this.pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
public Date getSomeDate() {
return someDate;
}
public void setSomeDate(Date someDate) {
this.someDate = someDate;
}
public Object getNullTest() {
return nullTest;
}
public void setNullTest(Object nullTest) {
this.nullTest = nullTest;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Calendar getCal() {
return cal;
}
public void setCal(Calendar cal) {
this.cal = cal;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((cal == null) ? 0 : cal.hashCode());
result = prime * result + ((map == null) ? 0 : map.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((nullTest == null) ? 0 : nullTest.hashCode());
result = prime * result + ((pet == null) ? 0 : pet.hashCode());
result = prime * result + ((someDate == null) ? 0 : someDate.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Person other = (Person) obj;
if (age != other.age) return false;
if (cal == null) {
if (other.cal != null) return false;
}
else if (!cal.equals(other.cal)) return false;
if (map == null) {
if (other.map != null) return false;
}
else if (!map.equals(other.map)) return false;
if (name == null) {
if (other.name != null) return false;
}
else if (!name.equals(other.name)) return false;
if (nullTest == null) {
if (other.nullTest != null) return false;
}
else if (!nullTest.equals(other.nullTest)) return false;
if (pet == null) {
if (other.pet != null) return false;
}
else if (!pet.equals(other.pet)) return false;
if (someDate == null) {
if (other.someDate != null) return false;
}
else if (!someDate.equals(other.someDate)) return false;
return true;
}
}
public static class Pet {
private String name;
private Integer age;
private List list;
private int[] array;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer agr) {
this.age = agr;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + Arrays.hashCode(array);
result = prime * result + ((list == null) ? 0 : list.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Pet other = (Pet) obj;
if (age == null) {
if (other.age != null) return false;
}
else if (!age.equals(other.age)) return false;
if (!Arrays.equals(array,
other.array)) return false;
if (list == null) {
if (other.list != null) return false;
}
else if (!list.equals(other.list)) return false;
if (name == null) {
if (other.name != null) return false;
}
else if (!name.equals(other.name)) return false;
return true;
}
}
public static class Cheese {
private String type;
private int age;
private boolean edible;
public Cheese() {
}
public Cheese(String type,
int age) {
this.type = type;
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isEdible() {
return edible;
}
public void setEdible(boolean edible) {
this.edible = edible;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + (edible ? 1231 : 1237);
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Cheese other = (Cheese) obj;
if (age != other.age) return false;
if (edible != other.edible) return false;
if (type == null) {
if (other.type != null) return false;
}
else if (!type.equals(other.type)) return false;
return true;
}
}
}