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
+	 * &quot;requestedCheese&quot;, &quot;userid&quot; and &quot;backorder&quot;
+	 * <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;
+		}
+	}
 }