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

org.geotoolkit.metadata.AbstractMetadata Maven / Gradle / Ivy

Go to download

Implementations of metadata derived from ISO 19115. This module provides both an implementation of the metadata interfaces defined in GeoAPI, and a framework for handling those metadata through Java reflection.

There is a newer version: 3.20-geoapi-3.0
Show newest version
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2007-2011, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2011, 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.metadata;

import java.util.Map;
import java.util.logging.Logger;
import java.text.ParseException;
import java.lang.reflect.Modifier;
import javax.swing.tree.TreeModel;
import net.jcip.annotations.ThreadSafe;

import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.util.LenientComparable;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.gui.swing.tree.TreeTableNode;


/**
 * Base class for metadata implementations. Subclasses must implement the interfaces
 * of some {@linkplain MetadataStandard metadata standard}. This class uses
 * {@linkplain java.lang.reflect Java reflection} in order to provide default
 * implementation of {@linkplain #AbstractMetadata(Object) copy constructor},
 * {@link #equals(Object)} and {@link #hashCode()} methods.
 *
 * @author Martin Desruisseaux (Geomatys)
 * @version 3.19
 *
 * @since 2.4
 * @module
 */
@ThreadSafe
public abstract class AbstractMetadata implements LenientComparable {
    /**
     * The logger for metadata implementation.
     */
    protected static final Logger LOGGER = Logging.getLogger(AbstractMetadata.class);

    /**
     * Hash code value, or 0 if not yet computed. This field is reset to 0 by
     * {@link #invalidate()} in order to account for a change in metadata content.
     */
    private transient int hashCode;

    /**
     * A view of this metadata as a map. Will be created only when first needed.
     */
    private transient Map asMap;

    /**
     * Creates an initially empty metadata.
     */
    protected AbstractMetadata() {
    }

    /**
     * Constructs a metadata entity initialized with the values from the specified metadata.
     * The {@code source} metadata must implements the same metadata interface (defined by
     * the {@linkplain #getStandard standard}) than this class, but don't need to be the same
     * implementation class. The copy is performed using Java reflections.
     *
     * @param  source The metadata to copy values from, or {@code null} if none.
     * @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 AbstractMetadata(final Object source)
            throws ClassCastException, UnmodifiableMetadataException
    {
        if (source != null) {
            getStandard().shallowCopy(source, this, true);
        }
    }

    /**
     * Returns the metadata standard implemented by subclasses.
     *
     * @return The metadata standard implemented.
     */
    public abstract MetadataStandard getStandard();

    /**
     * Returns the metadata interface implemented by this class. It should be one of the
     * interfaces defined in the {@linkplain #getStandard metadata standard} implemented
     * by this class.
     *
     * @return The standard interface implemented by this implementation class.
     */
    public Class getInterface() {
        // No need to sychronize, since this method do not depends on property values.
        return getStandard().getInterface(getClass());
    }

    /**
     * Returns {@code true} if this metadata is modifiable. The default implementation
     * uses heuristic rules which return {@code false} if and only if:
     * 

*

    *
  • this class do not contains any {@code set*(...)} method
  • *
  • All {@code get*()} methods return a presumed immutable object. * The meaning of "presumed immutable" may vary in * different Geotk versions.
  • *
*

* Otherwise, this method conservatively returns {@code true}. Subclasses * should override this method if they can provide a more rigorous analysis. */ boolean isModifiable() { return getStandard().isModifiable(getClass()); } /** * Invoked when the metadata changed. Some cached informations will need * to be recomputed. */ void invalidate() { assert Thread.holdsLock(this); hashCode = 0; // Will recompute when needed. } /** * Returns a view of this metadata object as a {@linkplain Map map}. The map is backed by this * metadata object using Java reflection, so changes in the underlying metadata object are * immediately reflected in the map. The keys are the property names as determined by the list * of {@code getFoo()} methods declared in the {@linkplain #getInterface metadata interface}. *

* The map supports the {@link Map#put put} and {@link Map#remove remove} operations * if the underlying metadata object contains {@code setFoo(...)} methods. * * @return A view of this metadata object as a map. */ public synchronized Map asMap() { if (asMap == null) { asMap = getStandard().asMap(this); } return asMap; } /** * Returns a view of this metadata as a tree table. Note that while {@link TreeTableNode} is * defined in a {@link org.geotoolkit.gui.swing} sub-package, it can be seen as a data structure * independent of Swing. It will not force class loading of Swing framework. *

* In current implementation, the tree is not live (i.e. changes in metadata are not * reflected in the tree). However it may be improved in a future Geotk implementation. * * @return A view of this metadata object as a tree table. * * @since 3.19 */ public synchronized TreeTableNode asTreeTable() { return getStandard().asTreeTable(this); } /** * Returns a view of this metadata as a tree. Note that while {@link TreeModel} is * defined in the {@link javax.swing.tree} package, it can be seen as a data structure * independent of Swing. It will not force class loading of Swing framework. *

* In current implementation, the tree is not live (i.e. changes in metadata are not * reflected in the tree). However it may be improved in a future Geotk implementation. * * @return A view of this metadata object as a tree. */ public synchronized TreeModel asTree() { return getStandard().asTree(this); } /** * Fetches values from every nodes of the given tree except the root, and puts them in * this metadata object. The value of the root node is ignored (it is typically just the * name of this metadata class). *

* If the given metadata object already contains property values, then the parsing will be * merged with the existing values: attributes not defined in the tree will be left unchanged, * and collections will be augmented with new entries without change in the previously existing * entries. *

* This method can parse the tree created by {@link #asTree()}. * * {@note The current implementation expects the tree root to be an instance of * javax.swing.tree.TreeNode.} * * @param tree The tree from which to fetch the values. * @throws ParseException If a value can not be stored in this metadata object. * * @since 3.00 */ public synchronized void parse(final TreeModel tree) throws ParseException { getStandard().parse(tree, this); } /** * Compares this metadata with the specified object for equality. The default * implementation uses Java reflection. Subclasses may override this method * for better performances, or for comparing "hidden" attributes not specified * by the GeoAPI (or other standard) interface. *

* This method performs a deep comparison (i.e. if this metadata contains * other metadata, the comparison will walk through the other metadata content as well) * providing that every children implement the {@link Object#equals(Object)} method as well. * This is the case by default if every children are subclasses of {@code AbstractMetadata}. * * @param object The object to compare with this metadata. * @param mode The strictness level of the comparison. * @return {@code true} if the given object is equal to this metadata. * * @since 3.18 */ @Override public boolean equals(final Object object, final ComparisonMode mode) { if (object == this) { return true; } if (mode == ComparisonMode.STRICT) { if (object == null || getClass(object) != getClass(this)) { return false; } } final MetadataStandard standard = getStandard(); if (mode != ComparisonMode.STRICT) { if (!standard.getInterface(getClass()).isInstance(object)) { return false; } } /* * Opportunist usage of hash code if they are already computed. If they are not, we will * not compute them - they are not sure to be faster than checking directly for equality, * and hash code could be invalidated later anyway if the object change. Note that we * don't need to synchronize since reading int fields are guaranteed to be atomic in Java. */ if (object instanceof AbstractMetadata) { final int c0 = hashCode; if (c0 != 0) { final int c1 = ((AbstractMetadata) object).hashCode; if (c1 != 0 && c0 != c1) { return false; } } } /* * DEADLOCK WARNING: A deadlock may occur if the same pair of objects is being compared * in an other thread (see http://jira.codehaus.org/browse/GEOT-1777). Ideally we would * synchronize on 'this' and 'object' atomically (RFE #4210659). Since we can't in Java * a workaround is to always get the locks in the same order. Unfortunately we have no * guarantee that the caller didn't looked the object himself. For now the safest approach * is to not synchronize at all. */ return standard.shallowEquals(this, object, mode, false); } /** * Returns the class of the given metadata, ignoring Geotk private classes like * {@link org.geotoolkit.metadata.iso.citation.CitationConstant}. * * @see GEOTK-48 */ private static Class getClass(final Object metadata) { Class type = metadata.getClass(); while (!Modifier.isPublic(type.getModifiers()) && type.getName().startsWith("org.geotoolkit.metadata.iso.")) { type = type.getSuperclass(); } return type; } /** * Performs a {@linkplain ComparisonMode#STRICT strict} comparison of this metadata with * the given object. * * @param object The object to compare with this metadata for equality. */ @Override public final boolean equals(final Object object) { return equals(object, ComparisonMode.STRICT); } /** * Computes a hash code value for this metadata using Java reflection. The hash code * is defined as the sum of hash code values of all non-null properties. This is the * same contract than {@link java.util.Set#hashCode} and ensure that the hash code * value is insensitive to the ordering of properties. */ @Override public synchronized int hashCode() { int code = hashCode; if (code == 0) { code = getStandard().hashCode(this); if (!isModifiable()) { // In current implementation, we do not store the hash code if this metadata is // modifiable because we can not track change in dependencies (e.g. a change in // a metadata contained in this metadata). hashCode = code; } } return code; } /** * Returns a string representation of this metadata. */ @Override public synchronized String toString() { return getStandard().toString(this); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy