package org.geotools.util.lookup;

import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.geotools.util.lookup.Lookup.Entry;

/**
 * Lookup provides a common target for GeoTools FactoryFinder when working within different plugin
 * systems.
 * <p>
 * The concept is split into two:
 * <ul>
 * <li>All client code should be using FactoryFinder to look up an implementation of a Factory for
 * the task at hand. Factories are cached for reuse, however clients can use Hints when asking for a
 * factory and may get back an instance configured with those hints in mind. <br/>
 * Example:
 * 
 * <pre>
 * FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
 * </pre>
 * 
 * We force users to supply hints (even if null) so that they are aware that configuration is
 * possible.</li>
 * <li>All FactoryFinders should consult the GeoTools configuration class; and ask for a Lookup
 * instance used to retrieve a Service. <br/>
 * 
 * <pre>
 * Lookup lookup = GeoTools.lookup(FilterService.class);
 * 
 * FactoryRegisery registery = new FactoryRegistery(FilterFactory.class, lookup);
 * </pre>
 * 
 * Formally the class FactoryRegistery was used for this purpose; it is however not available on
 * Andriod providing motivation for this Lookup class. <br/>
 * <br/>
 * As indicated by the examples above; most of the time the Service we are interested in is a
 * factory. We have provided a Registery class that can be used to take on the responsibility of
 * managing Factory configuration and Hints.</li>
 * </ul>
 * Please note that the above example shows a FactoryFinder meeting its responsibility in two steps.
 * <ol>
 * <li>Lookup is used to determine to find a Service. Each jar can contribute one or more Service
 * instances. These Service instances have <b>no</b> configuration and are only once (The Lookup
 * will hold on to an instnace and return it again if asked). <br/>
 * A no argument consturctor is used to create each service instance.</li>
 * <li>Registery is used to specifically manage the relationship between Factory and Hints. It will
 * basically store Factory instances using Hints. <br/>
 * A Factory(Hints) constructor is used; with the Factory having a chance to return
 * getSupportedHints() in order to communicate the subset of hints it used. <br/>
 * The factories are basically stored as a Map<Hints,Factory> with the key being the supported hints
 * used for configuration.</li>
 * </ol>
 * Note some event facilities are provided in order to allow dynamic systems to register and
 * unregister additional services as they are made available.
 * <p>
 * Note: This is provided as an abstract class as we expect implementators to wrap the plugin
 * mechanism used (rather than extend).
 * <p>
 * This code is inspired by NetBeans Lookup class (which is inspired by the JINI solution to this
 * issue). As we do not expect client code to use this directly we focus on providing a Set of
 * resutls for use by FactoryFinders.
 * </p>
 * support the
 * 
 * @author Jody Garnett (LISAsoft)
 */
public abstract class Lookup {
    /**
     * This is the default lookup mechanism for this currently running environment.
     * <p>
     * The GeoTools library uses GeoTools.getLookup() rather than directly making use of this class.
     */
    private static Lookup GLOBAL;

    /**
     * Used to obtain a lookup for GeoTools.
     * <p>
     * To host the library in a different environment please configure the GeoTools class prior to
     * use of the library.
     * <p>
     * At the time of writing GeoTools uses:
     * <ul>
     * <li>Java 5: lookup mechanism provided by ServiceRegistery.</li>
     * <li>Java 6: lookup mechanism provided by ServiceLocator</li>
     * </ul>
     * In the future we will make this controllable by a system property; or checking in with the
     * configuration class GeoTools.
     * <p>
     * Options are:
     * <ul>
     * <li>javax.imageio.spi.ServiceRegistry: has the advantage of being avaialble on many platforms
     * as it is part of a javax package. Disadvantage is that it is set up as a registery requiring
     * instances to be registered and unregistered.</li>
     * <li>sun.misc.Service: this is the actual internal implementation used by java runtime to
     * perform classpath lookup. For example ServiceRegistery, Java Sound, CharsetProvider and so
     * on.</li>
     * <li>Ignore Java and make direct use of jar facilities</li>
     * </ul>
     * </p>
     * 
     * @see <a href="http://download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html">Jar File
     *      Specification</a>
     * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4640520">bug id 4640520</a>
     * @see <a
     *      href="http://weblogs.java.net/blog/enicholas/archive/2006/04/creating_a_serv.html">Creating
     *      a Service Provider Interface</a>
     */
    public static synchronized Lookup getGlobal() {
        if (GLOBAL == null) {
            try {
                Class<?> test = Class.forName("java.util.ServiceLoader");
                GLOBAL = new JDK6Lookup();
            } catch (ClassNotFoundException java5) {
                GLOBAL = new JDK5Lookup();
            }
        }
        return GLOBAL;
    }

    public static <T> Entry<T> entry(T instance, String id) {
        return new ServiceEntryImpl<T>(instance, id);
    }

    public static Lookup single(Object instance) {
        return new SingleLookup(instance);
    }

    public static Lookup list(Object... instances) {
        return new ListLookup(Arrays.asList(instances));
    }

    public static Lookup list(List<Object> list) {
        return new ListLookup(list);
    }

    /**
     * Create a chain of lookups; allowing you to mix several Lookup services together.
     * <p>
     * Example:
     * 
     * <pre>
     * Lookup localLookup = Lookup.single(&quot;local&quot;, myFilterFactory);
     * 
     * Lookup path = Lookup.chain(local, Lookup.getGlobal());
     * </pre>
     * 
     * @param lookups
     * @return Lookup
     */
    public static Lookup chain(Lookup... lookups) {
        return null;
    }

