/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * 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; import java.util.Map; import java.util.Set; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.IdentityHashMap; import java.util.Collections; import java.util.Iterator; import java.io.Writer; import java.io.IOException; import java.awt.RenderingHints; import javax.imageio.spi.ServiceRegistry; import javax.imageio.spi.RegisterableService; import org.opengis.referencing.AuthorityFactory; import org.geotools.util.Utilities; import org.geotools.io.TableWriter; import org.geotools.resources.Classes; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * Skeletal implementation of factories. This base classe provides no {@code createFoo} methods, * (they must be provided by subclasses), but provides two convenience features: * <p> * <ul> * <li>An initially empty {@linkplain #hints map of hints} to be filled by subclasses * constructors. They are the hints to be returned by {@link #getImplementationHints}.</li> * <li>An automatic {@linkplain ServiceRegistry#setOrdering ordering} applied * on the basis of subclasses-provided {@linkplain #priority} rank.</li> * </ul> * <p> * When more than one factory implementation is * {@linkplain ServiceRegistry#registerServiceProvider registered} for the same category (i.e. they * implement the same {@link Factory} sub-interface), the actual instance to be used is selected * according their {@linkplain ServiceRegistry#setOrdering ordering} and user-supplied * {@linkplain Hints hints}. Hints have precedence. If more than one factory matches the hints * (including the common case where the user doesn't provide any hint at all), then ordering * matter. * <p> * The ordering is unspecified for every pairs of factories with the same {@linkplain #priority}. * This implies that the ordering is unspecified between all factories created with the * {@linkplain #AbstractFactory() default constructor}, since they all have the same * {@linkplain #NORMAL_PRIORITY default priority} level. * * <h3>How hints are set</h3> * Hints are used for two purposes. The distinction is important because the set * of hints may not be identical in both cases: * <p> * <ol> * <li>Hints are used for creating new factories.</li> * <li>Hints are used in order to check if an <em>existing</em> factory is suitable.</li> * </ol> * <p> * {@code AbstractFactory} do <strong>not</strong> provides any facility for the first case. * Factories implementations shall inspect themselves all relevant hints supplied by the user, * and pass them to any dependencies. Do <strong>not</strong> use the {@link #hints} field for * that; use the hints provided by the user in the constructor. If all dependencies are created * at construction time (<cite>constructor injection</cite>), there is no need to keep user's hints * once the construction is finished. * <p> * The {@link #hints} field is for the second case only. Implementations shall copy in this * field only the user's hints that are know to be relevant to this factory. If a hint is * relevant but the user didn't specified any value, the hint key should be added to the * {@link #hints} map anyway with a {@code null} value. Only direct dependencies shall be put * in the {@link #hints} map. Indirect dependencies (i.e. hints used by other factories used * by this factory) will be inspected automatically by {@link FactoryRegistry} in a recursive way. * <p> * <strong>Note:</strong> The lack of constructor expecting a {@link Map} argument is intentional. * This is in order to discourage blind-copy of all user-supplied hints to the {@link #hints} map. * <p> * <strong>Example:</strong> Lets two factories, A and B. Factory A need an instance of Factory B. * Factory A can be implemented as below: * * <table border='1'> * <tr><th>Code</th><th>Observations</th></tr> * <tr><td><blockquote><pre> * class FactoryA extends AbstractFactory { * FactoryB fb; * * FactoryA(Hints userHints) { * fb = FactoryFinder.getFactoryB(userHints); * hints.put(Hints.FACTORY_B, fb); * } * } * </pre></blockquote></td> * <td> * <ul> * <li>The user-supplied map ({@code userHints}) is never modified.</li> * <li>All hints relevant to other factories are used in the constructor. Hints relevant to * factory B are used when {@code FactoryFinder.getFactoryB(...)} is invoked.</li> * <li>The {@code FactoryA} constructor stores only the hints relevant to {@code FactoryA}. * Indirect dependencies (e.g. hints relevant to {@code FactoryB}) will be inspected * recursively by {@link FactoryRegistry}.</li> * <li>In the above example, {@link #hints} will never be used for creating new factories.</li> * </ul> * </td></tr></table> * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public class AbstractFactory implements Factory, RegisterableService { /** * The minimum priority for a factory, which is {@value}. Factories with lowest priority * will be used only if there is no other factory in the same * {@linkplain ServiceRegistry#getCategories category}. * * @see #priority * @see #onRegistration */ public static final int MINIMUM_PRIORITY = 1; /** * The default priority, which is {@value}. * * @see #priority * @see #onRegistration */ public static final int NORMAL_PRIORITY = 50; /** * The maximum priority for a factory, which is {@value}. Factories with highest * priority will be preferred to any other factory in the same * {@linkplain ServiceRegistry#getCategories category}. * * @see #priority * @see #onRegistration */ public static final int MAXIMUM_PRIORITY = 100; /** * The priority for this factory, as a number between {@link #MINIMUM_PRIORITY} and * {@link #MAXIMUM_PRIORITY} inclusive. Priorities are used by {@link FactoryRegistry} * for selecting a preferred factory when many are found for the same service. * * @see #getPriority * * @todo Consider deprecating this field. See * <A HREF="http://jira.codehaus.org/browse/GEOT-1100">GEOT-1100</A> for details. */ protected final int priority; /** * The {@linkplain Factory#getImplementationHints implementation hints}. This map should be * filled by subclasses at construction time. If possible, constructors should not copy blindly * all user-provided hints. They should select only the relevant hints and resolve them as of * {@linkplain Factory#getImplementationHints implementation hints} contract. * <p> * <b>Note:</b> This field is not an instance of {@link Hints} because: * <ul> * <li>The primary use of this map is to check if this factory can be reused. * It is not for creating new factories.</li> * <li>This map needs to allow {@code null} values, as of * {@linkplain Factory#getImplementationHints implementation hints} contract.</li> * </ul> */ protected final Map<RenderingHints.Key, Object> hints = new LinkedHashMap<RenderingHints.Key, Object>(); /** * An unmodifiable view of {@link #hints}. This is the actual map to be returned * by {@link #getImplementationHints}. Its content reflects the {@link #hints} * map even if the later is modified. */ private final Map<RenderingHints.Key, Object> unmodifiableHints = Collections.unmodifiableMap(hints); /** * Creates a new factory with the {@linkplain #NORMAL_PRIORITY default priority}. */ protected AbstractFactory() { this(NORMAL_PRIORITY); } /** * Constructs a factory with the specified priority. * * @param priority The priority for this factory, as a number between * {@link #MINIMUM_PRIORITY} and {@link #MAXIMUM_PRIORITY} inclusive. */ protected AbstractFactory(final int priority) { this.priority = priority; if (priority<MINIMUM_PRIORITY || priority>MAXIMUM_PRIORITY) { throw new IllegalArgumentException( Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "priority", priority)); } } /** * Returns the priority for this factory, as a number between {@link #MINIMUM_PRIORITY} and * {@link #MAXIMUM_PRIORITY} inclusive. Priorities are used by {@link FactoryRegistry} for * selecting a preferred factory when many are found for the same service. The default * implementation returns {@link #priority} with no change. Subclasses should override * this method if they want to return a higher or lower priority. * * @since 2.3 */ public int getPriority() { return priority; } /** * Adds the specified hints to this factory {@linkplain #hints}. This method can be used * as a replacement for <code>{@linkplain #hints}.putAll(map)</code> when the map is an * instance of {@link Hints} - the above was allowed in Java 4, but is no longuer allowed * since Java 5 and parameterized types. * * @param map The hints to add. * @return {@code true} if at least one value changed as a result of this call. * * @since 2.5 */ protected boolean addImplementationHints(final RenderingHints map) { /* * Do NOT change the parameter signature to Map<?,?>. We want to keep type safety. * Use hints.putAll(...) if you have a Map<RenderingHints.Key,?>, or this method * if you have a RenderingHints map. Furthermore this method implementation needs * the garantee that the map do not contains null value (otherwise the 'changed' * computation could be inacurate) - this condition is enforced by RenderingHints * but not by Map. * * The implementation below strips non-RenderingHints.Key as a paranoiac check, * which should not be necessary since RenderingHints implementation prevented * that. If the parameter was changed to Map<?,?>, the stripping would be more * likely and could surprise the user since it is performed without warnings. */ boolean changed = false; if (map != null) { for (final Map.Entry<?,?> entry : map.entrySet()) { final Object key = entry.getKey(); if (key instanceof RenderingHints.Key) { final Object value = entry.getValue(); final Object old = hints.put((RenderingHints.Key) key, value); if (!changed && !Utilities.equals(value, old)) { changed = true; } } } } return changed; } /** * Returns an {@linkplain Collections#unmodifiableMap unmodifiable} view of * {@linkplain #hints}. * * @return The map of hints, or an empty map if none. */ public Map<RenderingHints.Key, ?> getImplementationHints() { return unmodifiableHints; } /** * Called when this factory is added to the given {@code category} of the given * {@code registry}. The factory may already be registered under another category * or categories. * <p> * This method is invoked automatically when this factory is registered as a plugin, * and should not be invoked directly by the user. The default implementation iterates * through all services under the same category that extends the {@code AbstractFactory} * class, and set the ordering according the priority given at construction time. * * @param registry A service registry where this factory has been registered. * @param category The registry category under which this object has been registered. * * @see #MINIMUM_PRIORITY * @see #MAXIMUM_PRIORITY */ public void onRegistration(final ServiceRegistry registry, final Class category) { for (final Iterator it=registry.getServiceProviders(category, false); it.hasNext();) { final Object provider = it.next(); if (provider!=this && provider instanceof AbstractFactory) { final AbstractFactory factory = (AbstractFactory) provider; final int priority = getPriority(); final int compare = factory.getPriority(); final Object first, second; if (priority > compare) { first = this; second = factory; } else if (priority < compare) { first = factory; second = this; } else { continue; // No ordering } registry.setOrdering(category, first, second); } } } /** * Called when this factory is removed from the given {@code category} of the given * {@code registry}. The object may still be registered under another category or categories. * <p> * This method is invoked automatically when this factory is no longer registered as a plugin, * and should not be invoked directly by the user. * * @param registry A service registry from which this object is being (wholly or partially) * deregistered. * @param category The registry category from which this object is being deregistered. */ public void onDeregistration(final ServiceRegistry registry, final Class category) { // No action needed. } /** * Returns a hash value for this factory. The default implementation computes the hash * value using only immutable properties. This computation do <strong>not</strong> relies * on {@linkplain #getImplementationHints implementation hints}, since there is no garantee * that they will not change. * * @since 2.3 */ @Override public final int hashCode() { return getClass().hashCode() + (37 * priority); } /** * Compares this factory with the specified object for equality. * The default implementation returns {@code true} if and only if: * <p> * <ul> * <li>Both objects are of the exact same class * (a <cite>is instance of</cite> relationship is not enough).</li> * <li>{@linkplain #getImplementationHints implementation hints} are * {@linkplain Map#equals equal}.</li> * </ul> * <p> * The requirement for the <cite>exact same class</cite> is needed for consistency with the * {@linkplain FactoryRegistry factory registry} working, since at most one instance of a given * class {@linkplain FactoryRegistry#getServiceProviderByClass) is allowed} in a registry. * * @param object The object to compare. * @return {@code true} if the given object is equals to this factory. * * @since 2.3 */ @Override public final boolean equals(final Object object) { if (object == this) { return true; } if (object!=null && object.getClass().equals(getClass())) { final AbstractFactory that = (AbstractFactory) object; if (this.priority == that.priority) { final Set<FactoryComparator> comparators = new HashSet<FactoryComparator>(); return new FactoryComparator(this, that).compare(comparators); } } return false; } /** * Returns a string representation of this factory. This method is mostly for debugging purpose, * so the string format may vary across different implementations or versions. The default * implementation formats all {@linkplain #getImplementationHints implementation hints} as a * tree. If the implementation hints include some {@linkplain Factory factory} dependencies, * then the implementation hints for those dependencies will appears under a tree branch. * * @since 2.3 */ @Override public String toString() { final String name = format(this); final Map<Factory,String> done = new IdentityHashMap<Factory,String>(); // We used IdentityHashMap above because we don't want to rely on Factory.equals(...) done.put(this, name); final String tree = format(getImplementationHints(), done); return name + System.getProperty("line.separator", "\n") + tree; } /** * Returns a string representation of the specified hints. This is used by * {@link Hints#toString} in order to share the code provided in this class. */ static String toString(final Map<?,?> hints) { return format(hints, new IdentityHashMap<Factory, String>()); } /** * Formats a name for the specified factory. */ private static String format(final Factory factory) { String name = Classes.getShortClassName(factory); if (factory instanceof AuthorityFactory) { name = name + "[\"" + ((AuthorityFactory) factory).getAuthority().getTitle() + "\"]"; } return name; } /** * Formats the specified hints. This method is just the starting * point for {@link #format(Writer, Map, String, Map)} below. */ private static String format(final Map<?,?> hints, final Map<Factory,String> done) { final Writer table; try { table = new TableWriter(null, " "); format(table, hints, " ", done); } catch (IOException e) { // Should never happen, since we are writing in a buffer. throw new AssertionError(e); } return table.toString(); } /** * Formats recursively the tree. This method invoke itself. */ private static void format(final Writer table, final Map<?,?> hints, final String indent, final Map<Factory,String> done) throws IOException { for (final Map.Entry<?,?> entry : hints.entrySet()) { final Object k = entry.getKey(); String key = (k instanceof RenderingHints.Key) ? Hints.nameOf((RenderingHints.Key) k) : String.valueOf(k); Object value = entry.getValue(); table.write(indent); table.write(key); table.write("\t= "); Factory recursive = null; if (value instanceof Factory) { recursive = (Factory) value; value = format(recursive); final String previous = done.put(recursive, key); if (previous != null) { done.put(recursive, previous); table.write("(same as "); // TODO: localize table.write(previous); value = ")"; recursive = null; } } table.write(String.valueOf(value)); table.write('\n'); if (recursive != null) { final String nextIndent = Utilities.spaces(indent.length() + 2); format(table, recursive.getImplementationHints(), nextIndent, done); } } } }