001 /* Encoder.java 002 Copyright (C) 2005, 2006 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package java.beans; 040 041 import gnu.java.beans.DefaultExceptionListener; 042 import gnu.java.beans.encoder.ArrayPersistenceDelegate; 043 import gnu.java.beans.encoder.ClassPersistenceDelegate; 044 import gnu.java.beans.encoder.CollectionPersistenceDelegate; 045 import gnu.java.beans.encoder.MapPersistenceDelegate; 046 import gnu.java.beans.encoder.PrimitivePersistenceDelegate; 047 048 import java.util.AbstractCollection; 049 import java.util.HashMap; 050 import java.util.IdentityHashMap; 051 052 /** 053 * @author Robert Schuster (robertschuster@fsfe.org) 054 * @since 1.4 055 */ 056 public class Encoder 057 { 058 059 /** 060 * An internal DefaultPersistenceDelegate instance that is used for every 061 * class that does not a have a special special PersistenceDelegate. 062 */ 063 private static PersistenceDelegate defaultPersistenceDelegate; 064 065 private static PersistenceDelegate fakePersistenceDelegate; 066 067 /** 068 * Stores the relation Class->PersistenceDelegate. 069 */ 070 private static HashMap delegates = new HashMap(); 071 072 /** 073 * Stores the relation oldInstance->newInstance 074 */ 075 private IdentityHashMap candidates = new IdentityHashMap(); 076 077 private ExceptionListener exceptionListener; 078 079 /** 080 * A simple number that is used to restrict the access to writeExpression and 081 * writeStatement. The rule is that both methods should only be used when an 082 * object is written to the stream (= writeObject). Therefore accessCounter is 083 * incremented just before the call to writeObject and decremented afterwards. 084 * Then writeStatement and writeExpression allow execution only if 085 * accessCounter is bigger than zero. 086 */ 087 private int accessCounter = 0; 088 089 public Encoder() 090 { 091 setupDefaultPersistenceDelegates(); 092 093 setExceptionListener(null); 094 } 095 096 /** 097 * Sets up a bunch of {@link PersistenceDelegate} instances which are needed 098 * for the basic working of a {@link Encoder}s. 099 */ 100 private static void setupDefaultPersistenceDelegates() 101 { 102 synchronized (delegates) 103 { 104 if (defaultPersistenceDelegate != null) 105 return; 106 107 delegates.put(Class.class, new ClassPersistenceDelegate()); 108 109 PersistenceDelegate pd = new PrimitivePersistenceDelegate(); 110 delegates.put(Boolean.class, pd); 111 delegates.put(Byte.class, pd); 112 delegates.put(Short.class, pd); 113 delegates.put(Integer.class, pd); 114 delegates.put(Long.class, pd); 115 delegates.put(Float.class, pd); 116 delegates.put(Double.class, pd); 117 118 delegates.put(Object[].class, new ArrayPersistenceDelegate()); 119 120 pd = new CollectionPersistenceDelegate(); 121 delegates.put(AbstractCollection.class, pd); 122 123 pd = new MapPersistenceDelegate(); 124 delegates.put(java.util.AbstractMap.class, pd); 125 delegates.put(java.util.Hashtable.class, pd); 126 127 defaultPersistenceDelegate = new DefaultPersistenceDelegate(); 128 delegates.put(Object.class, defaultPersistenceDelegate); 129 130 // Creates a PersistenceDelegate implementation which is 131 // returned for 'null'. In practice this instance is 132 // not used in any way and is just here to be compatible 133 // with the reference implementation which returns a 134 // similar instance when calling getPersistenceDelegate(null) . 135 fakePersistenceDelegate = new PersistenceDelegate() 136 { 137 protected Expression instantiate(Object o, Encoder e) 138 { 139 return null; 140 } 141 }; 142 143 } 144 } 145 146 protected void writeObject(Object o) 147 { 148 // 'null' has no PersistenceDelegate and will not 149 // create an Expression which has to be cloned. 150 // However subclasses should be aware that writeObject 151 // may be called with a 'null' argument and should 152 // write the proper representation of it. 153 if (o == null) 154 return; 155 156 PersistenceDelegate pd = getPersistenceDelegate(o.getClass()); 157 158 accessCounter++; 159 pd.writeObject(o, this); 160 accessCounter--; 161 162 } 163 164 /** 165 * Sets the {@link ExceptionListener} instance to be used for reporting 166 * recorable exceptions in the instantiation and initialization sequence. If 167 * the argument is <code>null</code> a default instance will be used that 168 * prints the thrown exception to <code>System.err</code>. 169 */ 170 public void setExceptionListener(ExceptionListener listener) 171 { 172 exceptionListener = (listener != null) 173 ? listener : DefaultExceptionListener.INSTANCE; 174 } 175 176 /** 177 * Returns the currently active {@link ExceptionListener} instance. 178 */ 179 public ExceptionListener getExceptionListener() 180 { 181 return exceptionListener; 182 } 183 184 public PersistenceDelegate getPersistenceDelegate(Class<?> type) 185 { 186 // This is not specified but the JDK behaves like this. 187 if (type == null) 188 return fakePersistenceDelegate; 189 190 // Treats all array classes in the same way and assigns 191 // them a shared PersistenceDelegate implementation tailored 192 // for array instantation and initialization. 193 if (type.isArray()) 194 return (PersistenceDelegate) delegates.get(Object[].class); 195 196 PersistenceDelegate pd = (PersistenceDelegate) delegates.get(type); 197 198 return (pd != null) ? pd : defaultPersistenceDelegate; 199 } 200 201 /** 202 * Sets the {@link PersistenceDelegate} instance for the given class. 203 * <p> 204 * Note: Throws a <code>NullPointerException</code> if the argument is 205 * <code>null</code>. 206 * </p> 207 * <p> 208 * Note: Silently ignores PersistenceDelegates for Array types and primitive 209 * wrapper classes. 210 * </p> 211 * <p> 212 * Note: Although this method is not declared <code>static</code> changes to 213 * the {@link PersistenceDelegate}s affect <strong>all</strong> 214 * {@link Encoder} instances. <strong>In this implementation</strong> the 215 * access is thread safe. 216 * </p> 217 */ 218 public void setPersistenceDelegate(Class<?> type, 219 PersistenceDelegate delegate) 220 { 221 // If the argument is null this will cause a NullPointerException 222 // which is expected behavior. 223 224 // This makes custom PDs for array, primitive types and their wrappers 225 // impossible but this is how the JDK behaves. 226 if (type.isArray() || type.isPrimitive() || type == Boolean.class 227 || type == Byte.class || type == Short.class || type == Integer.class 228 || type == Long.class || type == Float.class || type == Double.class) 229 return; 230 231 synchronized (delegates) 232 { 233 delegates.put(type, delegate); 234 } 235 236 } 237 238 public Object remove(Object oldInstance) 239 { 240 return candidates.remove(oldInstance); 241 } 242 243 /** 244 * Returns the replacement object which has been created by the encoder during 245 * the instantiation sequence or <code>null</code> if the object has not 246 * been processed yet. 247 * <p> 248 * Note: The <code>String</code> class acts as an endpoint for the 249 * inherently recursive algorithm of the {@link Encoder}. Therefore instances 250 * of <code>String</code> will always be returned by this method. In other 251 * words the assertion: <code> 252 * assert (anyEncoder.get(anyString) == anyString) 253 * </code< 254 * will always hold.</p> 255 * 256 * <p>Note: If <code>null</code> is requested, the result will 257 * always be <code>null</code>.</p> 258 */ 259 public Object get(Object oldInstance) 260 { 261 // String instances are handled in a special way. 262 // No one knows why this is not officially specified 263 // because this is a rather important design decision. 264 return (oldInstance == null) ? null : 265 (oldInstance.getClass() == String.class) ? 266 oldInstance : candidates.get(oldInstance); 267 } 268 269 /** 270 * <p> 271 * Note: If you call this method not from within an object instantiation and 272 * initialization sequence it will be silently ignored. 273 * </p> 274 */ 275 public void writeStatement(Statement stmt) 276 { 277 // Silently ignore out of bounds calls. 278 if (accessCounter <= 0) 279 return; 280 281 Object target = stmt.getTarget(); 282 283 Object newTarget = get(target); 284 if (newTarget == null) 285 { 286 writeObject(target); 287 newTarget = get(target); 288 } 289 290 Object[] args = stmt.getArguments(); 291 Object[] newArgs = new Object[args.length]; 292 293 for (int i = 0; i < args.length; i++) 294 { 295 newArgs[i] = get(args[i]); 296 if (newArgs[i] == null || isImmutableType(args[i].getClass())) 297 { 298 writeObject(args[i]); 299 newArgs[i] = get(args[i]); 300 } 301 } 302 303 Statement newStmt = new Statement(newTarget, stmt.getMethodName(), newArgs); 304 305 try 306 { 307 newStmt.execute(); 308 } 309 catch (Exception e) 310 { 311 exceptionListener.exceptionThrown(e); 312 } 313 314 } 315 316 /** 317 * <p> 318 * Note: If you call this method not from within an object instantiation and 319 * initialization sequence it will be silently ignored. 320 * </p> 321 */ 322 public void writeExpression(Expression expr) 323 { 324 // Silently ignore out of bounds calls. 325 if (accessCounter <= 0) 326 return; 327 328 Object target = expr.getTarget(); 329 Object value = null; 330 Object newValue = null; 331 332 try 333 { 334 value = expr.getValue(); 335 } 336 catch (Exception e) 337 { 338 exceptionListener.exceptionThrown(e); 339 return; 340 } 341 342 343 newValue = get(value); 344 345 if (newValue == null) 346 { 347 Object newTarget = get(target); 348 if (newTarget == null) 349 { 350 writeObject(target); 351 newTarget = get(target); 352 353 // May happen if exception was thrown. 354 if (newTarget == null) 355 { 356 return; 357 } 358 } 359 360 Object[] args = expr.getArguments(); 361 Object[] newArgs = new Object[args.length]; 362 363 for (int i = 0; i < args.length; i++) 364 { 365 newArgs[i] = get(args[i]); 366 if (newArgs[i] == null || isImmutableType(args[i].getClass())) 367 { 368 writeObject(args[i]); 369 newArgs[i] = get(args[i]); 370 } 371 } 372 373 Expression newExpr = new Expression(newTarget, expr.getMethodName(), 374 newArgs); 375 376 // Fakes the result of Class.forName(<primitiveType>) to make it possible 377 // to hand such a type to the encoding process. 378 if (value instanceof Class && ((Class) value).isPrimitive()) 379 newExpr.setValue(value); 380 381 // Instantiates the new object. 382 try 383 { 384 newValue = newExpr.getValue(); 385 386 candidates.put(value, newValue); 387 } 388 catch (Exception e) 389 { 390 exceptionListener.exceptionThrown(e); 391 392 return; 393 } 394 395 writeObject(value); 396 397 } 398 else if(value.getClass() == String.class || value.getClass() == Class.class) 399 { 400 writeObject(value); 401 } 402 403 } 404 405 /** Returns whether the given class is an immutable 406 * type which has to be handled differently when serializing it. 407 * 408 * <p>Immutable objects always have to be instantiated instead of 409 * modifying an existing instance.</p> 410 * 411 * @param type The class to test. 412 * @return Whether the first argument is an immutable type. 413 */ 414 boolean isImmutableType(Class type) 415 { 416 return type == String.class || type == Class.class 417 || type == Integer.class || type == Boolean.class 418 || type == Byte.class || type == Short.class 419 || type == Long.class || type == Float.class 420 || type == Double.class; 421 } 422 423 /** Sets the stream candidate for a given object. 424 * 425 * @param oldObject The object given to the encoder. 426 * @param newObject The object the encoder generated. 427 */ 428 void putCandidate(Object oldObject, Object newObject) 429 { 430 candidates.put(oldObject, newObject); 431 } 432 433 }