| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.metadata.iso.identification; |
| |
| import java.util.Collection; |
| import javax.xml.bind.annotation.XmlID; |
| import javax.xml.bind.annotation.XmlType; |
| import javax.xml.bind.annotation.XmlSchemaType; |
| import javax.xml.bind.annotation.XmlElement; |
| import javax.xml.bind.annotation.XmlAttribute; |
| import javax.xml.bind.annotation.XmlRootElement; |
| import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; |
| import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.identification.RepresentativeFraction; |
| import org.apache.sis.metadata.ModifiableMetadata; |
| import org.apache.sis.metadata.UnmodifiableMetadataException; |
| import org.apache.sis.internal.jaxb.ModifiableIdentifierMap; |
| import org.apache.sis.internal.jaxb.IdentifierMapAdapter; |
| import org.apache.sis.internal.jaxb.gco.GO_Integer64; |
| import org.apache.sis.internal.metadata.MetadataUtilities; |
| import org.apache.sis.internal.metadata.Resources; |
| import org.apache.sis.internal.util.CheckedArrayList; |
| import org.apache.sis.measure.ValueRange; |
| import org.apache.sis.xml.IdentifierMap; |
| import org.apache.sis.xml.IdentifierSpace; |
| import org.apache.sis.xml.IdentifiedObject; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.Emptiable; |
| import org.apache.sis.util.resources.Errors; |
| |
| import static org.apache.sis.util.collection.Containers.isNullOrEmpty; |
| import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive; |
| |
| |
| /** |
| * A scale defined as the inverse of a denominator. |
| * Scale is defined as a kind of {@link Number}. |
| * The following property is mandatory in a well-formed metadata according ISO 19115: |
| * |
| * <div class="preformat">{@code MD_RepresentativeFraction} |
| * {@code └─denominator…………………………} The number below the line in a vulgar fraction.</div> |
| * |
| * In addition to the standard properties, SIS provides the following methods: |
| * <ul> |
| * <li>{@link #setScale(double)} for computing the denominator from a scale value.</li> |
| * </ul> |
| * |
| * <div class="section">Limitations</div> |
| * <ul> |
| * <li>Instances of this class are not synchronized for multi-threading. |
| * Synchronization, if needed, is caller's responsibility.</li> |
| * <li>Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases. |
| * Serialization support is appropriate for short term storage or RMI between applications running the |
| * same version of Apache SIS. For long term storage, use {@link org.apache.sis.xml.XML} instead.</li> |
| * </ul> |
| * |
| * @author Cédric Briançon (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see DefaultResolution#getEquivalentScale() |
| * |
| * @since 0.3 |
| * @module |
| */ |
| @XmlType(name = "MD_RepresentativeFraction_Type") |
| @XmlRootElement(name = "MD_RepresentativeFraction") |
| public class DefaultRepresentativeFraction extends Number implements RepresentativeFraction, IdentifiedObject, Emptiable, Cloneable { |
| /** |
| * Serial number for compatibility with different versions. |
| */ |
| private static final long serialVersionUID = -6043871487256529207L; |
| |
| /** |
| * The number below the line in a vulgar fraction, or 0 if undefined. |
| */ |
| private long denominator; |
| |
| /** |
| * All identifiers associated with this metadata, or {@code null} if none. |
| * This field is initialized to a non-null value when first needed. |
| */ |
| private Collection<Identifier> identifiers; |
| |
| /** |
| * {@code true} if this representative fraction has been made unmodifiable. |
| */ |
| private transient boolean isUnmodifiable; |
| |
| /** |
| * Creates a uninitialized representative fraction. |
| * The {@linkplain #getDenominator() denominator} is initially zero |
| * and the {@linkplain #doubleValue() double value} is NaN. |
| */ |
| public DefaultRepresentativeFraction() { |
| } |
| |
| /** |
| * Creates a new representative fraction from the specified denominator. |
| * |
| * @param denominator the denominator as a positive number, or 0 if unspecified. |
| * @throws IllegalArgumentException if the given value is negative. |
| */ |
| public DefaultRepresentativeFraction(final long denominator) { |
| ArgumentChecks.ensurePositive("denominator", denominator); |
| this.denominator = denominator; |
| } |
| |
| /** |
| * Constructs a new representative fraction initialized to the value of the given object. |
| * |
| * <div class="note"><b>Note on properties validation:</b> |
| * This constructor does not verify the property values of the given metadata (e.g. whether it contains |
| * unexpected negative values). This is because invalid metadata exist in practice, and verifying their |
| * validity in this copy constructor is often too late. Note that this is not the only hole, as invalid |
| * metadata instances can also be obtained by unmarshalling an invalid XML document. |
| * </div> |
| * |
| * @param object the metadata to copy values from, or {@code null} if none. |
| */ |
| public DefaultRepresentativeFraction(final RepresentativeFraction object) { |
| if (object != null) { |
| denominator = object.getDenominator(); |
| } |
| } |
| |
| /** |
| * Returns a SIS metadata implementation with the same values than the given arbitrary |
| * implementation. If the given object is {@code null}, then this method returns {@code null}. |
| * Otherwise if the given object is already a SIS implementation, then the given object is |
| * returned unchanged. Otherwise a new SIS implementation is created and initialized to the |
| * property values of the given object, using a <cite>shallow</cite> copy operation |
| * (i.e. properties are not cloned). |
| * |
| * @param object the object to get as a SIS implementation, or {@code null} if none. |
| * @return a SIS implementation containing the values of the given object (may be the |
| * given object itself), or {@code null} if the argument was null. |
| */ |
| public static DefaultRepresentativeFraction castOrCopy(final RepresentativeFraction object) { |
| return (object == null) || (object instanceof DefaultRepresentativeFraction) |
| ? (DefaultRepresentativeFraction) object : new DefaultRepresentativeFraction(object); |
| } |
| |
| /** |
| * Returns the denominator of this representative fraction. |
| * |
| * @return the denominator. |
| */ |
| @Override |
| @ValueRange(minimum = 0) |
| @XmlJavaTypeAdapter(value = GO_Integer64.class, type = long.class) |
| @XmlElement(name = "denominator", required = true) |
| public long getDenominator() { |
| return denominator; |
| } |
| |
| /** |
| * Sets the denominator value. |
| * |
| * @param denominator the new denominator value, or 0 if none. |
| * @throws IllegalArgumentException if the given value is negative. |
| */ |
| public void setDenominator(final long denominator) { |
| if (isUnmodifiable) { |
| throw new UnmodifiableMetadataException(Resources.format(Resources.Keys.UnmodifiableMetadata)); |
| } |
| if (ensurePositive(DefaultRepresentativeFraction.class, "denominator", false, denominator)) { |
| this.denominator = denominator; |
| } |
| } |
| |
| /** |
| * Sets the denominator from a scale in the (0 … 1] range. |
| * The denominator is computed by {@code round(1 / scale)}. |
| * |
| * <p>The equivalent of a {@code getScale()} method is {@link #doubleValue()}.</p> |
| * |
| * @param scale the scale as a number between 0 exclusive and 1 inclusive, or NaN. |
| * @throws IllegalArgumentException if the given scale is our of range. |
| */ |
| public void setScale(final double scale) { |
| if (isUnmodifiable) { |
| throw new UnmodifiableMetadataException(Resources.format(Resources.Keys.UnmodifiableMetadata)); |
| } |
| /* |
| * For the following argument check, we do not need to use a Metadatautility method because |
| * 'setScale' is never invoked at (un)marshalling time. Note also that we accept NaN values |
| * since round(NaN) == 0, which is the desired value. |
| */ |
| if (scale <= 0 || scale > 1) { |
| throw new IllegalArgumentException((scale <= 0) |
| ? Errors.format(Errors.Keys.ValueNotGreaterThanZero_2, "scale", scale) |
| : Errors.format(Errors.Keys.ValueOutOfRange_4, "scale", 0, 1, scale)); |
| } |
| setDenominator(Math.round(1.0 / scale)); |
| } |
| |
| /** |
| * Returns the scale value of this representative fraction. |
| * This method is the converse of {@link #setScale(double)}. |
| * |
| * @return the scale value of this representative fraction, or NaN if none. |
| */ |
| @Override |
| public double doubleValue() { |
| return (denominator != 0) ? (1.0 / (double) denominator) : Double.NaN; |
| } |
| |
| /** |
| * Returns the scale as a {@code float} type. |
| * |
| * @return the scale. |
| */ |
| @Override |
| public float floatValue() { |
| return (denominator != 0) ? (1.0f / (float) denominator) : Float.NaN; |
| } |
| |
| /** |
| * Returns 1 if the {@linkplain #getDenominator() denominator} is equals to 1, or 0 otherwise. |
| * |
| * <div class="note"><b>Rational:</b> |
| * This method is defined that way because scales smaller than 1 can |
| * only be casted to 0, and NaN values are also represented by 0.</div> |
| * |
| * @return 1 if the denominator is 1, or 0 otherwise. |
| */ |
| @Override |
| public long longValue() { |
| return (denominator == 1) ? 1 : 0; |
| } |
| |
| /** |
| * Returns 1 if the {@linkplain #getDenominator() denominator} is equals to 1, or 0 otherwise. |
| * |
| * <div class="note"><b>Rational:</b> |
| * This method is defined that way because scales smaller than 1 can |
| * only be casted to 0, and NaN values are also represented by 0.</div> |
| * |
| * @return 1 if the denominator is 1, or 0 otherwise. |
| */ |
| @Override |
| public int intValue() { |
| return (denominator == 1) ? 1 : 0; |
| } |
| |
| /** |
| * Returns {@code true} if no scale is defined. |
| * The following relationship shall hold: |
| * |
| * {@preformat java |
| * assert isEmpty() == Double.isNaN(doubleValue()); |
| * } |
| * |
| * @return {@code true} if no scale is defined. |
| * |
| * @see #doubleValue() |
| * @see #floatValue() |
| * |
| * @since 0.6 |
| */ |
| @Override |
| public boolean isEmpty() { |
| return (denominator == 0); |
| } |
| |
| /** |
| * Makes this representative fraction unmodifiable. After invocation to this method, |
| * any call to a setter method will throw an {@link UnmodifiableMetadataException}. |
| * |
| * @see ModifiableMetadata#transition(ModifiableMetadata.State) |
| * |
| * @since 0.7 |
| */ |
| public void freeze() { |
| isUnmodifiable = true; |
| } |
| |
| /** |
| * Returns a modifiable copy of this representative fraction. |
| * |
| * @return a modifiable copy of this representative fraction. |
| */ |
| @Override |
| public DefaultRepresentativeFraction clone() { |
| final DefaultRepresentativeFraction c; |
| try { |
| c = (DefaultRepresentativeFraction) super.clone(); |
| } catch (CloneNotSupportedException e) { |
| throw new AssertionError(e); // Should never happen since we are cloneable. |
| } |
| c.isUnmodifiable = false; |
| return c; |
| } |
| |
| /** |
| * Compares this object with the specified value for equality. |
| * |
| * @param object the object to compare with. |
| * @return {@code true} if both objects are equal. |
| */ |
| @Override |
| public boolean equals(Object object) { |
| /* |
| * Note: 'equals(Object)' and 'hashCode()' implementations are defined in the interface, |
| * in order to ensure that the following requirements hold: |
| * |
| * - a.equals(b) == b.equals(a) (reflexivity) |
| * - a.equals(b) implies (a.hashCode() == b.hashCode()) |
| */ |
| if (object instanceof RepresentativeFraction) { |
| return ((RepresentativeFraction) object).getDenominator() == denominator; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a hash value for this representative fraction. |
| */ |
| @Override |
| public int hashCode() { |
| return (int) denominator; |
| } |
| |
| /** |
| * Returns a string representation of this scale, or {@code NaN} if undefined. |
| * If defined, the string representation uses the colon as in "1:20000". |
| * |
| * @return a string representation of this scale. |
| */ |
| @Override |
| public String toString() { |
| return (denominator != 0) ? "1:" + denominator : "NaN"; |
| } |
| |
| |
| |
| |
| // -------------------------------------------------------------------------------------- |
| // Code below this point is basically a copy-and-paste of ISOMetadata, with some edition. |
| // The JAXB attributes defined here shall be the same than the ISOMetadata ones. |
| // -------------------------------------------------------------------------------------- |
| |
| /** |
| * Returns all identifiers associated to this object, or an empty collection if none. |
| * Those identifiers are marshalled in XML as {@code id} or {@code uuid} attributes. |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| public Collection<Identifier> getIdentifiers() { |
| if (identifiers == null) { |
| identifiers = new CheckedArrayList<>(Identifier.class); |
| } |
| return identifiers; |
| } |
| |
| /** |
| * Returns a map view of the {@linkplain #getIdentifiers() identifiers} collection as (<var>authority</var>, |
| * <var>code</var>) entries. That map is <cite>live</cite>: changes in the identifiers list will be reflected |
| * in the map, and conversely. |
| */ |
| @Override |
| public IdentifierMap getIdentifierMap() { |
| final Collection<Identifier> identifiers = getIdentifiers(); |
| return isUnmodifiable ? new IdentifierMapAdapter(identifiers) |
| : new ModifiableIdentifierMap(identifiers); |
| } |
| |
| |
| |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////// |
| //////// //////// |
| //////// XML support with JAXB //////// |
| //////// //////// |
| //////// The following methods are invoked by JAXB using reflection (even if //////// |
| //////// they are private) or are helpers for other methods invoked by JAXB. //////// |
| //////// Those methods can be safely removed if Geographic Markup Language //////// |
| //////// (GML) support is not needed. //////// |
| //////// //////// |
| ////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Invoked by JAXB for fetching the unique identifier unique for the XML document. |
| * |
| * @see org.apache.sis.metadata.iso.ISOMetadata#getID() |
| */ |
| @XmlID |
| @XmlAttribute // Defined in "gco" as unqualified attribute. |
| @XmlSchemaType(name = "ID") |
| @XmlJavaTypeAdapter(CollapsedStringAdapter.class) |
| private String getID() { |
| return isNullOrEmpty(identifiers) ? null : MetadataUtilities.getObjectID(this); |
| } |
| |
| /** |
| * Invoked by JAXB for specifying the unique identifier. |
| * |
| * @see org.apache.sis.metadata.iso.ISOMetadata#setID(String) |
| */ |
| @SuppressWarnings("unused") |
| private void setID(String id) { |
| MetadataUtilities.setObjectID(this, id); |
| } |
| |
| /** |
| * Invoked by JAXB for fetching the unique identifier unique "worldwide". |
| * |
| * @see org.apache.sis.metadata.iso.ISOMetadata#getUUID() |
| */ |
| @XmlAttribute // Defined in "gco" as unqualified attribute. |
| @XmlJavaTypeAdapter(CollapsedStringAdapter.class) |
| private String getUUID() { |
| return isNullOrEmpty(identifiers) ? null : getIdentifierMap().get(IdentifierSpace.UUID); |
| } |
| |
| /** |
| * Invoked by JAXB for specifying the unique identifier. |
| * |
| * @see org.apache.sis.metadata.iso.ISOMetadata#setUUID(String) |
| */ |
| @SuppressWarnings("unused") |
| private void setUUID(final String id) { |
| getIdentifierMap().put(IdentifierSpace.UUID, id); |
| } |
| } |