Encoder
is a class which can be used to create
+ * files or streams that encode the state of a collection of
+ * JavaBeans in terms of their public APIs. The Encoder
,
+ * in conjunction with its persistence delegates, is responsible for
+ * breaking the object graph down into a series of Statements
s
+ * and Expression
s which can be used to create it.
+ * A subclass typically provides a syntax for these expressions
+ * using some human readable form - like Java source code or XML.
+ *
+ * @since 1.4
+ *
+ * @author Philip Milne
+ */
+
+public class Encoder {
+ private final MapexceptionListener
.
+ * The exception handler is notified when this stream catches recoverable
+ * exceptions.
+ *
+ * @param exceptionListener The exception handler for this stream;
+ * if null
the default exception listener will be used.
+ *
+ * @see #getExceptionListener
+ */
+ public void setExceptionListener(ExceptionListener exceptionListener) {
+ this.exceptionListener = exceptionListener;
+ }
+
+ /**
+ * Gets the exception handler for this stream.
+ *
+ * @return The exception handler for this stream;
+ * Will return the default exception listener if this has not explicitly been set.
+ *
+ * @see #setExceptionListener
+ */
+ public ExceptionListener getExceptionListener() {
+ return (exceptionListener != null) ? exceptionListener : Statement.defaultExceptionListener;
+ }
+
+ Object getValue(Expression exp) {
+ try {
+ return (exp == null) ? null : exp.getValue();
+ }
+ catch (Exception e) {
+ getExceptionListener().exceptionThrown(e);
+ throw new RuntimeException("failed to evaluate: " + exp.toString());
+ }
+ }
+
+ /**
+ * Returns the persistence delegate for the given type.
+ * The persistence delegate is calculated
+ * by applying the following of rules in order:
+ * BeanDescriptor
+ * which defined a "persistenceDelegate" property, this
+ * value is returned.
+ *
+ * public class Foo {
+ * public Foo() { ... }
+ * public Foo(int x) { ... }
+ * }
+ * public class Bar {
+ * public Bar() { ... }
+ * @ConstructorProperties({"x"})
+ * public Bar(int x) { ... }
+ * @ConstructorProperties({"x", "y"})
+ * public Bar(int x, int y) { ... }
+ * }
+ *
+ * type
to
+ * persistenceDelegate
.
+ *
+ * @param type The class of objects that persistenceDelegate
applies to.
+ * @param persistenceDelegate The persistence delegate for instances of type
.
+ *
+ * @see #getPersistenceDelegate
+ * @see java.beans.Introspector#getBeanInfo
+ * @see java.beans.BeanInfo#getBeanDescriptor
+ */
+ public void setPersistenceDelegate(Class> type,
+ PersistenceDelegate persistenceDelegate)
+ {
+ if (persistenceDelegate != null) {
+ this.delegates.put(type, persistenceDelegate);
+ } else {
+ this.delegates.remove(type);
+ }
+ }
+
+ /**
+ * Removes the entry for this instance, returning the old entry.
+ *
+ * @param oldInstance The entry that should be removed.
+ * @return The entry that was removed.
+ *
+ * @see #get
+ */
+ public Object remove(Object oldInstance) {
+ Expression exp = (Expression)bindings.remove(oldInstance);
+ return getValue(exp);
+ }
+
+ /**
+ * Returns a tentative value for oldInstance
in
+ * the environment created by this stream. A persistence
+ * delegate can use its mutatesTo
method to
+ * determine whether this value may be initialized to
+ * form the equivalent object at the output or whether
+ * a new object must be instantiated afresh. If the
+ * stream has not yet seen this value, null is returned.
+ *
+ * @param oldInstance The instance to be looked up.
+ * @return The object, null if the object has not been seen before.
+ */
+ public Object get(Object oldInstance) {
+ if (oldInstance == null || oldInstance == this ||
+ oldInstance.getClass() == String.class) {
+ return oldInstance;
+ }
+ Expression exp = (Expression)bindings.get(oldInstance);
+ return getValue(exp);
+ }
+
+ private Object writeObject1(Object oldInstance) {
+ Object o = get(oldInstance);
+ if (o == null) {
+ writeObject(oldInstance);
+ o = get(oldInstance);
+ }
+ return o;
+ }
+
+ private Statement cloneStatement(Statement oldExp) {
+ Object oldTarget = oldExp.getTarget();
+ Object newTarget = writeObject1(oldTarget);
+
+ Object[] oldArgs = oldExp.getArguments();
+ Object[] newArgs = new Object[oldArgs.length];
+ for (int i = 0; i < oldArgs.length; i++) {
+ newArgs[i] = writeObject1(oldArgs[i]);
+ }
+ if (oldExp.getClass() == Statement.class) {
+ return new Statement(newTarget, oldExp.getMethodName(), newArgs);
+ }
+ else {
+ return new Expression(newTarget, oldExp.getMethodName(), newArgs);
+ }
+ }
+
+ /**
+ * Writes statement oldStm
to the stream.
+ * The oldStm
should be written entirely
+ * in terms of the callers environment, i.e. the
+ * target and all arguments should be part of the
+ * object graph being written. These expressions
+ * represent a series of "what happened" expressions
+ * which tell the output stream how to produce an
+ * object graph like the original.
+ *
+ * The implementation of this method will produce
+ * a second expression to represent the same expression in
+ * an environment that will exist when the stream is read.
+ * This is achieved simply by calling writeObject
+ * on the target and all the arguments and building a new
+ * expression with the results.
+ *
+ * @param oldStm The expression to be written to the stream.
+ */
+ public void writeStatement(Statement oldStm) {
+ // System.out.println("writeStatement: " + oldExp);
+ Statement newStm = cloneStatement(oldStm);
+ if (oldStm.getTarget() != this && executeStatements) {
+ try {
+ newStm.execute();
+ } catch (Exception e) {
+ getExceptionListener().exceptionThrown(new Exception("Encoder: discarding statement "
+ + newStm, e));
+ }
+ }
+ }
+
+ /**
+ * The implementation first checks to see if an
+ * expression with this value has already been written.
+ * If not, the expression is cloned, using
+ * the same procedure as writeStatement
,
+ * and the value of this expression is reconciled
+ * with the value of the cloned expression
+ * by calling writeObject
.
+ *
+ * @param oldExp The expression to be written to the stream.
+ */
+ public void writeExpression(Expression oldExp) {
+ // System.out.println("Encoder::writeExpression: " + oldExp);
+ Object oldValue = getValue(oldExp);
+ if (get(oldValue) != null) {
+ return;
+ }
+ bindings.put(oldValue, (Expression)cloneStatement(oldExp));
+ writeObject(oldValue);
+ }
+
+ void clear() {
+ bindings.clear();
+ }
+
+ // Package private method for setting an attributes table for the encoder
+ void setAttribute(Object key, Object value) {
+ if (attributes == null) {
+ attributes = new HashMap();
+ }
+ attributes.put(key, value);
+ }
+
+ Object getAttribute(Object key) {
+ if (attributes == null) {
+ return null;
+ }
+ return attributes.get(key);
+ }
+}