DynamicAnnotationBuilder.java

package com.gh.mygreen.xlsmapper.xml;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import com.gh.mygreen.xlsmapper.xml.bind.AnnotationInfo;

import ognl.DefaultTypeConverter;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;


/**
 * Javassitを利用して、{@link Annotation}のインスタンスを動的に作成するクラス。
 * <p>独自のClassLoaderを設定することが可能。
 * <p>このクラスはシングルトンです。
 * 
 * @version 0.5
 * @author Naoki Takezoe
 * @author T.TSUCHIE
 *
 */
public class DynamicAnnotationBuilder {
    
    /**
     * シングルトンのインスタンス。
     */
    private static DynamicAnnotationBuilder INSTANCE = new DynamicAnnotationBuilder();
    
    private ClassLoader classLoader;
    
    private OgnlContext ognlContext;
    
    private DynamicAnnotationBuilder() {
        this.ognlContext = new OgnlContext(
                new MultipleLoaderClassResolver(), new DefaultTypeConverter(), new AllAllowMemberAccess());
        
    }
    
    /**
     * インスタンスを取得する。
     * @return
     */
    public static DynamicAnnotationBuilder getInstance() {
        return INSTANCE;
    }
    
    /**
     * アノテーションが定義された{@link ClassLoader}を設定する。
     * <p>nullの場合、標準のClassLoaderが使用される。
     * @param classLoader
     */
    public static void init(final ClassLoader classLoader) {
        getInstance().classLoader = classLoader;
    }
    
    /**
     * アノテーションが定義されたClassLoaderとJavaBeansを定義したClassLoaderを設定する。
     * @param classLoader アノテーションを見つけるために使用するClassLoader
     * @param propertyLoaders JavaBeansを見つけるために使用するClassLoader
     */
    public static void init(final ClassLoader classLoader, final ClassLoader[] propertyLoaders) {
        getInstance().classLoader = classLoader;
        
        if(propertyLoaders != null && propertyLoaders.length != 0) {
            Map<Integer, ClassLoader> loaderMap = new HashMap<>();
            for(ClassLoader propertyLoader : propertyLoaders) {
                loaderMap.put(propertyLoader.hashCode(), propertyLoader);
            }
            
            getInstance().ognlContext = new OgnlContext(
                    new AllAllowMemberAccess(), new MultipleLoaderClassResolver(), new DefaultTypeConverter(), loaderMap);
            
        }
    }
    
    /**
     * 指定したアノテーションのクラス情報から、アノテーションのインスタンスを組み立てる。
     * @param annoClass アノテーションのクラス
     * @param info アノテーションの情報
     * @return アノテーションのインスタンス。
     * @throws AnnotationReadException
     */
    public Annotation buildAnnotation(final Class<?> annoClass, final AnnotationInfo info) throws AnnotationReadException {
        
        final Map<String, Object> defaultValues = new HashMap<>();
        for(Method method : annoClass.getMethods()) {
            final Object defaultValue = method.getDefaultValue();
            if(defaultValue != null) {
                defaultValues.put(method.getName(), defaultValue);
            }
        }
        
        final Map<String, Object> xmlValues = new HashMap<>();
        for(String key : info.getAttributeKeys()) {
            try {
                Object value = Ognl.getValue(info.getAttribute(key), ognlContext, new Object());
                xmlValues.put(key, value);
            } catch(OgnlException e) {
                throw new AnnotationReadException(String.format("fail annotation attribute %s with ognl.", key), e);
            }
        }
        
        ClassLoader loader = classLoader;
        if(loader == null){
            loader = Thread.currentThread().getContextClassLoader();
        }
        
        Object obj = Proxy.newProxyInstance(loader, new Class[]{annoClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                        String name = method.getName();
                        if (name.equals("annotationType")) {
                            return annoClass;
                        } else if(xmlValues.containsKey(name)){
                            return xmlValues.get(name);
                        } else {
                            return defaultValues.get(name);
                        }
                    }
        });
        
        return (Annotation) obj;
    }
    
}