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: // J2SE dependencies
019: import java.util.HashMap;
020: import java.util.Map;
021: import javax.swing.tree.DefaultTreeModel;
022: import javax.swing.tree.TreeModel;
023:
024: // Geotools dependencies
025: import org.geotools.resources.i18n.ErrorKeys;
026: import org.geotools.resources.i18n.Errors;
027:
028: /**
029: * Enumeration of some metadata standards. A standard is defined by a set of Java interfaces
030: * in a specific package or subpackages. For example the {@linkplain #ISO_19115 ISO 19115}
031: * standard is defined by <A HREF="http://geoapi.sourceforge.net">GeoAPI</A> interfaces in
032: * the {@link org.opengis.metadata} package and subpackages.
033: * <p>
034: * This class provides some methods operating on metadata instances through
035: * {@linkplain java.lang.reflect Java reflection}. The following rules are
036: * assumed:
037: * <p>
038: * <ul>
039: * <li>Properties (or metadata attributes) are defined by the set of {@code get*()}
040: * (arbitrary return type) or {@code is*()} (boolean return type) methods found
041: * in the <strong>interface</strong>. Getters declared in the implementation
042: * only are ignored.</li>
043: * <li>A property is <cite>writable</cite> if a {@code set*(...)} method is defined
044: * in the implementation class for the corresponding {@code get*()} method. The
045: * setter don't need to be defined in the interface.</li>
046: * </ul>
047: *
048: * @since 2.4
049: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/metadata/MetadataStandard.java $
050: * @version $Id: MetadataStandard.java 25175 2007-04-16 13:40:57Z desruisseaux $
051: * @author Martin Desruisseaux (Geomatys)
052: */
053: public final class MetadataStandard {
054: /**
055: * An instance working on ISO 19115 standard as defined by
056: * <A HREF="http://geoapi.sourceforge.net">GeoAPI</A> interfaces
057: * in the {@link org.opengis.metadata} package and subpackages.
058: */
059: public static final MetadataStandard ISO_19115 = new MetadataStandard(
060: "org.opengis.metadata.");
061:
062: /**
063: * The root packages for metadata interfaces. Must ends with {@code "."}.
064: */
065: private final String interfacePackage;
066:
067: /**
068: * Accessors for the specified implementations.
069: */
070: private final Map/*<Class,PropertyAccessor>*/accessors = new HashMap();
071:
072: /**
073: * Shared pool of {@link PropertyTree} instances, once for each thread
074: * (in order to avoid the need for thread synchronization).
075: */
076: private final ThreadLocal treeBuilders = new ThreadLocal() {
077: //@Override
078: protected Object initialValue() {
079: return new PropertyTree(MetadataStandard.this );
080: }
081: };
082:
083: /**
084: * Creates a new instance working on implementation of interfaces defined
085: * in the specified package. For the ISO 19115 standard reflected by GeoAPI
086: * interfaces, it should be the {@link org.opengis.metadata} package.
087: *
088: * @param interfacePackage The root package for metadata interfaces.
089: */
090: public MetadataStandard(String interfacePackage) {
091: if (!interfacePackage.endsWith(".")) {
092: interfacePackage += '.';
093: }
094: this .interfacePackage = interfacePackage;
095: }
096:
097: /**
098: * Returns the accessor for the specified implementation.
099: *
100: * @throws ClassCastException if the specified implementation class do
101: * not implements a metadata interface of the expected package.
102: */
103: private PropertyAccessor getAccessor(final Class implementation)
104: throws ClassCastException {
105: final PropertyAccessor accessor = getAccessorOptional(implementation);
106: if (accessor == null) {
107: throw new ClassCastException(Errors.format(
108: ErrorKeys.UNKNOW_TYPE_$1, implementation.getName()));
109: }
110: return accessor;
111: }
112:
113: /**
114: * Returns the accessor for the specified implementation, or {@code null} if none.
115: */
116: final PropertyAccessor getAccessorOptional(
117: final Class implementation) {
118: synchronized (accessors) {
119: PropertyAccessor accessor = (PropertyAccessor) accessors
120: .get(implementation);
121: if (accessor == null) {
122: Class type = getType(implementation);
123: if (type != null) {
124: accessor = new PropertyAccessor(implementation,
125: type);
126: accessors.put(implementation, accessor);
127: }
128: }
129: return accessor;
130: }
131: }
132:
133: /**
134: * Returns the metadata interface implemented by the specified implementation.
135: * Only one metadata interface can be implemented.
136: *
137: * @param metadata The metadata implementation to wraps.
138: * @param interfacePackage The root package for metadata interfaces.
139: * @return The single interface, or {@code null} if none where found.
140: */
141: private Class getType(final Class implementation) {
142: return PropertyAccessor.getType(implementation,
143: interfacePackage);
144: }
145:
146: /**
147: * Returns the metadata interface implemented by the specified implementation class.
148: *
149: * @throws ClassCastException if the specified implementation class do
150: * not implements a metadata interface of the expected package.
151: *
152: * @see AbstractMap#getInterface
153: */
154: public Class getInterface(final Class implementation)
155: throws ClassCastException {
156: return getAccessor(implementation).type;
157: }
158:
159: /**
160: * Returns a view of the specified metadata object as a {@linkplain Map map}.
161: * The map is backed by the metadata object using Java reflection, so changes
162: * in the underlying metadata object are immediately reflected in the map.
163: * The keys are the property names as determined by the list of {@code get*()}
164: * methods declared in the {@linkplain #getInterface metadata interface}.
165: * <p>
166: * The map supports the {@link Map#put put} operations if the underlying
167: * metadata object contains {@link #set*(...)} methods.
168: *
169: * @param metadata The metadata object to view as a map.
170: * @return A map view over the metadata object.
171: * @throws ClassCastException if at the metadata object don't
172: * implements a metadata interface of the expected package.
173: *
174: * @see AbstractMap#asMap
175: */
176: public Map asMap(final Object metadata) throws ClassCastException {
177: return new PropertyMap(metadata, getAccessor(metadata
178: .getClass()));
179: }
180:
181: /**
182: * Returns a view of the specified metadata as a tree. Note that while {@link TreeModel}
183: * is defined in the {@link javax.swing.tree} package, it can be seen as a data structure
184: * independent of Swing. It will not force class loading of Swing framework.
185: * <p>
186: * In current implementation, the tree is not live (i.e. changes in metadata are not
187: * reflected in the tree). However it may be improved in a future Geotools implementation.
188: *
189: * @param metadata The metadata object to formats as a string.
190: * @return A tree representation of the specified metadata.
191: * @throws ClassCastException if at the metadata object don't
192: * implements a metadata interface of the expected package.
193: *
194: * @see AbstractMap#asTree
195: */
196: public TreeModel asTree(final Object metadata)
197: throws ClassCastException {
198: final PropertyTree builder = (PropertyTree) treeBuilders.get();
199: return new DefaultTreeModel(builder.asTree(metadata), true);
200: }
201:
202: /**
203: * Returns {@code true} if this metadata is modifiable. This method is not public because it
204: * uses heuristic rules. In case of doubt, this method conservatively returns {@code true}.
205: *
206: * @throws ClassCastException if the specified implementation class do
207: * not implements a metadata interface of the expected package.
208: *
209: * @see AbstractMap#isModifiable
210: */
211: final boolean isModifiable(final Class implementation)
212: throws ClassCastException {
213: return getAccessor(implementation).isModifiable();
214: }
215:
216: /**
217: * Replaces every properties in the specified metadata by their
218: * {@linkplain ModifiableMetadata#unmodifiable unmodifiable variant.
219: *
220: * @throws ClassCastException if the specified implementation class do
221: * not implements a metadata interface of the expected package.
222: *
223: * @see ModifiableMetadata#freeze()
224: */
225: final void freeze(final Object metadata) throws ClassCastException {
226: getAccessor(metadata.getClass()).freeze(metadata);
227: }
228:
229: /**
230: * Copies all metadata from source to target. The source must implements the same
231: * metadata interface than the target.
232: *
233: * @param source The metadata to copy.
234: * @param target The target metadata.
235: * @param skipNulls If {@code true}, only non-null values will be copied.
236: * @throws ClassCastException if the source or target object don't
237: * implements a metadata interface of the expected package.
238: * @throws UnmodifiableMetadataException if the target metadata is unmodifiable,
239: * or if at least one setter method was required but not found.
240: *
241: * @see AbstractMap#AbstractMap(Object)
242: */
243: public void shallowCopy(final Object source, final Object target,
244: final boolean skipNulls) throws ClassCastException,
245: UnmodifiableMetadataException {
246: ensureNonNull("target", target);
247: final PropertyAccessor accessor = getAccessor(target.getClass());
248: if (!accessor.type.isInstance(source)) {
249: ensureNonNull("source", source);
250: throw new ClassCastException(Errors.format(
251: ErrorKeys.ILLEGAL_CLASS_$2, source.getClass()
252: .getName(), accessor.type.getName()));
253: }
254: if (!accessor.shallowCopy(source, target, skipNulls)) {
255: throw new UnmodifiableMetadataException(Errors
256: .format(ErrorKeys.UNMODIFIABLE_METADATA));
257: }
258: }
259:
260: /**
261: * Compares the two specified metadata objects. The comparaison is <cite>shallow</cite>,
262: * i.e. all metadata attributes are compared using the {@link Object#equals} method without
263: * recursive call to this {@code shallowEquals(...)} method for child metadata.
264: * <p>
265: * This method can optionaly excludes null values from the comparaison. In metadata,
266: * null value often means "don't know", so in some occasion we want to consider two
267: * metadata as different only if an attribute value is know for sure to be different.
268: * <p>
269: * The first arguments must be an implementation of a metadata interface, otherwise an
270: * exception will be thrown. The two argument do not need to be the same implementation
271: * however.
272: *
273: * @param metadata1 The first metadata object to compare.
274: * @param metadata2 The second metadata object to compare.
275: * @param skipNulls If {@code true}, only non-null values will be compared.
276: * @throws ClassCastException if at least one metadata object don't
277: * implements a metadata interface of the expected package.
278: *
279: * @see AbstractMetadata#equals
280: */
281: public boolean shallowEquals(final Object metadata1,
282: final Object metadata2, final boolean skipNulls)
283: throws ClassCastException {
284: if (metadata1 == metadata2) {
285: return true;
286: }
287: if (metadata1 == null || metadata2 == null) {
288: return false;
289: }
290: final PropertyAccessor accessor = getAccessor(metadata1
291: .getClass());
292: if (!accessor.type.equals(getType(metadata2.getClass()))) {
293: return false;
294: }
295: return accessor.shallowEquals(metadata1, metadata2, skipNulls);
296: }
297:
298: /**
299: * Computes a hash code for the specified metadata. The hash code is defined as the
300: * sum of hash code values of all non-null properties. This is the same contract than
301: * {@link java.util.Set#hashCode} and ensure that the hash code value is insensitive
302: * to the ordering of properties.
303: *
304: * @param metadata The metadata object to compute hash code.
305: * @return A hash code value for the specified metadata.
306: * @throws ClassCastException if at the metadata object don't
307: * implements a metadata interface of the expected package.
308: *
309: * @see AbstractMap#hashCode
310: */
311: public int hashCode(final Object metadata)
312: throws ClassCastException {
313: return getAccessor(metadata.getClass()).hashCode(metadata);
314: }
315:
316: /**
317: * Returns a string representation of the specified metadata.
318: *
319: * @param metadata The metadata object to formats as a string.
320: * @return A string representation of the specified metadata.
321: * @throws ClassCastException if at the metadata object don't
322: * implements a metadata interface of the expected package.
323: *
324: * @see AbstractMap#toString
325: */
326: public String toString(final Object metadata)
327: throws ClassCastException {
328: final PropertyTree builder = (PropertyTree) treeBuilders.get();
329: return PropertyTree.toString(builder.asTree(metadata));
330: }
331:
332: /**
333: * Ensures that the specified argument is non-null.
334: */
335: private static void ensureNonNull(final String name,
336: final Object value) {
337: if (value == null) {
338: throw new NullPointerException(Errors.format(
339: ErrorKeys.NULL_ARGUMENT_$1, name));
340: }
341: }
342: }
|