1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.morph.reflect.reflectors;
17
18 import java.lang.reflect.Array;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.WeakHashMap;
26
27 import net.sf.composite.util.ObjectUtils;
28 import net.sf.morph.reflect.BeanReflector;
29 import net.sf.morph.reflect.ContainerReflector;
30 import net.sf.morph.reflect.InstantiatingReflector;
31 import net.sf.morph.reflect.ReflectionException;
32 import net.sf.morph.reflect.support.MethodHolder;
33 import net.sf.morph.reflect.support.ObjectIterator;
34 import net.sf.morph.reflect.support.ReflectionInfo;
35 import net.sf.morph.util.ClassUtils;
36
37 /**
38 * <p>
39 * A Reflector that exposes the properties of any Object as they are defined by
40 * the <a href="http://java.sun.com/products/javabeans/index.jsp">JavaBeans </a>
41 * specification. Also exposes any Object as a container.
42 * </p>
43 *
44 * <p>
45 * When an object is exposed as a container, the <code>getContainer</code>
46 * method returns an iterator that just iterates over the one reflected object.
47 * The <code>getType</code> method returns the type as specified by the
48 * property's setter method, if one is available. If no mutator is available, the
49 * <code>getType</code> method returns the type as specified by the property's
50 * getter method.
51 * </p>
52 *
53 * @author Matt Sgarlata
54 * @author Alexander Volanis
55 * @since Nov 7, 2004
56 */
57 public class ObjectReflector extends BaseBeanReflector implements InstantiatingReflector, ContainerReflector {
58
59 private static final Class[] REFLECTABLE_TYPES = new Class[] {
60 Object.class
61 };
62
63 /**
64 * Indicates whether primitive assignments to <code>null</code> are allowed.
65 * If they are allowed, primitive assignments to <code>null</code> will be
66 * ignored. If they are not allowed, a ReflectionException will be thrown.
67 */
68 private boolean allowNullPrimitiveAssignment = true;
69
70 private static final Map reflectionCache = new WeakHashMap();
71
72 /**
73 * {@inheritDoc}
74 */
75 public Class[] getReflectableClassesImpl() {
76 return REFLECTABLE_TYPES;
77 }
78
79 /**
80 * {@inheritDoc}
81 */
82 protected String[] getPropertyNamesImpl(Object bean) throws Exception {
83 String[] propertyNames = getReflectionInfo(bean.getClass()).getPropertyNames();
84 List propertyNamesWithoutClass = new ArrayList();
85 for (int i = 0; i < propertyNames.length; i++) {
86 if (!BeanReflector.IMPLICIT_PROPERTY_CLASS.equals(propertyNames[i])) {
87 propertyNamesWithoutClass.add(propertyNames[i]);
88 }
89 }
90 return (String[]) propertyNamesWithoutClass.toArray(new String[propertyNamesWithoutClass.size()]);
91 }
92
93 private Method getMutator(Object bean, String propertyName) throws Exception {
94 return getMethodHolder(bean, propertyName).getMutator();
95 }
96
97 private Method getAccessor(Object bean, String propertyName) throws Exception {
98 return getMethodHolder(bean, propertyName).getAccessor();
99 }
100
101 private Method getIndexedMutator(Object bean, String propertyName) throws Exception {
102 return getMethodHolder(bean, propertyName).getIndexedMutator();
103 }
104
105 private Method getIndexedAccessor(Object bean, String propertyName) throws Exception {
106 return getMethodHolder(bean, propertyName).getIndexedAccessor();
107 }
108
109 private MethodHolder getMethodHolder(Object bean, String propertyName) {
110 return getReflectionInfo(bean.getClass()).getMethodHolder(propertyName);
111 }
112
113 /**
114 * Returns the type of the property based on the parameter type for the
115 * property's setter method. If there is no setter method available,
116 * returns the return type of the property accessor method.
117 */
118 protected Class getTypeImpl(Object bean, String propertyName) throws Exception {
119 if (getReflectionInfo(bean.getClass()).isWriteable(propertyName)) {
120 Method mutator = getMutator(bean, propertyName);
121 if (mutator == null) {
122 return ClassUtils.getArrayClass(getIndexedMutator(bean, propertyName).getParameterTypes()[1]);
123 }
124 return mutator.getParameterTypes()[0];
125 }
126 Method accessor = getAccessor(bean, propertyName);
127 if (accessor == null) {
128 Method indexedAccessor = getIndexedAccessor(bean, propertyName);
129 return ClassUtils.getArrayClass(indexedAccessor.getReturnType());
130 }
131 return accessor.getReturnType();
132 }
133
134 /**
135 * {@inheritDoc}
136 */
137 protected boolean isReadableImpl(Object bean, String propertyName) throws Exception {
138 return getReflectionInfo(bean.getClass()).isReadable(propertyName);
139 }
140
141 /**
142 * {@inheritDoc}
143 */
144 protected boolean isWriteableImpl(Object bean, String propertyName) throws Exception {
145 return getReflectionInfo(bean.getClass()).isWriteable(propertyName);
146 }
147
148 /**
149 * Retrieves the value of the property <code>propertyName</code> in bean
150 * <code>bean</code>. If the property is an indexed property that is only
151 * accessible via an indexed getter method of the form <code>get(int)</code>,
152 * this implementation is O(n). Otherwise, this implementation is O(1).
153 */
154 protected Object getImpl(Object bean, String propertyName) throws Exception {
155 ReflectionInfo reflectionInfo = getReflectionInfo(bean.getClass());
156
157 if (reflectionInfo.getMethodHolder(propertyName).getAccessor() != null) {
158
159 return reflectionInfo.get(bean, propertyName);
160 }
161
162 List contents = new ArrayList();
163 boolean hasMoreElements = true;
164 Exception exception = null;
165
166 while (hasMoreElements) {
167 try {
168 Object value = reflectionInfo.get(bean, propertyName,
169 new Integer(contents.size()));
170 contents.add(value);
171 }
172 catch (Exception e) {
173 exception = e;
174 hasMoreElements = false;
175 }
176 }
177
178
179 if (contents.size() == 0) {
180
181
182 if (isExceptionOfType(exception, NullPointerException.class)) {
183 return null;
184 }
185
186
187 if (isExceptionOfType(exception, ArrayIndexOutOfBoundsException.class)) {
188 return ClassUtils.createArray(getType(bean, propertyName).getComponentType(), 0);
189 }
190
191
192 throw (Exception) exception.fillInStackTrace();
193 }
194
195
196 if (isExceptionOfType(exception, ArrayIndexOutOfBoundsException.class)) {
197
198 Object array = ClassUtils.createArray(getType(bean, propertyName).getComponentType(), contents.size());
199
200 for (int i = 0; i < contents.size(); i++) {
201 Array.set(array, i, contents.get(i));
202 }
203 return array;
204 }
205
206
207 throw (Exception) exception.fillInStackTrace();
208 }
209
210 private boolean isExceptionOfType(Exception exception, Class type) {
211 return exception instanceof InvocationTargetException
212 && type.isInstance(((InvocationTargetException) exception)
213 .getTargetException());
214 }
215
216 /**
217 * {@inheritDoc}
218 */
219 protected void setImpl(Object bean, String propertyName, Object value)
220 throws Exception {
221 if (isPrimitiveSetter(bean, propertyName) && value == null) {
222 if (isAllowNullPrimitiveAssignment()) {
223 if (log.isWarnEnabled()) {
224 log.warn("Attempted to set primitive property " + propertyName + " to null in bean " + ObjectUtils.getObjectDescription(bean));
225 }
226 }
227 else {
228 throw new ReflectionException("Cannot set the primitive property " + propertyName + " to null");
229 }
230 }
231 else {
232 ReflectionInfo reflectionInfo = getReflectionInfo(bean.getClass());
233 if (reflectionInfo.getMethodHolder(propertyName).getMutator() != null) {
234 reflectionInfo.set(bean, propertyName, value);
235 }
236 else {
237
238
239
240
241
242 if (value != null) {
243 for (int i=0; i<Array.getLength(value); i++) {
244 reflectionInfo.set(bean, propertyName, new Integer(i),
245 Array.get(value, i));
246 }
247 }
248 }
249 }
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 protected Object newInstanceImpl(Class clazz, Object parameters) throws Exception {
256 return clazz.newInstance();
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 protected Class getContainedTypeImpl(Class clazz) throws Exception {
263 return Object.class;
264 }
265
266 /**
267 * {@inheritDoc}
268 */
269 protected Iterator getIteratorImpl(Object container) throws Exception {
270 return new ObjectIterator(container);
271 }
272
273 /**
274 * Learn whether the mutator/setter method for the given property is primitive.
275 * @param bean
276 * @param propertyName
277 * @return boolean
278 * @throws Exception
279 */
280 protected boolean isPrimitiveSetter(Object bean, String propertyName) throws Exception {
281 Method mutator = getMutator(bean, propertyName);
282
283 if (mutator == null) {
284
285 if (getIndexedMutator(bean, propertyName) == null) {
286
287 throw new IllegalArgumentException(
288 propertyName
289 + " does not have any mutators, so it doesn't make sense to be checking if the setter for the property is primtive, since the property doesn't exist");
290 }
291
292 return false;
293 }
294
295 return mutator.getParameterTypes()[0].isPrimitive();
296 }
297
298 /**
299 * Get the ReflectionInfo for the given Class.
300 * @param clazz
301 * @return ReflectionInfo
302 */
303 protected ReflectionInfo getReflectionInfo(Class clazz) {
304 synchronized (reflectionCache) {
305 ReflectionInfo reflectionInfo = (ReflectionInfo) reflectionCache.get(clazz);
306 if (reflectionInfo == null) {
307 reflectionInfo = new ReflectionInfo(clazz);
308 reflectionCache.put(clazz, reflectionInfo);
309 }
310 return reflectionInfo;
311 }
312 }
313
314 /**
315 * Returns <code>true</code>.
316 * @return <code>true</code>
317 */
318 public boolean isStrictlyTyped() {
319 return true;
320 }
321
322 /**
323 * Indicates whether primitive assignments to <code>null</code> are
324 * allowed. If they are allowed, primitive assignments to <code>null</code>
325 * will be ignored. If they are not allowed, a ReflectionException will be
326 * thrown.
327 *
328 * @return whether primitive assignments to <code>null</code> are allowed
329 */
330 public boolean isAllowNullPrimitiveAssignment() {
331 return allowNullPrimitiveAssignment;
332 }
333
334 /**
335 * Sets whether primitive assignments to <code>null</code> are allowed. If
336 * they are allowed, primitive assignments to <code>null</code> will be
337 * ignored. If they are not allowed, a ReflectionException will be thrown.
338 *
339 * @param allowNullPrimitiveAssignment
340 * whether primitive assignments to <code>null</code> are
341 * allowed
342 */
343 public void setAllowNullPrimitiveAssignment(boolean allowNullPrimitiveAssignment) {
344 this.allowNullPrimitiveAssignment = allowNullPrimitiveAssignment;
345 }
346
347 }