1 /* 2 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.beans; 26 27 import com.sun.beans.finder.PersistenceDelegateFinder; 28 29 import java.util.HashMap; 30 import java.util.IdentityHashMap; 31 import java.util.Map; 32 33 /** 34 * An {@code Encoder} is a class which can be used to create 35 * files or streams that encode the state of a collection of 36 * JavaBeans in terms of their public APIs. The {@code Encoder}, 37 * in conjunction with its persistence delegates, is responsible for 38 * breaking the object graph down into a series of {@code Statement}s 39 * and {@code Expression}s which can be used to create it. 40 * A subclass typically provides a syntax for these expressions 41 * using some human readable form - like Java source code or XML. 42 * 43 * @since 1.4 44 * 45 * @author Philip Milne 46 */ 47 48 public class Encoder { 49 private final PersistenceDelegateFinder finder = new PersistenceDelegateFinder(); 50 private Map<Object, Expression> bindings = new IdentityHashMap<>(); 51 private ExceptionListener exceptionListener; 52 boolean executeStatements = true; 53 private Map<Object, Object> attributes; 54 55 /** 56 * Write the specified object to the output stream. 57 * The serialized form will denote a series of 58 * expressions, the combined effect of which will create 59 * an equivalent object when the input stream is read. 60 * By default, the object is assumed to be a <em>JavaBean</em> 61 * with a nullary constructor, whose state is defined by 62 * the matching pairs of "setter" and "getter" methods 63 * returned by the Introspector. 64 * 65 * @param o The object to be written to the stream. 66 * 67 * @see XMLDecoder#readObject 68 */ writeObject(Object o)69 protected void writeObject(Object o) { 70 if (o == this) { 71 return; 72 } 73 PersistenceDelegate info = getPersistenceDelegate(o == null ? null : o.getClass()); 74 info.writeObject(o, this); 75 } 76 77 /** 78 * Sets the exception handler for this stream to {@code exceptionListener}. 79 * The exception handler is notified when this stream catches recoverable 80 * exceptions. 81 * 82 * @param exceptionListener The exception handler for this stream; 83 * if {@code null} the default exception listener will be used. 84 * 85 * @see #getExceptionListener 86 */ setExceptionListener(ExceptionListener exceptionListener)87 public void setExceptionListener(ExceptionListener exceptionListener) { 88 this.exceptionListener = exceptionListener; 89 } 90 91 /** 92 * Gets the exception handler for this stream. 93 * 94 * @return The exception handler for this stream; 95 * Will return the default exception listener if this has not explicitly been set. 96 * 97 * @see #setExceptionListener 98 */ getExceptionListener()99 public ExceptionListener getExceptionListener() { 100 return (exceptionListener != null) ? exceptionListener : Statement.defaultExceptionListener; 101 } 102 getValue(Expression exp)103 Object getValue(Expression exp) { 104 try { 105 return (exp == null) ? null : exp.getValue(); 106 } 107 catch (Exception e) { 108 getExceptionListener().exceptionThrown(e); 109 throw new RuntimeException("failed to evaluate: " + exp.toString()); 110 } 111 } 112 113 /** 114 * Returns the persistence delegate for the given type. 115 * The persistence delegate is calculated by applying 116 * the following rules in order: 117 * <ol> 118 * <li> 119 * If a persistence delegate is associated with the given type 120 * by using the {@link #setPersistenceDelegate} method 121 * it is returned. 122 * <li> 123 * A persistence delegate is then looked up by the name 124 * composed of the fully qualified name of the given type 125 * and the "PersistenceDelegate" postfix. 126 * For example, a persistence delegate for the {@code Bean} class 127 * should be named {@code BeanPersistenceDelegate} 128 * and located in the same package. 129 * <pre> 130 * public class Bean { ... } 131 * public class BeanPersistenceDelegate { ... }</pre> 132 * The instance of the {@code BeanPersistenceDelegate} class 133 * is returned for the {@code Bean} class. 134 * <li> 135 * If the type is {@code null}, 136 * a shared internal persistence delegate is returned 137 * that encodes {@code null} value. 138 * <li> 139 * If the type is an {@code enum} declaration, 140 * a shared internal persistence delegate is returned 141 * that encodes constants of this enumeration 142 * by their names. 143 * <li> 144 * If the type is a primitive type or the corresponding wrapper, 145 * a shared internal persistence delegate is returned 146 * that encodes values of the given type. 147 * <li> 148 * If the type is an array, 149 * a shared internal persistence delegate is returned 150 * that encodes an array of the appropriate type and length, 151 * and each of its elements as if they are properties. 152 * <li> 153 * If the type is a proxy, 154 * a shared internal persistence delegate is returned 155 * that encodes a proxy instance by using 156 * the {@link java.lang.reflect.Proxy#newProxyInstance} method. 157 * <li> 158 * If the {@link BeanInfo} for this type has a {@link BeanDescriptor} 159 * which defined a "persistenceDelegate" attribute, 160 * the value of this named attribute is returned. 161 * <li> 162 * In all other cases the default persistence delegate is returned. 163 * The default persistence delegate assumes the type is a <em>JavaBean</em>, 164 * implying that it has a default constructor and that its state 165 * may be characterized by the matching pairs of "setter" and "getter" 166 * methods returned by the {@link Introspector} class. 167 * The default constructor is the constructor with the greatest number 168 * of parameters that has the {@link ConstructorProperties} annotation. 169 * If none of the constructors has the {@code ConstructorProperties} annotation, 170 * then the nullary constructor (constructor with no parameters) will be used. 171 * For example, in the following code fragment, the nullary constructor 172 * for the {@code Foo} class will be used, 173 * while the two-parameter constructor 174 * for the {@code Bar} class will be used. 175 * <pre> 176 * public class Foo { 177 * public Foo() { ... } 178 * public Foo(int x) { ... } 179 * } 180 * public class Bar { 181 * public Bar() { ... } 182 * @ConstructorProperties({"x"}) 183 * public Bar(int x) { ... } 184 * @ConstructorProperties({"x", "y"}) 185 * public Bar(int x, int y) { ... } 186 * }</pre> 187 * </ol> 188 * 189 * @param type the class of the objects 190 * @return the persistence delegate for the given type 191 * 192 * @see #setPersistenceDelegate 193 * @see java.beans.Introspector#getBeanInfo 194 * @see java.beans.BeanInfo#getBeanDescriptor 195 */ getPersistenceDelegate(Class<?> type)196 public PersistenceDelegate getPersistenceDelegate(Class<?> type) { 197 PersistenceDelegate pd = this.finder.find(type); 198 if (pd == null) { 199 pd = MetaData.getPersistenceDelegate(type); 200 if (pd != null) { 201 this.finder.register(type, pd); 202 } 203 } 204 return pd; 205 } 206 207 /** 208 * Associates the specified persistence delegate with the given type. 209 * 210 * @param type the class of objects that the specified persistence delegate applies to 211 * @param delegate the persistence delegate for instances of the given type 212 * 213 * @see #getPersistenceDelegate 214 * @see java.beans.Introspector#getBeanInfo 215 * @see java.beans.BeanInfo#getBeanDescriptor 216 */ setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate)217 public void setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate) { 218 this.finder.register(type, delegate); 219 } 220 221 /** 222 * Removes the entry for this instance, returning the old entry. 223 * 224 * @param oldInstance The entry that should be removed. 225 * @return The entry that was removed. 226 * 227 * @see #get 228 */ remove(Object oldInstance)229 public Object remove(Object oldInstance) { 230 Expression exp = bindings.remove(oldInstance); 231 return getValue(exp); 232 } 233 234 /** 235 * Returns a tentative value for {@code oldInstance} in 236 * the environment created by this stream. A persistence 237 * delegate can use its {@code mutatesTo} method to 238 * determine whether this value may be initialized to 239 * form the equivalent object at the output or whether 240 * a new object must be instantiated afresh. If the 241 * stream has not yet seen this value, null is returned. 242 * 243 * @param oldInstance The instance to be looked up. 244 * @return The object, null if the object has not been seen before. 245 */ get(Object oldInstance)246 public Object get(Object oldInstance) { 247 if (oldInstance == null || oldInstance == this || 248 oldInstance.getClass() == String.class) { 249 return oldInstance; 250 } 251 Expression exp = bindings.get(oldInstance); 252 return getValue(exp); 253 } 254 writeObject1(Object oldInstance)255 private Object writeObject1(Object oldInstance) { 256 Object o = get(oldInstance); 257 if (o == null) { 258 writeObject(oldInstance); 259 o = get(oldInstance); 260 } 261 return o; 262 } 263 cloneStatement(Statement oldExp)264 private Statement cloneStatement(Statement oldExp) { 265 Object oldTarget = oldExp.getTarget(); 266 Object newTarget = writeObject1(oldTarget); 267 268 Object[] oldArgs = oldExp.getArguments(); 269 Object[] newArgs = new Object[oldArgs.length]; 270 for (int i = 0; i < oldArgs.length; i++) { 271 newArgs[i] = writeObject1(oldArgs[i]); 272 } 273 Statement newExp = Statement.class.equals(oldExp.getClass()) 274 ? new Statement(newTarget, oldExp.getMethodName(), newArgs) 275 : new Expression(newTarget, oldExp.getMethodName(), newArgs); 276 newExp.loader = oldExp.loader; 277 return newExp; 278 } 279 280 /** 281 * Writes statement {@code oldStm} to the stream. 282 * The {@code oldStm} should be written entirely 283 * in terms of the callers environment, i.e. the 284 * target and all arguments should be part of the 285 * object graph being written. These expressions 286 * represent a series of "what happened" expressions 287 * which tell the output stream how to produce an 288 * object graph like the original. 289 * <p> 290 * The implementation of this method will produce 291 * a second expression to represent the same expression in 292 * an environment that will exist when the stream is read. 293 * This is achieved simply by calling {@code writeObject} 294 * on the target and all the arguments and building a new 295 * expression with the results. 296 * 297 * @param oldStm The expression to be written to the stream. 298 */ writeStatement(Statement oldStm)299 public void writeStatement(Statement oldStm) { 300 // System.out.println("writeStatement: " + oldExp); 301 Statement newStm = cloneStatement(oldStm); 302 if (oldStm.getTarget() != this && executeStatements) { 303 try { 304 newStm.execute(); 305 } catch (Exception e) { 306 getExceptionListener().exceptionThrown(new Exception("Encoder: discarding statement " 307 + newStm, e)); 308 } 309 } 310 } 311 312 /** 313 * The implementation first checks to see if an 314 * expression with this value has already been written. 315 * If not, the expression is cloned, using 316 * the same procedure as {@code writeStatement}, 317 * and the value of this expression is reconciled 318 * with the value of the cloned expression 319 * by calling {@code writeObject}. 320 * 321 * @param oldExp The expression to be written to the stream. 322 */ writeExpression(Expression oldExp)323 public void writeExpression(Expression oldExp) { 324 // System.out.println("Encoder::writeExpression: " + oldExp); 325 Object oldValue = getValue(oldExp); 326 if (get(oldValue) != null) { 327 return; 328 } 329 bindings.put(oldValue, (Expression)cloneStatement(oldExp)); 330 writeObject(oldValue); 331 } 332 clear()333 void clear() { 334 bindings.clear(); 335 } 336 337 // Package private method for setting an attributes table for the encoder setAttribute(Object key, Object value)338 void setAttribute(Object key, Object value) { 339 if (attributes == null) { 340 attributes = new HashMap<>(); 341 } 342 attributes.put(key, value); 343 } 344 getAttribute(Object key)345 Object getAttribute(Object key) { 346 if (attributes == null) { 347 return null; 348 } 349 return attributes.get(key); 350 } 351 } 352