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

import java.util.ArrayList;
import java.util.List;

import org.seasar.framework.container.AutoBindingDef;
import org.seasar.framework.container.ComponentCustomizer;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.InstanceDef;
import org.seasar.framework.container.factory.AnnotationHandler;
import org.seasar.framework.container.factory.AnnotationHandlerFactory;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ClassTraversal.ClassHandler;

/**
 * コンポーネントを自動登録するための抽象クラスです。
 * 
 * @author higa
 */
public abstract class AbstractComponentAutoRegister extends
        AbstractAutoRegister implements ClassHandler {

    /** クラスファイルの拡張子 */
    protected static final String CLASS_SUFFIX = ".class";

    /**
     * BINDINGアノテーションの定義です。
     */
    public static final String autoNaming_BINDING = "bindingType=may";

    private AutoNaming autoNaming = new DefaultAutoNaming();

    /**
     * BINDINGアノテーションの定義です。
     */
    public static final String instanceDef_BINDING = "bindingType=may";

    private InstanceDef instanceDef;

    /**
     * BINDINGアノテーションの定義です。
     */
    public static final String autoBindingDef_BINDING = "bindingType=may";

    private AutoBindingDef autoBindingDef;

    private boolean externalBinding = false;

    /**
     * BINDINGアノテーションの定義です。
     */
    public static final String customizer_BINDING = "bindingType=may";

    private ComponentCustomizer customizer;

    /**
     * AutoNamingを返します。
     * 
     * @return AutoNaming
     */
    public AutoNaming getAutoNaming() {
        return autoNaming;
    }

    /**
     * AutoNamingを設定します。
     * 
     * @param autoNaming
     *            AutoNaming
     */
    public void setAutoNaming(AutoNaming autoNaming) {
        this.autoNaming = autoNaming;
    }

    /**
     * インスタンス定義を返します。
     * 
     * @return インスタンス定義
     */
    public InstanceDef getInstanceDef() {
        return instanceDef;
    }

    /**
     * インスタンス定義を設定します。
     * 
     * @param instanceDef
     *            インスタンス定義
     */
    public void setInstanceDef(InstanceDef instanceDef) {
        this.instanceDef = instanceDef;
    }

    /**
     * 自動バインディング定義を返します。
     * 
     * @return 自動バインディング定義
     */
    public AutoBindingDef getAutoBindingDef() {
        return autoBindingDef;
    }

    /**
     * 自動バインディング定義を設定します。
     * 
     * @param autoBindingDef
     *            自動バインディング定義
     */
    public void setAutoBindingDef(AutoBindingDef autoBindingDef) {
        this.autoBindingDef = autoBindingDef;
    }

    /**
     * 外部バインディングのデフォルト値を返します。
     * 
     * @return 外部バインディングのデフォルト値
     */
    public boolean isExternalBinding() {
        return externalBinding;
    }

    /**
     * 外部バインディングのデフォルト値を設定します。
     * 
     * @param externalBinding
     *            外部バインディングのデフォルト値
     */
    public void setExternalBinding(boolean externalBinding) {
        this.externalBinding = externalBinding;
    }

    /**
     * コンポーネントカスタマイザを返します。
     * 
     * @return コンポーネントカスタマイザ
     */
    public ComponentCustomizer getCustomizer() {
        return customizer;
    }

    /**
     * コンポーネントカスタマイザを設定します。
     * 
     * @param customizer
     *            コンポーネントカスタマイザ
     */
    public void setCustomizer(ComponentCustomizer customizer) {
        this.customizer = customizer;
    }

    public void processClass(final String packageName,
            final String shortClassName) {
        if (isIgnore(packageName, shortClassName)) {
            return;
        }

        for (int i = 0; i < getClassPatternSize(); ++i) {
            final ClassPattern cp = getClassPattern(i);
            if (cp.isAppliedPackageName(packageName)
                    && cp.isAppliedShortClassName(shortClassName)) {
                register(ClassUtil.concatName(packageName, shortClassName));
                return;
            }
        }
    }

    /**
     * コンポーネント定義を作成してコンテナに登録します。
     * 
     * @param className
     *            コンポーネントのクラス
     */
    protected void register(final String className) {
        final AnnotationHandler annoHandler = AnnotationHandlerFactory
                .getAnnotationHandler();
        final ComponentDef cd = annoHandler.createComponentDef(className,
                instanceDef, autoBindingDef, externalBinding);
        if (cd.getComponentName() == null) {
            String[] names = ClassUtil.splitPackageAndShortClassName(className);
            cd.setComponentName(autoNaming.defineName(names[0], names[1]));
        }
        annoHandler.appendDI(cd);
        customize(cd);
        annoHandler.appendInitMethod(cd);
        annoHandler.appendDestroyMethod(cd);
        annoHandler.appendAspect(cd);
        annoHandler.appendInterType(cd);
        getContainer().register(cd);
    }

    /**
     * コンポーネント定義をカスタマイズします。
     * 
     * @param componentDef
     *            コンポーネント定義
     */
    protected void customize(ComponentDef componentDef) {
        if (customizer != null) {
            customizer.customize(componentDef);
        }
    }

    /**
     * コンポーネントを検索する対象となるパッケージの配列を返します。
     * <p>
     * コンポーネントを検索する対象のパッケージは<code>ClassPattern</code>に設定されたパッケージ名から
     * 重複やサブパッケージを除いたものになります。 例えば<code>ClassPattern</code>に<code>aaa, aaa.bbb, bbb</code>が指定された場合、
     * <code>aaa.bbb</code>は<code>aaa</code>のサブパッケージなので取り除かれ、
     * <code>aaa, bbb</code>が検索対象のパッケージとなります。
     * </p>
     * 
     * @return コンポーネントを検索する対象となるパッケージの配列
     */
    protected String[] getTargetPackages() {
        final List result = new ArrayList();
        for (int i = 0; i < getClassPatternSize(); ++i) {
            final String packageName = getClassPattern(i).getPackageName();
            boolean append = true;
            for (int j = 0; j < result.size(); ++j) {
                final String root = (String) result.get(j);
                if (packageName.equals(root)) {
                    append = false;
                    break;
                } else if (packageName.startsWith(root)) {
                    append = false;
                    break;
                } else if (root.startsWith(packageName)) {
                    result.set(j, packageName);
                    append = false;
                    break;
                }
            }
            if (append) {
                result.add(packageName);
            }
        }
        return (String[]) result.toArray(new String[result.size()]);
    }

}
