All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.factory.IdentifiedObjectSet Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    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.geotoolkit.referencing.factory;

import java.io.Serializable;
import java.io.ObjectStreamException;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collection;
import java.util.AbstractSet;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import net.jcip.annotations.NotThreadSafe;

import org.opengis.metadata.Identifier;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.util.FactoryException;
import org.opengis.util.NoSuchIdentifierException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.resources.Loggings;
import org.geotoolkit.util.collection.BackingStoreException;

import static org.geotoolkit.util.collection.XCollections.isNullOrEmpty;


/**
 * A lazy set of {@linkplain IdentifiedObject identified objects}. This set creates
 * {@link IdentifiedObject}s from authority codes only when first needed. This class
 * is typically used as the set returned by implementations of the
 * {@link CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes(String, String)}
 * method. Deferred creation in this case may have great performance impact since a set can contains
 * about 40 entries (e.g. transformations from "ED50" (EPSG:4230) to "WGS 84"
 * (EPSG:4326)) while some users only want to look for the first entry (e.g. the default
 * {@link org.geotoolkit.referencing.operation.AuthorityBackedFactory} implementation).
 *
 * {@note This is mostly a helper class for implementors, especially the
 *        org.geotoolkit.referencing.factory.epsg package.
 *        This class is not expected to be useful to users.}
 *
 * {@section Exception handling}
 * If the underlying factory failed to creates an object because of an unsupported
 * operation method ({@link NoSuchIdentifierException}), the exception is logged with
 * the {@link Level#FINE FINE} level (because this is a recoverable failure) and
 * the iteration continue. If the operation creation failed for any other kind of
 * reason ({@link FactoryException}), then the exception is rethrown as an unchecked
 * {@link BackingStoreException}. This default behavior can be changed if a subclass
 * overrides the {@link #isRecoverableFailure isRecoverableFailure} method.
 *
 * {@section Serialization}
 * Serialization of this class forces the immediate creation of all
 * {@linkplain IdentifiedObject identified objects} not yet created.
 * The serialized set is disconnected from the {@linkplain #getAuthorityFactory underlying factory}.
 *
 * @param  The type of objects to be included in this set.
 *
 * @author Martin Desruisseaux (IRD, Geomatys)
 * @version 3.18
 *
 * @since 2.2
 * @module
 */
@NotThreadSafe
public class IdentifiedObjectSet extends AbstractSet implements Serializable {
    /**
     * For cross-version compatibility during serialisation.
     */
    private static final long serialVersionUID = -4221260663706882719L;

    /**
     * The map of object codes (keys), and the actual identified objects (values) when it has
     * been created. Each entry has a null value until the corresponding object is created.
     */
    private final Map objects = new LinkedHashMap();

    /**
     * The authority factory given at construction time.
     */
    private final AuthorityFactory factory;

    /**
     * The factory to use for creating {@linkplain IdentifiedObject identified objects}
     * when first needed.
     */
    private final AuthorityFactoryProxy proxy;

    /**
     * The type of objects included in this set.
     *
     * @since 3.00
     */
    protected final Class type;

    /**
     * Creates an initially empty set. The {@linkplain IdentifiedObject}s
     * will be created when first needed using the specified factory.
     *
     * @param factory The factory to use for deferred {@link IdentifiedObject}s creations.
     * @param type The type of objects included in this set.
     */
    public IdentifiedObjectSet(final AuthorityFactory factory, final Class type) {
        proxy = AuthorityFactoryProxy.getInstance(type);
        this.factory = factory;
        this.type = type;
    }

    /**
     * Removes all of the elements from this collection.
     */
    @Override
    public void clear() {
        objects.clear();
    }

    /**
     * Returns the number of objects available in this set. Note that this
     * number may decrease during the iteration process if the creation of
     * some {@linkplain IdentifiedObject identified objects} failed.
     */
    @Override
    public int size() {
        return objects.size();
    }

    /**
     * Ensures that this collection contains an object for the specified authority code.
     * The {@linkplain IdentifiedObject identified object} will be created from the specified
     * code only when first needed.
     *
     * @param  code The code authority code that shall be included in this set.
     * @return {@code true} if this set changed as a result of this call.
     */
    public boolean addAuthorityCode(final String code) {
        final boolean already = objects.containsKey(code);
        final T old = objects.put(code, null);
        if (old != null) {
            // A fully created object was already there. Keep it.
            objects.put(code, old);
            return false;
        }
        return !already;
    }

    /**
     * Ensures that this collection contains the specified object. This set does not allow multiple
     * objects for the same {@linkplain #getAuthorityCode authority code}. If this set already
     * contains an object using the same {@linkplain #getAuthorityCode authority code} than the
     * specified one, then the old object is replaced by the new one even if the objects are not
     * otherwise identical.
     *
     * @param object The object to add to the set.
     * @return {@code true} if this set changed as a result of this call.
     */
    @Override
    public boolean add(final T object) {
        final String code = getAuthorityCode(object);
        return !Utilities.equals(objects.put(code, object), object);
    }

