/*
 * Copyright 2004-2010 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.doma.jdbc.id;

import java.sql.Statement;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.seasar.doma.jdbc.JdbcException;

/**
 * INSERT文の実行前に識別子を生成するジェネレータの骨格実装です。
 * 
 * @author taedium
 * 
 */
public abstract class AbstractPreGenerateIdGenerator extends
        AbstractIdGenerator {

    /** 初期値 */
    protected long initialValue;

    /** 割り当てサイズ */
    protected long allocationSize;

    /** データソース名をキー、識別子コンテキストを値とするマップ */
    protected ConcurrentMap<String, IdContext> idContextMap = new ConcurrentHashMap<String, IdContext>();

    /**
     * 初期値を設定します。
     * 
     * @param initialValue
     *            初期値
     */
    public void setInitialValue(long initialValue) {
        this.initialValue = initialValue;
    }

    /**
     * 割り当てサイズを設定します。
     * 
     * @param allocationSize
     *            割り当てサイズ
     */
    public void setAllocationSize(long allocationSize) {
        this.allocationSize = allocationSize;
    }

    @Override
    public boolean supportsBatch(IdGenerationConfig config) {
        return true;
    }

    @Override
    public boolean supportsAutoGeneratedKeys(IdGenerationConfig config) {
        return false;
    }

    @Override
    public boolean includesIdentityColumn(IdGenerationConfig config) {
        return true;
    }

    @Override
    public Long generatePreInsert(IdGenerationConfig config) {
        IdContext idContext = getIdContext(config);
        return idContext.getNextValue(config);
    }

    @Override
    public Long generatePostInsert(IdGenerationConfig config,
            Statement statement) {
        return null;
    }

    /**
     * 識別子コンテキストを返します。
     * 
     * @param config
     *            識別子生成の設定
     * @return 識別子コンテキスト
     */
    protected IdContext getIdContext(IdGenerationConfig config) {
        String dataSourceName = config.getDataSourceName();
        IdContext context = idContextMap.get(dataSourceName);
        if (context != null) {
            return context;
        }
        context = new IdContext();
        IdContext existent = idContextMap.putIfAbsent(dataSourceName, context);
        if (existent != null) {
            return existent;
        }
        return context;
    }

    /**
     * 新しい初期値を返します。
     * 
     * @param config
     *            識別子生成の設定
     * @return 新しい初期値
     * @throws JdbcException
     *             新しい初期値の取得に失敗した場合
     */
    protected abstract long getNewInitialValue(IdGenerationConfig config);

    /**
     * 識別子コンテキストです。
     * <p>
     * 識別子の増分と保持を行います。識別子の増分処理は同期化されます。
     * 
     * @author taedium
     * 
     */
    public class IdContext {

        /** 初期値 */
        protected long initValue = AbstractPreGenerateIdGenerator.this.initialValue;

        /** 割り当てサイズ */
        protected long allocated = Long.MAX_VALUE;

        /**
         * 次の識別子を返します。
         * 
         * @param config
         *            識別子生成の設定
         * @return 次の識別子
         * @throws JdbcException
         *             次の識別子の生成に失敗した場合
         */
        public synchronized long getNextValue(IdGenerationConfig config) {
            if (allocated < allocationSize) {
                return initValue + allocated++;
            }
            initValue = getNewInitialValue(config);
            allocated = 1;
            return initValue;
        }

    }
}
