/** * */ package com.ewjordan.util.objectWrap; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * Wraps an object so that its primitive fields (including primitive arrays) * of numerical and boolean type are condensed into a single array of doubles. * * @author eric * */ public class WrappedObject { private Object object; private double[] values; private static final double booleanThreshold = 0.5; private static final double defaultBooleanFalse = 0.25; private static final double defaultBooleanTrue = 0.75; private PrimitiveReference[] references; //fields that correspond to values stored here public WrappedObject(PrimitiveReference ... references) { this.references = references; this.values = new double[this.references.length]; pullValuesFromObject(); } public WrappedObject(Object objectToWrap) { this(PrimitiveReference.getAllReferencesFrom(objectToWrap, true)); object = objectToWrap; } /** * Clone the wrapper (does not clone the underlying object). * * Note that a cloned wrapper may be changed separately from the original * wrapper, and the underlying object's values will only change when values * are pushed through. * @param cloneMe */ public WrappedObject(WrappedObject cloneMe) { this.values = new double[cloneMe.values.length]; this.references = new PrimitiveReference[cloneMe.references.length]; this.object = cloneMe.object; System.arraycopy(cloneMe.values,0,this.values,0,cloneMe.values.length); System.arraycopy(cloneMe.references,0,this.references,0,cloneMe.references.length); } /** * Save the current values to an {@link ObjectOutputStream}. * Does not save the object or the references - these must be * re-constituted manually at load time. * * Load saved objects using {@link #load(Object, ObjectInputStream)} * * @param out * @throws IOException */ public void save(ObjectOutputStream out) throws IOException { out.writeObject(values); out.flush(); } /** * Save the current values of the {@link WrappedObject} to an {@link ObjectOutputStream}. * Does not save the object or the references - these must be * re-constituted manually at load time. * * Load saved objects using {@link #load(Object, ObjectInputStream)} * * @param obj the {@link WrappedObject} to save * @param out the stream to save the values to * @throws IOException */ static public void save(WrappedObject obj, ObjectOutputStream out) throws IOException { obj.save(out); } /** * Load values from an {@link ObjectInputStream} and interpret them * as a WrappedObject with the target as the target object. The values * will be loaded into the object. * * Note that the saved object contains no information about the target object * other than the length of the fields, so no error checking can be done * beyond a length check, which will throw an {@link IllegalArgumentException} * if failed. * * In particular, if the class of the object has changed since saving, the * object may not be properly restored - in particular, if the order that * the fields are returned via reflection changes, things will not work properly, * and if the count remains the same, there will be no indication that anything * is wrong. * * This function loads values saved via {@link #save(ObjectOutputStream)} * * @param target the base object to wrap * @param in the input stream containing the values to load * @return a {@link WrappedObject} that wraps the target * @throws IOException * @throws ClassNotFoundException */ static public WrappedObject load(Object target, ObjectInputStream in) throws IOException, ClassNotFoundException { WrappedObject obj = new WrappedObject(target); obj.values = (double[])in.readObject(); if (obj.references.length != obj.values.length) { throw new IllegalArgumentException("The target object does not match the format of the values you are trying to load into it."); } obj.pushValuesToObject(); return obj; } /** * Convert the {@link WrappedObject} to an {@link OptimizableWrappedObject}. * The underlying {@link Object} that is wrapped must implement the {@link HasValue} interface, * otherwise a {@link RuntimeException} will be thrown. * * @return an {@link OptimizableWrappedObject} that is a clone of this {@link WrappedObject} */ public OptimizableWrappedObject optimizable() { final WrappedObject obj = this; if (obj.getObject() instanceof HasValue) { return new OptimizableWrappedObject(this) { // Note that from here on, "this" refers to the OptimizableWrappedObject, // and NOT the WrappedObject (obj). It's very important not to confuse // the two, otherwise values won't get pushed properly @Override public double getValue() { this.pushValuesToObject(); double val = ((HasValue)this.getObject()).getValue(); return val; } }; } else { throw new RuntimeException("Base object " + obj.getObject() + " does not implement HasValue interface"); } } /** * Create an OptimizableWrappedObject with a given function as * the objective function. * @param func * @return */ public OptimizableWrappedObject optimizable(final WrappedObjectToDoubleFunction func) { final WrappedObject obj = this; OptimizableWrappedObject opt = new OptimizableWrappedObject(this) { @Override public double getValue() { return func.evaluate(obj); } }; return opt; } /** * @return a direct reference to the values array - alterations will * be reflected in the WrappedObject values, but will not be pushed to * the underlying object until {@link #pushValuesToObject()} is called. */ public double[] getValues() { return values; } /** * Sets a new target object, and instructs all references to reseat * themselves on the new target. */ public void setObject(Object o) { object = o; for (int i=0; i<references.length; ++i) { references[i].object = o; } } public Object getObject() { return object; } public PrimitiveReference getReferenceAt(int index) { return references[index]; } public double getValue(int index) { return values[index]; } public void setValue(int index, double value) { values[index] = value; } public void setValues(double[] values) { System.arraycopy(values,0,this.values,0,values.length); } public int getNumberOfMembers() { return values.length; } /** * Pulls values from object into WrappedObject's member array. */ public double[] pullValuesFromObject() { for (int i=0; i<references.length; ++i) { try { switch(references[i].type) { case BOOLEAN: values[i] = references[i].getBoolean()?defaultBooleanTrue:defaultBooleanFalse; break; case INT: values[i] = references[i].getInt(); break; case LONG: values[i] = references[i].getLong(); break; case FLOAT: values[i] = references[i].getFloat(); break; case DOUBLE: values[i] = references[i].getDouble(); break; } } catch (Exception e) { System.err.println("Could not retrieve value from index " + i + ", reference " + references[i]); throw new RuntimeException(e); } } return getValues(); } public void pushValuesToObject() { for (int i=0; i<references.length; ++i) { try { switch(references[i].type) { case BOOLEAN: references[i].set(getBooleanAt(i)); break; case INT: references[i].set(getIntAt(i)); break; case LONG: references[i].set(getLongAt(i)); break; case FLOAT: references[i].set(getFloatAt(i)); break; case DOUBLE: references[i].set(getDoubleAt(i)); break; } } catch (Exception e) { System.err.println("Could not update index " + i + " to reference " + references[i]); throw new RuntimeException(e); } } } /** * Pushes a set of values to the object, and stores * them in the WrappedObject. * @param values */ public void pushValuesToObject(double[] values) { if (values.length != this.getNumberOfMembers()) { throw new IllegalArgumentException("The array of values passed had length " + values.length + ", but this object has " + this.getNumberOfMembers() + " fields to be set."); } setValues(values); pushValuesToObject(); } public ReferenceType getTypeAt(int index) { return references[index].getType(); } private double getDoubleAt(int index) { return values[index]; } private float getFloatAt(int index) { return (float)values[index]; } private long getLongAt(int index) { return (long)values[index]; } private int getIntAt(int index) { return (int)values[index]; } private boolean getBooleanAt(int index) { return (values[index] > booleanThreshold); } }