    /**
     * Returns the identified object for the specified value, {@linkplain #createObject creating}
     * it if needed.
     *
     * @throws BackingStoreException if the object creation failed.
     */
    private T get(final String code) throws BackingStoreException {
        T object = objects.get(code);
        if (object == null && objects.containsKey(code)) {
            try {
                object = createObject(code);
                objects.put(code, object);
            } catch (FactoryException exception) {
                if (!isRecoverableFailure(exception)) {
                    throw new BackingStoreException(exception);
                }
                log(exception, code);
                objects.remove(code);
            }
        }
        return object;
    }

    /**
     * Returns {@code true} if this collection contains the specified object.
     *
     * @param  object The object to test for presence in this set.
     * @return {@code true} if the given object is presents in this set.
     */
    @Override
    public boolean contains(final Object object) {
        final String code = getAuthorityCode(type.cast(object));
        final T current = get(code);
        return object.equals(current);
    }

    /**
     * Removes a single instance of the specified element from this collection,
     * if it is present.
     *
     * @param  object The object to remove from this set.
     * @return {@code true} if this set changed as a result of this call.
     */
    @Override
    public boolean remove(final Object object) {
        final String code = getAuthorityCode(type.cast(object));
        final T current = get(code);
        if (object.equals(current)) {
            objects.remove(code);
            return true;
        }
        return false;
    }

    /**
     * Removes from this collection all of its elements that are contained in
     * the specified collection.
     *
     * @param  collection The objects to remove from this set.
     * @return {@code true} if this set changed as a result of this call.
     */
    @Override
    public boolean removeAll(final Collection collection) {
        boolean modified = false;
        for (final Object object : collection) {
            if (remove(object)) {
                modified = true;
            }
        }
        return modified;
    }

    /**
     * Returns an iterator over the objects in this set. If the iteration encounter any
     * kind of {@link FactoryException} other than {@link NoSuchIdentifierException}, then
     * the exception will be rethrown as an unchecked {@link BackingStoreException}.
     *
     * @throws BackingStoreException if the underlying factory failed to creates the
     *         first coordinate operation in the set.
     */
    @Override
    public Iterator iterator() throws BackingStoreException {
        return new Iter(objects.entrySet().iterator());
    }

    /**
     * Ensures that the n first objects in this set are created. This method is
     * typically invoked after some calls to {@link #addAuthorityCode} in order to make sure
     * that the {@linkplain #getAuthorityFactory underlying factory} is really capable to
     * create at least one object. {@link FactoryException} (except the ones accepted as
     * {@linkplain #isRecoverableFailure recoverable failures}) are thrown as if they were
     * never wrapped into {@link BackingStoreException}.
     *
     * @param n The number of object to resolve. If this number is equals or greater than the
     *          {@linkplain #size set's size}, then the creation of all objects is guaranteed
     *          successful.
     * @throws FactoryException if an {@linkplain #createObject object creation} failed.
     */
    public void resolve(int n) throws FactoryException {
        if (n > 0) try {
            // Note: iterator creation resolves the first element.
            for (final Iterator it=iterator(); it.hasNext();) {
                if (--n == 0) {
                    break;
                }
                it.next();
            }
        } catch (BackingStoreException exception) {
            throw exception.unwrapOrRethrow(FactoryException.class);
        }
    }

