/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2007, GeoTools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.factory;

// J2SE dependencies
import java.util.Iterator;
import java.util.Set;
import java.util.LinkedHashSet;

// Geotools dependencies
import org.geotools.resources.XArray;


/**
 * Utility methods that apply to all {@linkplain FactoryRegistry factory registries}.
 *
 * @since 2.4 PENDING PROPOSAL
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/factory/Factories.java $
 * @version $Id: Factories.java 25406 2007-05-03 18:08:10Z desruisseaux $
 * @author Martin Desruisseaux
 *
 * @deprecated Consider moving those methods to {@link GeoTools}. We should also remove
 *             {@link FactoryRegistry#globalConfiguration} and relies on listeners instead.
 */
public final class Factories {
    /**
     * The system-wide configuration. This is the instance configured by
     * the public static methods provided in this class.
     */
    private static final Factories GLOBAL = new Factories();

    /**
     * Incremented every time a modification is performed.
     */
    private int modifications = 0;

    /**
     * Alternative scanning methods used by {@link FactoryRegistry#scanForPlugins(Collection,Class)}
     * in addition of the default lookup mechanism. Will be created only when first needed.
     */
    private Set/*<FactoryIteratorProvider>*/ iteratorProviders;

    /**
     * Creates an initially empty set of factories.
     */
    Factories() {
    }

    /**
     * Synchronizes the content of the {@link #iteratorProviders} map with the {@linkplain #GLOBAL
     * global} one. New providers are returned for later {@linkplain FactoryRegistry#register
     * registration}. Note that this method is typically invoked in a different thread than
     * {@link Factories} public static method calls.
     *
     * @return The new iterators providers {@linkplain #addFactoryIteratorProvider added} since
     *         the last time this method was invoked, or {@code null} if none.
     */
    final FactoryIteratorProvider[] synchronizeIteratorProviders() {
        FactoryIteratorProvider[] newProviders = null;
        int count = 0;
        synchronized (GLOBAL) {
            if (modifications == GLOBAL.modifications) {
                return null;
            }
            modifications = GLOBAL.modifications;
            if (GLOBAL.iteratorProviders == null) {
                /*
                 * Should never happen. If GLOBAL.iteratorProviders was null, then every
                 * 'modifications' count should be 0 and this method should have returned 'null'.
                 */
                throw new AssertionError(modifications);
            }
            /*
             * If  'Factories.removeFactoryIteratorProvider(...)'  has been invoked since the last
             * time this method was run, then synchronize 'iteratorProviders' accordingly. Current
             * implementation do not unregister the factories that were created by those iterators.
             */
            if (iteratorProviders != null) {
                iteratorProviders.retainAll(GLOBAL.iteratorProviders);
            } else if (!GLOBAL.iteratorProviders.isEmpty()) {
                iteratorProviders = new LinkedHashSet();
            }
            /*
             * If 'Factories.addFactoryIteratorProvider(...)' has been invoked since the last
             * time this method was run, then synchronize 'iteratorProviders' accordingly. We
             * keep trace of new providers in order to allow 'FactoryRegistry' to use them for
             * a immediate scanning.
             */
            int remaining = GLOBAL.iteratorProviders.size();
            for (final Iterator it=GLOBAL.iteratorProviders.iterator(); it.hasNext();) {
                final FactoryIteratorProvider candidate = (FactoryIteratorProvider) it.next();
                if (iteratorProviders.add(candidate)) {
                    if (newProviders == null) {
                        newProviders = new FactoryIteratorProvider[remaining];
                    }
                    newProviders[count++] = candidate;
                }
                remaining--;
            }
        }
        // Note: newProviders may be null.
        return (FactoryIteratorProvider[]) XArray.resize(newProviders, count);
    }    

    /**
     * Adds an alternative way to search for factory implementations. {@link FactoryRegistry} has
     * a default mechanism bundled in it, which uses the content of all {@code META-INF/services}
     * directories found on the classpath. This {@code addFactoryIteratorProvider} method allows
     * to specify additional discovery algorithms. It may be useful in the context of some
     * frameworks that use the <cite>constructor injection</cite> pattern, like the
     * <a href="http://www.springframework.org/">Spring framework</a>.
     *
     * @deprecated Moved to {@link GeoTools} class.
     */
    public static void addFactoryIteratorProvider(FactoryIteratorProvider provider) {
        synchronized (GLOBAL) {
            if (GLOBAL.iteratorProviders == null) {
                GLOBAL.iteratorProviders = new LinkedHashSet();
            }
            if (GLOBAL.iteratorProviders.add(provider)) {
                GLOBAL.modifications++;
            }
        }
    }

    /**
     * Removes a provider that was previously {@linkplain #addFactoryIteratorProvider added}.
     * Note that factories already obtained from the specified provider will not be
     * {@linkplain FactoryRegistry#deregisterServiceProvider deregistered} by this method.
     *
     * @deprecated Moved to {@link GeoTools} class.
     */
    public static void removeFactoryIteratorProvider(FactoryIteratorProvider provider) {
        synchronized (GLOBAL) {
            if (GLOBAL.iteratorProviders != null) {
                if (GLOBAL.iteratorProviders.remove(provider)) {
                    GLOBAL.modifications++;
                }
            }
        }
    }

    /**
     * Returns all iterator providers. This method do not returns any live collection
     * since the array will be used outside the synchronized block.
     */
    static FactoryIteratorProvider[] getIteratorProviders() {
        synchronized (GLOBAL) {
            if (GLOBAL.iteratorProviders == null) {
                return new FactoryIteratorProvider[0];
            }
            return (FactoryIteratorProvider[]) GLOBAL.iteratorProviders.toArray(
                    new FactoryIteratorProvider[GLOBAL.iteratorProviders.size()]);
        }
    }
}