    /**
     * Represents a category of services currently made available.
     * <p>
     * FactoryFinder authors are advised to hold on to this Category object and listen to it for any
     * events reflecting changing conditions. In a dynamic an environment events will tell you if
     * services have been added or removed.
     * <p>
     * If you are using a Registery to hold on to your Category object it will use these events to
     * update its cache of factories (removing any that can no longer be supported by the changed
     * system).
     * <p>
     * This method is insprided by NetBeans Lookup.Result; it has been named Category in keeping
     * with the Java 5 ServiceRegistery concept of a category for each service provider interface
     * supported.
     * 
     * @author Jody Garnett (LISAsoft)
     */
    public abstract static class Category<T> {
        /** Type of service represented by this category */
        private Class<T> type;
        
        /**
         * Internal instance list allowing getEntryList
         * to lazily provided content.
         */
        List<T> instanceList = new AbstractList<T>() {
            public T get(int index) {
                return getEntryList().get(index).getInstance();
            }
            @Override
            public int size() {
                return getEntryList().size();
            }
        };
        
        protected Category(Class<T> type) {
            this.type = type;
        }

        public Class<T> getType() {
            return type;
        }

        /**
         * List of Entry for the requested category type.
         * <p>
         * Each entry is responsible for holding on to instance
         * it represents. We recommend using {@link CopyOnWriteArrayList)
         * for thread safety the other methods here are implemented
         * in terms of calls to {@link #getEntryList()}.
         * 
         * @return List of Entry for this category
         */
        public abstract List<Entry<T>> getEntryList();
        
        public List<T> getInstanceList(){
            return instanceList;
        }
        public Entry<T> getEntry(){
            if( getEntryList().isEmpty()){
                return null;
            }
            else {
                return getEntryList().get(0);
            }
        }
        public T getInstance(){
            Entry<T> entry = getEntry();
            if( entry == null){
                return null;
            }
            else {
                return entry.getInstance();
            }
        }
        /**
         * Add a listener for any changes to this category,
         * additional enteries may be made available at runtime
         * in a dynamic system.
         */
        public void addCategoryListener(){
        }
        /**
         * Remove a listener for any changes to this category.
         */
        public void removeCategoryListener(){
        }
    }

    /**
     * Entry used to record the availability of a serivce.
     * <p>
     * ServiceEntry is captured as a data structure to allow for lazy creation of a Service instance
     * if required. It also gives us a chance to communicate identifier, display name any additional
     * book keeping associated with the service.
     * </p>
     * <p>
     * Also not the isAvailable() method allows a service entry to report itself as listed but not
     * available to operate. As an example the jar for the gt-epsg-postgres plugin could be
     * available on the CLASSPATH, but without the appropraite JDBC driver it would repot back as
     * unavailable. In a similar fashion OSGi bundles can report the presense of their identifier
     * while gracefully indicating that their dependencies are not available in order to be
     * isAvailable().
     * </p>
     * 
     * @author Jody Garnett (LISAsoft)
     * @param <T> service type example FilterFactoryService
     */
    public abstract static class Entry<T> {
        /**
         * Identifier used by the plugin system to identify the service.
         * <p>
         * This identifier is used in event notification and log messages as should agree with the
         * identifiers used by the underlying plug-in system.
         * <p>
         * Examples: <ul
         * <li>"org.geotools.filter.FilterFactoryImpl" - Java Service Provider Interface</li>
         * <li>>net.refractions.udig.core:net.refractions.udig.geotools.filter.AdatableFilter" -
         * OSGi Bundle reference</li> </ul>
         * 
         * @return identifier, example "org.geotools.filter.FilterFactoryImpl"
         */
        public abstract String getId();

        /**
         * Human readable name for service provided, there is no need to append the word "service"
         * or "factory" in this name as that is an implementation detail.
         * <p>
         * Examples:
         * <ul>
         * <li>"GeoTools Filter Implementation"</li>
         * <li>"Strict Filter 1.0 Implementation"</li>
         * <li>"Strict Filter 2.0 Implementation"</li>
         * <li>"uDig Adaptable Filter"</li>
         * <li>"PostGIS Support"</li>
         * </ul>
         * 
         * @return human readible name of service, example "Default Filter Implementation"
         */
        public String getDisplayName() {
            return getId();
        }

        /**
         * The service instance itself.
         * <p>
         * Please note this is a service instance (and not a factory) as such the same instance will
         * be returned each time this method is called.
         * </p>
         * 
         * @return service instance
         */
        public abstract T getInstance();

        /**
         * Type of instance being made available; please be careful
         * to use class.isInstance checks here in order to respect
         * types provided from different classloaders.
         * 
         * @return type of instance being made available.
         */
        public abstract Class<T> getType();
        
        /**
         * Check if the instnace can be created.
         * <p>
         * This is mostly used to allow instances to perform a check of their dependencies (as an
         * example the epsg-postgres plugin will need to ensure that the postgres jdbc drivers are
         * available in order to function).
         * 
         * @return
         */
        public boolean isAvalable() {
            return true;
        }

        public String toString() {
            return getDisplayName();
        }
    }
}
