001: /*
002: * Copyright (C) 2004, 2005, 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 07. March 2004 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.converters.reflection;
013:
014: import com.thoughtworks.xstream.core.JVM;
015:
016: import java.io.ByteArrayInputStream;
017: import java.io.ByteArrayOutputStream;
018: import java.io.DataOutputStream;
019: import java.io.IOException;
020: import java.io.ObjectInputStream;
021: import java.io.ObjectStreamClass;
022: import java.io.ObjectStreamConstants;
023: import java.io.Serializable;
024: import java.lang.reflect.Constructor;
025: import java.lang.reflect.Field;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Modifier;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Map;
032:
033: /**
034: * Pure Java ObjectFactory that instantiates objects using standard Java reflection, however the types of objects
035: * that can be constructed are limited.
036: * <p/>
037: * Can newInstance: classes with public visibility, outer classes, static inner classes, classes with default constructors
038: * and any class that implements java.io.Serializable.
039: * Cannot newInstance: classes without public visibility, non-static inner classes, classes without default constructors.
040: * Note that any code in the constructor of a class will be executed when the ObjectFactory instantiates the object.
041: * </p>
042: * @author Joe Walnes
043: */
044: public class PureJavaReflectionProvider implements ReflectionProvider {
045:
046: private transient Map serializedDataCache = Collections
047: .synchronizedMap(new HashMap());
048: protected FieldDictionary fieldDictionary;
049:
050: public PureJavaReflectionProvider() {
051: this (new FieldDictionary(new ImmutableFieldKeySorter()));
052: }
053:
054: public PureJavaReflectionProvider(FieldDictionary fieldDictionary) {
055: this .fieldDictionary = fieldDictionary;
056: }
057:
058: public Object newInstance(Class type) {
059: try {
060: Constructor[] constructors = type.getDeclaredConstructors();
061: for (int i = 0; i < constructors.length; i++) {
062: if (constructors[i].getParameterTypes().length == 0) {
063: if (!Modifier.isPublic(constructors[i]
064: .getModifiers())) {
065: constructors[i].setAccessible(true);
066: }
067: return constructors[i].newInstance(new Object[0]);
068: }
069: }
070: if (Serializable.class.isAssignableFrom(type)) {
071: return instantiateUsingSerialization(type);
072: } else {
073: throw new ObjectAccessException("Cannot construct "
074: + type.getName()
075: + " as it does not have a no-args constructor");
076: }
077: } catch (InstantiationException e) {
078: throw new ObjectAccessException("Cannot construct "
079: + type.getName(), e);
080: } catch (IllegalAccessException e) {
081: throw new ObjectAccessException("Cannot construct "
082: + type.getName(), e);
083: } catch (InvocationTargetException e) {
084: if (e.getTargetException() instanceof RuntimeException) {
085: throw (RuntimeException) e.getTargetException();
086: } else if (e.getTargetException() instanceof Error) {
087: throw (Error) e.getTargetException();
088: } else {
089: throw new ObjectAccessException("Constructor for "
090: + type.getName() + " threw an exception", e
091: .getTargetException());
092: }
093: }
094: }
095:
096: private Object instantiateUsingSerialization(Class type) {
097: try {
098: byte[] data;
099: if (serializedDataCache.containsKey(type)) {
100: data = (byte[]) serializedDataCache.get(type);
101: } else {
102: ByteArrayOutputStream bytes = new ByteArrayOutputStream();
103: DataOutputStream stream = new DataOutputStream(bytes);
104: stream.writeShort(ObjectStreamConstants.STREAM_MAGIC);
105: stream.writeShort(ObjectStreamConstants.STREAM_VERSION);
106: stream.writeByte(ObjectStreamConstants.TC_OBJECT);
107: stream.writeByte(ObjectStreamConstants.TC_CLASSDESC);
108: stream.writeUTF(type.getName());
109: stream.writeLong(ObjectStreamClass.lookup(type)
110: .getSerialVersionUID());
111: stream.writeByte(2); // classDescFlags (2 = Serializable)
112: stream.writeShort(0); // field count
113: stream.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);
114: stream.writeByte(ObjectStreamConstants.TC_NULL);
115: data = bytes.toByteArray();
116: serializedDataCache.put(type, data);
117: }
118:
119: ObjectInputStream in = new ObjectInputStream(
120: new ByteArrayInputStream(data));
121: return in.readObject();
122: } catch (IOException e) {
123: throw new ObjectAccessException("Cannot create "
124: + type.getName() + " by JDK serialization", e);
125: } catch (ClassNotFoundException e) {
126: throw new ObjectAccessException("Cannot find class "
127: + e.getMessage());
128: }
129: }
130:
131: public void visitSerializableFields(Object object,
132: ReflectionProvider.Visitor visitor) {
133: for (Iterator iterator = fieldDictionary.fieldsFor(object
134: .getClass()); iterator.hasNext();) {
135: Field field = (Field) iterator.next();
136: if (!fieldModifiersSupported(field)) {
137: continue;
138: }
139: validateFieldAccess(field);
140: try {
141: Object value = field.get(object);
142: visitor.visit(field.getName(), field.getType(), field
143: .getDeclaringClass(), value);
144: } catch (IllegalArgumentException e) {
145: throw new ObjectAccessException("Could not get field "
146: + field.getClass() + "." + field.getName(), e);
147: } catch (IllegalAccessException e) {
148: throw new ObjectAccessException("Could not get field "
149: + field.getClass() + "." + field.getName(), e);
150: }
151: }
152: }
153:
154: public void writeField(Object object, String fieldName,
155: Object value, Class definedIn) {
156: Field field = fieldDictionary.field(object.getClass(),
157: fieldName, definedIn);
158: validateFieldAccess(field);
159: try {
160: field.set(object, value);
161: } catch (IllegalArgumentException e) {
162: throw new ObjectAccessException("Could not set field "
163: + object.getClass() + "." + field.getName(), e);
164: } catch (IllegalAccessException e) {
165: throw new ObjectAccessException("Could not set field "
166: + object.getClass() + "." + field.getName(), e);
167: }
168: }
169:
170: public Class getFieldType(Object object, String fieldName,
171: Class definedIn) {
172: return fieldDictionary.field(object.getClass(), fieldName,
173: definedIn).getType();
174: }
175:
176: public boolean fieldDefinedInClass(String fieldName, Class type) {
177: try {
178: Field field = fieldDictionary.field(type, fieldName, null);
179: return fieldModifiersSupported(field)
180: || Modifier.isTransient(field.getModifiers());
181: } catch (ObjectAccessException e) {
182: return false;
183: }
184: }
185:
186: protected boolean fieldModifiersSupported(Field field) {
187: return !(Modifier.isStatic(field.getModifiers()) || Modifier
188: .isTransient(field.getModifiers()));
189: }
190:
191: protected void validateFieldAccess(Field field) {
192: if (Modifier.isFinal(field.getModifiers())) {
193: if (JVM.is15()) {
194: field.setAccessible(true);
195: } else {
196: throw new ObjectAccessException("Invalid final field "
197: + field.getDeclaringClass().getName() + "."
198: + field.getName());
199: }
200: }
201: }
202:
203: public Field getField(Class definedIn, String fieldName) {
204: return fieldDictionary.field(definedIn, fieldName, null);
205: }
206:
207: public void setFieldDictionary(FieldDictionary dictionary) {
208: this .fieldDictionary = dictionary;
209: }
210:
211: protected Object readResolve() {
212: serializedDataCache = Collections
213: .synchronizedMap(new HashMap());
214: return this;
215: }
216:
217: }
|