View Javadoc

1   /*
2    * Copyright 2004-2009 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of 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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.cubby.spi.beans.impl;
17  
18  import java.beans.BeanInfo;
19  import java.beans.IntrospectionException;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.lang.annotation.Annotation;
23  import java.lang.reflect.Array;
24  import java.lang.reflect.GenericArrayType;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.ParameterizedType;
28  import java.lang.reflect.Type;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.LinkedHashMap;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  
35  import org.seasar.cubby.spi.BeanDescProvider;
36  import org.seasar.cubby.spi.beans.BeanDesc;
37  import org.seasar.cubby.spi.beans.IllegalPropertyException;
38  import org.seasar.cubby.spi.beans.ParameterizedClassDesc;
39  import org.seasar.cubby.spi.beans.PropertyDesc;
40  import org.seasar.cubby.spi.beans.PropertyNotFoundException;
41  
42  /**
43   * {@link BeanDesc} のプロバイダの標準的な実装です。
44   * <p>
45   * {@link Introspector} によって生成されるメタ情報を元に {@link BeanDesc} を構築します。
46   * </p>
47   * 
48   * @author baba
49   * @since 2.0.0
50   */
51  public class DefaultBeanDescProvider implements BeanDescProvider {
52  
53  	/** プリミティブ型のデフォルト値の <code>Map</code>。 */
54  	private static final Map<Class<?>, Object> PRIMITIVE_TYPE_DEFAULT_VALUES;
55  	static {
56  		final Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
57  		map.put(boolean.class, Boolean.FALSE);
58  		map.put(char.class, Character.valueOf('\u0000'));
59  		map.put(byte.class, Byte.valueOf((byte) 0));
60  		map.put(short.class, Short.valueOf((short) 0));
61  		map.put(int.class, Integer.valueOf(0));
62  		map.put(long.class, Long.valueOf(0L));
63  		map.put(float.class, Float.valueOf(0F));
64  		map.put(double.class, Double.valueOf(0D));
65  		PRIMITIVE_TYPE_DEFAULT_VALUES = Collections.unmodifiableMap(map);
66  	}
67  
68  	/** <code>BeanDesc</code> のキャッシュ。 */
69  	private final Map<Class<?>, BeanDesc> beanDescCache = new ConcurrentHashMap<Class<?>, BeanDesc>(
70  			1024);
71  
72  	/**
73  	 * {@inheritDoc}
74  	 */
75  	public BeanDesc getBeanDesc(final Class<?> clazz) {
76  		if (beanDescCache.containsKey(clazz)) {
77  			return beanDescCache.get(clazz);
78  		}
79  
80  		synchronized (clazz) {
81  			if (beanDescCache.containsKey(clazz)) {
82  				return beanDescCache.get(clazz);
83  			}
84  
85  			try {
86  				final BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
87  				final BeanDesc beanDesc = new BeanDescImpl(clazz, beanInfo);
88  				beanDescCache.put(clazz, beanDesc);
89  				return beanDesc;
90  			} catch (final IntrospectionException e) {
91  				throw new IllegalStateException(e);
92  			}
93  		}
94  	}
95  
96  	/**
97  	 * {@link BeanDesc} の実装です。
98  	 * <p>
99  	 * {@link Introspector} に処理を委譲します。
100 	 * </p>
101 	 * 
102 	 * @author baba
103 	 * @since 2.0.0
104 	 */
105 	private static class BeanDescImpl implements BeanDesc {
106 
107 		/** JavaBean のクラス。 */
108 		private final Class<?> clazz;
109 
110 		/** {@link PropertyDesc} のキャッシュ。 */
111 		private final Map<String, PropertyDesc> propertyDescMap = new LinkedHashMap<String, PropertyDesc>();
112 
113 		/**
114 		 * インスタンス化します。
115 		 * 
116 		 * @param clazz
117 		 *            JavaBean のクラス
118 		 * @param beanInfo
119 		 *            JavaBean の情報
120 		 */
121 		public BeanDescImpl(final Class<?> clazz, final BeanInfo beanInfo) {
122 			this.clazz = clazz;
123 			for (final PropertyDescriptor propertyDescriptor : beanInfo
124 					.getPropertyDescriptors()) {
125 				propertyDescMap.put(propertyDescriptor.getName(),
126 						new PropertyDescImpl(clazz, propertyDescriptor));
127 			}
128 		}
129 
130 		/**
131 		 * {@inheritDoc}
132 		 */
133 		public boolean hasPropertyDesc(final String propertyName) {
134 			return propertyDescMap.containsKey(propertyName);
135 		}
136 
137 		/**
138 		 * {@inheritDoc}
139 		 */
140 		public PropertyDesc getPropertyDesc(final String propertyName)
141 				throws PropertyNotFoundException {
142 			if (!propertyDescMap.containsKey(propertyName)) {
143 				throw new PropertyNotFoundException(clazz, propertyName);
144 			}
145 			return propertyDescMap.get(propertyName);
146 		}
147 
148 		/**
149 		 * {@inheritDoc}
150 		 */
151 		public PropertyDesc[] getPropertyDescs() {
152 			return propertyDescMap.values().toArray(new PropertyDesc[0]);
153 		}
154 
155 	}
156 
157 	/**
158 	 * {@link PropertyDesc} の実装です。
159 	 * <p>
160 	 * {@link PropertyDescriptor} に処理を委譲します。
161 	 * </p>
162 	 * 
163 	 * @author baba
164 	 * @since 2.0.0
165 	 */
166 	private static class PropertyDescImpl implements PropertyDesc {
167 
168 		/** JavaBean のクラス。 */
169 		private final Class<?> clazz;
170 
171 		/** プロパティの記述。 */
172 		private final PropertyDescriptor propertyDescriptor;
173 
174 		/** パラメタ化されたクラスの記述。 */
175 		private final ParameterizedClassDesc parameterizedClassDesc;
176 
177 		/** アノテーションのキャッシュ。 */
178 		private final Map<Class<? extends Annotation>, Annotation> annotationCache = new HashMap<Class<? extends Annotation>, Annotation>();
179 
180 		/**
181 		 * インスタンス化します。
182 		 * 
183 		 * @param clazz
184 		 *            JavaBean のクラス
185 		 * @param propertyDescriptor
186 		 *            プロパティの記述
187 		 */
188 		PropertyDescImpl(final Class<?> clazz,
189 				final PropertyDescriptor propertyDescriptor) {
190 			this.clazz = clazz;
191 			this.propertyDescriptor = propertyDescriptor;
192 
193 			if (propertyDescriptor.getReadMethod() != null) {
194 				parameterizedClassDesc = createParameterizedClassDesc(propertyDescriptor
195 						.getReadMethod().getGenericReturnType());
196 			} else if (propertyDescriptor.getWriteMethod() != null) {
197 				parameterizedClassDesc = createParameterizedClassDesc(propertyDescriptor
198 						.getWriteMethod().getParameterTypes()[0]);
199 			} else {
200 				parameterizedClassDesc = null;
201 			}
202 		}
203 
204 		/**
205 		 * {@inheritDoc}
206 		 */
207 		public String getPropertyName() {
208 			return propertyDescriptor.getName();
209 		}
210 
211 		/**
212 		 * {@inheritDoc}
213 		 */
214 		public Class<?> getPropertyType() {
215 			return propertyDescriptor.getPropertyType();
216 		}
217 
218 		/**
219 		 * {@inheritDoc}
220 		 */
221 		public Method getReadMethod() {
222 			return propertyDescriptor.getReadMethod();
223 		}
224 
225 		/**
226 		 * {@inheritDoc}
227 		 */
228 		public boolean hasReadMethod() {
229 			return this.getReadMethod() != null;
230 		}
231 
232 		/**
233 		 * {@inheritDoc}
234 		 */
235 		public Method getWriteMethod() {
236 			return propertyDescriptor.getWriteMethod();
237 		}
238 
239 		/**
240 		 * {@inheritDoc}
241 		 */
242 		public boolean hasWriteMethod() {
243 			return this.getWriteMethod() != null;
244 		}
245 
246 		/**
247 		 * {@inheritDoc}
248 		 */
249 		public boolean isReadable() {
250 			return propertyDescriptor.getReadMethod() != null;
251 		}
252 
253 		/**
254 		 * {@inheritDoc}
255 		 */
256 		public boolean isWritable() {
257 			return propertyDescriptor.getWriteMethod() != null;
258 		}
259 
260 		/**
261 		 * {@inheritDoc}
262 		 */
263 		public Object getValue(final Object target)
264 				throws IllegalPropertyException {
265 			final Method method = this.getReadMethod();
266 			if (method == null) {
267 				throw new IllegalPropertyException(clazz, propertyDescriptor
268 						.getName(), new IllegalStateException(
269 						propertyDescriptor.getName() + " is not readable."));
270 			}
271 			try {
272 				return method.invoke(target);
273 			} catch (final IllegalAccessException e) {
274 				throw new IllegalPropertyException(clazz, propertyDescriptor
275 						.getName(), e);
276 			} catch (final InvocationTargetException e) {
277 				final Throwable t = e.getTargetException();
278 				if (t instanceof Error) {
279 					throw (Error) t;
280 				}
281 				throw new IllegalPropertyException(clazz, propertyDescriptor
282 						.getName(), e);
283 			}
284 		}
285 
286 		/**
287 		 * {@inheritDoc}
288 		 */
289 		public void setValue(final Object target, final Object value)
290 				throws IllegalPropertyException {
291 			final Method method = this.getWriteMethod();
292 			if (method == null) {
293 				throw new IllegalPropertyException(clazz, propertyDescriptor
294 						.getName(), new IllegalStateException(
295 						propertyDescriptor.getName() + " is not writable."));
296 			}
297 			try {
298 				final Class<?> propertyType = propertyDescriptor
299 						.getPropertyType();
300 				if (value == null && propertyType.isPrimitive()) {
301 					method.invoke(target, PRIMITIVE_TYPE_DEFAULT_VALUES
302 							.get(propertyType));
303 				} else {
304 					method.invoke(target, value);
305 				}
306 			} catch (final IllegalArgumentException e) {
307 				throw new IllegalPropertyException(clazz, propertyDescriptor
308 						.getName(), e);
309 			} catch (final IllegalAccessException e) {
310 				throw new IllegalPropertyException(clazz, propertyDescriptor
311 						.getName(), e);
312 			} catch (final InvocationTargetException e) {
313 				final Throwable t = e.getTargetException();
314 				if (t instanceof Error) {
315 					throw (Error) t;
316 				}
317 				throw new IllegalPropertyException(clazz, propertyDescriptor
318 						.getName(), e);
319 			}
320 		}
321 
322 		/**
323 		 * {@inheritDoc}
324 		 */
325 		public boolean isParameterized() {
326 			return parameterizedClassDesc != null
327 					&& parameterizedClassDesc.isParameterizedClass();
328 		}
329 
330 		/**
331 		 * {@inheritDoc}
332 		 */
333 		public ParameterizedClassDesc getParameterizedClassDesc() {
334 			return parameterizedClassDesc;
335 		}
336 
337 		/**
338 		 * {@inheritDoc}
339 		 */
340 		public <T extends Annotation> T getAnnotation(
341 				final Class<T> annotationClass) {
342 			if (annotationCache.containsKey(annotationClass)) {
343 				return annotationClass.cast(annotationCache
344 						.get(annotationClass));
345 			}
346 
347 			final Method readMethod = this.getReadMethod();
348 			if (readMethod != null) {
349 				final T annotation = findAnnotation(annotationClass, readMethod);
350 				if (annotation != null) {
351 					annotationCache.put(annotationClass, annotation);
352 					return annotation;
353 				}
354 			}
355 
356 			final Method writeMethod = this.getWriteMethod();
357 			if (writeMethod != null) {
358 				final T annotation = findAnnotation(annotationClass,
359 						writeMethod);
360 				if (annotation != null) {
361 					annotationCache.put(annotationClass, annotation);
362 					return annotation;
363 				}
364 			}
365 
366 			annotationCache.put(annotationClass, null);
367 			return null;
368 		}
369 
370 		/**
371 		 * 指定されたメソッドのアノテーションを検索します。
372 		 * <p>
373 		 * インターフェイスやスーパークラスに定義されたメソッドの定義からもアノテーションが見つかるまで検索します。
374 		 * アノテーションが見つからなかった場合は <code>null</code> を返します。
375 		 * </p>
376 		 * 
377 		 * @param <T>
378 		 *            アノテーションの型
379 		 * @param annotationClass
380 		 *            アノテーションの型
381 		 * @param method
382 		 *            メソッド
383 		 * @return アノテーションが見つかった場合はそのアノテーション、見つからなかった場合は <code>null</code>
384 		 */
385 		private static <T extends Annotation> T findAnnotation(
386 				final Class<T> annotationClass, final Method method) {
387 			final String methodName = method.getName();
388 			final Class<?>[] parameterTypes = method.getParameterTypes();
389 			for (Class<?> target = method.getDeclaringClass(); !target
390 					.equals(Object.class); target = target.getSuperclass()) {
391 				final T annotation = getAnnotation(annotationClass, target,
392 						methodName, parameterTypes);
393 				if (annotation != null) {
394 					return annotation;
395 				}
396 				final T annotationOfInterfaces = getAnnotationOfInterfaces(
397 						annotationClass, target, methodName, parameterTypes);
398 				if (annotationOfInterfaces != null) {
399 					return annotationOfInterfaces;
400 				}
401 			}
402 			return null;
403 		}
404 
405 		/**
406 		 * 指定されたクラスが実装するインターフェイスにメソッド名、パラメータ型でシグニチャを指定されたメソッドが定義されていれば、
407 		 * そのメソッドに定義されたアノテーションを返します。
408 		 * 
409 		 * @param <T>
410 		 *            アノテーションの型
411 		 * @param annotationClass
412 		 *            アノテーションの型
413 		 * @param clazz
414 		 *            クラス
415 		 * @param methodName
416 		 *            メソッド名
417 		 * @param parameterTypes
418 		 *            パラメータの型
419 		 * @return アノテーション
420 		 */
421 		private static <T extends Annotation> T getAnnotationOfInterfaces(
422 				final Class<T> annotationClass, final Class<?> clazz,
423 				final String methodName, final Class<?>[] parameterTypes) {
424 			for (final Class<?> interfaceClass : clazz.getInterfaces()) {
425 				final T annotation = getAnnotation(annotationClass,
426 						interfaceClass, methodName, parameterTypes);
427 				if (annotation != null) {
428 					return annotation;
429 				}
430 			}
431 			return null;
432 		}
433 
434 		/**
435 		 * 指定されたクラスにメソッド名、パラメータ型でシグニチャを指定されたメソッドが定義されていれば、
436 		 * そのメソッドに定義されたアノテーションを返します。
437 		 * 
438 		 * @param <T>
439 		 *            アノテーションの型
440 		 * @param annotationClass
441 		 *            アノテーションの型
442 		 * @param clazz
443 		 *            クラス
444 		 * @param methodName
445 		 *            メソッド名
446 		 * @param parameterTypes
447 		 *            パラメータの型
448 		 * @return アノテーション
449 		 */
450 		private static <T extends Annotation> T getAnnotation(
451 				final Class<T> annotationClass, final Class<?> clazz,
452 				final String methodName,
453 				@SuppressWarnings("unchecked") final Class[] parameterTypes) {
454 			try {
455 				final Method method = clazz.getDeclaredMethod(methodName,
456 						parameterTypes);
457 				if (method.isAnnotationPresent(annotationClass)) {
458 					return method.getAnnotation(annotationClass);
459 				}
460 			} catch (final NoSuchMethodException e) {
461 				// do nothing
462 			}
463 
464 			return null;
465 		}
466 
467 		/**
468 		 * {@inheritDoc}
469 		 */
470 		public boolean isAnnotationPresent(
471 				final Class<? extends Annotation> annotationClass) {
472 			return this.getAnnotation(annotationClass) != null;
473 		}
474 
475 	}
476 
477 	/**
478 	 * {@link ParameterizedClassDesc}の実装クラスです。
479 	 * 
480 	 * @since 2.0.0
481 	 * @author baba
482 	 */
483 	private static class ParameterizedClassDescImpl implements
484 			ParameterizedClassDesc {
485 
486 		/** 原型となるクラス */
487 		protected Class<?> rawClass;
488 
489 		/** 型引数を表す{@link ParameterizedClassDesc}の配列 */
490 		protected ParameterizedClassDesc[] arguments;
491 
492 		/**
493 		 * インスタンスを構築します。
494 		 */
495 		public ParameterizedClassDescImpl() {
496 		}
497 
498 		/**
499 		 * インスタンスを構築します。
500 		 * 
501 		 * @param rawClass
502 		 *            原型となるクラス
503 		 */
504 		public ParameterizedClassDescImpl(final Class<?> rawClass) {
505 			this.rawClass = rawClass;
506 		}
507 
508 		/**
509 		 * インスタンスを構築します。
510 		 * 
511 		 * @param rawClass
512 		 *            原型となるクラス
513 		 * @param arguments
514 		 *            型引数を表す{@link ParameterizedClassDesc}の配列
515 		 */
516 		public ParameterizedClassDescImpl(final Class<?> rawClass,
517 				final ParameterizedClassDesc[] arguments) {
518 			this.rawClass = rawClass;
519 			this.arguments = arguments;
520 		}
521 
522 		/**
523 		 * {@inheritDoc}
524 		 */
525 		public boolean isParameterizedClass() {
526 			return arguments != null;
527 		}
528 
529 		/**
530 		 * {@inheritDoc}
531 		 */
532 		public Class<?> getRawClass() {
533 			return rawClass;
534 		}
535 
536 		/**
537 		 * {@inheritDoc}
538 		 */
539 		public ParameterizedClassDesc[] getArguments() {
540 			return arguments;
541 		}
542 
543 	}
544 
545 	/**
546 	 * {@link Type}を表現する{@link ParameterizedClassDesc}を作成して返します。
547 	 * 
548 	 * @param type
549 	 *            型
550 	 * @return 型を表現する{@link ParameterizedClassDesc}
551 	 */
552 	private static ParameterizedClassDesc createParameterizedClassDesc(
553 			final Type type) {
554 		final Class<?> rowClass = getRawClass(type);
555 		if (rowClass == null) {
556 			return null;
557 		}
558 		final Type[] parameterTypes = getGenericParameter(type);
559 		if (parameterTypes == null) {
560 			final ParameterizedClassDescImpl desc = new ParameterizedClassDescImpl(
561 					rowClass);
562 			return desc;
563 		} else {
564 			final ParameterizedClassDesc[] parameterDescs = new ParameterizedClassDesc[parameterTypes.length];
565 			for (int i = 0; i < parameterTypes.length; ++i) {
566 				parameterDescs[i] = createParameterizedClassDesc(parameterTypes[i]);
567 			}
568 			final ParameterizedClassDescImpl desc = new ParameterizedClassDescImpl(
569 					rowClass, parameterDescs);
570 			return desc;
571 		}
572 	}
573 
574 	/**
575 	 * <code>type</code>の原型を返します。
576 	 * <p>
577 	 * <code>type</code>が原型でもパラメータ化された型でもない場合は<code>null</code>を返します。
578 	 * </p>
579 	 * 
580 	 * @param type
581 	 *            タイプ
582 	 * @return <code>type</code>の原型
583 	 */
584 	private static Class<?> getRawClass(final Type type) {
585 		if (Class.class.isInstance(type)) {
586 			return Class.class.cast(type);
587 		}
588 		if (ParameterizedType.class.isInstance(type)) {
589 			final ParameterizedType parameterizedType = ParameterizedType.class
590 					.cast(type);
591 			return getRawClass(parameterizedType.getRawType());
592 		}
593 		if (GenericArrayType.class.isInstance(type)) {
594 			final GenericArrayType genericArrayType = GenericArrayType.class
595 					.cast(type);
596 			final Class<?> rawClass = getRawClass(genericArrayType
597 					.getGenericComponentType());
598 			return Array.newInstance(rawClass, 0).getClass();
599 		}
600 		return null;
601 	}
602 
603 	/**
604 	 * <code>type</code>の型引数の配列を返します。
605 	 * <p>
606 	 * <code>type</code>がパラメータ化された型でない場合は<code>null</code>を返します。
607 	 * </p>
608 	 * 
609 	 * @param type
610 	 *            タイプ
611 	 * @return <code>type</code>の型引数の配列
612 	 */
613 	private static Type[] getGenericParameter(final Type type) {
614 		if (ParameterizedType.class.isInstance(type)) {
615 			return ParameterizedType.class.cast(type).getActualTypeArguments();
616 		}
617 		if (GenericArrayType.class.isInstance(type)) {
618 			return getGenericParameter(GenericArrayType.class.cast(type)
619 					.getGenericComponentType());
620 		}
621 		return null;
622 	}
623 
624 }