/*
 * 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.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.seasar.framework.aop.annotation.Interceptor;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.container.AutoBindingDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.InstanceDef;
import org.seasar.framework.container.PropertyDef;
import org.seasar.framework.container.factory.aspect.AspectAnnotationAspectDefBuilder;
import org.seasar.framework.container.factory.aspect.EJB3AnnotationAspectDefBuilder;
import org.seasar.framework.container.factory.aspect.MetaAnnotationAspectDefBuilder;
import org.seasar.framework.container.factory.component.EJB3ComponentDefBuilder;
import org.seasar.framework.container.factory.component.PojoComponentDefBuilder;
import org.seasar.framework.container.factory.destroymethod.S2DestroyMethodDefBuilder;
import org.seasar.framework.container.factory.initmethod.EJB3InitMethodDefBuilder;
import org.seasar.framework.container.factory.initmethod.S2InitMethodDefBuilder;
import org.seasar.framework.container.factory.intertype.EJB3IntertypeDefBuilder;
import org.seasar.framework.container.factory.intertype.S2IntertypeDefBuilder;
import org.seasar.framework.container.factory.property.BindingPropertyDefBuilder;
import org.seasar.framework.container.factory.property.EJBPropertyDefBuilder;
import org.seasar.framework.container.factory.property.PersistenceContextPropertyDefBuilder;
import org.seasar.framework.container.factory.property.PersistenceUnitPropertyDefBuilder;
import org.seasar.framework.container.factory.property.ResourcePropertyDefBuilder;
import org.seasar.framework.util.Disposable;
import org.seasar.framework.util.DisposableUtil;

/**
 * クラスに指定されたTigerアノテーションからコンポーネント定義を作成する実装クラスです。
 * 
 * @author higa
 */
public class TigerAnnotationHandler extends ConstantAnnotationHandler {

    /** イニシャライズ済みなら<code>true</code> */
    protected static boolean initialized;

    /** EJB3が有効なら<code>true</code> */
    protected static final boolean enableEJB3;
    static {
        boolean enable = false;
        try {
            Class.forName("javax.ejb.Stateless"); // geronimo-ejb_3.0_spec-1.0.jar
            Class.forName("javax.interceptor.Interceptors"); // geronimo-interceptor_3.0_spec-1.0.jar
            enable = true;
        } catch (final Throwable ignore) {
        }
        enableEJB3 = enable;
    }

    /** JPAが有効なら<code>true</code> */
    protected static final boolean enableJPA;
    static {
        boolean enable = false;
        try {
            Class.forName("javax.persistence.PersistenceContext"); // geronimo-jpa_3.0_spec-1.0.jar
            enable = true;
        } catch (final Throwable ignore) {
        }
        enableJPA = enable;
    }

    /** Common Annotationsが有効なら<code>true</code> */
    protected static final boolean enableCommonAnnotations;
    static {
        boolean enable = false;
        try {
            Class.forName("javax.annotation.Resource"); // geronimo-annotation_1.0_spec-1.0.jar
            enable = true;
        } catch (final Throwable ignore) {
        }
        enableCommonAnnotations = enable;
    }

    /** {@link ComponentDefBuilder}の配列 */
    protected static final List<ComponentDefBuilder> componentDefBuilders = Collections
            .synchronizedList(new ArrayList<ComponentDefBuilder>());

    /** {@link PropertyDefBuilder}の配列 */
    protected static final List<PropertyDefBuilder> propertyDefBuilders = Collections
            .synchronizedList(new ArrayList<PropertyDefBuilder>());

    /** {@link AspectDefBuilder}の配列 */
    protected static final List<AspectDefBuilder> aspectDefBuilders = Collections
            .synchronizedList(new ArrayList<AspectDefBuilder>());

    /** {@link IntertypeDefBuilder}の配列 */
    protected static final List<IntertypeDefBuilder> intertypeDefBuilders = Collections
            .synchronizedList(new ArrayList<IntertypeDefBuilder>());

    /** {@link InitMethodDefBuilder}の配列 */
    protected static final List<InitMethodDefBuilder> initMethodDefBuilders = Collections
            .synchronizedList(new ArrayList<InitMethodDefBuilder>());

    /** {@link DestroyMethodDefBuilder}の配列 */
    protected static final List<DestroyMethodDefBuilder> destroyMethodDefBuilders = Collections
            .synchronizedList(new ArrayList<DestroyMethodDefBuilder>());

    static {
        initialize();
    }

    /**
     * インスタンスの状態を初期化します。
     */
    public static void initialize() {
        if (initialized) {
            return;
        }
        loadDefaultComponentDefBuilder();
        loadDefaultPropertyDefBuilder();
        loadDefaultAspectDefBuilder();
        loadDefaultIntertypeDefBuilder();
        loadDefaultInitMethodDefBuilder();
        loadDefaultDestroyMethodDefBuilder();
        DisposableUtil.add(new Disposable() {

            public void dispose() {
                TigerAnnotationHandler.dispose();
            }
        });
        initialized = true;
    }

