View Javadoc

1   /*
2    * Copyright 2004-2005, 2007-2008 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
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 		// if a simple getter is available
157 		if (reflectionInfo.getMethodHolder(propertyName).getAccessor() != null) {
158 			// use it
159 			return reflectionInfo.get(bean, propertyName);
160 		}
161 		// we're using an indexed getter
162 		List contents = new ArrayList();
163 		boolean hasMoreElements = true;
164 		Exception exception = null;
165 		// try to read elements from the indexed getter
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 		// if there are no elements ...
179 		if (contents.size() == 0) {
180 			// ... and our exception was NullPointer, that probably means
181 			// there was no array available to start with, so return null
182 			if (isExceptionOfType(exception, NullPointerException.class)) {
183 				return null;
184 			}
185 			// ... and our exception was ArrayIndexOutOfBoundsException,
186 			// return an empty array
187 			if (isExceptionOfType(exception, ArrayIndexOutOfBoundsException.class)) {
188 				return ClassUtils.createArray(getType(bean, propertyName).getComponentType(), 0);
189 			}
190 			// ... and we encountered a random exception
191 			// probably need to propagate this to the user
192 			throw (Exception) exception.fillInStackTrace();
193 		}
194 		// if there are some elements ...
195 		// ... and we found the end of the list of valid elements
196 		if (isExceptionOfType(exception, ArrayIndexOutOfBoundsException.class)) {
197 			// create a new array of the required type
198 			Object array = ClassUtils.createArray(getType(bean, propertyName).getComponentType(), contents.size());
199 			// copy the contents we've constructed into the array
200 			for (int i = 0; i < contents.size(); i++) {
201 				Array.set(array, i, contents.get(i));
202 			}
203 			return array;
204 		}
205 		// .. and we encountered an exception
206 		// rethrow the exception
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 				// assume the value is an array, and loop through all elements,
238 				// using the indexed setter to set each method.  The base
239 				// reflector should be checking to make sure the value passed
240 				// to this method is of the correct type, so it should be ok to
241 				// just assume we're dealing with an array
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 		// if we're dealing with an indexed mutator
283 		if (mutator == null) {
284 			// if there's no indexed mutator
285 			if (getIndexedMutator(bean, propertyName) == null) {
286 				// error... shouldn't even be calling this in the first place
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 			// there is an indexed mutator, which means it's an array, which means its not a primitive type
292 			return false;
293 		}
294 		// we're dealing with a simple mutator
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 }