    /**
     * Returns the {@linkplain #getAuthorityCode authority code} of all objects in this set.
     * The returned array contains the codes in iteration order. This method does not trig the
     * {@linkplain #createObject creation} of any new object.
     * 

* This method is typically used together with {@link #setAuthorityCodes} for altering the * iteration order on the basis of authority codes. * * @return The authority codes in iteration order. */ public String[] getAuthorityCodes() { final Set codes = objects.keySet(); return codes.toArray(new String[codes.size()]); } /** * Sets the content of this set as an array of authority codes. For any code in the given list, * this method will preserve the corresponding {@linkplain IdentifiedObject identified object} * if it was already created. Other objects will be {@linkplain #createObject created} only * when first needed, as usual in this {@code IdentifiedObjectSet} implementation. *

* This method is typically used together with {@link #getAuthorityCodes} for altering the * iteration order on the basis of authority codes. If the specified {@code codes} array * contains the same elements than {@link #getAuthorityCodes} in a different order, then * this method just set the new ordering. * * @param codes The authority codes of identified objects to store in this set. * * @see #addAuthorityCode */ public void setAuthorityCodes(final String[] codes) { final Map copy = new HashMap(objects); objects.clear(); for (final String code : codes) { objects.put(code, copy.get(code)); } } /** * Returns the code to uses as a key for the specified object. The default implementation * returns the code of the first {@linkplain IdentifiedObject#getIdentifiers identifier}, * if any, or the code of the {@linkplain IdentifiedObject#getName primary name} otherwise. * Subclasses may override this method if they want to use a different key for this set. * * @param object The object for which to get the authority code. * @return The authority code of the given identified object. */ protected String getAuthorityCode(final T object) { final Identifier id; final Set identifiers = object.getIdentifiers(); if (!isNullOrEmpty(identifiers)) { id = identifiers.iterator().next(); } else { id = object.getName(); } return id.getCode(); } /** * Returns the authority factory used by the {@link #createObject createObject} method. * This is the factory given at construction time. * * @return The authority factory. * * @since 3.00 */ protected AuthorityFactory getAuthorityFactory() { return factory; } /** * Creates an object for the specified authority code. This method is invoked during the * iteration process if an object was not already created. * * @param code The code for which to create the identified object. * @return The identified object created from the given code. * @throws FactoryException If the object creation failed. */ protected T createObject(final String code) throws FactoryException { return type.cast(proxy.createFromAPI(factory, code)); } /** * Returns {@code true} if the specified exception should be handled as a recoverable failure. * This method is invoked during the iteration process if the factory failed to create some * objects. If this method returns {@code true} for the given exception, then the exception * will be logged in the {@linkplain AbstractAuthorityFactory#LOGGER Geotk factory logger} * with the {@link Level#FINE FINE} level. If this method returns {@code false}, then the * exception will be retrown as a {@link BackingStoreException}. *

* The default implementation returns applies the following rules: *

*

    *
  • If {@link NoSuchAuthorityCodeException}, returns {@code false} since failure to find * a code declared in the set would be an inconsistency. Note that this exception is a * subtype of {@code NoSuchIdentifierException}, so it must be tested before the last * case below.
  • *
  • If {@link NoSuchIdentifiedResource}, returns {@code true} since operations that * depend on external resources are considered optional.
  • *
  • If {@link NoSuchIdentifierException}, returns {@code true} since this exception is caused by an attempt to * {@linkplain org.opengis.referencing.operation.MathTransformFactory#createParameterizedTransform * create a parameterized transform} for an unimplemented operation.
  • *
* * @param exception The exception that occurred while creating an object. * @return {@code true} if the given exception should be considered recoverable, or * {@code false} if it should be considered fatal. */ protected boolean isRecoverableFailure(final FactoryException exception) { return (exception instanceof NoSuchIdentifierException) && !(exception instanceof NoSuchAuthorityCodeException); } /** * Logs a message for the specified exception. */ static void log(final FactoryException exception, final String code) { final LogRecord record = Loggings.format(Level.FINE, Loggings.Keys.CANT_CREATE_OBJECT_FROM_CODE_$1, code); record.setSourceClassName(IdentifiedObjectSet.class.getName()); record.setSourceMethodName("createObject"); record.setThrown(exception); final Logger logger = AbstractAuthorityFactory.LOGGER; record.setLoggerName(logger.getName()); logger.log(record); } /** * Returns a serializable copy of this set. This method is invoked automatically during * serialization. The serialized set of identified objects is disconnected from the * {@linkplain #getAuthorityFactory underlying factory}. * * @return The object to write in replacement of this set. * @throws ObjectStreamException If this set can not be serialized. */ protected Object writeReplace() throws ObjectStreamException { return new LinkedHashSet(this); } /** * The iterator over the entries in the enclosing set. This iterator will creates the * {@linkplain IdentifiedObject identified objects} when first needed. * * @author Martin Desruisseaux (IRD) * @version 3.00 * * @since 2.2 * @module */ private final class Iter implements Iterator { /** * The iterator over the entries from the underlying map. */ private final Iterator> iterator; /** * The next object to returns, or {@code null} if the iteration is over. */ private T element; /** * Creates a new instance of this iterator. * * @throws BackingStoreException if the underlying factory failed to creates the * first coordinate operation in the set. */ public Iter(final Iterator> iterator) throws BackingStoreException { this.iterator = iterator; toNext(); } /** * Moves to the next element. * * @throws BackingStoreException if the underlying factory failed to creates the * coordinate operation. */ private void toNext() throws BackingStoreException { while (iterator.hasNext()) { final Map.Entry entry = iterator.next(); element = entry.getValue(); if (element == null) { final String code = entry.getKey(); try { element = createObject(code); } catch (FactoryException exception) { if (!isRecoverableFailure(exception)) { throw new BackingStoreException(exception); } log(exception, code); iterator.remove(); continue; } entry.setValue(element); } return; // Element found. } element = null; // No more element found. } /** * Returns {@code true} if there is more elements. */ @Override public boolean hasNext() { return element != null; } /** * Returns the next element. * * @throws NoSuchElementException if there is no more operations in the set. */ @Override public T next() throws NoSuchElementException { final T next = element; if (next == null) { throw new NoSuchElementException(); } toNext(); return next; } /** * Removes the last element from the underlying set. */ @Override public void remove() { iterator.remove(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy