/*
 * Copyright 2004-2012 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.mayaa.impl.cycle;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.seasar.mayaa.cycle.CycleLocalInstantiator;

/**
 * リクエストのライフサイクルだけ有効なオブジェクトを管理する。
 * @author Taro Kato (Gluegent, Inc.)
 */
public class CycleThreadLocalFactory {

    private static ThreadLocal/*<Map>*/ _cycleLocalVariables = new ThreadLocal();
    private static Map/*<String, Instantiator>*/ _instantiators = new HashMap();

    private CycleThreadLocalFactory() {
        throw new UnsupportedOperationException();
    }

    protected static void cycleLocalInitialize() {
        _cycleLocalVariables.set(new HashMap());
    }

    protected static Map getThreadLocalMap() {
        Map map = (Map) _cycleLocalVariables.get();
        if (map == null) {
            cycleLocalInitialize();
            map = (Map) _cycleLocalVariables.get();
        }
        return map;
    }

    public static void cycleLocalFinalize() {
        Map map = (Map) _cycleLocalVariables.get();
        if (map != null) {
            for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry entry = (Map.Entry) it.next();
                Object value = entry.getValue();
                Object key = entry.getKey();
                if (key instanceof InstanceKey) {
                    key = ((InstanceKey)key).getKey();
                }
                CycleLocalInstantiator instantiator =
                    (CycleLocalInstantiator) _instantiators.get(key);
                instantiator.destroy(value);
            }
            map.clear();
        }
        _cycleLocalVariables.set(null);
    }

    public static void registFactory(String key, CycleLocalInstantiator instantiator) {
        _instantiators.put(key, instantiator);
    }

    public static void clearLocalVariable(Object key) {
        Map map = getThreadLocalMap();
        Object instance = map.get(key);
        if (instance != null) {
            String strKey;
            if (key instanceof InstanceKey) {
                strKey = ((InstanceKey) key).getKey();
            } else {
                strKey = key.toString();
            }
            CycleLocalInstantiator instantiator =
                (CycleLocalInstantiator) _instantiators.get(strKey);
            if (instantiator != null) {
                instantiator.destroy(instance);
            }
            map.put(key, null);
        }
    }

    public static Object get(Object key, Object[] params) {
        Map localVariables = getThreadLocalMap();
        Object result = localVariables.get(key);
        if (result == null) {
            String strKey;
            Object owner;
            if (key instanceof InstanceKey) {
                strKey = ((InstanceKey) key).getKey();
                owner = ((InstanceKey) key).getOwner();
            } else {
                strKey = key.toString();
                owner = null;
            }
            CycleLocalInstantiator instantiator = (CycleLocalInstantiator)_instantiators.get(strKey);
            if (instantiator == null) {
                throw new IllegalStateException("inistantiator unknown. key = " + key);
            }
            if (owner == null) {
                result = instantiator.create(params);
            } else {
                result = instantiator.create(owner, params);
            }
            localVariables.put(key, result);
        }
        return result;
    }

    public static void set(Object key, Object value) {
        Map localVariables = getThreadLocalMap();
        Object existValue = localVariables.get(key);
        if (existValue != null) {
            clearLocalVariable(key);
        }
        localVariables.put(key, value);
    }

    public static class InstanceKey {
        private String _key;
        private Object _owner;

        public InstanceKey(String key, Object owner) {
            _key = key;
            _owner = owner;
        }

        public String getKey() {
            return _key;
        }

        public Object getOwner() {
            return _owner;
        }

        public int hashCode() {
            return _key.hashCode() + _owner.hashCode();
        }

        public boolean equals(Object obj) {
            if (obj instanceof InstanceKey) {
                InstanceKey other = (InstanceKey) obj;
                return (other._owner == _owner
                        && other._key.equals(_key));
            }
            return false;
        }
    }
}
