/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.seasar.extension.dxo.annotation.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import org.seasar.extension.dxo.annotation.AnnotationReader;
import org.seasar.extension.dxo.annotation.ConversionRule;
import org.seasar.extension.dxo.annotation.DatePattern;
import org.seasar.extension.dxo.annotation.DestPrefix;
import org.seasar.extension.dxo.annotation.DxoConverter;
import org.seasar.extension.dxo.annotation.ExcludeNull;
import org.seasar.extension.dxo.annotation.ExcludeWhitespace;
import org.seasar.extension.dxo.annotation.SourcePrefix;
import org.seasar.extension.dxo.annotation.TimePattern;
import org.seasar.extension.dxo.annotation.TimestampPattern;
import org.seasar.extension.dxo.converter.Converter;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.beans.util.BeanUtil;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.util.tiger.AnnotationUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;
/**
* DxoインタフェースまたはクラスやそのメソッドからTigerアノテーションを読み取るクラスです。
*
* @author koichik
*/
public class TigerAnnotationReader implements AnnotationReader {
/** S2コンテナ */
protected S2Container container;
/** 後続の{@link AnnotationReader} */
protected AnnotationReader next;
/** コンバータのキャッシュ */
protected Map<Class<?>, Map<String, Converter>> convertersCache = CollectionsUtil
.newConcurrentHashMap();
/**
* インスタンスを構築します。
*
* @param container
* S2コンテナ
*/
public TigerAnnotationReader(final S2Container container) {
this(container, null);
}
/**
* インスタンスを構築します。
*
* @param container
* S2コンテナ
* @param next
* 後続の{@link AnnotationReader}
*/
public TigerAnnotationReader(final S2Container container,
final AnnotationReader next) {
this.container = container.getRoot();
this.next = next;
}
@SuppressWarnings("unchecked")
public String getDatePattern(final Class dxoClass, final Method method) {
final DatePattern datePattern = getAnnotation(dxoClass, method,
DatePattern.class);
if (datePattern != null) {
return datePattern.value();
}
if (next != null) {
return next.getDatePattern(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public String getTimePattern(final Class dxoClass, final Method method) {
final TimePattern timePattern = getAnnotation(dxoClass, method,
TimePattern.class);
if (timePattern != null) {
return timePattern.value();
}
if (next != null) {
return next.getTimePattern(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public String getTimestampPattern(final Class dxoClass, final Method method) {
final TimestampPattern timestampPattern = getAnnotation(dxoClass,
method, TimestampPattern.class);
if (timestampPattern != null) {
return timestampPattern.value();
}
if (next != null) {
return next.getTimestampPattern(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public String getConversionRule(final Class dxoClass, final Method method) {
final ConversionRule mapConversion = method
.getAnnotation(ConversionRule.class);
if (mapConversion != null) {
return mapConversion.value();
}
if (next != null) {
return next.getConversionRule(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public boolean isExcludeNull(final Class dxoClass, final Method method) {
final ExcludeNull excludeNull = getAnnotation(dxoClass, method,
ExcludeNull.class);
if (excludeNull != null) {
return true;
}
if (next != null) {
return next.isExcludeNull(dxoClass, method);
}
return false;
}
@SuppressWarnings("unchecked")
public boolean isExcludeWhitespace(final Class dxoClass, final Method method) {
final ExcludeWhitespace excludeWhitespace = getAnnotation(dxoClass,
method, ExcludeWhitespace.class);
if (excludeWhitespace != null) {
return true;
}
if (next != null) {
return next.isExcludeWhitespace(dxoClass, method);
}
return false;
}
@SuppressWarnings("unchecked")
public String getSourcePrefix(Class dxoClass, Method method) {
final SourcePrefix sourcePrefix = getAnnotation(dxoClass, method,
SourcePrefix.class);
if (sourcePrefix != null) {
return sourcePrefix.value();
}
if (next != null) {
return next.getSourcePrefix(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public String getDestPrefix(Class dxoClass, Method method) {
final DestPrefix destPrefix = getAnnotation(dxoClass, method,
DestPrefix.class);
if (destPrefix != null) {
return destPrefix.value();
}
if (next != null) {
return next.getDestPrefix(dxoClass, method);
}
return null;
}
@SuppressWarnings("unchecked")
public Map getConverters(final Class destClass) {
final Map<String, Converter> converters = convertersCache
.get(destClass);
if (converters != null) {
return converters;
}
return createConverters(destClass);
}
/**
* 指定アノテーションを取得して返します。
* <p>
* メソッドに指定のアノテーションが付与されていればそれを返します。
* メソッドに指定のアノテーションが付与されていなければ、クラスに付与されているアノテーションを返します。
* メソッドにもクラスに指定アノテーションが付与されていなければ<code>null</code>を返します。
* </p>
*
* @param <T>
* アノテーションの型
* @param dxoClass
* Dxoクラス
* @param method
* Dxoメソッド
* @param annotationType
* アノテーションの型
* @return メソッドまたはクラスに付けられたアノテーション
*/
protected <T extends Annotation> T getAnnotation(final Class<?> dxoClass,
final Method method, final Class<T> annotationType) {
final T annotation = method.getAnnotation(annotationType);
if (annotation != null) {
return annotation;
}
return dxoClass.getAnnotation(annotationType);
}
/**
* クラスに指定されたコンバータの{@link Map}を返します。
* <p>
* 指定されたクラスのコンバータの指定されたプロパティについて、プロパティ名をキー、コンバータを値とする{@link Map}を作成します。
* </p>
*
* @param destClass
* 変換先のクラス
* @return クラスに指定されたコンバータの{@link Map}
*/
protected Map<String, Converter> createConverters(final Class<?> destClass) {
final Map<String, Converter> converters = CollectionsUtil.newHashMap();
final BeanDesc beanDesc = BeanDescFactory.getBeanDesc(destClass);
for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
final PropertyDesc propertyDesc = beanDesc.getPropertyDesc(i);
if (!propertyDesc.isWritable()) {
continue;
}
final Annotation[] annotations = (propertyDesc.hasWriteMethod()) ? propertyDesc
.getWriteMethod().getDeclaredAnnotations()
: propertyDesc.getField().getDeclaredAnnotations();
final Converter converter = detectConverter(annotations);
if (converter != null) {
converters.put(propertyDesc.getPropertyName(), converter);
}
}
convertersCache.put(destClass, converters);
return converters;
}
/**
* アノテーションの配列に{@link DxoConverter}メタアノテーションで注釈されたアノテーションが含まれていれば、
* そのアノテーションに従い{@link Converter}を作成して返します。
* <p>
* アノテーションの配列に{@link DxoConverter}メタアノテーションで注釈されたアノテーションが含まれていない場合は
* <code>null</code>を返します。
* </p>
*
* @param annotations
* プロパティのsetterメソッドまたはpublicフィールドに指定されたアノテーションの配列
* @return {@link DxoConverter}メタアノテーションで注釈されたアノテーションに従い作成された{@link Converter}
*/
protected Converter detectConverter(final Annotation[] annotations) {
for (final Annotation annotation : annotations) {
final Class<? extends Annotation> annotationType = annotation
.annotationType();
final Annotation metaAnnotation = annotationType
.getAnnotation(DxoConverter.class);
if (metaAnnotation == null) {
continue;
}
final DxoConverter dxoConverterAnnotation = DxoConverter.class
.cast(metaAnnotation);
final String converterName = dxoConverterAnnotation.value();
final Converter converter = Converter.class.cast(container
.getComponent(converterName));
final Map<?, ?> props = AnnotationUtil.getProperties(annotation);
BeanUtil.copyProperties(props, converter);
return converter;
}
return null;
}
}