アノテーションの勉強

スタティック変数アノテーションを利用しやすくするための方法を考えながら、Seasar2の勉強。


J2SE5.0のアノテーションについてちょっと学習。


IBMdeveloper worksが役だった。




S2Daoってどうやってインターフェースだけなのに処理を実行できてるんだろー。これも調査。



S2DaoInterceptorをみて感動。


Interceptorのすごさがわかった。
なるほど。
よこどりできるってのはこんなこともできるってことなんだ。なんでもありだ。すごい。




DICONファイルを使わないでアスペクトってさせることができるのかなー。


おっ、「diconファイルを使用しないでアスペクトを組み込む方法」ってそのままの記述がある。
簡単にできそう。


なんか作れそうな気がしてきた。



今知ってること
アノテーションは3種類あって


で、アノテーションのターゲットには

  • TYPE
  • FIELD
  • METHOD
  • PARAMETER
  • CONSTRUCTOR
  • LOCAL_VARIABLE
  • ANNOTATION_TYPE
  • PACKAGE

こんなにあるけど、今必要なのはTYPEとMETHODくらいかなー。


どのアノテーションを適用しているかのきっかけはサフィックスで判断?うーん。これは流れにまかせよう。


スタティック変数アノテーションの種類は、

    public static final String logon_INFO = "name=logonForm, scope=request";
    public static final String logon_VALIDATE = "true";
    public static final String logon_ERRFORWARD = "logonError";
  • 上記2つの複合型

みたいな感じかなー


とりあえずは1つのスタティック変数で複数の情報を定義しているアノテーションから作ってみよう。


テストはこんなもんかな。

public class ClassAnnotationTest extends TestCase {

    public void testClassOneFieldAnnotation() {
        OneFieldAnnotation annotation = (OneFieldAnnotation) AnnotationUtil
                .get(OneField.class, OneFieldAnnotation.class);
        
        assertNotNull(annotation);
        assertEquals("testForm", annotation.name());
        assertEquals("request", annotation.scope());
        assertEquals(true, annotation.validate());
    }

}
public interface OneFieldAnnotation {

    public static final String TARGET = "TYPE";
    
    public static final String SUFFIX = "ACTION";
    
    public String name();
    
    public String scope();
    
    public boolean validate();

}
public class OneField {

    public static final String[] ACTION = { "name=testForm", "scope=request",
            "validate=true" };

}

Eclipseの力でコンパイルを通してテスト実行。そして失敗。


グリーンバーにするためにAnnotatilUtilを作成

public class AnnotationUtil {

    private static final String SUFFIX = "SUFFIX";

    public static Object get(Class clazz, Class annClazz) {
        BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz);
        Field annField = annBeanDesc.getField(SUFFIX);
        String suffix = (String) FieldUtil.get(annField, null);

        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);
        if (!beanDesc.hasField(suffix)) {
            return null;
        }

        Field field = beanDesc.getField(suffix);
        Map params = AnnotationConfigUtil.getParameterMap(field);

        Aspect aspect = new AspectImpl(new ClassAnnotationInterceptor(params));
        AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect });
        return aopProxy.create();
    }

}

あと、ClassAnnotationInterceptorも

public class ClassAnnotationInterceptor extends AbstractInterceptor {

    private Map params;

    public ClassAnnotationInterceptor(Map params) {
        this.params = params;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        if (!MethodUtil.isAbstract(method)) {
            return invocation.proceed();
        }

        Object ret = params.get(method.getName());
        Class retType = method.getReturnType();
        if (retType.isPrimitive()) {
            return NumberConversionUtil.convertPrimitiveWrapper(retType, ret);
        } else if (Number.class.isAssignableFrom(retType)) {
            return NumberConversionUtil.convertNumber(retType, ret);
        }
        return ret;
    }

}

こんな感じ。ほとんどをS2DaoInterceptorの真似をして作った。


テスト実行。グリーンバー


だけど、うれしくないなー
やる気がないせいか・・・






テストを作りながらやる気をだそうかな。



簡単なテストからしてみるか。

    public void testNotDefineForClass() {
        // 対象となるアノテーションインターフェースが定義されてない場合、
        // nullが戻ってくることを確認。
        NotDefineAnnotation annotation = (NotDefineAnnotation) AnnotationUtil
                .get(OneField.class, NotDefineAnnotation.class);

        assertNull(annotation);
    }

    public void testNotDefineSuffix() {
        // AnnotationインターフェースにSUFFIXが定義されていない場合、
        // インタフェース名をSUFFIXとして処理を行う。
        NotDefineSuffixAnnotation annotation = (NotDefineSuffixAnnotation) AnnotationUtil
                .get(NotDefineSuffix.class, NotDefineSuffixAnnotation.class);

        assertNotNull(annotation);
        assertEquals("testName", annotation.name());
    }

これは通った。


TARGETがTYPE以外の場合、nullがかえってくるようにしよう。定義されていない場合は・・・きちんとインターフェイスを実装して返してあげよう。

    public void testIllegalTarget() {
        // AnnotationインターフェースのTARGETがTYPE以外の場合、
        // nullが戻ってくることを確認。
        IllegalTargetAnnotation annotation = (IllegalTargetAnnotation) AnnotationUtil
                .get(OneField.class, IllegalTargetAnnotation.class);
        
        assertNull(annotation);
    }
    
    public void testNotDefineTarget() {
        // AnnotationインターフェースにTARGETが定義されていない場合でも
        // Annotationのターゲットとなっていることを確認
        NotDefineTargetAnnotation annotation = (NotDefineTargetAnnotation) AnnotationUtil
                .get(OneField.class, NotDefineTargetAnnotation.class);

        assertNotNull(annotation);
    }


次は、マーカー・アノテーションとか単一値アノテーションのテスト

    public void testMarkerAnnotation() {
        OneFieldMarkerAnnotation annotation = (OneFieldMarkerAnnotation) AnnotationUtil
                .get(OneField.class, OneFieldMarkerAnnotation.class);

        assertNotNull(annotation);
    }

    public void testSingleValueAnnotation() {
        OneFieldSingleValueAnnotation annotation = (OneFieldSingleValueAnnotation) AnnotationUtil
                .get(OneField.class, OneFieldSingleValueAnnotation.class);
        
        assertNotNull(annotation);
        assertEquals("test", annotation.value());
    }

    public void testFullAnnotation() {
        OneFieldFullAnnotation annotation = (OneFieldFullAnnotation) AnnotationUtil
                .get(OneField.class, OneFieldFullAnnotation.class);

        assertNotNull(annotation);
        assertEquals("testForm", annotation.name());
        assertEquals("request", annotation.scope());
        assertEquals(true, annotation.validate());
    }

その前につくっていたtestClassOneFieldAnnotation()はtestFullAnnotation()に名前を変えてみた。
こっちのほうがしっくりくるし。


あとAnnotationUtil#get()の処理内容も修正して、ClassAnnotationInterceptorをFullAnnotationInterceptor、SingleValueAnnotationInterceptorの2つにわけた。

public class AnnotationUtil {

    private static final String SUFFIX = "SUFFIX";
    
    private static final String TARGET = "TARGET";
    
    private static final String TARGET_TYPE = "TYPE";

    public static Object get(Class clazz, Class annClazz) {
        BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz);
        if (!isTypeAnnotation(annBeanDesc)) {
            return null;
        }
        
