You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by jd...@apache.org on 2009/09/26 14:07:24 UTC
svn commit: r819116 - in /wicket/trunk/wicket/src:
main/java/org/apache/wicket/PageParameters.java
main/java/org/apache/wicket/PageParametersMarshaller.java
test/java/org/apache/wicket/PageParametersTest.java
Author: jdonnerstag
Date: Sat Sep 26 12:07:24 2009
New Revision: 819116
URL: http://svn.apache.org/viewvc?rev=819116&view=rev
Log:
committed initial contribution
Issue: WICKET-2388
Added:
wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParametersMarshaller.java
Modified:
wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParameters.java
wicket/trunk/wicket/src/test/java/org/apache/wicket/PageParametersTest.java
Modified: wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParameters.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParameters.java?rev=819116&r1=819115&r2=819116&view=diff
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParameters.java (original)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParameters.java Sat Sep 26 12:07:24 2009
@@ -202,4 +202,66 @@
}
return params;
}
+
+ /**
+ * Get an instance of an interface, which transparently reads and writes to this PageParameters.
+ * Useful if you would prefer to deal in Java objects rather than extracting key/value pairs.
+ * <h4>Usage:</h4> Write an interface that follows the beans pattern, such as
+ *
+ * <pre>
+ * public interface MyData
+ * {
+ * double getUserid();
+ *
+ * void setUserId(double id);
+ *
+ * String getRequestedCheese();
+ *
+ * void setRequestedCheese(String cheese);
+ *
+ * boolean isBackorder();
+ *
+ * void setBackorder(boolean val);
+ * }
+ * </pre>
+ *
+ * It <em>must</em> be a Java interface, because the implementation uses dynamic proxies to
+ * generate an implementation of that interface.
+ * <p/>
+ * Your PageParameters should contain key/value pairs with names such as
+ * "requestedCheese", "userid" and "backorder"
+ * <p/>
+ * You will be returned an implementation of your interface, which delegates to the
+ * PageParameters for the actual values, but handles typecasting and avoids issues with typos in
+ * string keys.
+ * <p/>
+ * The resulting object is read/write, so you can use it both to populate and to read from a
+ * PageParameters object.
+ * <p/>
+ * If the requested value does not exist in the PageParameters, the return value will be null,
+ * -1 or false depending on the requested type.
+ * <p/>
+ * Note that it is possible to read and write <code>Serializable</code> objects; however, this
+ * should not be done for objects with many fields, as it may exceed the browser's URL-length
+ * limit.
+ *
+ * @param <T>
+ * The return type
+ * @param ifaceType
+ * The concrete type of the interface you need
+ * @return An instance of that interface, dynamically generated
+ */
+ public <T> T asObject(Class<T> ifaceType)
+ {
+ if (ifaceType == null)
+ {
+ throw new NullPointerException("Parameter 'ifaceType' must not be null");
+ }
+ if (!ifaceType.isInterface())
+ {
+ throw new IllegalArgumentException("ifaceType is not an interface: " +
+ ifaceType.getName());
+ }
+ return new PageParametersMarshaller().read(ifaceType, this);
+ }
}
Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParametersMarshaller.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParametersMarshaller.java?rev=819116&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParametersMarshaller.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/PageParametersMarshaller.java Sat Sep 26 12:07:24 2009
@@ -0,0 +1,516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.wicket.util.crypt.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Pass in an interface type to the read method. The code will find all getters and setters, and use
+ * their property names as keys for reading and writing to the underlying properties object.
+ *
+ * @author Tim Boudreau
+ */
+public final class PageParametersMarshaller
+{
+ /** Log */
+ private static final Logger log = LoggerFactory.getLogger(PageParametersMarshaller.class);
+
+ /**
+ * Construct.
+ */
+ public PageParametersMarshaller()
+ {
+ }
+
+ /**
+ * Get a proxy object
+ *
+ * @param <T>
+ * @param returnType
+ * @param data
+ * @return A new proxy object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T read(final Class<T> returnType, final PageParameters data)
+ {
+ if (!returnType.isInterface())
+ {
+ throw new IllegalArgumentException("Must be an interface: returnType=" +
+ returnType.getName());
+ }
+ return (T)Proxy.newProxyInstance(returnType.getClassLoader(), new Class[] { returnType },
+ new MyInvocationHandler(data));
+ }
+
+ /**
+ *
+ */
+ private static final class MyInvocationHandler implements InvocationHandler
+ {
+ private final PageParameters params;
+
+ /**
+ * Construct.
+ *
+ * @param params
+ */
+ public MyInvocationHandler(final PageParameters params)
+ {
+ this.params = params;
+ }
+
+ /**
+ * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
+ * java.lang.reflect.Method, java.lang.Object[])
+ */
+ public Object invoke(final Object proxy, final Method method, final Object[] args)
+ throws Throwable
+ {
+ try
+ {
+ String mname = method.getName();
+ boolean isSetter = mname.startsWith("set");
+ boolean isGetter = mname.startsWith("get") || mname.startsWith("is");
+ Class<?> type = isSetter ? method.getParameterTypes()[0] : method.getReturnType();
+
+ if (!isSetter && !isGetter)
+ {
+ return nullOrNumber(isGetter, type);
+ }
+ if (isSetter &&
+ ((method.getParameterTypes() == null) || (method.getParameterTypes().length == 0)))
+ {
+ return nullOrNumber(isGetter, type);
+ }
+ if (isGetter && method.getParameterTypes() != null &&
+ method.getParameterTypes().length != 0)
+ {
+ return nullOrNumber(isGetter, type);
+ }
+
+ String name = stripName(method);
+ for (Type t : Type.values())
+ {
+ if (t.match(type))
+ {
+ if (isGetter)
+ {
+ return read(params, t, name);
+ }
+ else
+ {
+ Object val = args == null ? null : args.length == 0 ? null : args[0];
+ if (val == null)
+ {
+ params.remove(name);
+ }
+ else
+ {
+ write(params, t, name, val);
+ }
+ }
+ break;
+ }
+ }
+ }
+ catch (RuntimeException e)
+ {
+ log.error("Error while reading/updating PageParamaters. ", e);
+ throw e;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @param method
+ * @return method name without set/get/is
+ */
+ private static final String stripName(final Method method)
+ {
+ String s = method.getName();
+ if (s.startsWith("get") || s.startsWith("set"))
+ {
+ s = s.substring(3);
+ }
+ else if (s.startsWith("is"))
+ {
+ s = s.substring(2);
+ }
+ StringBuilder sb = new StringBuilder(s);
+ sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
+ return sb.toString();
+ }
+
+ /**
+ *
+ * @param getter
+ * @param c
+ * @return
+ */
+ private static final Object nullOrNumber(final boolean getter, final Class<?> c)
+ {
+ if (!getter)
+ {
+ return null;
+ }
+ if (c.isArray())
+ {
+ return Array.newInstance(c, 0);
+ }
+ for (Type t : Type.values())
+ {
+ if (t.match(c))
+ {
+ return t.noValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param p
+ * @param type
+ * @param name
+ * @param o
+ */
+ private static void write(final PageParameters p, final Type type, final String name,
+ final Object o)
+ {
+ new PageParametersWriteStrategy().write(p, type, name, o);
+ }
+
+ /**
+ *
+ * @param p
+ * @param t
+ * @param name
+ * @return
+ */
+ private static Object read(final PageParameters p, final Type t, final String name)
+ {
+ return new PageParametersReadStrategy().read(p, t, name);
+ }
+
+ /**
+ *
+ */
+ private static enum Type {
+
+ BOOLEAN, INT, STRING, LONG, DOUBLE, FLOAT, BYTE, SHORT, CHAR, BYTE_ARRAY, SERIALIZABLE;
+
+ public boolean match(Class<?> type)
+ {
+ for (Class<?> c : getTypes())
+ {
+ if (c.isAssignableFrom(type))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Set<Class<?>> types;
+
+ public Set<Class<?>> getTypes()
+ {
+ if (types == null)
+ {
+ switch (this)
+ {
+ case BOOLEAN :
+ types = toSet(Boolean.class, Boolean.TYPE);
+ break;
+ case BYTE :
+ types = toSet(Byte.class, Byte.TYPE);
+ break;
+ case BYTE_ARRAY :
+ types = toSet(new byte[0].getClass());
+ break;
+ case DOUBLE :
+ types = toSet(Double.class, Double.TYPE);
+ break;
+ case FLOAT :
+ types = toSet(Float.class, Float.TYPE);
+ break;
+ case INT :
+ types = toSet(Integer.class, Integer.TYPE);
+ break;
+ case LONG :
+ types = toSet(Long.class, Long.TYPE);
+ break;
+ case SHORT :
+ types = toSet(Short.class, Short.TYPE);
+ break;
+ case STRING :
+ types = toSet(String.class);
+ break;
+ case CHAR :
+ types = toSet(Character.TYPE, Character.class);
+ break;
+ // Note: Serializable *must* remain the last item tested for
+ // or everything will be resolved as serializable
+ case SERIALIZABLE :
+ types = toSet(Serializable.class);
+ break;
+ default :
+ throw new AssertionError();
+ }
+ }
+ return Collections.unmodifiableSet(types);
+ }
+
+ /**
+ *
+ * @param types
+ * @return a new HashSet with all the types provided
+ */
+ private Set<Class<?>> toSet(final Class<?>... types)
+ {
+ return new HashSet<Class<?>>(Arrays.asList(types));
+ }
+
+ /**
+ *
+ * @return The "null" or "no" values for all types
+ */
+ public Object noValue()
+ {
+ switch (this)
+ {
+ case BOOLEAN :
+ return Boolean.FALSE;
+ case INT :
+ return -1;
+ case STRING :
+ return null;
+ case LONG :
+ return -1L;
+ case DOUBLE :
+ return -1D;
+ case FLOAT :
+ return -1F;
+ case BYTE :
+ return new Byte((byte)-1);
+ case SHORT :
+ return new Short((short)-1);
+ case CHAR :
+ return (char)0;
+ default :
+ return null;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private static final class PageParametersWriteStrategy
+ {
+ public void write(final PageParameters p, final Type type, final String name, final Object o)
+ {
+ if (o == null)
+ {
+ p.remove(name);
+ return;
+ }
+
+ switch (type)
+ {
+ case BOOLEAN :
+ case INT :
+ case STRING :
+ case LONG :
+ case DOUBLE :
+ case FLOAT :
+ case BYTE :
+ case SHORT :
+ case CHAR :
+ p.put(name, o);
+ break;
+ case BYTE_ARRAY :
+ byte[] bytes = (byte[])o;
+ String s = bytesToString(bytes);
+ p.put(name, s);
+ break;
+ case SERIALIZABLE :
+ ByteArrayOutputStream out = new ByteArrayOutputStream(512);
+ try
+ {
+ ObjectOutputStream oout = new ObjectOutputStream(out);
+ oout.writeObject(o);
+ out.close();
+ String asString = bytesToString(out.toByteArray());
+ p.put(name, asString);
+ }
+ catch (IOException ex)
+ {
+ log.error("Error while reading Serializable into a String", ex);
+ }
+ finally
+ {
+ try
+ {
+ out.close();
+ }
+ catch (IOException ex)
+ {
+ // Should normally not happen
+ log.error(null, ex);
+ }
+ }
+ break;
+ default :
+ throw new AssertionError(
+ "Must be primitive type, byte array or serializable: " + o +
+ " does not match type " + type);
+ }
+ }
+
+ /**
+ * Convert a byte array into a hexadecimal string
+ *
+ * @param bytes
+ * @return String
+ */
+ static String bytesToString(byte[] bytes)
+ {
+ try
+ {
+ return new String(Base64.encodeBase64(bytes), "UTF-8");
+ }
+ catch (UnsupportedEncodingException ex)
+ {
+ log.error(
+ "Error while converting UTF-8 byte[] into String. Retrying with default Locale.",
+ ex);
+
+ return new String(Base64.decodeBase64(bytes));
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private static final class PageParametersReadStrategy
+ {
+ public Object read(final PageParameters p, final Type type, final String name)
+ {
+ switch (type)
+ {
+ case BOOLEAN :
+ return Boolean.valueOf(p.getAsBoolean(name, false));
+ case INT :
+ return new Integer(p.getInt(name, -1));
+ case STRING :
+ return p.getString(name, null);
+ case LONG :
+ return new Long(p.getLong(name, -1));
+ case DOUBLE :
+ return new Double(p.getDouble(name, -1));
+ case FLOAT :
+ return new Float(p.getAsDouble(name, -1));
+ case BYTE :
+ return new Byte((byte)p.getInt(name, -1));
+ case SHORT :
+ return new Short((short)p.getInt(name, -1));
+ case CHAR :
+ String s = p.getString(name);
+ if (s == null || s.length() == 0)
+ {
+ return -1;
+ }
+ return s.charAt(0);
+ case BYTE_ARRAY :
+ String hex = p.getString(name);
+ return stringToBytes(hex);
+ case SERIALIZABLE :
+ byte[] data = (byte[])read(p, Type.BYTE_ARRAY, name);
+ if (data != null && data.length > 0)
+ {
+ try
+ {
+ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
+ data));
+ try
+ {
+ return in.readObject();
+ }
+ catch (ClassNotFoundException ex)
+ {
+ log.error("Error converting serialized data stream into an Object",
+ ex);
+ }
+ finally
+ {
+ in.close();
+ }
+ }
+ catch (IOException ex)
+ {
+ log.error("Error reading serialized data", ex);
+ }
+ }
+ break;
+ default :
+ throw new AssertionError();
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param s
+ * @return String converted into byte[] using UTF-8
+ */
+ private static byte[] stringToBytes(final String s)
+ {
+ try
+ {
+ return Base64.decodeBase64(s.getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException ex)
+ {
+ // should not happen
+ log.error("Error while converting String into byte[]", ex);
+
+ return Base64.encodeBase64(s.getBytes(), false);
+ }
+ }
+ }
+}
Modified: wicket/trunk/wicket/src/test/java/org/apache/wicket/PageParametersTest.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/test/java/org/apache/wicket/PageParametersTest.java?rev=819116&r1=819115&r2=819116&view=diff
==============================================================================
--- wicket/trunk/wicket/src/test/java/org/apache/wicket/PageParametersTest.java (original)
+++ wicket/trunk/wicket/src/test/java/org/apache/wicket/PageParametersTest.java Sat Sep 26 12:07:24 2009
@@ -16,6 +16,10 @@
*/
package org.apache.wicket;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Map;
+
import junit.framework.TestCase;
/**
@@ -123,4 +127,428 @@
params.put("myint", 12345);
assertEquals(params.getAsInteger("myint").intValue(), 12345);
}
+
+ /**
+ *
+ */
+ public void testAsObject()
+ {
+ System.out.println("testObjectDereferencing");
+ PageParameters params = new PageParameters();
+ Face face = new Face();
+ IFace fromParams = params.asObject(IFace.class);
+ assertNotNull(fromParams);
+ face.copyInto(fromParams);
+
+ IFace another = params.asObject(IFace.class);
+ Face copy = new Face(another);
+ assertEquals(face, copy);
+
+ System.err.println("Params:\n" + params);
+
+ // Do the equivalent of passing this as a URL and reconstructing it
+
+ Map<String, String[]> x = params.toRequestParameters();
+ PageParameters nue = new PageParameters(x);
+
+ another = nue.asObject(IFace.class);
+ copy = new Face(another);
+ assertEquals(face, copy);
+ assertNotSame(face, copy);
+
+ PageParameters pp = new PageParameters();
+ IFace i = pp.asObject(IFace.class);
+ Face one = new Face(new SerializableThing("whee"), 72, false, 1.23, 3.75F, (short)450,
+ (byte)-5, new byte[] { 1, 2, 3 }, 'q', 342L, "testAllThis");
+ one.copyInto(i);
+
+ Face two = new Face(i);
+ assertFalse(copy.equals(two));
+ assertEquals(one, two);
+
+ assertEquals(72, i.getIntVal());
+ assertNotNull(pp.get("intVal"));
+
+ assertEquals(72, (int)pp.getAsInteger("intVal"));
+
+ i.setIntVal(325);
+ assertEquals(325, (int)pp.getAsInteger("intVal"));
+
+ }
+
+
+ public static interface IFace extends Serializable
+ {
+ byte[] getByteArrVal();
+
+ byte getByteVal();
+
+ char getCharVal();
+
+ double getDoubleVal();
+
+ float getFloatVal();
+
+ int getIntVal();
+
+ long getLongVal();
+
+ short getShortVal();
+
+ String getStringVal();
+
+ SerializableThing getThing();
+
+ boolean isBoolVal();
+
+ void setBoolVal(boolean boolVal);
+
+ void setByteArrVal(byte[] byteArrVal);
+
+ void setByteVal(byte byteVal);
+
+ void setCharVal(char charVal);
+
+ void setDoubleVal(double doubleVal);
+
+ void setFloatVal(float floatVal);
+
+ void setIntVal(int intVal);
+
+ void setLongVal(long longVal);
+
+ void setShortVal(short shortVal);
+
+ void setStringVal(String stringVal);
+
+ void setThing(SerializableThing thing);
+ }
+
+ /**
+ *
+ */
+ public static final class Face implements IFace
+ {
+ private static final long serialVersionUID = 1L;
+
+ private SerializableThing thing = new SerializableThing("Foo");
+ private int intVal = 23;
+ private boolean boolVal = true;
+ private double doubleVal = 0.135D;
+ private float floatVal = 12.230F;
+ private short shortVal = 32766;
+ private byte byteVal = -123;
+ private byte[] byteArrVal = new byte[] { -124, -3, 0, 14, 22 };
+ private char charVal = 'c';
+ private long longVal = 1294380151L;
+ private String stringVal = "Hello World";
+
+ /**
+ * Construct.
+ *
+ * @param thing
+ * @param intVal
+ * @param boolVal
+ * @param doubleVal
+ * @param floatVal
+ * @param shortVal
+ * @param byteVal
+ * @param byteArrVal
+ * @param charVal
+ * @param longVal
+ * @param stringVal
+ */
+ public Face(SerializableThing thing, int intVal, boolean boolVal, double doubleVal,
+ float floatVal, short shortVal, byte byteVal, byte[] byteArrVal, char charVal,
+ long longVal, String stringVal)
+ {
+ this.thing = thing;
+ this.intVal = intVal;
+ this.boolVal = boolVal;
+ this.doubleVal = doubleVal;
+ this.floatVal = floatVal;
+ this.shortVal = shortVal;
+ this.byteVal = byteVal;
+ this.byteArrVal = byteArrVal;
+ this.charVal = charVal;
+ this.longVal = longVal;
+ this.stringVal = stringVal;
+ }
+
+ /**
+ * Construct.
+ */
+ public Face()
+ {
+
+ }
+
+ /**
+ * Construct.
+ *
+ * @param o
+ */
+ public Face(IFace o)
+ {
+ thing = o.getThing();
+ intVal = o.getIntVal();
+ boolVal = o.isBoolVal();
+ doubleVal = o.getDoubleVal();
+ floatVal = o.getFloatVal();
+ shortVal = o.getShortVal();
+ byteVal = o.getByteVal();
+ byteArrVal = o.getByteArrVal();
+ charVal = o.getCharVal();
+ longVal = o.getLongVal();
+ stringVal = o.getStringVal();
+ }
+
+ /**
+ *
+ * @param o
+ */
+ public void copyInto(IFace o)
+ {
+ o.setThing(thing);
+ o.setIntVal(intVal);
+ o.setBoolVal(boolVal);
+ o.setDoubleVal(doubleVal);
+ o.setFloatVal(floatVal);
+ o.setShortVal(shortVal);
+ o.setByteVal(byteVal);
+ o.setByteArrVal(byteArrVal);
+ o.setCharVal(charVal);
+ o.setLongVal(longVal);
+ o.setStringVal(stringVal);
+ }
+
+ public boolean isBoolVal()
+ {
+ return boolVal;
+ }
+
+ public void setBoolVal(boolean boolVal)
+ {
+ this.boolVal = boolVal;
+ }
+
+ public byte[] getByteArrVal()
+ {
+ return byteArrVal;
+ }
+
+ public void setByteArrVal(byte[] byteArrVal)
+ {
+ this.byteArrVal = byteArrVal;
+ }
+
+ public byte getByteVal()
+ {
+ return byteVal;
+ }
+
+ public void setByteVal(byte byteVal)
+ {
+ this.byteVal = byteVal;
+ }
+
+ public char getCharVal()
+ {
+ return charVal;
+ }
+
+ public void setCharVal(char charVal)
+ {
+ this.charVal = charVal;
+ }
+
+ public double getDoubleVal()
+ {
+ return doubleVal;
+ }
+
+ public void setDoubleVal(double doubleVal)
+ {
+ this.doubleVal = doubleVal;
+ }
+
+ public float getFloatVal()
+ {
+ return floatVal;
+ }
+
+ public void setFloatVal(float floatVal)
+ {
+ this.floatVal = floatVal;
+ }
+
+ public int getIntVal()
+ {
+ return intVal;
+ }
+
+ public void setIntVal(int intVal)
+ {
+ this.intVal = intVal;
+ }
+
+ public long getLongVal()
+ {
+ return longVal;
+ }
+
+ public void setLongVal(long longVal)
+ {
+ this.longVal = longVal;
+ }
+
+ public short getShortVal()
+ {
+ return shortVal;
+ }
+
+ public void setShortVal(short shortVal)
+ {
+ this.shortVal = shortVal;
+ }
+
+ public String getStringVal()
+ {
+ return stringVal;
+ }
+
+ public void setStringVal(String stringVal)
+ {
+ this.stringVal = stringVal;
+ }
+
+ public SerializableThing getThing()
+ {
+ return thing;
+ }
+
+ public void setThing(SerializableThing thing)
+ {
+ this.thing = thing;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final Face other = (Face)obj;
+ if (thing != other.thing && (thing == null || !thing.equals(other.thing)))
+ {
+ return false;
+ }
+ if (intVal != other.intVal)
+ {
+ return false;
+ }
+ if (boolVal != other.boolVal)
+ {
+ return false;
+ }
+ if (doubleVal != other.doubleVal)
+ {
+ return false;
+ }
+ if (floatVal != other.floatVal)
+ {
+ return false;
+ }
+ if (shortVal != other.shortVal)
+ {
+ return false;
+ }
+ if (byteVal != other.byteVal)
+ {
+ return false;
+ }
+ if (!Arrays.equals(byteArrVal, other.byteArrVal))
+ {
+ return false;
+ }
+ if (charVal != other.charVal)
+ {
+ return false;
+ }
+ if (longVal != other.longVal)
+ {
+ return false;
+ }
+ if ((stringVal == null) ? (other.stringVal != null)
+ : !stringVal.equals(other.stringVal))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 3;
+ hash = 23 * hash + (thing != null ? thing.hashCode() : 0);
+ hash = 23 * hash + intVal;
+ hash = 23 * hash + (boolVal ? 1 : 0);
+ hash = 23 *
+ hash +
+ (int)(Double.doubleToLongBits(doubleVal) ^ (Double.doubleToLongBits(doubleVal) >>> 32));
+ hash = 23 * hash + Float.floatToIntBits(floatVal);
+ hash = 23 * hash + shortVal;
+ hash = 23 * hash + byteVal;
+ hash = 23 * hash + Arrays.hashCode(byteArrVal);
+ hash = 23 * hash + charVal;
+ hash = 23 * hash + (int)(longVal ^ (longVal >>> 32));
+ hash = 23 * hash + (stringVal != null ? stringVal.hashCode() : 0);
+ return hash;
+ }
+
+
+ }
+
+ public static final class SerializableThing implements Serializable
+ {
+ public final String word;
+
+ public SerializableThing(String word)
+ {
+ this.word = word;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final SerializableThing other = (SerializableThing)obj;
+ if ((word == null) ? (other.word != null) : !word.equals(other.word))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 23 * hash + (word != null ? word.hashCode() : 0);
+ return hash;
+ }
+ }
}