| /* |
| * 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.xml; |
| |
| import java.util.Map; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.io.Serializable; |
| import java.io.ObjectStreamException; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.InvocationHandler; |
| import org.opengis.util.InternationalString; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.LenientComparable; |
| import org.apache.sis.util.collection.WeakHashSet; |
| import org.apache.sis.util.collection.WeakValueHashMap; |
| import org.apache.sis.internal.jaxb.PrimitiveTypeProperties; |
| |
| |
| /** |
| * Explanation for a missing XML element. The nil reason can be parsed and formatted as a |
| * string using the {@link #valueOf(String)} and {@link #toString()} methods respectively. |
| * The string can be either a {@link URI} or an enumeration value described below. |
| * More specifically, {@code NilReason} can be: |
| * |
| * <ul> |
| * <li>One of the predefined {@link #INAPPLICABLE}, {@link #MISSING}, {@link #TEMPLATE}, |
| * {@link #UNKNOWN} or {@link #WITHHELD} enumeration values.</li> |
| * <li>The {@link #OTHER} enumeration value, or a new enumeration value formatted as |
| * {@code "other:"} concatenated with a brief textual explanation.</li> |
| * <li>A URI which should refer to a resource which describes the reason for the exception.</li> |
| * </ul> |
| * |
| * {@code NilReason} is used in a number of XML elements where it is necessary to permit |
| * one of the above values as an alternative to the primary element. |
| * |
| * <h2>Immutability and thread safety</h2> |
| * This final class is immutable and thus inherently thread-safe. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.4 |
| * |
| * @see NilObject |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public final class NilReason implements Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 5553785821187789895L; |
| |
| /** |
| * There is no value. |
| * |
| * <p>The string representation is {@code "inapplicable"}. |
| * Other properties (explanation and URI) are {@code null}.</p> |
| */ |
| public static final NilReason INAPPLICABLE = new NilReason("inapplicable"); |
| |
| /** |
| * The correct value is not readily available to the sender of this data. |
| * Furthermore, a correct value may not exist. |
| * |
| * <p>The string representation is {@code "missing"}. |
| * Other properties (explanation and URI) are {@code null}.</p> |
| */ |
| public static final NilReason MISSING = new NilReason("missing"); |
| |
| /** |
| * The value will be available later. |
| * |
| * <p>The string representation is {@code "template"}. |
| * Other properties (explanation and URI) are {@code null}.</p> |
| */ |
| public static final NilReason TEMPLATE = new NilReason("template"); |
| |
| /** |
| * The correct value is not known to, and not computable by, the sender of this data. |
| * However, a correct value probably exists. |
| * |
| * <p>The string representation is {@code "unknown"}. |
| * Other properties (explanation and URI) are {@code null}.</p> |
| */ |
| public static final NilReason UNKNOWN = new NilReason("unknown"); |
| |
| /** |
| * The value is not divulged. |
| * |
| * <p>The string representation is {@code "withheld"}. |
| * Other properties (explanation and URI) are {@code null}.</p> |
| */ |
| public static final NilReason WITHHELD = new NilReason("withheld"); |
| |
| /** |
| * The {@value} string. |
| */ |
| private static final String other = "other"; |
| |
| /** |
| * Other reason without explanation. |
| * The string representation of this constant is {@code "other"}. |
| * The explanation property is an empty string, and the URI is {@code null}. |
| * |
| * <h4>Providing an explanation</h4> |
| * Users are encouraged to use the {@link #valueOf(String)} method instead than this constant, |
| * in order to provide a brief explanation. The string representation for {@code valueOf(…)} |
| * is <code>"other:<var>explanation</var>"</code> where <var>explanation</var> is a string of |
| * two or more characters with no included spaces. |
| * |
| * <p>When testing if a {@code NilReason} instance is any kind of {@code "other"} reason, |
| * users should test if <code>{@linkplain #getOtherExplanation()} != null</code> instead |
| * than comparing the reference against this constant.</p> |
| */ |
| public static final NilReason OTHER = new NilReason(other); |
| |
| /** |
| * List of predefined constants. |
| */ |
| private static final NilReason[] PREDEFINED = { |
| INAPPLICABLE, MISSING, TEMPLATE, UNKNOWN, WITHHELD, OTHER |
| }; |
| |
| /** |
| * The pool of other nil reasons created up to date. |
| */ |
| private static final WeakHashSet<NilReason> POOL = new WeakHashSet<>(NilReason.class); |
| |
| /** |
| * Either the XML value as a {@code String} (including the explanation if the prefix |
| * is "{@code other}", or a {@link URI}. |
| */ |
| private final Object reason; |
| |
| /** |
| * The invocation handler for {@link NilObject} instances, created when first needed. |
| * The same handler can be shared for all objects having the same {@code NilReason}, |
| * no matter the interface they implement. |
| */ |
| private transient InvocationHandler handler; |
| |
| /** |
| * The values created by {@link #createNilObject(Class)}. They are often instances of {@link NilObject}, |
| * except for some JDK types like {@link String}, {@link Boolean} or {@link Integer} which are handled |
| * in a special way. |
| */ |
| private transient Map<Class<?>, Object> nilObjects; |
| |
| /** |
| * Creates a new reason for the given XML value or the given URI. |
| */ |
| private NilReason(final Object reason) { |
| this.reason = reason; |
| } |
| |
| /** |
| * Returns an array containing every instances of this type that have not yet been |
| * garbage collected. The first elements of the returned array are the constants |
| * defined in this class, in declaration order. All other elements are the instances |
| * created by the {@link #valueOf(String)} method, in no particular order. |
| * |
| * @return an array containing the instances of {@code NilReason}. |
| */ |
| public static NilReason[] values() { |
| final int predefinedCount = PREDEFINED.length; |
| NilReason[] reasons; |
| synchronized (POOL) { |
| reasons = POOL.toArray(new NilReason[predefinedCount + POOL.size()]); |
| } |
| int count = reasons.length; |
| while (count != 0 && reasons[count-1] == null) { |
| count--; |
| } |
| count += predefinedCount; |
| final NilReason[] source = reasons; |
| if (count != reasons.length) { |
| reasons = new NilReason[count]; |
| } |
| System.arraycopy(source, 0, reasons, predefinedCount, count - predefinedCount); |
| System.arraycopy(PREDEFINED, 0, reasons, 0, predefinedCount); |
| return reasons; |
| } |
| |
| /** |
| * Parses the given nil reason. This method accepts the following cases: |
| * |
| * <ul> |
| * <li>If the given argument is one of the {@code "inapplicable"}, {@code "missing"}, |
| * {@code "template"}, {@code "unknown"}, {@code "withheld"} or {@code "other"} |
| * strings (ignoring cases and leading/trailing spaces), then the corresponding |
| * pre-defined constant is returned.</li> |
| * <li>Otherwise if the given argument is {@code "other:"} followed by an explanation |
| * text, then an instance for that explanation is returned. More specifically: |
| * <ul> |
| * <li>{@linkplain Character#isSpaceChar(int) Space characters} and |
| * {@linkplain Character#isISOControl(int) ISO controls} are silently omitted.</li> |
| * <li>If the remaining string has less than two characters, then the {@link #OTHER} |
| * constant is returned.</li> |
| * </ul> |
| * </li> |
| * <li>Otherwise this method attempts to parse the given argument as a {@link URI}. |
| * Such URI should refer to a resource which describes the reason for the exception.</li> |
| * </ul> |
| * |
| * This method returns existing instances when possible. |
| * |
| * @param reason the reason why an element is not present. |
| * @return the reason as a {@code NilReason} object. |
| * @throws URISyntaxException if the given string is not one of the predefined enumeration |
| * values and can not be parsed as a URI. |
| */ |
| public static NilReason valueOf(String reason) throws URISyntaxException { |
| reason = CharSequences.trimWhitespaces(reason); |
| int i = reason.indexOf(':'); |
| if (i < 0) { |
| for (final NilReason candidate : PREDEFINED) { |
| if (reason.equalsIgnoreCase((String) candidate.reason)) { |
| return candidate; |
| } |
| } |
| } else { |
| final int lower = CharSequences.skipLeadingWhitespaces(reason, 0, i); |
| final int upper = CharSequences.skipTrailingWhitespaces(reason, lower, i); |
| if (reason.regionMatches(true, lower, other, 0, upper - lower)) { |
| final int length = reason.length(); |
| final StringBuilder buffer = new StringBuilder(length).append(other); |
| while (i < length) { |
| final int c = reason.codePointAt(i); |
| if (!Character.isSpaceChar(c) && !Character.isISOControl(c)) { |
| buffer.appendCodePoint(c); |
| } |
| i += Character.charCount(c); |
| } |
| if (buffer.length() < other.length() + 2) { |
| return OTHER; |
| } |
| String result = buffer.toString(); |
| if (result.equals(reason)) { |
| result = reason; // Use the existing instance. |
| } |
| return POOL.unique(new NilReason(result)); |
| } |
| } |
| return POOL.unique(new NilReason(new URI(reason))); |
| } |
| |
| /** |
| * Invoked after deserialization in order to return a unique instance if possible. |
| * |
| * @return a unique instance of the deserialized {@code NilReason}. |
| * @throws ObjectStreamException required by specification but should never be thrown. |
| */ |
| private Object readResolve() throws ObjectStreamException { |
| if (reason instanceof String) { |
| for (final NilReason candidate : PREDEFINED) { |
| if (reason.equals(candidate.reason)) { |
| return candidate; |
| } |
| } |
| } |
| return POOL.unique(this); |
| } |
| |
| /** |
| * If this {@code NilReason} is an enumeration of kind {@link #OTHER}, returns the explanation |
| * text. Otherwise returns {@code null}. If non-null, then the explanation is a string without |
| * whitespace. |
| * |
| * <p>Note that in the special case where {@code this} nil reason is the {@link #OTHER} |
| * instance itself, then this method returns an empty string. For all other cases, the |
| * string contains at least two characters.</p> |
| * |
| * @return the explanation, or {@code null} if this {@code NilReason} is not of kind {@link #OTHER}. |
| */ |
| public String getOtherExplanation() { |
| if (reason instanceof String) { |
| final String text = (String) reason; |
| final int s = text.indexOf(':'); |
| if (s >= 0) { |
| return text.substring(s + 1); |
| } |
| if (text.equals(other)) { |
| return ""; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * If the explanation of this {@code NilReason} is referenced by a URI, returns that URI. |
| * Otherwise returns {@code null}. The URI and the {@linkplain #getOtherExplanation() other |
| * explanation} attributes are mutually exclusive. |
| * |
| * @return the URI, or {@code null} if the explanation of this {@code NilReason} is not referenced by a URI. |
| */ |
| public URI getURI() { |
| return (reason instanceof URI) ? (URI) reason : null; |
| } |
| |
| /** |
| * Returns the GML string representation of this {@code NilReason}. The returned string |
| * is a simple enumeration value (e.g. {@code "inapplicable"}) if this {@code NilReason} |
| * is one of the predefined constants, or a string of the form {@code "other:explanation"}, |
| * or a URI. |
| * |
| * @return the GML string representation of this {@code NilReason}. |
| */ |
| @Override |
| public String toString() { |
| return reason.toString(); |
| } |
| |
| /** |
| * Returns a hash code value for this {@code NilReason}. |
| */ |
| @Override |
| public int hashCode() { |
| return reason.hashCode() ^ (int) serialVersionUID; |
| } |
| |
| /** |
| * Compares this {@code NilReason} with the specified object for equality. |
| * |
| * @param other the object to compare with this {@code NilReason}. |
| */ |
| @Override |
| public boolean equals(final Object other) { |
| if (other instanceof NilReason) { |
| return reason.equals(((NilReason) other).reason); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns an object of the given type which is nil for the reason represented by this instance. |
| * The {@code type} argument can be one of the following cases: |
| * |
| * <ul class="verbose"> |
| * <li>An <strong>interface</strong>: in such case, this method returns an object which implement the given |
| * interface together with the {@link NilObject} and {@link LenientComparable} interfaces: |
| * <ul> |
| * <li>The {@link NilObject#getNilReason()} method will return this {@code NilReason} instance.</li> |
| * <li>The {@code equals(…)} and {@code hashCode()} methods behave as documented in {@link LenientComparable}.</li> |
| * <li>The {@code toString()} method is unspecified (may contain debugging information).</li> |
| * <li>All other methods return an empty collections, empty arrays, {@code null}, {@link Double#NaN NaN}, |
| * {@code 0} or {@code false}, in this preference order, depending on the method return type.</li> |
| * </ul> |
| * </li> |
| * <li>One of {@code Boolean}, {@link Byte}, {@link Short}, {@code Integer}, {@link Long}, {@link Float}, |
| * {@code Double} or {@code String} types: in such case, this method returns a specific instance which |
| * will be recognized as "nil" by the XML marshaller.</li> |
| * </ul> |
| * |
| * @param <T> the compile-time type of the {@code type} argument. |
| * @param type the object type as an <strong>interface</strong> |
| * (usually a <a href="http://www.geoapi.org">GeoAPI</a> one) or one of the special types. |
| * @throws IllegalArgumentException if the given type is not a supported type. |
| * @return an {@link NilObject} of the given type. |
| */ |
| @SuppressWarnings("unchecked") |
| public synchronized <T> T createNilObject(final Class<T> type) { |
| ArgumentChecks.ensureNonNull("type", type); |
| /* |
| * Check for existing instance in the cache before to create a new object. Returning a unique |
| * instance is mandatory for the types handled by 'createNilPrimitive(Class)'. Since we have |
| * to cache those values anyway, we opportunistically extend the caching to other types too. |
| * |
| * Implementation note: we have two synchronizations here: one lock on 'this' because of the |
| * 'synchronized' statement in this method signature, and an other lock in WeakValueHashMap. |
| * The second lock may seem useless since we already hold a lock on 'this'. But it is actually |
| * needed because the garbage-collected entries are removed from the map in a background thread |
| * (see ReferenceQueueConsumer), which is synchronized on the map itself. It is better to keep |
| * the synchronization on the map shorter than the snychronization on 'this' because a single |
| * ReferenceQueueConsumer thread is shared by all the SIS library. |
| */ |
| if (nilObjects == null) { |
| nilObjects = new WeakValueHashMap<>((Class) Class.class); |
| } |
| Object object = nilObjects.get(type); |
| if (object == null) { |
| /* |
| * If no object has been previously created, check for the usual case where the requested type |
| * is an interface. We still have a special case for InternationalString. For all other cases, |
| * we will rely on the proxy. |
| */ |
| if (type.isInterface()) { |
| if (NilObjectHandler.isIgnoredInterface(type)) { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type)); |
| } |
| if (type == InternationalString.class) { |
| object = new NilInternationalString(this); |
| } else { |
| if (handler == null) { |
| handler = new NilObjectHandler(this); |
| } |
| object = Proxy.newProxyInstance(NilReason.class.getClassLoader(), |
| new Class<?>[] {type, NilObject.class, LenientComparable.class}, handler); |
| } |
| } else { |
| /* |
| * If the requested type is not an interface, this is usually an error except for some |
| * special cases: Boolean, Byte, Short, Integer, Long, Float, Double or String. |
| */ |
| object = createNilPrimitive(type); |
| PrimitiveTypeProperties.associate(object, this); |
| } |
| if (nilObjects.put(type, object) != null) { |
| throw new AssertionError(type); // Should never happen. |
| } |
| } |
| return type.cast(object); |
| } |
| |
| /** |
| * Returns a new {@code Boolean}, {@link Byte}, {@link Short}, {@code Integer}, {@link Long}, |
| * {@link Float}, {@code Double} or {@code String} instance to be considered as a nil value. |
| * The caller is responsible for registering the value in {@link PrimitiveTypeProperties}. |
| * |
| * <p><b>REMINDER:</b> If more special cases are added, do not forget to update the {@link #mayBeNil(Object)} |
| * method and to update the {@link #createNilObject(Class)} and {@link #forObject(Object)} javadoc.</p> |
| * |
| * <div class="note"><b>Implementation note:</b> |
| * There is no special case for {@link Character} because Java {@code char}s are not really full Unicode characters. |
| * They are parts of UTF-16 encoding instead. If there is a need to represent a single Unicode character, we should |
| * probably still use a {@link String} where the string contain 1 or 2 Java characters. This may also facilitate the |
| * encoding in the XML files, since many files use an other encoding than UTF-16 anyway.</div> |
| * |
| * @throws IllegalArgumentException if the given type is not a supported type. |
| */ |
| @SuppressWarnings({"deprecation", "RedundantStringConstructorCall", "BooleanConstructorCall", "UnnecessaryBoxing"}) |
| private static Object createNilPrimitive(final Class<?> type) { |
| if (type == String .class) return new String(""); // REALLY need a new instance. |
| if (type == Boolean.class) return new Boolean(false); // REALLY need a new instance, not Boolean.FALSE. |
| if (type == Byte .class) return new Byte((byte) 0); // REALLY need a new instance, not Byte.valueOf(0). |
| if (type == Short .class) return new Short((byte) 0); // REALLY need a new instance, not Short.valueOf(0). |
| if (type == Integer.class) return new Integer(0); // REALLY need a new instance, not Integer.valueOf(0). |
| if (type == Long .class) return new Long(0); // REALLY need a new instance, not Long.valueOf(0). |
| if (type == Float .class) return new Float(Float.NaN); // REALLY need a new instance, not Float.valueOf(…). |
| if (type == Double .class) return new Double(Double.NaN); // REALLY need a new instance, not Double.valueOf(…). |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type)); |
| } |
| |
| /** |
| * Returns {@code true} if the given object may be one of the sentinel values |
| * created by {@link #createNilPrimitive(Class)}. This method only checks the value. |
| * If this method returns {@code true}, then the caller still needs to check the actual instance using the |
| * {@link PrimitiveTypeProperties} class. The purpose of this method is to filter the values that can not |
| * be sentinel values, in order to avoid the synchronization done by {@code PrimitiveTypeProperties}. |
| */ |
| private static boolean mayBeNil(final Object object) { |
| // 'instanceof' checks on instances of final classes are expected to be very fast. |
| if (object instanceof String) return ((String) object).isEmpty(); |
| if (object instanceof Boolean) return !((Boolean) object) && (object != Boolean.FALSE); |
| if (object instanceof Number) { |
| /* |
| * Following test may return false positives for Long, Float and Double types, but this is okay |
| * since the real check will be done by PrimitiveTypeProperties. The purpose of this method is |
| * only to perform a cheap filtering. Note that this method relies on the fact that casting NaN |
| * to 'int' produces 0. |
| */ |
| return ((Number) object).intValue() == 0; |
| } |
| return false; |
| } |
| |
| /** |
| * If the given object is nil, returns the reason why it does not contain information. |
| * This method performs the following choices: |
| * |
| * <ul> |
| * <li>If the given object implements the {@link NilObject} interface, then this method delegates |
| * to the {@link NilObject#getNilReason()} method.</li> |
| * <li>Otherwise if the given object is one of the {@code Boolean}, {@link Byte}, {@link Short}, {@code Integer}, |
| * {@link Long}, {@link Float}, {@code Double} or {@code String} instances returned by |
| * {@link #createNilObject(Class)}, then this method returns the associated reason.</li> |
| * <li>Otherwise this method returns {@code null}.</li> |
| * </ul> |
| * |
| * @param object the object for which to get the {@code NilReason}, or {@code null}. |
| * @return the reason why the given object contains no information, or {@code null} if the given object is not nil. |
| * |
| * @see NilObject#getNilReason() |
| * |
| * @since 0.4 |
| */ |
| public static NilReason forObject(final Object object) { |
| if (object != null) { |
| if (object instanceof NilObject) { |
| return ((NilObject) object).getNilReason(); |
| } |
| if (mayBeNil(object)) { |
| return (NilReason) PrimitiveTypeProperties.property(object); |
| } |
| } |
| return null; |
| } |
| } |