        String suffix = getAnnotationSuffix(annBeanDesc);
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);
        if (!beanDesc.hasField(suffix)) {
            return null;
        }

        Field field = beanDesc.getField(suffix);
        if (field.getType().isArray()) {
            Map params = AnnotationConfigUtil.getParameterMap(field);
            Aspect aspect = new AspectImpl(
                    new FullAnnotationInterceptor(params));
            AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect });
            return aopProxy.create();
        } else {
            String value = AnnotationConfigUtil.getParameter(field);
            Aspect aspect = new AspectImpl(
                    new SingleValueAnnotationInterceptor(value));
            AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect });
            return aopProxy.create();
        }
    }
    
    private static boolean isTypeAnnotation(BeanDesc beanDesc) {
        if (beanDesc.hasField(TARGET)) {
            Field field = beanDesc.getField(TARGET);
            String target = (String) FieldUtil.get(field, null);
            return TARGET_TYPE.equals(target);
        }
        return true;
    }

    private static String getAnnotationSuffix(BeanDesc beanDesc) {
        if (beanDesc.hasField(SUFFIX)) {
            Field field = beanDesc.getField(SUFFIX);
            return (String) FieldUtil.get(field, null);
        } else {
            return ClassUtil.getShortClassName(beanDesc.getBeanClass())
                    .toUpperCase();
        }
    }
}


次はデフォルト値の適用。
テストはこんな感じで

    public void testDefaultValue() {
        DefaultValueAnnotation annotation = (DefaultValueAnnotation) AnnotationUtil
                .get(OneField.class, DefaultValueAnnotation.class);
        
        assertNotNull(annotation);
        assertEquals("/test/path", annotation.path());
    }

アノテーションインターフェースは

public interface DefaultValueAnnotation {

    public static final String TARGET = "TYPE";

    public static final String SUFFIX = "FULL";

    public String name();

    public String scope();

    public boolean validate();
    
    public static final String path_DEFAULT = "/test/path";
    
    public String path();

}

これでいいと思う。なんかアノテーションインターフェースの中にアノテーションがあるって雰囲気になるのかなー。


とりあえず、テストを実行して失敗することを確認したけど、これを作る前にメソッドごとのアノテーションを先につくることにしよう。




いまさら、思ったんだけどマーカー・アノテーションって、空のインターフェースを明確にしたものって感じなのかな・・・・もしかして。
ってことは、作っても使う用途がないかも・・・。

テストから削除。


それで、メソッドごとのアノテーションの実装に入ろう。最初のテストは

public class MethodAnnotationTest extends TestCase {

    public void testSingleValueAnnotation() {
        Method method = OneFieldMethod.class.getDeclaredMethods()[0];
        OneFieldSingleValueAnnotation annotation = (OneFieldSingleValueAnnotation) AnnotationUtil
                .get(method, OneFieldSingleValueAnnotation.class);

        assertNotNull(annotation);
        assertEquals("test", annotation.value());
    }
}

で、アノテーションインターフェース(タイプ?)は

public interface OneFieldSingleValueAnnotation {

    public static final String TARGET = "METHOD";

    public static final String SUFFIX = "SINGLEVALUE";
    
    public String value();

}

で、アノテーションをつけてるクラスは

public class OneFieldMethod {

    public static final String getSingleValue_SINGLEVALUE = "test";

    public String getSingleValue() {
        return "singleValue";
    }

}

こんな感じ。Eclipseの力でコンパイルを通してテスト実行。そして失敗。


次はグリーンバーにする作業だけど、これまたAnnotationUtilを修正しないといけないなー。

public class AnnotationUtil {

    private static final String SUFFIX = "SUFFIX";

    private static final String TARGET = "TARGET";

    private static final String TARGET_TYPE = "TYPE";

    private static final String TARGET_METHOD = "METHOD";

    public static Object get(Class clazz, Class annClazz) {
        BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz);
        if (!isTypeAnnotation(annBeanDesc)) {
            return null;
        }

        String suffix = getAnnotationSuffix(annBeanDesc);
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);

        return createAnnotationObject(beanDesc, annClazz, suffix);
    }

    private static boolean isTypeAnnotation(BeanDesc beanDesc) {
        if (beanDesc.hasField(TARGET)) {
            Field field = beanDesc.getField(TARGET);
            String target = (String) FieldUtil.get(field, null);
            return TARGET_TYPE.equals(target);
        }
        return true;
    }

    // -----------------------------------------------------------------------

    public static Object get(Method method, Class annClazz) {
        BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz);
        if (!isMethodAnnotation(annBeanDesc)) {
            return null;
        }

        String suffix = method.getName() + "_"
                + getAnnotationSuffix(annBeanDesc);
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(method
                .getDeclaringClass());

        return createAnnotationObject(beanDesc, annClazz, suffix);
    }

    private static boolean isMethodAnnotation(BeanDesc beanDesc) {
        if (beanDesc.hasField(TARGET)) {
            Field field = beanDesc.getField(TARGET);
            String target = (String) FieldUtil.get(field, null);
            return TARGET_METHOD.equals(target);
        }
        return true;
    }

    // -----------------------------------------------------------------------

    private static String getAnnotationSuffix(BeanDesc beanDesc) {
        if (beanDesc.hasField(SUFFIX)) {
            Field field = beanDesc.getField(SUFFIX);
            return (String) FieldUtil.get(field, null);
        } else {
            return ClassUtil.getShortClassName(beanDesc.getBeanClass())
                    .toUpperCase();
        }
    }

    private static Object createAnnotationObject(BeanDesc beanDesc,
            Class annClazz, String suffix) {
        if (!beanDesc.hasField(suffix)) {
            return null;
        }

        Field field = beanDesc.getField(suffix);

        if (field.getType().isAssignableFrom(String[].class)) {
            Map params = AnnotationConfigUtil.getParameterMap(field);
            Aspect aspect = new AspectImpl(
                    new FullAnnotationInterceptor(params));
            AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect });
            return aopProxy.create();
        } else {
            Object value = AnnotationConfigUtil.getParameter(field);
            Aspect aspect = new AspectImpl(
                    new SingleValueAnnotationInterceptor(value));
            AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect });
            return aopProxy.create();
        }
    }

}

こんな感じに修正してテスト実行したら、グリーンバーにはなったけど・・・きたない。

そして、ふーんって感じ。クラスに対するアノテーションとメソッドに対するアノテーションは、SUFFIXを生成する場所以外はほぼ同じだ。
うまくやればもっといい感じで作れるのかも。


グリーンバーになることはわかってるんだけど、メソッドごとのアノテーションにテストを追加していくか。





やっと追加が終わった。結構つかれた・・・



これでアノテーションインタフェース(タイプ?)のデフォルト値適用に臨める。

これは、FullAnnotationInterceptorを改造すればグリーンバーにできそうだなー。
こんな感じに直して

public class FullAnnotationInterceptor extends AbstractAnnotationInterceptor {

    private Map params;

    public FullAnnotationInterceptor(Map params) {
        this.params = params;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        if (!MethodUtil.isAbstract(method)) {
            return invocation.proceed();
        }

        Object ret = null;
        if (params.containsKey(method.getName())) {
            ret = params.get(method.getName());
        } else {
            DefaultValueAnnotation annotation = (DefaultValueAnnotation) AnnotationUtil
                    .get(method, DefaultValueAnnotation.class);
            if (annotation == null) {
                return null;
            }
            ret = annotation.value();
        }
        return convertReturnValue(method, ret);
    }

}

DefaultValueAnnotationを追加すれば、

public interface DefaultValueAnnotation {
    
    public static final String TARGET = "METHOD";
    
    public static final String SUFFIX = "DEFAULT";
    
    public Object value();
    
}

グリーンバーになった。



デフォルト値適用のテストは、フル・アノテーションのときのみだから、単一値アノテーションについてもテストを作って実装しないと。



作ってて思ったんだけど、単一値アノテーションのデフォルト値って使うのかなーってちょっと思った。
とりあえず、作っておこう。




まだまだ機能は足りないけど、一段落ついたかな。


きたない部分が増えてるから、ちょっと見直しをしよう。