blob: 0939e7e1bcc0b70151930f308025687cd942ae49 [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;
import java.util.Map;
import java.util.Collection;
import java.lang.reflect.Array;
import org.apache.sis.xml.NilObject;
import org.apache.sis.xml.NilReason;
/**
* Whatever {@link MetadataStandard#asValueMap MetadataStandard.asValueMap(…)} shall contain entries for null,
* {@linkplain org.apache.sis.xml.NilObject nil} or empty values. By default the value map does not provide
* {@linkplain java.util.Map.Entry entries} for {@code null} metadata properties, nil objects or
* {@linkplain java.util.Collection#isEmpty() empty collections}.
* This enumeration allows to control this behavior.
*
* <h2>Difference between null and nil</h2>
* A null property is a reference which is {@code null} in the Java sense.
* Null references can be used for missing properties when no information is provided about why the property is missing.
* On the other hand, a nil object is a placeholder for a missing property similar in purpose to {@code null} references,
* except that an explanation about why the property is missing can be attached to those objects.
* Those explanations can be obtained by calls to the {@link org.apache.sis.xml.NilReason#forObject(Object)} method.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @see MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)
*
* @since 0.3
*/
public enum ValueExistencePolicy {
/*
* Implementation note: enumeration order matter.
* The `acceptNilValues()` method relies on it.
*/
/**
* Includes all entries in the map, including those having a null value or an empty collection.
*/
ALL() {
/** Never skip values. */
@Override boolean isSkipped(final Object value) {
return false;
}
/** Substitutes null or empty collections by a null singleton element
in order to make the property visible in {@code TreeNode}. */
@Override boolean substituteByNullElement(final Collection<?> values) {
return (values == null) || values.isEmpty();
}
},
/**
* Includes only the non-null properties.
* {@link org.apache.sis.xml.NilObject}s are included.
* Collections are included no matter if they are empty or not.
*
* <p>The set of {@code NON_NULL} properties is a subset of {@link #ALL} properties.</p>
*/
NON_NULL() {
/** Skips all null values. */
@Override boolean isSkipped(final Object value) {
return (value == null);
}
/** Substitutes empty collections by a null singleton element, but not
null references since they are supposed to be skipped by this policy. */
@Override boolean substituteByNullElement(final Collection<?> values) {
return (values != null) && values.isEmpty();
}
},
/**
* Includes only the non-null and non-nil properties.
* Collections are included no matter if they are empty or not.
*
* <p>The set of {@code NON_NIL} properties is a subset of {@link #NON_NULL} properties.</p>
*
* @since 0.4
*/
NON_NIL() {
/** Skips all null or nil values. */
@Override boolean isSkipped(final Object value) {
return (value == null) || (value instanceof NilObject) || NilReason.forObject(value) != null;
}
/** Substitutes empty collections by a null singleton element, but not
null references since they are supposed to be skipped by this policy. */
@Override boolean substituteByNullElement(final Collection<?> values) {
return (values != null) && values.isEmpty();
}
},
/**
* Includes only the properties that are non-null, non-nil and non empty.
* A non-null and non-nil property is considered empty in any of the following cases:
*
* <ul>
* <li>It is a character sequence containing only {@linkplain Character#isWhitespace(int) whitespaces}.</li>
* <li>It is an {@linkplain Collection#isEmpty() empty collection}.</li>
* <li>It is an {@linkplain Map#isEmpty() empty map}.</li>
* <li>It is an empty array (of length 0).</li>
* </ul>
*
* This is the default behavior of {@link AbstractMetadata#asMap()}.
*
* <p>The set of {@code NON_EMPTY} properties is a subset of {@link #NON_NIL} properties.</p>
*/
NON_EMPTY() {
/** Skips all null or empty values. */
@Override boolean isSkipped(final Object value) {
return isNullOrEmpty(value);
}
/** Never substitute null or empty collections since they should be skipped. */
@Override boolean substituteByNullElement(final Collection<?> values) {
return false;
}
},
/**
* Includes non-empty properties but omits {@linkplain TitleProperty title properties}.
* Values associated to title properties are instead associated with the parent node.
* This policy is relevant for metadata classes annotated with {@link TitleProperty};
* for all other classes, this policy is identical to {@link #NON_EMPTY}.
*
* <h4>Example</h4>
* the {@link org.apache.sis.metadata.iso.citation.DefaultCitation} and
* {@link org.apache.sis.metadata.iso.citation.DefaultCitationDate} classes are annotated with
* <code>&#64;TitleProperty(name="title")</code> and <code>&#64;TitleProperty(name="date")</code>
* respectively. The following table compares the trees produced by two policies:
*
* <table class="sis">
* <caption>Comparison of "non-empty" and "compact" policy on the same metadata</caption>
* <tr>
* <th>{@code NON_EMPTY}</th>
* <th class="sep">{@code COMPACT}</th>
* </tr><tr><td>
* <pre class="text">
* Citation
* ├─Title……………………… My document
* └─Date
* ├─Date………………… 2012/01/01
* └─Date type…… Creation</pre>
* </td><td class="sep">
* <pre class="text">
* Citation……………………… My document
* └─Date………………………… 2012/01/01
* └─Date type…… Creation</pre>
* </td></tr>
* </table>
*
* This policy is the default behavior of {@link AbstractMetadata#asTreeTable()},
* and consequently defines the default rendering of {@link AbstractMetadata#toString()}.
*
* @see TitleProperty
*
* @since 0.8
*/
COMPACT() {
/** Skips all null or empty values. */
@Override boolean isSkipped(final Object value) {
return isNullOrEmpty(value);
}
/** Never substitute null or empty collections since they should be skipped. */
@Override boolean substituteByNullElement(final Collection<?> values) {
return false;
}
};
/**
* {@return whether this policy accepts nil values}.
*/
final boolean acceptNilValues() {
return ordinal() < NON_NIL.ordinal();
}
/**
* Returns {@code true} if the given value shall be skipped for this policy.
*/
abstract boolean isSkipped(Object value);
/**
* Returns {@code true} if {@link TreeNode} shall substitute the given collection by
* a singleton containing only a null element.
*
* <h4>Purpose</h4>
* When a collection is null or empty, while not excluded according this {@code ValueExistencePolicy},
* we need an empty space for making the metadata property visible in {@code TreeNode}.
*/
abstract boolean substituteByNullElement(Collection<?> values);
/**
* Returns {@code true} if the specified object is null or an empty collection, array or string.
*
* <p>This method intentionally does not inspect array or collection elements, since this method
* is invoked from methods doing shallow copy or comparison. If we were inspecting elements,
* we would need to add a check against infinite recursivity.</p>
*
* <p>This method does not check for the {@link org.apache.sis.util.Emptiable} interface because
* the {@code isEmpty()} method may be costly (for example {@link AbstractMetadata#isEmpty()}
* iterates over all the metadata tree). Instead, the check for {@code Emptiable} will be done
* explicitly by the caller when appropriate.</p>
*/
static boolean isNullOrEmpty(final Object value) {
if (value == null) return true;
if (value instanceof NilObject) return true;
if (value instanceof CharSequence) return isEmpty((CharSequence) value);
if (value instanceof Collection<?>) return ((Collection<?>) value).isEmpty();
if (value instanceof Map<?,?>) return ((Map<?,?>) value).isEmpty();
if (value.getClass().isArray()) return Array.getLength(value) == 0;
return NilReason.forObject(value) != null;
}
/**
* Returns {@code true} if the given character sequence shall be considered empty.
* The current implementation returns {@code true} if the sequence contains only
* whitespaces in the sense of Java (i.e. ignoring line feeds, but not ignoring
* non-breaking spaces). The exact criterion is not a committed part of the API
* and may change in future SIS versions according experiences.
*/
private static boolean isEmpty(final CharSequence value) {
final int length = value.length();
for (int i=0; i<length;) {
final int c = Character.codePointAt(value, i);
if (!Character.isWhitespace(c)) {
return false;
}
i += Character.charCount(c);
}
return true;
}
}