    /**
     * インスタンスの状態を破棄します。
     */
    public static void dispose() {
        clearComponentDefBuilder();
        clearPropertyDefBuilder();
        clearAspectDefBuilder();
        clearIntertypeDefBuilder();
        clearInitMethodDefBuilder();
        clearDestroyMethodDefBuilder();
        initialized = false;
    }

    /**
     * デフォルトの{@link ComponentDefBuilder}を追加します。
     */
    public static void loadDefaultComponentDefBuilder() {
        if (enableEJB3) {
            componentDefBuilders.add(new EJB3ComponentDefBuilder());
        }
        componentDefBuilders.add(new PojoComponentDefBuilder());
    }

    /**
     * {@link ComponentDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link ComponentDefBuilder}
     */
    public static void addComponentDefBuilder(final ComponentDefBuilder builder) {
        componentDefBuilders.add(builder);
    }

    /**
     * {@link ComponentDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link ComponentDefBuilder}
     */
    public static void removeComponentDefBuilder(
            final ComponentDefBuilder builder) {
        componentDefBuilders.remove(builder);
    }

    /**
     * {@link ComponentDefBuilder}をクリアします。
     */
    public static void clearComponentDefBuilder() {
        componentDefBuilders.clear();
    }

    /**
     * デフォルトの{@link PropertyDefBuilder}を追加します。
     */
    public static void loadDefaultPropertyDefBuilder() {
        clearPropertyDefBuilder();
        propertyDefBuilders.add(new BindingPropertyDefBuilder());
        if (enableEJB3) {
            propertyDefBuilders.add(new EJBPropertyDefBuilder());
        }
        if (enableJPA) {
            propertyDefBuilders.add(new PersistenceContextPropertyDefBuilder());
            propertyDefBuilders.add(new PersistenceUnitPropertyDefBuilder());
        }
        if (enableCommonAnnotations) {
            propertyDefBuilders.add(new ResourcePropertyDefBuilder());
        }
    }

    /**
     * {@link PropertyDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link PropertyDefBuilder}
     */
    public static void addPropertyDefBuilder(final PropertyDefBuilder builder) {
        propertyDefBuilders.add(builder);
    }

    /**
     * {@link PropertyDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link PropertyDefBuilder}
     */
    public static void removePropertyDefBuilder(final PropertyDefBuilder builder) {
        propertyDefBuilders.remove(builder);
    }

    /**
     * {@link PropertyDefBuilder}をクリアします。
     */
    public static void clearPropertyDefBuilder() {
        propertyDefBuilders.clear();
    }

    /**
     * デフォルトの{@link AspectDefBuilder}を追加します。
     */
    public static void loadDefaultAspectDefBuilder() {
        if (enableEJB3) {
            aspectDefBuilders.add(new EJB3AnnotationAspectDefBuilder());
        }
        aspectDefBuilders.add(new AspectAnnotationAspectDefBuilder());
        aspectDefBuilders.add(new MetaAnnotationAspectDefBuilder(
                Interceptor.class, "Interceptor"));
    }

    /**
     * {@link AspectDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link AspectDefBuilder}
     */
    public static void addAspectDefBuilder(final AspectDefBuilder builder) {
        aspectDefBuilders.add(builder);
    }

    /**
     * {@link AspectDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link AspectDefBuilder}
     */
    public static void removeAspectDefBuilder(final AspectDefBuilder builder) {
        aspectDefBuilders.remove(builder);
    }

    /**
     * {@link AspectDefBuilder}をクリアします。
     */
    public static void clearAspectDefBuilder() {
        aspectDefBuilders.clear();
    }

    /**
     * デフォルトの{@link IntertypeDefBuilder}を追加します。
     */
    public static void loadDefaultIntertypeDefBuilder() {
        if (enableEJB3) {
            intertypeDefBuilders.add(new EJB3IntertypeDefBuilder());
        }
        intertypeDefBuilders.add(new S2IntertypeDefBuilder());
    }

    /**
     * {@link IntertypeDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link IntertypeDefBuilder}
     */
    public static void addIntertypeDefBuilder(final IntertypeDefBuilder builder) {
        intertypeDefBuilders.add(builder);
    }

    /**
     * {@link IntertypeDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link IntertypeDefBuilder}
     */
    public static void removeIntertypeDefBuilder(
            final IntertypeDefBuilder builder) {
        intertypeDefBuilders.remove(builder);
    }

    /**
     * {@link IntertypeDefBuilder}をクリアします。
     */
    public static void clearIntertypeDefBuilder() {
        intertypeDefBuilders.clear();
    }

