001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2007, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.metadata;
017:
018: // J2SE dependencies
019: import java.util.Arrays;
020: import java.util.LinkedHashMap;
021: import java.util.LinkedHashSet;
022: import java.util.Set;
023: import java.util.Map;
024: import java.util.List;
025: import java.util.Iterator;
026: import java.util.Collection;
027: import java.util.Collections;
028:
029: // Geotools dependencies
030: import org.geotools.util.CheckedArrayList;
031: import org.geotools.util.CheckedHashSet;
032: import org.geotools.util.logging.Logging;
033: import org.geotools.resources.i18n.Errors;
034: import org.geotools.resources.i18n.ErrorKeys;
035:
036: /**
037: * Base class for metadata that may (or may not) be modifiable. Implementations will typically
038: * provide {@code set*(...)} methods for each corresponding {@code get*()} method. An initially
039: * modifiable metadata may become unmodifiable at a later stage (typically after its construction
040: * is completed) by the call to the {@link #freeze} method.
041: * <p>
042: * Subclasses should follow the pattern below for every {@code get} and {@code set} methods,
043: * with a special processing for {@linkplain Collection collections}:
044: *
045: * <blockquote><pre>
046: * private Foo property;
047: *
048: * public Foo getProperty() {
049: * return property;
050: * }
051: *
052: * public synchronized void setProperty(Foo newValue) {
053: * {@linkplain #checkWritePermission()};
054: * property = newValue;
055: * }
056: * </pre></blockquote>
057: *
058: * For collections (note that the call to {@link #checkWritePermission()} is implicit):
059: *
060: * <blockquote><pre>
061: * private Collection<Foo> properties;
062: *
063: * public synchronized Collection<Foo> getProperties() {
064: * return properties = {@linkplain #nonNullCollection nonNullCollection}(properties, Foo.class);
065: * }
066: *
067: * public synchronized void setProperties(Collection<Foo> newValues) {
068: * properties = {@linkplain #copyCollection copyCollection}(newValues, properties, Foo.class);
069: * }
070: * </pre></blockquote>
071: *
072: * @since 2.4
073: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/metadata/ModifiableMetadata.java $
074: * @version $Id: ModifiableMetadata.java 27848 2007-11-12 13:10:32Z desruisseaux $
075: * @author Martin Desruisseaux
076: */
077: public abstract class ModifiableMetadata extends AbstractMetadata
078: implements Cloneable {
079: /**
080: * A null implementation for the {@link #FREEZING} constant.
081: */
082: private static final class Null extends ModifiableMetadata {
083: public MetadataStandard getStandard() {
084: return null;
085: }
086: }
087:
088: /**
089: * A flag used for {@link #unmodifiable} in order to specify that {@link #freeze}
090: * is under way.
091: */
092: private static final ModifiableMetadata FREEZING = new Null();
093:
094: /**
095: * An unmodifiable copy of this metadata. Will be created only when first needed.
096: * If {@code null}, then no unmodifiable entity is available.
097: * If {@code this}, then this entity is itself unmodifiable.
098: */
099: private transient ModifiableMetadata unmodifiable;
100:
101: /**
102: * Constructs an initially empty metadata.
103: */
104: protected ModifiableMetadata() {
105: super ();
106: }
107:
108: /**
109: * Constructs a metadata entity initialized with the values from the specified metadata.
110: * This constructor behavior is as in {@linkplain AbstractMetadata#AbstractMetadata(Object)
111: * superclass constructor}.
112: *
113: * @param source The metadata to copy values from.
114: * @throws ClassCastException if the specified metadata don't implements the expected
115: * metadata interface.
116: * @throws UnmodifiableMetadataException if this class don't define {@code set} methods
117: * corresponding to the {@code get} methods found in the implemented interface,
118: * or if this instance is not modifiable for some other reason.
119: */
120: protected ModifiableMetadata(final Object source)
121: throws ClassCastException, UnmodifiableMetadataException {
122: super (source);
123: }
124:
125: /**
126: * Returns {@code true} if this metadata is modifiable. This method returns
127: * {@code false} if {@link #freeze()} has been invoked on this object.
128: */
129: //@Override
130: public final boolean isModifiable() {
131: return unmodifiable != this ;
132: }
133:
134: /**
135: * Returns an unmodifiable copy of this metadata. Any attempt to modify an attribute of the
136: * returned object will throw an {@link UnmodifiableMetadataException}. If this metadata is
137: * already unmodifiable, then this method returns {@code this}.
138: * <p>
139: * The default implementation {@linkplain #clone() clone} this metadata and
140: * {@linkplain #freeze() freeze} the clone before to return it.
141: *
142: * @return An unmodifiable copy of this metadata.
143: */
144: public synchronized AbstractMetadata unmodifiable() {
145: // Reminder: 'unmodifiable' is reset to null by checkWritePermission().
146: if (unmodifiable == null) {
147: final ModifiableMetadata candidate;
148: try {
149: /*
150: * Need a SHALLOW copy of this metadata, because some attributes
151: * may already be unmodifiable and we don't want to clone them.
152: */
153: candidate = (ModifiableMetadata) clone();
154: } catch (CloneNotSupportedException exception) {
155: /*
156: * The metadata is not cloneable for some reason left to the user
157: * (for example it may be backed by some external database).
158: * Assumes that the metadata is unmodifiable.
159: */
160: Logging.unexpectedException(LOGGER, exception);
161: return this ;
162: }
163: candidate.freeze();
164: // Set the field only after success. The 'unmodifiable' field must
165: // stay null if an exception occured during clone() or freeze().
166: unmodifiable = candidate;
167: }
168: assert !unmodifiable.isModifiable();
169: return unmodifiable;
170: }
171:
172: /**
173: * Returns an unmodifiable copy of the specified object. This method performs the
174: * following heuristic tests:
175: * <p>
176: * <ul>
177: * <li>If the specified object is an instance of {@code ModifiableMetadata},
178: * then {@link #unmodifiable()} is invoked on this object.</li>
179: * <li>Otherwise, if the object is a {@linkplain Collection collection}, then the
180: * content is copied into a new collection of similar type, with values replaced
181: * by their unmodifiable variant.</li>
182: * <li>Otherwise, if the object implements the {@link org.opengis.util.Cloneable}
183: * interface, then a clone is returned.</li>
184: * <li>Otherwise, the object is assumed immutable and returned unchanged.</li>
185: * </ul>
186: *
187: * @param object The object to convert in an immutable one.
188: * @return A presumed immutable view of the specified object.
189: */
190: static Object unmodifiable(final Object object) {
191: /*
192: * CASE 1 - The object is an implementation of ModifiableMetadata. It may have
193: * its own algorithm for creating an unmodifiable view of metadata.
194: */
195: if (object instanceof ModifiableMetadata) {
196: return ((ModifiableMetadata) object).unmodifiable();
197: }
198: /*
199: * CASE 2 - The object is a collection. All elements are replaced by their
200: * unmodifiable variant and stored in a new collection of similar
201: * type.
202: */
203: if (object instanceof Collection) {
204: final Collection collection = (Collection) object;
205: if (collection.isEmpty()) {
206: return (collection instanceof List) ? (Collection) Collections.EMPTY_LIST
207: : (Collection) Collections.EMPTY_SET;
208: }
209: final Object[] array = collection.toArray();
210: for (int i = 0; i < array.length; i++) {
211: array[i] = unmodifiable(array[i]);
212: }
213: // Uses standard Java collections rather than Geotools Checked* classes,
214: // since we don't need anymore synchronization or type checking.
215: final List asList = Arrays.asList(array);
216: if (collection instanceof Set) {
217: return Collections.unmodifiableSet(new LinkedHashSet(
218: asList));
219: } else {
220: // Conservatively assumes a List if we are not sure to have a Set,
221: // since the list is less destructive (no removal of duplicated).
222: return Collections.unmodifiableList(asList);
223: }
224: }
225: /*
226: * CASE 3 - The object is a map. Copies all entries in a new map and replaces all values
227: * by their unmodifiable variant. The keys are assumed already immutable.
228: */
229: if (object instanceof Map) {
230: Map map = (Map) object;
231: if (map.isEmpty()) {
232: return Collections.EMPTY_MAP;
233: }
234: map = new LinkedHashMap(map);
235: for (final Iterator it = map.entrySet().iterator(); it
236: .hasNext();) {
237: final Map.Entry entry = (Map.Entry) it.next();
238: entry.setValue(unmodifiable(entry.getValue()));
239: }
240: return Collections.unmodifiableMap(map);
241: }
242: /*
243: * CASE 4 - The object is cloneable.
244: */
245: if (object instanceof org.opengis.util.Cloneable) {
246: return ((org.opengis.util.Cloneable) object).clone();
247: }
248: /*
249: * CASE 5 - Any other case. The object is assumed immutable and returned unchanged.
250: */
251: return object;
252: }
253:
254: /**
255: * Declares this metadata and all its attributes as unmodifiable. This method is invoked
256: * automatically by the {@link #unmodifiable()} method. Subclasses usually don't need to
257: * override it since the default implementation performs its work using Java reflection.
258: */
259: public synchronized void freeze() {
260: ModifiableMetadata success = null;
261: try {
262: unmodifiable = FREEZING;
263: getStandard().freeze(this );
264: success = this ;
265: } finally {
266: unmodifiable = success;
267: }
268: }
269:
270: /**
271: * Checks if changes in the metadata are allowed. All {@code setFoo(...)} methods in
272: * subclasses should invoke this method (directly or indirectly) before to apply any
273: * change.
274: *
275: * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
276: */
277: protected void checkWritePermission()
278: throws UnmodifiableMetadataException {
279: assert Thread.holdsLock(this );
280: if (!isModifiable()) {
281: throw new UnmodifiableMetadataException(Errors
282: .format(ErrorKeys.UNMODIFIABLE_METADATA));
283: }
284: invalidate();
285: }
286:
287: /**
288: * Invoked when the metadata changed. Some cached informations will need
289: * to be recomputed.
290: */
291: //@Override
292: final void invalidate() {
293: super .invalidate();
294: unmodifiable = null;
295: }
296:
297: /**
298: * Tests if the specified collection is modifiable. This method should
299: * be used for assertions only since it destroy the collection content
300: * in case of assertion failure.
301: */
302: private static boolean isModifiable(final Collection collection) {
303: if (!collection.isEmpty())
304: try {
305: collection.clear();
306: return true;
307: } catch (UnsupportedOperationException e) {
308: // This is the expected exception.
309: }
310: return false;
311: }
312:
313: /**
314: * Copies the content of one collection ({@code source}) into an other ({@code target}).
315: * If the target collection is {@code null}, or if its type ({@link List} vs {@link Set})
316: * doesn't matches the type of the source collection, a new target collection is expected.
317: * <p>
318: * A call to {@link #checkWritePermission} is implicit before the copy is performed.
319: *
320: * @param source The source collection. {@code null} is synonymous to empty.
321: * @param target The target collection, or {@code null} if not yet created.
322: * @param elementType The base type of elements to put in the collection.
323: * @return {@code target}, or a newly created collection.
324: * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
325: */
326: protected final Collection copyCollection(final Collection source,
327: Collection target, final Class elementType)
328: throws UnmodifiableMetadataException {
329: if (unmodifiable == FREEZING) {
330: /*
331: * freeze() method is under progress. The source collection is already
332: * an unmodifiable instance created by unmodifiable(Object).
333: */
334: assert !isModifiable(source);
335: return source;
336: }
337: checkWritePermission();
338: if (source == null) {
339: if (target != null) {
340: target.clear();
341: }
342: } else {
343: final boolean isList = (source instanceof List);
344: if (target != null && (target instanceof List) == isList) {
345: target.clear();
346: } else {
347: int capacity = source.size();
348: if (isList) {
349: target = new CheckedArrayList(elementType, capacity);
350: } else {
351: capacity = Math.round(capacity / 0.75f) + 1;
352: target = new CheckedHashSet(elementType, capacity);
353: }
354: }
355: target.addAll(source);
356: }
357: return target;
358: }
359:
360: /**
361: * Returns the specified collection, or a new one if {@code c} is null.
362: * This is a convenience method for implementation of {@code getFoo()}
363: * methods.
364: *
365: * @param c The collection to checks.
366: * @param elementType The element type (used only if {@code c} is null).
367: * @return {@code c}, or a new collection if {@code c} is null.
368: */
369: protected final Collection nonNullCollection(final Collection c,
370: final Class elementType) {
371: assert Thread.holdsLock(this );
372: if (c != null) {
373: return c;
374: }
375: if (isModifiable()) {
376: return new CheckedHashSet(elementType);
377: }
378: return Collections.EMPTY_SET;
379: }
380:
381: /**
382: * Returns the specified list, or a new one if {@code c} is null.
383: * This is a convenience method for implementation of {@code getFoo()}
384: * methods.
385: *
386: * @param c The list to checks.
387: * @param elementType The element type (used only if {@code c} is null).
388: * @return {@code c}, or a new list if {@code c} is null.
389: */
390: protected final List nonNullList(final List c,
391: final Class elementType) {
392: assert Thread.holdsLock(this );
393: if (c != null) {
394: return c;
395: }
396: if (isModifiable()) {
397: return new CheckedArrayList(elementType);
398: }
399: return Collections.EMPTY_LIST;
400: }
401:
402: /**
403: * Returns a shallow copy of this metadata.
404: * <P>
405: * While {@linkplain Cloneable cloneable}, this class do not provides the {@code clone()}
406: * operation as part of the public API. The clone operation is required for the internal
407: * working of the {@link #unmodifiable()} method, which expect from {@code clone()} a
408: * <strong>shallow</strong> copy of this metadata entity. The default implementation of
409: * {@link Object#clone()} is suffisient for most use.
410: *
411: * @return A <strong>shallow</strong> copy of this metadata.
412: * @throws CloneNotSupportedException if the clone is not supported.
413: */
414: protected Object clone() throws CloneNotSupportedException {
415: return super.clone();
416: }
417: }
|