package org.codehaus.jettison.badgerfish; import java.awt.Color; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.net.URI; import java.util.HashSet; import java.util.Iterator; import java.util.Random; import java.util.Set; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import junit.framework.TestCase; /** * Test large scale object round trips. */ public class RoundTripTest extends TestCase { // ======================================================================== // JUnit Tests // ======================================================================== public void testEqualsHashCode() { DataSet ds1, ds2, ds3; ds1 = new DataSet(); ds2 = new DataSet(); ds3 = new DataSet(); // test null.. try { ds3.setDataPoints(null); fail("Suppose to throw null pointer exception.."); } catch (NullPointerException npe) { // success } // test null.. try { ds3.addDataPoint(null); fail("Suppose to throw null pointer exception.."); } catch (NullPointerException npe) { // success } // standard checks.. assertTrue(ds1.equals(ds1)); assertFalse(ds1.equals(null)); assertFalse(ds1.equals(new Object())); // setup the name of the data set.. ds1.setName("Some name"); ds2.setName("Some name"); // add random data points.. addRandomDataPoints(15, ds1, ds2); addRandomDataPoints(15, ds3); assertTrue(ds1.equals(ds2)); assertFalse(ds1.equals(ds3)); Set set = new HashSet(); set.add(ds1); set.add(ds2); set.add(ds3); assertTrue(set.size() == 2); } public void testSerializeDeserialize() throws Exception { DataSet ds = new DataSet(); ds.setName("Some Name!"); // addRandomDataPoints(50, ds); addRandomDataPoints(90, ds); assertRoundTrip(ds, new DataSet()); } // ======================================================================== // Helper Methods // ======================================================================== public File serialize(File f, XMLStreamObject t) throws Exception { XMLStreamWriter xwrt = new BadgerFishXMLOutputFactory().createXMLStreamWriter(new FileWriter(f)); xwrt.writeStartDocument(); t.toXMLStream(xwrt); xwrt.writeEndDocument(); xwrt.flush(); xwrt.close(); return f; } public XMLStreamObject deserialize(File f, XMLStreamObject t) throws Exception { XMLStreamReader xrdr = new BadgerFishXMLInputFactory().createXMLStreamReader(new FileReader(f)); t.fromXMLStream(xrdr); return t; } public void assertRoundTrip(XMLStreamObject instance, XMLStreamObject newInstance) throws Exception { File f = File.createTempFile("json", ".txt"); try { serialize(f, instance); XMLStreamObject t = deserialize(f, newInstance); assertEquals(instance, t); } finally { f.delete(); } } /** * Adds the number of random data points to the data set. * * @param size * @param ds */ void addRandomDataPoints(int size, DataSet ds) { addRandomDataPoints(size, ds, null); } /** * Adds the number of random data point to both data sets. */ void addRandomDataPoints(int size, DataSet d1, DataSet d2) { Random rnd = new Random(System.currentTimeMillis()); for (int i = 0; i < size; i++) { String label = "Label--" + i; Number value = new Double(rnd.nextDouble()); d1.addDataPoint(new DataPoint(label, value)); if (d2 != null) { d2.addDataPoint(new DataPoint(label, value)); } } } /** * Simple model class to represent a data point in the chart. */ public static class DataPoint implements XMLStreamObject { // ======================================================================= // Fields // ======================================================================= URI uri; Color color; String label; Number value; // ======================================================================= // Constructors // ======================================================================= /** * Default constructor. */ public DataPoint() { } /** * When only a value exists. */ public DataPoint(Number value) { this(null, null, value, null); } /** * When only a label and value exist for the data point. */ public DataPoint(String label, Number value) { this(null, label, value, null); } /** * When only a label, value, and color exist for the data point. */ public DataPoint(String label, Number value, Color color) { this(null, label, value, color); } /** * Simple constructor to set all properties. * * @param uri used for image maps. * @param label used to label each point. * @param value used as the range value for the point. * @param color used to change the color of the point. */ public DataPoint(URI uri, String label, Number value, Color color) { if (uri != null) { setURI(uri); } setColor(color); setLabel(label); setValue(value); } // ======================================================================= // Color Property // ======================================================================= /** * Color of the data point. This could be a pie slice or a bar in a * chart. */ public Color getColor() { return color; } /** * Value can be null to signify default value. Otherwise must be a * color. */ public void setColor(Color color) { this.color = color; } /** * Sets the color of the data point based on the string received. * * @throws NumberFormatException if the string does not conform to a * number color string. * @throws NullPointerException if the string is null. * @param color */ public void setColor(String color) { this.color = Color.decode(color); } // ======================================================================= // Label Property // ======================================================================= /** * Label for the slice or bar. */ public String getLabel() { return label; } /** * Sets the label. Blank input will result in a 'null' value. */ public void setLabel(String label) { this.label = (StringUtil.isBlank(label)) ? null : label; } // ======================================================================= // Value Property // ======================================================================= /** * Actual value of the bar or slice. */ public Number getValue() { return value; } /** * Sets the range value for this data point. * * @throws NullPointerException if the parameter provided is null. * @param value Any number preferably a Double. */ public void setValue(Number value) { if (value == null) { NullPointerException npe = new NullPointerException(); throw npe; } this.value = value; } /** * Set the value based on the string. A Double is used for the basis. * * @throws NumberFormatException if value is not a valid number. * @see java.lang.Double */ void setValue(String value) { // use double since it has a fairly large range this.value = Double.valueOf(value); } // ======================================================================= // URI Property // ======================================================================= /** * Links for image maps etc.. */ public URI getURI() { return this.uri; } /** * Sets the URI for the data point. * * @throws NullPointerException if value is null. */ public void setURI(URI value) { if (value == null) { NullPointerException npe = new NullPointerException(); throw npe; } this.uri = value; } /** * Sets a URI for a image map parameter. * * @throws IllegalArgumentException - If the given string violates RFC * 2396 or is blank. */ public void setURI(String value) { final String ERROR = "Invalid URI value!"; if (StringUtil.isBlank(value)) { throw new IllegalArgumentException(ERROR); } this.uri = java.net.URI.create(value); } // ======================================================================= // Object Overrides // ======================================================================= /** * Determine if the object is equal. */ public boolean equals(Object obj) { if (obj == null || !(obj instanceof DataPoint)) { return false; } if (this == obj) { return true; } DataPoint dp = (DataPoint)obj; EqualsBuilder bld = new EqualsBuilder(); bld.append(getColor(), dp.getColor()); bld.append(getLabel(), dp.getLabel()); bld.append(getValue(), dp.getValue()); bld.append(getURI(), dp.getURI()); return bld.isEquals(); } /** * Must implement hashCode if implementing equals. */ public int hashCode() { HashCodeBuilder bld = new HashCodeBuilder(); bld.append(getColor()); bld.append(getLabel()); bld.append(getValue()); bld.append(getURI()); return bld.toHashCode(); } // ======================================================================= // XML // ======================================================================= static final String ELEMENT = "DataPoint"; static final String VALUE = "value"; static final String COLOR = "color"; static final String LABEL = "label"; static final String URI = "uri"; /** * Value is represented by a Double on parsing. * * @see com.sun.idm.xml.XMLStreamObject#fromXMLStream(javax.xml.stream.XMLStreamReader) */ public void fromXMLStream(XMLStreamReader xrdr) throws XMLStreamException { // clear out all variables that may not be there.. uri = null; label = null; color = null; do { int tag = xrdr.getEventType(); if (XMLStreamReader.START_ELEMENT == tag && ELEMENT.equals(xrdr.getLocalName())) { int count = xrdr.getAttributeCount(); for (int x = 0; x < count; x++) { String name = xrdr.getAttributeLocalName(x); String value = xrdr.getAttributeValue(x); if (VALUE.equals(name)) { setValue(value); } else if (LABEL.equals(name)) { setLabel(value); } else if (COLOR.equals(name)) { setColor(value); } else if (URI.equals(name)) { setURI(value); } } // found it so we're done here.. break; } } while (xrdr.next() != XMLStreamReader.END_DOCUMENT); } /* * (non-Javadoc) * * @see com.sun.idm.xml.XMLStreamObject#toXMLStream(javax.xml.stream.XMLStreamWriter) */ public void toXMLStream(XMLStreamWriter xwrt) throws XMLStreamException { xwrt.writeStartElement(ELEMENT); if (getColor() != null) { String color = ColorUtil.colorToString(getColor()); xwrt.writeAttribute(COLOR, color); } if (StringUtil.isNotBlank(getLabel())) { xwrt.writeAttribute(LABEL, getLabel()); } if (getURI() != null) { xwrt.writeAttribute(URI, getURI().toString()); } xwrt.writeAttribute(VALUE, getValue().toString()); xwrt.writeEndElement(); } } /** * Simple interface that should the object supports streaming XML data * in/out. */ public interface XMLStreamObject { /** * Writes the object to the stream. This stream writer has support for * various formats including XML, FastInfoSet and JSON. * * @param wrt Writer for streaming the content through. * @throws XMLStreamException */ public void toXMLStream(XMLStreamWriter wrt) throws XMLStreamException; /** * Reads the object from the stream this is an example of pull parsing. * It can be used on various formats a well. Formats include XML, * FastInfo Set, and JSON. * * @param rdr Reader for streaming the content through. * @throws XMLStreamException if the content is cut off or malformed. */ public void fromXMLStream(XMLStreamReader rdr) throws XMLStreamException; } /** * Simple model to represent a chart's data set. */ public class DataSet implements XMLStreamObject { /** * Name of the data set or category depending on the translation. */ String name; /** * Use a 'Set' to avoid any duplicates. */ Set dataPoints; /** * Empty constructor for just using the set methods. */ public DataSet() { // set the defaults.. this(null, new HashSet()); } /** * Set the name and data points for the data set. */ public DataSet(String name, Set dataPoints) { setName(name); setDataPoints(dataPoints); } /** * Get the group of data points. */ public Set getDataPoints() { return dataPoints; } /** * Set the group of data points. Using a set to avoid duplicates. * * @throws NullPointerException if value is null. */ public void setDataPoints(Set value) { if (value == null) { throw new NullPointerException(); } this.dataPoints = value; } /** * Add a data point to the set of data points.. * * @throws NullPointerException if value is null. */ public void addDataPoint(DataPoint value) { if (value == null) { throw new NullPointerException(); } this.dataPoints.add(value); } /** * Get the name/category of the data set. */ public String getName() { return name; } /** * Set the name of the data set. */ public void setName(String value) { this.name = StringUtil.isBlank(value) ? null : value; } /** * Determine if two objects values are equal. */ public boolean equals(Object obj) { if (obj == null || !(obj instanceof DataSet)) { return false; } if (obj == this) { return true; } DataSet ds = (DataSet)obj; EqualsBuilder bld = new EqualsBuilder(); bld.append(getName(), ds.getName()); bld.append(getDataPoints(), ds.getDataPoints()); return bld.isEquals(); } /** * Must override hashCode if using equals. */ public int hashCode() { HashCodeBuilder bld = new HashCodeBuilder(); bld.append(getName()); bld.append(getDataPoints()); return bld.toHashCode(); } // ======================================================================= // XMLStreamObject Interface // ======================================================================= static final String ELEMENT = "dataset"; private static final String NAME = "name"; /* * (non-Javadoc) * * @see com.sun.idm.xml.XMLStreamObject#fromXMLStream(javax.xml.stream.XMLStreamReader) */ public void fromXMLStream(XMLStreamReader xrdr) throws XMLStreamException { // clear the current data.. name = null; dataPoints.clear(); // optimization.. boolean found = false; do { int tag = xrdr.getEventType(); if (XMLStreamReader.START_ELEMENT == tag) { String name = xrdr.getLocalName(); // find the start element if (!found && ELEMENT.equals(name)) { found = true; // get the various attributes.. int count = xrdr.getAttributeCount(); for (int x = 0; x < count; x++) { name = xrdr.getAttributeLocalName(x); String value = xrdr.getAttributeValue(x); if (NAME.equals(name)) { setName(value); } } } else if (found && DataPoint.ELEMENT.equals(name)) { DataPoint dp = new DataPoint(); dp.fromXMLStream(xrdr); dataPoints.add(dp); } } else if (found && XMLStreamReader.END_ELEMENT == tag && ELEMENT.equals(xrdr.getLocalName())) { // we're done so stop parsing.. break; } } while (xrdr.next() != XMLStreamReader.END_DOCUMENT); } /* * (non-Javadoc) * * @see com.sun.idm.xml.XMLStreamObject#toXMLStream(javax.xml.stream.XMLStreamWriter) */ public void toXMLStream(XMLStreamWriter xwrt) throws XMLStreamException { xwrt.writeStartElement(ELEMENT); // write data set attributes if (name != null) { xwrt.writeAttribute(NAME, getName()); } // write out each data point for (Iterator iter = getDataPoints().iterator(); iter.hasNext();) { DataPoint dp = (DataPoint)iter.next(); dp.toXMLStream(xwrt); } xwrt.writeEndElement(); } } public static class ColorUtil { /** * Create the hex string representing a color. */ public static String colorToString(Color color) { int rgb = color.getRGB(); String value = Integer.toString(rgb); return value; } /** * Create a color object from a hex string. */ public static Color stringToColor(String color) { Color ret = Color.WHITE; try { ret = Color.decode(color); } catch (NumberFormatException e) { // do nothing.. } return ret; } } /** * String utilities.. */ public static class StringUtil { /** * <pre> * StringUtil.isEmpty(null) = true * StringUtil.isEmpty("") = true * StringUtil.isEmpty(" ") = false * StringUtil.isEmpty("bob") = false * StringUtil.isEmpty(" bob ") = false * </pre> */ public static boolean isEmpty(String val) { return (val == null) ? true : "".equals(val) ? true : false; } public static boolean isNotEmpty(String val) { return !isEmpty(val); } /** * <pre> * StringUtil.isBlank(null) = true * StringUtil.isBlank("") = true * StringUtil.isBlank(" ") = true * StringUtil.isBlank("bob") = false * StringUtil.isBlank(" bob ") = false * </pre> */ public static boolean isBlank(String val) { return (val == null) ? true : isEmpty(val.trim()); } public static boolean isNotBlank(String val) { return !isBlank(val); } } public static class EqualsBuilder { private boolean _equals = true; public EqualsBuilder() { // do nothing for now. } public EqualsBuilder appendSuper(boolean value) { // quick exit if already false.. if (isEquals()) { setEquals(value); } return this; } public EqualsBuilder append(Object lhs, Object rhs) { // quick exit if already false.. if (isEquals() == false) { return this; } // identity check.. if (lhs == rhs) { return this; } // this will check if one is null and the other is not.. if (lhs == null || rhs == null) { setEquals(false); return this; } // check that they are the same class.. Class rhsClass = rhs.getClass(); Class lhsClass = lhs.getClass(); if (rhsClass != lhsClass) { setEquals(false); } else if (!lhsClass.isArray()) { // The simple case, not an array, just test the element setEquals(lhs.equals(rhs)); } else if (lhs instanceof Set) { // get the arrays for each an test them.. } // 'Switch' on type of array, to dispatch to the correct handler // This handles multi dimensional arrays of the same depth else if (lhs instanceof long[]) { append((long[])lhs, (long[])rhs); } else if (lhs instanceof int[]) { append((int[])lhs, (int[])rhs); } else if (lhs instanceof short[]) { append((short[])lhs, (short[])rhs); } else if (lhs instanceof char[]) { append((char[])lhs, (char[])rhs); } else if (lhs instanceof byte[]) { append((byte[])lhs, (byte[])rhs); } else if (lhs instanceof double[]) { append((double[])lhs, (double[])rhs); } else if (lhs instanceof float[]) { append((float[])lhs, (float[])rhs); } else if (lhs instanceof boolean[]) { append((boolean[])lhs, (boolean[])rhs); } else { // Not an array of primitives append((Object[])lhs, (Object[])rhs); } return this; } public EqualsBuilder appendIgnoreCase(String lhs, String rhs) { if (isEquals() && lhs.equals(rhs)) { if (rhs == null) { setEquals(false); } else if (lhs.equalsIgnoreCase(rhs)) { setEquals(false); } } return this; } public EqualsBuilder append(long lhs, long rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(int lhs, int rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(short lhs, short rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(char lhs, char rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(byte lhs, byte rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(double lhs, double rhs) { if (_equals == false) { return this; } return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); } public EqualsBuilder append(float lhs, float rhs) { if (_equals == false) { return this; } return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); } public EqualsBuilder append(boolean lhs, boolean rhs) { if (isEquals() && lhs != rhs) { setEquals(false); } return this; } public EqualsBuilder append(Object[] lhs, Object[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(long[] lhs, long[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(int[] lhs, int[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(short[] lhs, short[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(char[] lhs, char[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(byte[] lhs, byte[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(double[] lhs, double[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(float[] lhs, float[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public EqualsBuilder append(boolean[] lhs, boolean[] rhs) { if (isEquals() && lhs != rhs) { if (lhs == null || rhs == null) { setEquals(false); } else if (lhs.length != rhs.length) { setEquals(false); } else { for (int i = 0; i < lhs.length && isEquals(); ++i) { append(lhs[i], rhs[i]); } } } return this; } public boolean isEquals() { return _equals; } protected void setEquals(boolean value) { _equals = value; } } public static class HashCodeBuilder { private final int _constant; /** * Running total used to produce hash code. */ private int _total = 0; public HashCodeBuilder() { _constant = 37; _total = 17; } public HashCodeBuilder append(boolean value) { _total = _total * _constant + (value ? 0 : 1); return this; } public HashCodeBuilder append(boolean[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(byte value) { _total = _total * _constant + value; return this; } public HashCodeBuilder append(byte[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(char value) { _total = _total * _constant + value; return this; } public HashCodeBuilder append(char[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(double value) { return append(Double.doubleToLongBits(value)); } public HashCodeBuilder append(double[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(float value) { _total = _total * _constant + Float.floatToIntBits(value); return this; } public HashCodeBuilder append(float[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(int value) { _total = _total * _constant + value; return this; } public HashCodeBuilder append(int[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(long value) { _total = _total * _constant + ((int)(value ^ (value >> 32))); return this; } public HashCodeBuilder append(long[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(Object object) { if (object == null) { _total = _total * _constant; } else { if (!object.getClass().isArray()) { // the simple case, not an array, just the element _total = _total * _constant + object.hashCode(); } else { // 'Switch' on type of array, to dispatch to the correct // handler // This handles multi dimensional arrays if (object instanceof long[]) { append((long[])object); } else if (object instanceof int[]) { append((int[])object); } else if (object instanceof short[]) { append((short[])object); } else if (object instanceof char[]) { append((char[])object); } else if (object instanceof byte[]) { append((byte[])object); } else if (object instanceof double[]) { append((double[])object); } else if (object instanceof float[]) { append((float[])object); } else if (object instanceof boolean[]) { append((boolean[])object); } else { // Not an array of primitives append((Object[])object); } } } return this; } public HashCodeBuilder append(Object[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder append(short value) { _total = _total * _constant + value; return this; } public HashCodeBuilder append(short[] array) { if (array == null) { _total = _total * _constant; } else { for (int i = 0; i < array.length; i++) { append(array[i]); } } return this; } public HashCodeBuilder appendSuper(int value) { _total = _total * _constant + value; return this; } public int toHashCode() { return _total; } } }