/*
 * 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;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.container.AccessTypeDef;
import org.seasar.framework.container.AutoBindingDef;
import org.seasar.framework.container.BindingTypeDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.DestroyMethodDef;
import org.seasar.framework.container.InitMethodDef;
import org.seasar.framework.container.InstanceDef;
import org.seasar.framework.container.PropertyDef;
import org.seasar.framework.container.assembler.AccessTypeDefFactory;
import org.seasar.framework.container.assembler.AutoBindingDefFactory;
import org.seasar.framework.container.assembler.BindingTypeDefFactory;
import org.seasar.framework.container.deployer.InstanceDefFactory;
import org.seasar.framework.container.impl.ComponentDefImpl;
import org.seasar.framework.container.impl.PropertyDefImpl;
import org.seasar.framework.container.ognl.OgnlExpression;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.StringUtil;

/**
 * AnnotationHandlerの抽象クラスです。
 * <p>
 * このクラスでは、主にクラス、メソッド、フィールドに書かれたアノーテションを取得します。
 * アノーテションから、コンポーネントに対しての各定義の作成はサブクラスで行います。
 * </p>
 * 
 * @author vestige
 */
public abstract class AbstractAnnotationHandler implements AnnotationHandler {

    /**
     * COMPONENTアノテーションを表す定数名です。
     */
    protected static final String COMPONENT = "COMPONENT";

    /**
     * COMPONENTアノテーションのname属性「コンポーネント名」を表すキーです。
     */
    protected static final String NAME = "name";

    /**
     * COMPONENTアノテーションのinstance属性「インスタンス定義」を表すキーです。
     */
    protected static final String INSTANCE = "instance";

    /**
     * COMPONENTアノテーションのautoBinding属性「自動バインディング定義」を表すキーです。
     */
    protected static final String AUTO_BINDING = "autoBinding";

    /**
     * Bindingアノテーションとして識別するための定数です。
     */
    protected static final String BINDING_SUFFIX = "_BINDING";

    /**
     * BindingアノテーションのbindingType属性「バインディングタイプ」を表すキーです。
     */
    protected static final String BINDING_TYPE = "bindingType";

    /**
     * COMPONENTアノテーションのexternalBinding属性「外部バインディング定義」を表すキーです。
     */
    protected static final String EXTERNAL_BINDING = "externalBinding";

    /**
     * 各アノテーション内のvalue属性「VALUE」を表すキーです。
     */
    protected static final String VALUE = "value";

    /**
     * ASPECTアノテーションを表す定数名です。
     */
    protected static final String ASPECT = "ASPECT";

    /**
     * InterTypeアノテーションを表す定数名です。
     */
    protected static final String INTER_TYPE = "INTER_TYPE";

    /**
     * InitMethodアノテーションを表す定数名です。
     */
    protected static final String INIT_METHOD = "INIT_METHOD";

    /**
     * DestroyMethodアノテーションを表す定数名です。
     */
    protected static final String DESTROY_METHOD = "DESTROY_METHOD";

    /**
     * ASPECTアノテーションのinterceptor属性「インターセプター定義」を表すキーです。
     */
    protected static final String INTERCEPTOR = "interceptor";

    /**
     * ASPECTアノテーションのpointcut属性「ポイントカット定義」を表すキーです。
     */
    protected static final String POINTCUT = "pointcut";

    public ComponentDef createComponentDef(String className,
            InstanceDef instanceDef) {
        return createComponentDef(ClassUtil.forName(className), instanceDef);
    }

    public ComponentDef createComponentDef(String className,
            InstanceDef instanceDef, AutoBindingDef autoBindingDef) {
        return createComponentDef(ClassUtil.forName(className), instanceDef,
                autoBindingDef);
    }

    public ComponentDef createComponentDef(String className,
            InstanceDef instanceDef, AutoBindingDef autoBindingDef,
            boolean externalBinding) {
        return createComponentDef(ClassUtil.forName(className), instanceDef,
                autoBindingDef, externalBinding);
    }

    public ComponentDef createComponentDef(Class componentClass,
            InstanceDef instanceDef) {
        return createComponentDef(componentClass, instanceDef, null);
    }

    public ComponentDef createComponentDef(Class componentClass,
            InstanceDef instanceDef, AutoBindingDef autoBindingDef) {
        return createComponentDef(componentClass, instanceDef, autoBindingDef,
                false);
    }

    public void appendDI(ComponentDef componentDef) {
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(componentDef
                .getComponentClass());
        for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
            PropertyDesc pd = beanDesc.getPropertyDesc(i);
            if (!pd.isWritable()) {
                continue;
            }
            PropertyDef propDef = createPropertyDef(beanDesc, pd);
            if (propDef == null) {
                continue;
            }
            componentDef.addPropertyDef(propDef);
        }
        for (int i = 0; i < beanDesc.getFieldSize(); ++i) {
            Field field = beanDesc.getField(i);
            if (componentDef.hasPropertyDef(field.getName())) {
                continue;
            }
            if (!isFieldInjectionTarget(field)) {
                continue;
            }
            PropertyDef propDef = createPropertyDef(beanDesc, field);
            if (propDef == null) {
                continue;
            }
            componentDef.addPropertyDef(propDef);
        }
    }

    /**
     * インスタンス定義を返します。
     * <p>
     * 指定した名前のインスタンス定義が存在しない場合、デフォルトのインスタンス定義を返します。
     * </p>
     * 
     * @param name
     *            インスタンス定義の種類
     * @param defaultInstanceDef
     *            デフォルトのインスタンス定義（singleton）
     * @return インスタンス定義
     */
    protected InstanceDef getInstanceDef(String name,
            InstanceDef defaultInstanceDef) {
        InstanceDef instanceDef = getInstanceDef(name);
        if (instanceDef != null) {
            return instanceDef;
        }
        return defaultInstanceDef;
    }

    /**
     * インスタンス定義を返します。
     * 
     * @param name
     *            インスタンス定義の種類
     * @return インスタンス定義
     */
    protected InstanceDef getInstanceDef(String name) {
        if (StringUtil.isEmpty(name)) {
            return null;
        }
        return InstanceDefFactory.getInstanceDef(name);
    }

    /**
     * 自動バインディング定義を返します。
     * 
     * @param name
     *            自動バインディング定義の種類
     * @return 自動バインディング定義
     */
    protected AutoBindingDef getAutoBindingDef(String name) {
        if (StringUtil.isEmpty(name)) {
            return null;
        }
        return AutoBindingDefFactory.getAutoBindingDef(name);
    }

    /**
     * 指定したコンポーネントクラスからコンポーネント定義を作成します。
     * <p>
     * 以下の設定がある場合はおのおのコンポーネント定義に設定します。
     * <ul>
     * <li>コンポーネント名
     * <li>インスタンス定義
     * <li>自動バインディング定義
     * <li>外部バインディングの有無
     * </ul>
     * </p>
     * 
     * @param componentClass
     *            コンポーネントクラス
     * @param name
     *            コンポーネント名
     * @param instanceDef
     *            インスタンス定義
     * @param autoBindingDef
     *            自動バインディング定義
     * @param externalBinding
     *            外部バインディングの有無
     * @return コンポーネント定義
     */
    protected ComponentDef createComponentDef(Class componentClass,
            String name, InstanceDef instanceDef,
            AutoBindingDef autoBindingDef, boolean externalBinding) {
        ComponentDef componentDef = new ComponentDefImpl(componentClass);
        if (!StringUtil.isEmpty(name)) {
            componentDef.setComponentName(name);
        }
        if (instanceDef != null) {
            componentDef.setInstanceDef(instanceDef);
        }
        if (autoBindingDef != null) {
            componentDef.setAutoBindingDef(autoBindingDef);
        }
        componentDef.setExternalBinding(externalBinding);
        return componentDef;
    }

    /**
     * プロパティ定義を作成します。
     * 
     * @param propertyName
     *            プロパティ名
     * @param expression
     *            引数定義の値となる式
     * @param bindingTypeName
     *            バインディングタイプ定義
     * @param accessTypeName
     *            アクセスタイプ定義
     * @return プロパティ定義
     */
    protected PropertyDef createPropertyDef(String propertyName,
            String expression, String bindingTypeName, String accessTypeName) {
        PropertyDef propertyDef = new PropertyDefImpl(propertyName);
        if (!StringUtil.isEmpty(bindingTypeName)) {
            BindingTypeDef bindingTypeDef = BindingTypeDefFactory
                    .getBindingTypeDef(bindingTypeName);
            propertyDef.setBindingTypeDef(bindingTypeDef);
        }
        if (!StringUtil.isEmpty(accessTypeName)) {
            AccessTypeDef accessTypeDef = AccessTypeDefFactory
                    .getAccessTypeDef(accessTypeName);
            propertyDef.setAccessTypeDef(accessTypeDef);
        }
        if (!StringUtil.isEmpty(expression)) {
            propertyDef.setExpression(new OgnlExpression(expression));
        }
        return propertyDef;
    }

    public boolean isInitMethodRegisterable(ComponentDef cd, String methodName) {
        if (StringUtil.isEmpty(methodName)) {
            return false;
        }
        for (int i = 0; i < cd.getInitMethodDefSize(); ++i) {
            InitMethodDef other = cd.getInitMethodDef(i);
            if (methodName.equals(other.getMethodName())
                    && other.getArgDefSize() == 0) {
                return false;
            }
        }
        return true;
    }

    public boolean isDestroyMethodRegisterable(ComponentDef cd,
            String methodName) {
        if (StringUtil.isEmpty(methodName)) {
            return false;
        }
        for (int i = 0; i < cd.getDestroyMethodDefSize(); ++i) {
            DestroyMethodDef other = cd.getDestroyMethodDef(i);
            if (methodName.equals(other.getMethodName())
                    && other.getArgDefSize() == 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 指定したフィールドがインジェクション可能かどうかの判定を行います。
     * <P>
     * <code>static</code>または、<code>final</code>でない場合はインジェクションが可能になります。
     * </p>
     * 
     * @param field
     *            フィールド
     * @return フィールドが<code>static</code>、<code>final</code>でない場合は<code>true</code>、そうでない場合は<code>false</code>を返す。
     */
    protected boolean isFieldInjectionTarget(Field field) {
        return !Modifier.isStatic(field.getModifiers())
                && !Modifier.isFinal(field.getModifiers());
    }
}