/*
 * 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.framework.container.factory.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.seasar.framework.aop.annotation.Interceptor;
import org.seasar.framework.aop.impl.PointcutImpl;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.container.AspectDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.Expression;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.AnnotationHandler;
import org.seasar.framework.container.factory.AspectDefFactory;
import org.seasar.framework.container.impl.AspectDefImpl;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.StringUtil;

/**
 * メタアノテーションで注釈されたアノテーションを読み取り{@link AspectDef}を作成するコンポーネントの実装クラスです。
 * 
 * @author koichik
 */
public class MetaAnnotationAspectDefBuilder extends AbstractAspectDefBuilder {

    /** メタアノテーションの型 */
    protected Class<? extends Annotation> metaAnnotationType;

    /** インターセプタの名前空間 */
    protected String interceptorNamespace;

    /** インターセプタの接尾辞 */
    protected String interceptorSuffix;

    /**
     * インスタンスを構築します。
     */
    public MetaAnnotationAspectDefBuilder() {
    }

    /**
     * インスタンスを構築します。
     * 
     * @param metaAnnotationType
     *            メタアノテーションの型
     * @param interceptorSuffix
     *            インターセプタの接尾辞
     */
    public MetaAnnotationAspectDefBuilder(
            final Class<? extends Annotation> metaAnnotationType,
            final String interceptorSuffix) {
        this.metaAnnotationType = metaAnnotationType;
        this.interceptorNamespace = null;
        this.interceptorSuffix = interceptorSuffix;
    }

    /**
     * インスタンスを構築します。
     * 
     * @param metaAnnotationType
     *            メタアノテーションの型
     * @param interceptorNamespace
     *            インターセプタの名前空間
     * @param interceptorSuffix
     *            インターセプタの接尾辞
     */
    public MetaAnnotationAspectDefBuilder(
            final Class<? extends Annotation> metaAnnotationType,
            final String interceptorNamespace, final String interceptorSuffix) {
        this.metaAnnotationType = metaAnnotationType;
        this.interceptorNamespace = interceptorNamespace;
        this.interceptorSuffix = interceptorSuffix;
    }

    /**
     * メタアノテーションの型を返します。
     * 
     * @return メタアノテーションの型
     */
    public Class<? extends Annotation> getMetaAnnotationType() {
        return metaAnnotationType;
    }

    /**
     * メタアノテーションの型を設定します。
     * 
     * @param metaAnnotationType
     *            メタアノテーションの型
     */
    public void setMetaAnnotationType(
            final Class<? extends Annotation> metaAnnotationType) {
        this.metaAnnotationType = metaAnnotationType;
    }

    /**
     * インターセプタの名前空間を返します。
     * 
     * @return インターセプタの名前空間
     */
    public String getInterceptorNamespace() {
        return interceptorNamespace;
    }

    /**
     * インターセプタの名前空間を設定します。
     * 
     * @param interceptorNamespace
     *            インターセプタの名前空間
     */
    public void setInterceptorNamespace(final String interceptorNamespace) {
        this.interceptorNamespace = interceptorNamespace;
    }

    /**
     * インターセプタの接尾辞を返します。
     * 
     * @return インターセプタの接尾辞
     */
    public String getInterceptorSuffix() {
        return interceptorSuffix;
    }

    /**
     * インターセプタの接尾辞を設定します。
     * 
     * @param interceptorSuffix
     *            インターセプタの接尾辞
     */
    public void setInterceptorSuffix(final String interceptorSuffix) {
        this.interceptorSuffix = interceptorSuffix;
    }

    public void appendAspectDef(final AnnotationHandler annotationHandler,
            final ComponentDef componentDef) {
        final Class<?> componentClass = componentDef.getComponentClass();
        if (componentClass == null) {
            return;
        }
        processClass(componentDef, componentClass);
        processMethod(componentDef, componentClass);
    }

    /**
     * クラスに付けられたメタアノテーションで注釈されたアノテーションを読み取り{@link AspectDef アスペクト定義}を作成して{@link ComponentDef コンポーネント定義}に追加します。
     * 
     * @param componentDef
     *            コンポーネント定義
     * @param componentClass
     *            コンポーネントの型
     */
    protected void processClass(final ComponentDef componentDef,
            final Class<?> componentClass) {
        for (final Annotation annotation : componentClass.getAnnotations()) {
            final Class<? extends Annotation> annotationType = annotation
                    .annotationType();
            final Annotation metaAnnotation = annotationType
                    .getAnnotation(getMetaAnnotationType());
            if (metaAnnotation == null) {
                continue;
            }
            final String pointcut = getPointcut(annotation);
            final AspectDef aspectDef = new AspectDefImpl();
            if (!StringUtil.isEmpty(pointcut)) {
                aspectDef
                        .setPointcut(AspectDefFactory.createPointcut(pointcut));
            }
            aspectDef.setExpression(new ExpressionImpl(annotation));
            componentDef.addAspectDef(aspectDef);
        }
    }

    /**
     * メソッドに付けられたメタアノテーションで注釈されたアノテーションを読み取り{@link AspectDef アスペクト定義}を作成して{@link ComponentDef コンポーネント定義}に追加します。
     * 
     * @param componentDef
     *            コンポーネント定義
     * @param componentClass
     *            コンポーネントの型
     */
    protected void processMethod(final ComponentDef componentDef,
            final Class<?> componentClass) {
        for (final Method method : componentClass.getMethods()) {
            if (method.isBridge() || method.isSynthetic()) {
                continue;
            }
            final int modifiers = method.getModifiers();
            if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)
                    || Modifier.isFinal(modifiers)) {
                continue;
            }
            for (final Annotation annotation : method.getAnnotations()) {
                final Class<? extends Annotation> annotationType = annotation
                        .annotationType();
                final Annotation metaAnnotation = annotationType
                        .getAnnotation(getMetaAnnotationType());
                if (metaAnnotation == null) {
                    continue;
                }
                final AspectDef aspectDef = new AspectDefImpl(new PointcutImpl(
                        method));
                aspectDef.setExpression(new ExpressionImpl(annotation));
                componentDef.addAspectDef(aspectDef);
            }
        }
    }

    /**
     * アノテーションに指定されているポイントカットを返します。
     * 
     * @param annotation
     *            アノテーション
     * @return アノテーションに指定されているポイントカット
     */
    protected String getPointcut(final Annotation annotation) {
        for (final Method method : annotation.getClass().getMethods()) {
            if (method.isBridge() || method.isSynthetic()) {
                continue;
            }
            if ("pointcut".equals(method.getName())
                    && method.getReturnType() == String.class) {
                return String.class.cast(MethodUtil.invoke(method, annotation,
                        null));
            }
        }
        return null;
    }

    /**
     * アノテーションに指定されているインターセプタのコンポーネント名を返します。
     * 
     * @param annotation
     *            アノテーション
     * @return インターセプタのコンポーネント名
     */
    protected String getInterceptorName(final Annotation annotation) {
        final Class<? extends Annotation> annotationType = annotation
                .annotationType();
        final Interceptor interceptor = annotationType
                .getAnnotation(Interceptor.class);
        final String value = interceptor.value();
        if (!StringUtil.isEmpty(value)) {
            return value;
        }
        final String namespace = getInterceptorNamespace();
        final String interceptorName = StringUtil.decapitalize(annotationType
                .getSimpleName());
        final String suffix = getInterceptorSuffix();
        if (namespace != null) {
            return namespace + "." + interceptorName + suffix;
        }
        return interceptorName + suffix;
    }

    /**
     * 評価されると指定されたアノテーションで指定された名前のコンポーネント (インターセプタ) をコンテナから取得して返す{@link Expression}の実装です。
     * <p>
     * コンテナから取得したインターセプタのインスタンスにアノテーションの要素と名前の一致するプロパティがあれば、
     * アノテーションの要素の値がインターセプタのプロパティにコピーされます。
     * </p>
     * 
     * @author koichik
     */
    public class ExpressionImpl implements Expression {

        /** アノテーション */
        protected Annotation annotation;

        /**
         * インスタンスを構築します。
         * 
         * @param annotation
         *            アノテーション
         */
        public ExpressionImpl(final Annotation annotation) {
            this.annotation = annotation;
        }

        @SuppressWarnings("unchecked")
        public Object evaluate(final S2Container container, final Map context) {
            final MethodInterceptor interceptor = MethodInterceptor.class
                    .cast(container
                            .getComponent(getInterceptorName(annotation)));
            final BeanDesc beanDesc = BeanDescFactory.getBeanDesc(interceptor
                    .getClass());
            for (final Method method : annotation.annotationType().getMethods()) {
                if (method.isBridge() || method.isSynthetic()) {
                    continue;
                }
                final String propertyName = method.getName();
                if ("pointcut".equals(propertyName)
                        || !beanDesc.hasPropertyDesc(propertyName)) {
                    continue;
                }
                final PropertyDesc propertyDesc = beanDesc
                        .getPropertyDesc(propertyName);
                if (!propertyDesc.isWritable()) {
                    continue;
                }
                propertyDesc.setValue(interceptor, MethodUtil.invoke(method,
                        annotation, null));
            }
            return interceptor;
        }

    }

}
