001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 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: import java.util.Map;
019: import java.util.logging.Logger;
020: import javax.swing.tree.TreeModel;
021: import org.geotools.util.logging.Logging;
022:
023: /**
024: * Base class for metadata implementations. Subclasses must implement the interfaces
025: * of some {@linkplain MetadataStandard metadata standard}. This class uses
026: * {@linkplain java.lang.reflect Java reflection} in order to provide default
027: * implementation of {@linkplain #AbstractMetadata(Object) copy constructor},
028: * {@link #equals} and {@link #hashCode} methods.
029: *
030: * @since 2.4
031: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/metadata/AbstractMetadata.java $
032: * @version $Id: AbstractMetadata.java 27862 2007-11-12 19:51:19Z desruisseaux $
033: * @author Martin Desruisseaux (Geomatys)
034: */
035: public abstract class AbstractMetadata {
036: /**
037: * The logger for metadata implementation.
038: */
039: protected static final Logger LOGGER = Logging
040: .getLogger("org.geotools.metadata");
041:
042: /**
043: * Hash code value, or 0 if not yet computed. This field is reset to 0 by
044: * {@link #invalidate} in order to account for a change in metadata content.
045: */
046: private transient int hashCode;
047:
048: /**
049: * A view of this metadata as a map. Will be created only when first needed.
050: */
051: private transient Map asMap;
052:
053: /**
054: * Creates an initially empty metadata.
055: */
056: protected AbstractMetadata() {
057: }
058:
059: /**
060: * Constructs a metadata entity initialized with the values from the specified metadata.
061: * The {@code source} metadata must implements the same metadata interface (defined by
062: * the {@linkplain #getStandard standard}) than this class, but don't need to be the same
063: * implementation class. The copy is performed using Java reflections.
064: *
065: * @param source The metadata to copy values from.
066: * @throws ClassCastException if the specified metadata don't implements the expected
067: * metadata interface.
068: * @throws UnmodifiableMetadataException if this class don't define {@code set} methods
069: * corresponding to the {@code get} methods found in the implemented interface,
070: * or if this instance is not modifiable for some other reason.
071: */
072: protected AbstractMetadata(final Object source)
073: throws ClassCastException, UnmodifiableMetadataException {
074: getStandard().shallowCopy(source, this , true);
075: }
076:
077: /**
078: * Returns the metadata standard implemented by subclasses.
079: */
080: public abstract MetadataStandard getStandard();
081:
082: /**
083: * Returns the metadata interface implemented by this class. It should be one of the
084: * interfaces defined in the {@linkplain #getStandard metadata standard} implemented
085: * by this class.
086: */
087: public Class getInterface() {
088: // No need to sychronize, since this method do not depends on property values.
089: return getStandard().getInterface(getClass());
090: }
091:
092: /**
093: * Returns {@code true} if this metadata is modifiable. The default implementation
094: * uses heuristic rules which return {@code false} if and only if:
095: * <p>
096: * <ul>
097: * <li>this class do not contains any {@code set*(...)} method</li>
098: * <li>All {@code get*()} methods return a presumed immutable object.
099: * The maining of "<cite>presumed immutable</cite>" may vary in
100: * different Geotools versions.</li>
101: * </ul>
102: * <p>
103: * Otherwise, this method conservatively returns {@code true}. Subclasses
104: * should override this method if they can provide a more rigorous analysis.
105: */
106: boolean isModifiable() {
107: return getStandard().isModifiable(getClass());
108: }
109:
110: /**
111: * Invoked when the metadata changed. Some cached informations will need
112: * to be recomputed.
113: */
114: void invalidate() {
115: assert Thread.holdsLock(this );
116: hashCode = 0; // Will recompute when needed.
117: }
118:
119: /**
120: * Returns a view of this metadata object as a {@linkplain Map map}. The map is backed by this
121: * metadata object using Java reflection, so changes in the underlying metadata object are
122: * immediately reflected in the map. The keys are the property names as determined by the list
123: * of {@code get*()} methods declared in the {@linkplain #getInterface metadata interface}.
124: * <p>
125: * The map supports the {@link Map#put put} operations if the underlying
126: * metadata object contains {@link #set*(...)} methods.
127: */
128: public synchronized Map asMap() {
129: if (asMap == null) {
130: asMap = getStandard().asMap(this );
131: }
132: return asMap;
133: }
134:
135: /**
136: * Returns a view of this metadata as a tree. Note that while {@link TreeModel} is
137: * defined in the {@link javax.swing.tree} package, it can be seen as a data structure
138: * independent of Swing. It will not force class loading of Swing framework.
139: * <p>
140: * In current implementation, the tree is not live (i.e. changes in metadata are not
141: * reflected in the tree). However it may be improved in a future Geotools implementation.
142: */
143: public synchronized TreeModel asTree() {
144: return getStandard().asTree(this );
145: }
146:
147: /**
148: * Compares this metadata with the specified object for equality. The default
149: * implementation uses Java reflection. Subclasses may override this method
150: * for better performances.
151: * <p>
152: * This method performs a <cite>deep</cite> comparaison (i.e. if this metadata contains
153: * other metadata, the comparaison will walk through the other metadata content as well)
154: * providing that every childs implement the {@link Object#equals} method as well. This
155: * is the case by default if every childs are subclasses of {@code AbstractMetadata}.
156: */
157: public synchronized boolean equals(final Object object) {
158: if (object != null && object.getClass().equals(getClass())) {
159: return getStandard().shallowEquals(this , object, false);
160: }
161: return false;
162: }
163:
164: /**
165: * Computes a hash code value for this metadata using Java reflection. The hash code
166: * is defined as the sum of hash code values of all non-null properties. This is the
167: * same contract than {@link java.util.Set#hashCode} and ensure that the hash code
168: * value is insensitive to the ordering of properties.
169: */
170: public synchronized int hashCode() {
171: int code = hashCode;
172: if (code == 0) {
173: code = getStandard().hashCode(this );
174: if (!isModifiable()) {
175: // In current implementation, we do not store the hash code if this metadata is
176: // modifiable because we can not track change in dependencies (e.g. a change in
177: // a metadata contained in this metadata).
178: hashCode = code;
179: }
180: }
181: return code;
182: }
183:
184: /**
185: * Returns a string representation of this metadata.
186: */
187: public synchronized String toString() {
188: return getStandard().toString(this);
189: }
190: }
|