/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.metadata; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; import java.util.Map; import java.util.List; import java.util.Iterator; import java.util.Collection; import java.util.Collections; import org.geotools.util.CheckedArrayList; import org.geotools.util.CheckedHashSet; import org.geotools.util.logging.Logging; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.UnmodifiableArrayList; /** * Base class for metadata that may (or may not) be modifiable. Implementations will typically * provide {@code set*(...)} methods for each corresponding {@code get*()} method. An initially * modifiable metadata may become unmodifiable at a later stage (typically after its construction * is completed) by the call to the {@link #freeze} method. * <p> * Subclasses should follow the pattern below for every {@code get} and {@code set} methods, * with a special processing for {@linkplain Collection collections}: * * <blockquote><pre> * private Foo property; * * public Foo getProperty() { * return property; * } * * public synchronized void setProperty(Foo newValue) { * {@linkplain #checkWritePermission()}; * property = newValue; * } * </pre></blockquote> * * For collections (note that the call to {@link #checkWritePermission()} is implicit): * * <blockquote><pre> * private Collection<Foo> properties; * * public synchronized Collection<Foo> getProperties() { * return properties = {@linkplain #nonNullCollection nonNullCollection}(properties, Foo.class); * } * * public synchronized void setProperties(Collection<Foo> newValues) { * properties = {@linkplain #copyCollection copyCollection}(newValues, properties, Foo.class); * } * </pre></blockquote> * * @since 2.4 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public abstract class ModifiableMetadata extends AbstractMetadata implements Cloneable { /** * A null implementation for the {@link #FREEZING} constant. */ private static final class Null extends ModifiableMetadata { public MetadataStandard getStandard() { return null; } } /** * A flag used for {@link #unmodifiable} in order to specify that {@link #freeze} * is under way. */ private static final ModifiableMetadata FREEZING = new Null(); /** * An unmodifiable copy of this metadata. Will be created only when first needed. * If {@code null}, then no unmodifiable entity is available. * If {@code this}, then this entity is itself unmodifiable. */ private transient ModifiableMetadata unmodifiable; /** * Constructs an initially empty metadata. */ protected ModifiableMetadata() { super(); } /** * Constructs a metadata entity initialized with the values from the specified metadata. * This constructor behavior is as in {@linkplain AbstractMetadata#AbstractMetadata(Object) * superclass constructor}. * * @param source The metadata to copy values from. * @throws ClassCastException if the specified metadata don't implements the expected * metadata interface. * @throws UnmodifiableMetadataException if this class don't define {@code set} methods * corresponding to the {@code get} methods found in the implemented interface, * or if this instance is not modifiable for some other reason. */ protected ModifiableMetadata(final Object source) throws ClassCastException, UnmodifiableMetadataException { super(source); } /** * Returns {@code true} if this metadata is modifiable. This method returns * {@code false} if {@link #freeze()} has been invoked on this object. * * @return {@code true} if this metadata is modifiable. */ @Override public final boolean isModifiable() { return unmodifiable != this; } /** * Returns an unmodifiable copy of this metadata. Any attempt to modify an attribute of the * returned object will throw an {@link UnmodifiableMetadataException}. If this metadata is * already unmodifiable, then this method returns {@code this}. * <p> * The default implementation {@linkplain #clone() clone} this metadata and * {@linkplain #freeze() freeze} the clone before to return it. * * @return An unmodifiable copy of this metadata. */ public synchronized AbstractMetadata unmodifiable() { // Reminder: 'unmodifiable' is reset to null by checkWritePermission(). if (unmodifiable == null) { final ModifiableMetadata candidate; try { /* * Need a SHALLOW copy of this metadata, because some attributes * may already be unmodifiable and we don't want to clone them. */ candidate = clone(); } catch (CloneNotSupportedException exception) { /* * The metadata is not cloneable for some reason left to the user * (for example it may be backed by some external database). * Assumes that the metadata is unmodifiable. */ Logging.unexpectedException(LOGGER, exception); return this; } candidate.freeze(); // Set the field only after success. The 'unmodifiable' field must // stay null if an exception occured during clone() or freeze(). unmodifiable = candidate; } assert !unmodifiable.isModifiable(); return unmodifiable; } /** * Returns an unmodifiable copy of the specified object. This method performs the * following heuristic tests: * <p> * <ul> * <li>If the specified object is an instance of {@code ModifiableMetadata}, * then {@link #unmodifiable()} is invoked on this object.</li> * <li>Otherwise, if the object is a {@linkplain Collection collection}, then the * content is copied into a new collection of similar type, with values replaced * by their unmodifiable variant.</li> * <li>Otherwise, if the object implements the {@link org.opengis.util.Cloneable} * interface, then a clone is returned.</li> * <li>Otherwise, the object is assumed immutable and returned unchanged.</li> * </ul> * * @param object The object to convert in an immutable one. * @return A presumed immutable view of the specified object. */ @SuppressWarnings("unchecked") // We really don't know the collection types. static Object unmodifiable(final Object object) { /* * CASE 1 - The object is an implementation of ModifiableMetadata. It may have * its own algorithm for creating an unmodifiable view of metadata. */ if (object instanceof ModifiableMetadata) { return ((ModifiableMetadata) object).unmodifiable(); } /* * CASE 2 - The object is a collection. All elements are replaced by their * unmodifiable variant and stored in a new collection of similar * type. */ if (object instanceof Collection) { Collection<?> collection = (Collection) object; if (collection.isEmpty()) { if (collection instanceof List) { collection = Collections.EMPTY_LIST; } else { collection = Collections.EMPTY_SET; } } else { final Object[] array = collection.toArray(); for (int i=0; i<array.length; i++) { array[i] = unmodifiable(array[i]); } // Uses standard Java collections rather than Geotools Checked* classes, // since we don't need anymore synchronization or type checking. collection = UnmodifiableArrayList.wrap(array); if (collection instanceof Set) { collection = Collections.unmodifiableSet(new LinkedHashSet<Object>(collection)); } else { // Conservatively assumes a List if we are not sure to have a Set, // since the list is less destructive (no removal of duplicated). } } return collection; } /* * CASE 3 - The object is a map. Copies all entries in a new map and replaces all values * by their unmodifiable variant. The keys are assumed already immutable. */ if (object instanceof Map) { Map map = (Map) object; if (map.isEmpty()) { return Collections.EMPTY_MAP; } map = new LinkedHashMap(map); for (final Iterator<Map.Entry> it=map.entrySet().iterator(); it.hasNext();) { final Map.Entry entry = it.next(); entry.setValue(unmodifiable(entry.getValue())); } return Collections.unmodifiableMap(map); } /* * CASE 4 - The object is cloneable. */ if (object instanceof org.opengis.util.Cloneable) { return ((org.opengis.util.Cloneable) object).clone(); } /* * CASE 5 - Any other case. The object is assumed immutable and returned unchanged. */ return object; } /** * Declares this metadata and all its attributes as unmodifiable. This method is invoked * automatically by the {@link #unmodifiable()} method. Subclasses usually don't need to * override it since the default implementation performs its work using Java reflection. */ public synchronized void freeze() { ModifiableMetadata success = null; try { unmodifiable = FREEZING; getStandard().freeze(this); success = this; } finally { unmodifiable = success; } } /** * Checks if changes in the metadata are allowed. All {@code setFoo(...)} methods in * subclasses should invoke this method (directly or indirectly) before to apply any * change. * * @throws UnmodifiableMetadataException if this metadata is unmodifiable. */ protected void checkWritePermission() throws UnmodifiableMetadataException { assert Thread.holdsLock(this); if (!isModifiable()) { throw new UnmodifiableMetadataException(Errors.format(ErrorKeys.UNMODIFIABLE_METADATA)); } invalidate(); } /** * Invoked when the metadata changed. Some cached informations will need * to be recomputed. */ @Override final void invalidate() { super.invalidate(); unmodifiable = null; } /** * Tests if the specified collection is modifiable. This method should * be used for assertions only since it destroy the collection content * in case of assertion failure. */ private static boolean isModifiable(final Collection collection) { if (!collection.isEmpty()) try { collection.clear(); return true; } catch (UnsupportedOperationException e) { // This is the expected exception. } return false; } /** * Copies the content of one list ({@code source}) into an other ({@code target}). * If the target list is {@code null}, a new target list is created. * <p> * A call to {@link #checkWritePermission} is implicit before the copy is performed. * * @param <E> The type of elements in the list. * @param source The source list. {@code null} is synonymous to empty. * @param target The target list, or {@code null} if not yet created. * @param elementType The base type of elements to put in the list. * @return {@code target}, or a newly created list. * @throws UnmodifiableMetadataException if this metadata is unmodifiable. * * @since 2.5 */ protected final <E> List<E> copyList(final Collection<? extends E> source, List<E> target, final Class<E> elementType) throws UnmodifiableMetadataException { if (unmodifiable == FREEZING) { /* * freeze() method is under progress. The source list is already * an unmodifiable instance created by unmodifiable(Object). */ assert !isModifiable(source); @SuppressWarnings("unchecked") final List<E> unmodifiable = (List<E>) source; return unmodifiable; } checkWritePermission(); /* * It is not worth to copy the content if the current and the new instance are the * same. This is safe only using the != operator, not the equals(Object) method. * This optimization is required for efficient working of PropertyAccessor.set(...). */ if (source != target) { if (source == null) { if (target != null) { target.clear(); } } else { if (target != null) { target.clear(); } else { int capacity = source.size(); target = new MutableList<E>(elementType, capacity); } target.addAll(source); } } return target; } /** * Copies the content of one collection ({@code source}) into an other ({@code target}). * If the target collection is {@code null}, or if its type ({@link List} vs {@link Set}) * doesn't matches the type of the source collection, a new target collection is created. * <p> * A call to {@link #checkWritePermission} is implicit before the copy is performed. * * @param <E> The type of elements in the collection. * @param source The source collection. {@code null} is synonymous to empty. * @param target The target collection, or {@code null} if not yet created. * @param elementType The base type of elements to put in the collection. * @return {@code target}, or a newly created collection. * @throws UnmodifiableMetadataException if this metadata is unmodifiable. */ protected final <E> Collection<E> copyCollection(final Collection<? extends E> source, Collection<E> target, final Class<E> elementType) throws UnmodifiableMetadataException { if (unmodifiable == FREEZING) { /* * freeze() method is under progress. The source collection is already * an unmodifiable instance created by unmodifiable(Object). */ assert !isModifiable(source); @SuppressWarnings("unchecked") final Collection<E> unmodifiable = (Collection<E>) source; return unmodifiable; } checkWritePermission(); /* * It is not worth to copy the content if the current and the new instance are the * same. This is safe only using the != operator, not the equals(Object) method. * This optimization is required for efficient working of PropertyAccessor.set(...). */ if (source != target) { if (source == null) { if (target != null) { target.clear(); } } else { final boolean isList = (source instanceof List); if (target != null && (target instanceof List) == isList) { target.clear(); } else { int capacity = source.size(); if (isList) { target = new MutableList<E>(elementType, capacity); } else { capacity = Math.round(capacity / 0.75f) + 1; target = new MutableSet<E>(elementType, capacity); } } target.addAll(source); } } return target; } /** * Returns the specified collection, or a new one if {@code c} is null. * This is a convenience method for implementation of {@code getFoo()} * methods. * * @param <E> The type of elements in the collection. * @param c The collection to checks. * @param elementType The element type (used only if {@code c} is null). * @return {@code c}, or a new collection if {@code c} is null. */ protected final <E> Collection<E> nonNullCollection( final Collection<E> c, final Class<E> elementType) { assert Thread.holdsLock(this); if (c != null) { return c; } if (isModifiable()) { return new MutableSet<E>(elementType); } return Collections.emptySet(); } /** * Returns the specified set, or a new one if {@code c} is null. * This is a convenience method for implementation of {@code getFoo()} * methods. * * @param <E> The type of elements in the set. * @param c The set to checks. * @param elementType The element type (used only if {@code c} is null). * @return {@code c}, or a new set if {@code c} is null. * * @since 2.5 */ protected final <E> Set<E> nonNullSet(final Set<E> c, final Class<E> elementType) { assert Thread.holdsLock(this); if (c != null) { return c; } if (isModifiable()) { return new MutableSet<E>(elementType); } return Collections.emptySet(); } /** * Returns the specified list, or a new one if {@code c} is null. * This is a convenience method for implementation of {@code getFoo()} * methods. * * @param <E> The type of elements in the list. * @param c The list to checks. * @param elementType The element type (used only if {@code c} is null). * @return {@code c}, or a new list if {@code c} is null. */ protected final <E> List<E> nonNullList(final List<E> c, final Class<E> elementType) { assert Thread.holdsLock(this); if (c != null) { return c; } if (isModifiable()) { return new MutableList<E>(elementType); } return Collections.emptyList(); } /** * A checked set synchronized on the enclosing {@link ModifiableMetadata}. * Used for mutable sets only. Note that the lock most be modified after * {@link #clone}. This is currently done in {@link #unmodifiable(Object)}. */ private final class MutableSet<E> extends CheckedHashSet<E> { private static final long serialVersionUID = 2337350768744454264L; public MutableSet(Class<E> type) { super(type); } public MutableSet(Class<E> type, int capacity) { super(type, capacity); } @Override protected Object getLock() { return ModifiableMetadata.this; } @Override protected void checkWritePermission() throws UnsupportedOperationException { ModifiableMetadata.this.checkWritePermission(); } } /** * A checked list synchronized on the enclosing {@link ModifiableMetadata}. * Used for mutable lists only. Note that the lock most be modified after * {@link #clone}. This is currently done in {@link #unmodifiable(Object)}. */ private final class MutableList<E> extends CheckedArrayList<E> { private static final long serialVersionUID = -5016778173550153002L; public MutableList(Class<E> type) { super(type); } public MutableList(Class<E> type, int capacity) { super(type, capacity); } @Override protected Object getLock() { return ModifiableMetadata.this; } @Override protected void checkWritePermission() throws UnsupportedOperationException { ModifiableMetadata.this.checkWritePermission(); } } /** * Returns a shallow copy of this metadata. * <P> * While {@linkplain Cloneable cloneable}, this class do not provides the {@code clone()} * operation as part of the public API. The clone operation is required for the internal * working of the {@link #unmodifiable()} method, which expect from {@code clone()} a * <strong>shallow</strong> copy of this metadata entity. The default implementation of * {@link Object#clone()} is suffisient for most use. * * @return A <strong>shallow</strong> copy of this metadata. * @throws CloneNotSupportedException if the clone is not supported. */ @Override protected ModifiableMetadata clone() throws CloneNotSupportedException { return (ModifiableMetadata) super.clone(); } }