/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.openjpa.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.openjpa.kernel.StoreManager;

/**
 * Repository of store-specific facade classes. This is used by facade
 * implementations to wrap store-specific components without knowing
 * about all possible back-ends.
 */
public class StoreFacadeTypeRegistry {

    private Map _impls = new ConcurrentHashMap();

    /**
     * Register a facade implementation.
     *
     * @param facadeType the facade interface
     * @param storeType the store's
     * {@link org.apache.openjpa.kernel.StoreManager} type, or null for generic
     * @param implType the class implementing the facade
     */
    public void registerImplementation(Class facadeType, Class storeType,
        Class implType) {
        Object key = (storeType == null) ? (Object) facadeType
            : new Key(facadeType, storeType);
        _impls.put(key, implType);
    }

    /**
     * Return the implementation for the given facade and store.
     *
     * @param facadeType the facade interface
     * @param storeType the store's
     * {@link org.apache.openjpa.kernel.StoreManager} type, or null for generic
     * @param implType the registered implementor
     */
    public Class getImplementation(Class facadeType, Class storeType) {
        // traverse store type hierarchy to store manager to find most specific
        // store avaialble
        Class impl;
        for (; storeType != null && storeType != StoreManager.class;
            storeType = storeType.getSuperclass()) {
            impl = (Class) _impls.get(new Key(facadeType, storeType));
            if (impl != null)
                return impl;
        }
        return (Class) _impls.get(facadeType);
    }

    /**
     * Return the implementation for the given facade and store. If no
     * registered implementation is found then returns the given default type
     * provided it the facade type is assignable from the deafult type.
     *
     * @param facadeType the facade interface
     * @param storeType the store's
     * {@link org.apache.openjpa.kernel.StoreManager} type, or null for generic
     * @param implType the registered implementor
     * @param defaultType class if no registered implementation is available.
     */
    public Class getImplementation(Class facadeType, Class storeType,
    	Class defaultType) {
    	Class result = getImplementation(facadeType, storeType);
    	if (result == null)
    		result = defaultType;
    	if (facadeType == null || !facadeType.isAssignableFrom(result))
    		throw new InternalException();
    	return result;
    }

    /**
     * Lookup key for facade+store hash.
     */
    private static class Key {
        public final Class _facadeType;
        public final Class _storeType;

        public Key(Class facadeType, Class storeType) {
            _facadeType = facadeType;
            _storeType = storeType;
        }

        @Override
        public int hashCode() {
            return _facadeType.hashCode() ^ _storeType.hashCode();
        }

        @Override
        public boolean equals(Object other) {
            if (other == this)
                return true;
            Key k = (Key) other;
            return _facadeType == k._facadeType && _storeType == k._storeType;
        }
    }
}
