blob: 2d5f6c2e8f6ea6aa13dc4b1f28cf9da4ce14159e [file] [log] [blame]
/*
* 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.quality;
import java.util.Iterator;
import java.util.Collection;
import java.util.AbstractList;
import java.util.Objects;
import java.io.Serializable;
import java.time.temporal.Temporal;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.quality.EvaluationMethodType;
import org.apache.sis.system.Semaphores;
import org.apache.sis.util.privy.CloneAccess;
import org.apache.sis.util.collection.CheckedContainer;
import org.apache.sis.util.resources.Errors;
import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.metadata.quality.EvaluationMethod;
import org.opengis.metadata.quality.DataEvaluation;
import org.opengis.metadata.quality.AggregationDerivation;
/**
* Description of the evaluation method and procedure applied.
* See the {@link EvaluationMethod} GeoAPI interface for more details.
*
* <h2>Limitations</h2>
* <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 Alexis Gaillard (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.4
* @since 1.3
*/
@XmlType(name = "DQ_EvaluationMethod_Type", propOrder = {
"evaluationMethodType",
"evaluationMethodDescription",
"evaluationProcedure",
"referenceDocuments",
"dates"
})
@XmlRootElement(name = "DQ_EvaluationMethod")
@XmlSeeAlso({
AbstractDataEvaluation.class,
DefaultAggregationDerivation.class
})
public class DefaultEvaluationMethod extends ISOMetadata implements EvaluationMethod {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 5196994626251088685L;
/**
* Type of method used to evaluate quality of the data.
*/
private EvaluationMethodType evaluationMethodType;
/**
* Description of the evaluation method.
*/
@SuppressWarnings("serial")
private InternationalString evaluationMethodDescription;
/**
* Reference to the procedure information.
*/
@SuppressWarnings("serial")
private Citation evaluationProcedure;
/**
* Information on documents which are referenced in developing and applying a data quality evaluation method.
*/
@SuppressWarnings("serial")
private Collection<Citation> referenceDocuments;
/**
* Date or range of dates on which a data quality measure was applied.
*/
private Dates dates;
/**
* The start and end times as a list of O, 1 or 2 elements.
*/
private static final class Dates extends AbstractList<Temporal>
implements CheckedContainer<Temporal>, CloneAccess, Serializable
{
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 1210175223467194009L;
/**
* Start time ({@code date1}) and end time ({@code date2}) on which a data quality measure was applied.
* Value is {@code null} if this information is not available.
*/
@SuppressWarnings("serial")
private Temporal date1, date2;
/**
* Creates a new list initialized with no dates.
*/
Dates() {
clear();
}
/**
* Returns the type of elements in this list.
*/
@Override
public Class<Temporal> getElementType() {
return Temporal.class;
}
/**
* Removes all dates in this list.
*/
@Override
public void clear() {
date1 = null;
date2 = null;
}
/**
* Returns the number of elements in this list.
*/
@Override
public int size() {
if (date2 != null) return 2;
if (date1 != null) return 1;
return 0;
}
/**
* Returns the value at the given index.
*/
@Override
public Temporal get(final int index) {
Temporal date;
switch (index) {
case 0: date = date1; break;
case 1: date = date2; break;
default: date = null; break;
}
if (date == null) {
throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, index));
}
return date;
}
/**
* Sets the value at the given index.
* Null values are not allowed.
*/
@Override
public Temporal set(final int index, final Temporal date) {
Objects.requireNonNull(date);
final Temporal previous = get(index);
switch (index) {
case 0: date1 = date; break;
case 1: date2 = date; break;
}
modCount++;
return previous;
}
/**
* Removes the value at the given index.
*/
@Override
@SuppressWarnings("fallthrough")
public Temporal remove(final int index) {
final Temporal previous = get(index);
switch (index) {
case 0: date1 = date2; // Fallthrough
case 1: date2 = null; break;
}
modCount++;
return previous;
}
/**
* Adds a date at the given position.
* Null values are not allowed.
*/
@Override
public void add(final int index, final Temporal date) {
if (date2 == null) {
switch (index) {
case 0: {
date2 = date1;
date1 = date;
modCount++;
return;
}
case 1: {
if (date1 == null) break; // Exception will be thrown below.
date2 = date;
modCount++;
return;
}
}
}
throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, index));
}
/**
* Adds all content from the given collection into this collection.
*/
@Override
@SuppressWarnings("fallthrough")
public boolean addAll(final Collection<? extends Temporal> dates) {
final int c = modCount;
if (dates != null) {
final Iterator<? extends Temporal> it = dates.iterator();
switch (size()) { // Fallthrough everywhere.
case 0: if (!it.hasNext()) break;
date1 = it.next();
modCount++;
case 1: if (!it.hasNext()) break;
date2 = it.next();
modCount++;
default: if (!it.hasNext()) break;
throw new IllegalArgumentException(Errors.format(
Errors.Keys.TooManyCollectionElements_3, "dates", 2, dates.size()));
}
}
return modCount != c;
}
/**
* Returns a clone of this list.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
/**
* Constructs an initially empty evaluation method.
*/
public DefaultEvaluationMethod() {
}
/**
* Constructs a new instance initialized with the values from the specified metadata object.
* This is a <em>shallow</em> copy constructor, because the other metadata contained in the
* given object are not recursively copied.
*
* @param object the metadata to copy values from, or {@code null} if none.
*
* @see #castOrCopy(EvaluationMethod)
*/
public DefaultEvaluationMethod(final EvaluationMethod object) {
super(object);
if (object != null) {
evaluationMethodType = object.getEvaluationMethodType();
evaluationMethodDescription = object.getEvaluationMethodDescription();
evaluationProcedure = object.getEvaluationProcedure();
referenceDocuments = copyCollection(object.getReferenceDocuments(), Citation.class);
writeDates(object.getDates());
}
}
/**
* Returns a SIS metadata implementation with the values of the given arbitrary implementation.
* This method performs the first applicable action in the following choices:
*
* <ul>
* <li>If the given object is {@code null}, then this method returns {@code null}.</li>
* <li>Otherwise if the given object is an instance of {@link DataEvaluation} or {@link AggregationDerivation},
* then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
* Note that if the given object implements more than one of the above-cited interfaces,
* then the {@code castOrCopy(…)} method to be used is unspecified.</li>
* <li>Otherwise if the given object is already an instance of
* {@code DefaultEvaluationMethod}, then it is returned unchanged.</li>
* <li>Otherwise a new {@code DefaultEvaluationMethod} instance is created using the
* {@linkplain #DefaultEvaluationMethod(EvaluationMethod) copy constructor} and returned.
* Note that this is a <em>shallow</em> copy operation, because the other
* metadata contained in the given object are not recursively copied.</li>
* </ul>
*
* @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 DefaultEvaluationMethod castOrCopy(final EvaluationMethod object) {
if (object instanceof DataEvaluation) {
return AbstractDataEvaluation.castOrCopy((DataEvaluation) object);
}
if (object instanceof AggregationDerivation) {
return DefaultAggregationDerivation.castOrCopy((AggregationDerivation) object);
}
// Intentionally tested after the sub-interfaces.
if (object == null || object instanceof DefaultEvaluationMethod) {
return (DefaultEvaluationMethod) object;
}
return new DefaultEvaluationMethod(object);
}
/**
* Returns the type of method used to evaluate quality of the data.
*
* @return type of method used to evaluate quality, or {@code null} if none.
*/
@Override
@XmlElement(name = "evaluationMethodType")
public EvaluationMethodType getEvaluationMethodType() {
return evaluationMethodType;
}
/**
* Sets the type of method used to evaluate quality of the data.
*
* @param newValue the new evaluation method type.
*/
public void setEvaluationMethodType(final EvaluationMethodType newValue) {
checkWritePermission(evaluationMethodType);
evaluationMethodType = newValue;
}
/**
* Returns the description of the evaluation method.
*
* @return description of the evaluation method, or {@code null} if none.
*/
@Override
@XmlElement(name = "evaluationMethodDescription")
public InternationalString getEvaluationMethodDescription() {
return evaluationMethodDescription;
}
/**
* Sets the description of the evaluation method.
*
* @param newValue the new evaluation method description.
*/
public void setEvaluationMethodDescription(final InternationalString newValue) {
checkWritePermission(evaluationMethodDescription);
evaluationMethodDescription = newValue;
}
/**
* Returns the reference to the procedure information.
*
* @return reference to the procedure information, or {@code null} if none.
*/
@Override
@XmlElement(name = "evaluationProcedure")
public Citation getEvaluationProcedure() {
return evaluationProcedure;
}
/**
* Sets the reference to the procedure information.
*
* @param newValue the new evaluation procedure.
*/
public void setEvaluationProcedure(final Citation newValue) {
checkWritePermission(evaluationProcedure);
evaluationProcedure = newValue;
}
/**
* Returns information on documents which are referenced in developing and applying a data quality evaluation method.
*
* @return documents referenced in data quality evaluation method.
*/
@Override
@XmlElement(name = "referenceDoc")
public Collection<Citation> getReferenceDocuments() {
return referenceDocuments = nonNullCollection(referenceDocuments, Citation.class);
}
/**
* Sets the information on documents referenced in data quality evaluation method.
*
* @param newValues the new name of measures.
*/
public void setReferenceDocuments(final Collection<? extends Citation> newValues) {
referenceDocuments = writeCollection(newValues, referenceDocuments, Citation.class);
}
/**
* Returns the date or range of dates on which a data quality measure was applied.
* The collection size is 1 for a single date, or 2 for a range.
* Returns an empty collection if this information is not available.
*
* @return date or range of dates on which a data quality measure was applied.
*/
@Override
@XmlElement(name = "dateTime")
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public Collection<Temporal> getDates() {
if (Semaphores.query(Semaphores.NULL_COLLECTION)) {
return isNullOrEmpty(dates) ? null : dates;
}
if (dates == null) {
dates = new Dates();
}
return dates;
}
/**
* Sets the date or range of dates on which a data quality measure was applied.
* The collection size is 1 for a single date, or 2 for a range.
*
* @param newValues the new dates, or {@code null}.
*/
public void setDates(final Collection<? extends Temporal> newValues) {
if (newValues != dates) { // Mandatory check for avoiding the call to 'dates.clear()'.
checkWritePermission(valueIfDefined(dates));
writeDates(newValues);
}
}
/**
* Implementation of {@link #setDates(Collection)}.
*/
private void writeDates(final Collection<? extends Temporal> newValues) {
if (isNullOrEmpty(newValues)) {
dates = null;
} else {
if (dates == null) {
dates = new Dates();
}
dates.clear();
dates.addAll(newValues);
}
}
}