    /**
     * デフォルトの{@link InitMethodDefBuilder}を追加します。
     */
    public static void loadDefaultInitMethodDefBuilder() {
        if (enableEJB3) {
            initMethodDefBuilders.add(new EJB3InitMethodDefBuilder());
        }
        initMethodDefBuilders.add(new S2InitMethodDefBuilder());
    }

    /**
     * {@link InitMethodDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link InitMethodDefBuilder}
     */
    public static void addInitMethodDefBuilder(
            final InitMethodDefBuilder builder) {
        initMethodDefBuilders.add(builder);
    }

    /**
     * {@link InitMethodDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link InitMethodDefBuilder}
     */
    public static void removeInitMethodDefBuilder(
            final InitMethodDefBuilder builder) {
        initMethodDefBuilders.remove(builder);
    }

    /**
     * {@link InitMethodDefBuilder}をクリアします。
     */
    public static void clearInitMethodDefBuilder() {
        initMethodDefBuilders.clear();
    }

    /**
     * デフォルトの{@link DestroyMethodDefBuilder}を追加します。
     */
    public static void loadDefaultDestroyMethodDefBuilder() {
        destroyMethodDefBuilders.add(new S2DestroyMethodDefBuilder());
    }

    /**
     * {@link DestroyMethodDefBuilder}を追加します。
     * 
     * @param builder
     *            {@link DestroyMethodDefBuilder}
     */
    public static void addDestroyMethodDefBuilder(
            final DestroyMethodDefBuilder builder) {
        destroyMethodDefBuilders.add(builder);
    }

    /**
     * {@link DestroyMethodDefBuilder}を削除します。
     * 
     * @param builder
     *            {@link DestroyMethodDefBuilder}
     */
    public static void removeDestroyMethodDefBuilder(
            final DestroyMethodDefBuilder builder) {
        destroyMethodDefBuilders.remove(builder);
    }

    /**
     * {@link DestroyMethodDefBuilder}をクリアします。
     */
    public static void clearDestroyMethodDefBuilder() {
        destroyMethodDefBuilders.clear();
    }

    @SuppressWarnings("unchecked")
    @Override
    public ComponentDef createComponentDef(final Class componentClass,
            final InstanceDef defaultInstanceDef,
            final AutoBindingDef defaultAutoBindingDef,
            final boolean defaultExternalBinding) {
        initialize();
        for (final ComponentDefBuilder builder : componentDefBuilders) {
            final ComponentDef componentDef = builder.createComponentDef(this,
                    componentClass, defaultInstanceDef, defaultAutoBindingDef,
                    defaultExternalBinding);
            if (componentDef != null) {
                return componentDef;
            }
        }
        return super.createComponentDef(componentClass, defaultInstanceDef,
                defaultAutoBindingDef, defaultExternalBinding);
    }

    @Override
    public PropertyDef createPropertyDef(final BeanDesc beanDesc,
            PropertyDesc propertyDesc) {
        if (propertyDesc.isWritable()) {
            for (final PropertyDefBuilder builder : propertyDefBuilders) {
                final PropertyDef propertyDef = builder.createPropertyDef(this,
                        beanDesc, propertyDesc);
                if (propertyDef != null) {
                    return propertyDef;
                }
            }
        }
        return super.createPropertyDef(beanDesc, propertyDesc);
    }

    @Override
    public PropertyDef createPropertyDef(final BeanDesc beanDesc,
            final Field field) {
        for (final PropertyDefBuilder builder : propertyDefBuilders) {
            final PropertyDef propertyDef = builder.createPropertyDef(this,
                    beanDesc, field);
            if (propertyDef != null) {
                return propertyDef;
            }
        }
        return super.createPropertyDef(beanDesc, field);
    }

    @Override
    public void appendAspect(final ComponentDef componentDef) {
        for (final AspectDefBuilder builder : aspectDefBuilders) {
            builder.appendAspectDef(this, componentDef);
        }
        super.appendAspect(componentDef);
    }

    @Override
    public void appendInterType(final ComponentDef componentDef) {
        for (final IntertypeDefBuilder builder : intertypeDefBuilders) {
            builder.appendIntertypeDef(this, componentDef);
        }
        super.appendInterType(componentDef);
    }

    @Override
    public void appendInitMethod(final ComponentDef componentDef) {
        Class<?> componentClass = componentDef.getComponentClass();
        if (componentClass == null) {
            return;
        }
        for (final InitMethodDefBuilder builder : initMethodDefBuilders) {
            builder.appendInitMethodDef(this, componentDef);
        }
        super.appendInitMethod(componentDef);
    }

    @Override
    public void appendDestroyMethod(final ComponentDef componentDef) {
        Class<?> componentClass = componentDef.getComponentClass();
        if (componentClass == null) {
            return;
        }
        for (final DestroyMethodDefBuilder builder : destroyMethodDefBuilders) {
            builder.appendDestroyMethodDef(this, componentDef);
        }
        super.appendDestroyMethod(componentDef);
    }

}
