sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1415260 - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/util/ main/java/org/apache/sis/xml/ test/java/org/apache/sis/test/suite/ test/java/org/apache/sis/xml/
Date Thu, 29 Nov 2012 16:36:10 GMT
Author: desruisseaux
Date: Thu Nov 29 16:36:09 2012
New Revision: 1415260

URL: http://svn.apache.org/viewvc?rev=1415260&view=rev
Log:
Initial commit of ISO 19139 NilReason.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java   (with
props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java 
 (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java   (with
props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java   (with
props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt   (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Numbers.java?rev=1415260&r1=1415259&r2=1415260&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Numbers.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Numbers.java Thu Nov 29
16:36:09 2012
@@ -18,11 +18,20 @@ package org.apache.sis.util;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.Collections;
+import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 
 import org.apache.sis.util.resources.Errors;
 
+import static org.apache.sis.util.collection.Collections.emptyQueue;
+import static org.apache.sis.util.collection.Collections.emptySortedSet;
+
 
 /**
  * Static methods working with {@link Number} objects, and a few primitive types by extension.
@@ -590,6 +599,63 @@ public final class Numbers extends Stati
     }
 
     /**
+     * Returns a {@code NaN}, zero, empty or {@code null} value of the given type. This method
+     * tries to return the closest value that can be interpreted as "<cite>none</cite>",
which
+     * is usually not the same than "<cite>zero</cite>". More specifically:
+     *
+     * <ul>
+     *   <li>If the given type is a floating point <strong>primitive</strong>
type ({@code float}
+     *       or {@code double}), then this method returns {@link Float#NaN} or {@link Double#NaN}
+     *       depending on the given type.</li>
+     *
+     *   <li>If the given type is an integer <strong>primitive</strong>
type or the character type
+     *       ({@code long}, {@code int}, {@code short}, {@code byte} or {@code char}), then
this
+     *       method returns the zero value of the given type.</li>
+     *
+     *   <li>If the given type is the {@code boolean} <strong>primitive</strong>
type, then this
+     *       method returns {@link Boolean#FALSE}.</li>
+     *
+     *   <li>If the given type is an array or a collection, then this method returns
an empty
+     *       array or collection. The given type is honored on a <cite>best effort</cite>
basis.</li>
+     *
+     *   <li>For all other cases, including the wrapper classes of primitive types,
this method
+     *       returns {@code null}.</li>
+     * </ul>
+     *
+     * Despite being defined in the {@code Numbers} class, the scope of this method has been
+     * extended to array and collection types because those objects can also be seen as
+     * mathematical concepts.
+     *
+     * @param  <T> The compile-time type of the requested object.
+     * @param  type The type of the object for which to get a nil value.
+     * @return An object of the given type which represents a nil value, or {@code null}.
+     *
+     * @see org.apache.sis.xml.NilObject
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T valueOfNil(final Class<T> type) {
+        final Numbers mapping = MAPPING.get(type);
+        if (mapping != null) {
+            if (type.isPrimitive()) {
+                return (T) mapping.nullValue;
+            }
+        } else if (type != null && type != Object.class) {
+            if (type == Map      .class) return (T) Collections.EMPTY_MAP;
+            if (type == List     .class) return (T) Collections.EMPTY_LIST;
+            if (type == Queue    .class) return (T) emptyQueue();
+            if (type == SortedSet.class) return (T) emptySortedSet();
+            if (type.isAssignableFrom(Set.class)) {
+                return (T) Collections.EMPTY_SET;
+            }
+            final Class<?> element = type.getComponentType();
+            if (element != null) {
+                return (T) Array.newInstance(element, 0);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns one of {@link #DOUBLE}, {@link #FLOAT}, {@link #LONG}, {@link #INTEGER},
      * {@link #SHORT}, {@link #BYTE}, {@link #CHARACTER}, {@link #BOOLEAN} or {@link #OTHER}
      * constants for the given type. This is a commodity for usage in {@code switch} statements.

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java?rev=1415260&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java Thu Nov
29 16:36:09 2012
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+
+/**
+ * A marker interface for empty XML elements. Note that an "nil" XML element may not be an
+ * empty Java object, since the Java object can still be associated with {@link XLink} or
+ * {@link NilReason} attributes. Those attributes are not part of ISO 19115, but may appear
+ * in ISO 19139 XML documents like below:
+ *
+ * <blockquote><table class="sis" border="1"><tr>
+ *   <th>Non-empty {@code Series} element</th>
+ *   <th>Empty {@code Series} element</th>
+ * </tr><tr><td>
+ * {@preformat xml
+ *   <gmd:CI_Citation>
+ *     <gmd:series>
+ *       <gmd:CI_Series>
+ *         <!-- Some content here -->
+ *       </gmd:CI_Series>
+ *     </gmd:series>
+ *   </gmd:CI_Citation>
+ * }
+ * </td><td>
+ * {@preformat xml
+ *   <gmd:CI_Citation>
+ *     <gmd:series nilReason="unknown"/>
+ *   </gmd:CI_Citation>
+ * }
+ * </td></tr></table></blockquote>
+ *
+ * The reason why an object is empty can be obtained by the {@link #getNilReason()} method.
+ *
+ * {@section Instantiation}
+ * The following example instantiates a {@link org.opengis.metadata.citation.Citation} object
+ * which is empty because the information are missing:
+ *
+ * {@preformat java
+ *     Citation nil = NilReason.MISSING.createNilObject(Citation.class);
+ * }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ *
+ * @see NilReason#createNilObject(Class)
+ * @see ObjectLinker#resolve(Class, NilReason)
+ * @see org.apache.sis.util.Numbers#valueOfNil(Class)
+ */
+public interface NilObject {
+    /**
+     * Returns the reason why this object is empty.
+     *
+     * @return The reason why this object is empty.
+     */
+    NilReason getNilReason();
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObject.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java?rev=1415260&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java Thu
Nov 29 16:36:09 2012
@@ -0,0 +1,229 @@
+/*
+ * 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.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+
+import org.opengis.metadata.Identifier;
+
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.Numbers;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
+
+
+/**
+ * The handler for an object where all methods returns null or empty collections, except
+ * a few methods related to object identity. This handler is used only when no concrete
+ * definition were found for a XML element identified by {@code xlink} or {@code uuidref}
+ * attributes.
+ *
+ * {@note The same handler could be used for every proxy having the same XLink.
+ *        For now, it doesn't seem worth to cache the handlers.}
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ */
+final class NilObjectHandler implements InvocationHandler {
+    /**
+     * The identifiers as an {@link IdentifierMapAdapter} object, or the {@code nilReason}
+     * attribute as a {@link NilReason} object. We don't use separated fields because
+     * those attributes are exclusive, and some operations like {@code toString()},
+     * {@code hashCode()} and {@code equals(Object)} are the same for both types.
+     */
+    private final Object attribute;
+
+    /**
+     * Creates a new handler for an object identified by the given identifiers.
+     * The identifiers are wrapped in a mutable list, so users can add, remove
+     * or modify identifiers.
+     */
+    NilObjectHandler(final Identifier[] identifiers) {
+        attribute = IdentifierMapAdapter.create(new ArrayList<>(Arrays.asList(identifiers)));
+    }
+
+    /**
+     * Creates a new handler for an object which is nil for the given reason.
+     */
+    NilObjectHandler(final NilReason nilReason) {
+        attribute = nilReason;
+    }
+
+    /**
+     * Returns {@code true} if the given type is one of the interfaces ignored by
+     * {@link #getInterface(Object)}.
+     */
+    static boolean isIgnoredInterface(final Class<?> type) {
+        return IdentifiedObject.class.isAssignableFrom(type) ||
+               NilObject.class.isAssignableFrom(type) ||
+               LenientComparable.class.isAssignableFrom(type);
+    }
+
+    /**
+     * Returns the interface implemented by the given proxy.
+     */
+    private static Class<?> getInterface(final Object proxy) {
+        for (final Class<?> type : proxy.getClass().getInterfaces()) {
+            if (!isIgnoredInterface(type)) {
+                return type;
+            }
+        }
+        throw new AssertionError(proxy); // Should not happen.
+    }
+
+    /**
+     * Processes a method invocation. For any invocation of a getter method, there is a choice:
+     *
+     * <ul>
+     *   <li>If the invoked method is {@code getIdentifiers()}, returns the identifiers
given at
+     *       construction time.</li>
+     *   <li>If the invoked method is {@code getIdentifierMap()}, returns a view over
the
+     *       identifiers given at construction time.</li>
+     *   <li>If the invoked method is any other kind of getter, returns null except
if:<ul>
+     *       <li>the return type is a collection, in which case an empty collection
is returned;</li>
+     *       <li>the return type is a primitive, in which case the nil value for that
primitive
+     *           type is returned.</li></ul></li>
+     *   <li>If the invoked method is a setter method, throw a {@link UnsupportedOperationException}
+     *       since the proxy instance is assumed unmodifiable.</li>
+     *   <li>If the invoked method is one of the {@link Object} method, delegate to
the
+     *       {@link #reference}.</li>
+     * </ul>
+     */
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws
Throwable {
+        final String name = method.getName();
+        if (args == null) {
+            switch (name) {
+                case "getNilReason": {
+                    return (attribute instanceof NilReason) ? (NilReason) attribute : null;
+                }
+                case "getIdentifierMap": {
+                    return (attribute instanceof IdentifierMap) ? (IdentifierMap) attribute
: null;
+                }
+                case "getIdentifiers": {
+                    return (attribute instanceof IdentifierMapAdapter) ?
+                            ((IdentifierMapAdapter) attribute).identifiers : null;
+                }
+                case "toString": {
+                    return getInterface(proxy).getSimpleName() + '[' + attribute + ']';
+                }
+                case "hashCode": {
+                    return ~attribute.hashCode();
+                }
+            }
+            if (name.startsWith("get") || name.startsWith("is")) {
+                return Numbers.valueOfNil(method.getReturnType());
+            }
+        } else switch (args.length) {
+            case 1: {
+                if (name.equals("equals")) {
+                    return equals(proxy, args[0], ComparisonMode.STRICT);
+                }
+                if (name.startsWith("set")) {
+                    throw new UnsupportedOperationException(Errors.format(
+                            Errors.Keys.UnmodifiableObject_1, getInterface(proxy)));
+                }
+                break;
+            }
+            case 2: {
+                if (name.equals("equals")) {
+                    return equals(proxy, args[0], (ComparisonMode) args[1]);
+                }
+                break;
+            }
+        }
+        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1,
+                getInterface(proxy).getSimpleName() + '.' + name));
+    }
+
+    /**
+     * Compares the given objects to the given level of strictness. The first object shall
+     * be the proxy, and the second object an arbitrary implementation. This method returns
+     * {@code true} if the given arbitrary implementation is empty.
+     */
+    private boolean equals(final Object proxy, final Object other, final ComparisonMode mode)
throws Throwable {
+        if (other == proxy) return true;
+        if (other == null) return false;
+        if (proxy.getClass() == other.getClass()) {
+            if (mode.ordinal() >= ComparisonMode.IGNORE_METADATA.ordinal()) {
+                return true;
+            }
+            final NilObjectHandler h = (NilObjectHandler) Proxy.getInvocationHandler(other);
+            return attribute.equals(h.attribute);
+        }
+        switch (mode) {
+            case STRICT: return false; // The above test is the only relevant one for this
mode.
+            case BY_CONTRACT: {
+                Object tx = attribute, ox = null;
+                if (tx instanceof IdentifierMapAdapter) {
+                    tx = ((IdentifierMapAdapter) tx).identifiers;
+                    if (other instanceof IdentifiedObject) {
+                        ox = ((IdentifiedObject) other).getIdentifiers();
+                    }
+                } else {
+                    if (other instanceof NilObject) {
+                        ox = ((NilObject) other).getNilReason();
+                    }
+                }
+                if (!Objects.equals(tx, ox)) {
+                    return false;
+                }
+                break;
+            }
+        }
+        /*
+         * Having two objects declaring the same identifiers and implementing the same interface,
+         * ensures that all properties in the other objects are null or empty collections.
+         */
+        final Class<?> type = getInterface(proxy);
+        if (!type.isInstance(other)) {
+            return false;
+        }
+        for (final Method getter : type.getMethods()) {
+            if (Classes.isPossibleGetter(getter)) {
+                final Object value;
+                try {
+                    value = getter.invoke(other, (Object[]) null);
+                } catch (InvocationTargetException e) {
+                    throw e.getTargetException();
+                }
+                if (value != null) {
+                    if ((value instanceof Collection<?>) && ((Collection<?>)
value).isEmpty()) {
+                        continue; // Empty collection, which is consistent with this proxy
behavior.
+                    }
+                    if ((value instanceof Map<?,?>) && ((Map<?,?>) value).isEmpty())
{
+                        continue; // Empty collection, which is consistent with this proxy
behavior.
+                    }
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java?rev=1415260&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java Thu Nov
29 16:36:09 2012
@@ -0,0 +1,344 @@
+/*
+ * 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.net.URI;
+import java.net.URISyntaxException;
+import java.io.Serializable;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.InvocationHandler;
+import net.jcip.annotations.Immutable;
+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;
+
+
+/**
+ * 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.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ *
+ * @see NilObject
+ */
+@Immutable
+public final class NilReason implements Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -1302619137838086028L;
+
+    /**
+     * There is no value.
+     *
+     * <p>The string representation is {@code "inapplicable"}.</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"}.</p>
+     */
+    public static final NilReason MISSING = new NilReason("missing");
+
+    /**
+     * The value will be available later.
+     *
+     * <p>The string representation is {@code "template"}.</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"}.</p>
+     */
+    public static final NilReason UNKNOWN = new NilReason("unknown");
+
+    /**
+     * The value is not divulged.
+     *
+     * <p>The string representation is {@code "withheld"}.</p>
+     */
+    public static final NilReason WITHHELD = new NilReason("withheld");
+
+    /**
+     * Other reason without explanation. Users are encouraged to use the {@link #valueOf(String)}
+     * method instead than this constant, in order to provide a brief explanation.
+     *
+     * <p>When testing if a {@code NilReason} is {@code "other"}, users should test
if
+     * <code>{@linkplain #getExplanation()} != null</code> instead than comparing
+     * against this constant.</p>
+     *
+     * <p>The string representation of this constant is {@code "other"}.
+     * The string representation of other values created by {@code valueOf(String)} is
+     * {@code "other:text"} where {@code text} is a string of two or more characters
+     * with no included spaces.</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 an {@link URI}.
+     */
+    private final Object reason;
+
+    /**
+     * The invocation handler for empty objects, created when first needed.
+     * The same handler can be shared for all objects.
+     */
+    private transient InvocationHandler handler;
+
+    /**
+     * 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 a new instance is created and returned for that explanation.
+     *       Note that if the given explanation contains any characters that are not
+     *       {@linkplain Character#isUnicodeIdentifierPart(char) unicode identifier}
+     *       (for example white spaces), then those characters are omitted.</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:");
+                i++; // Skip the ':' character.
+                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() == 6) { // 6 is the length of "other:"
+                    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.
+     */
+    private Object readResolve() {
+        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.</p>
+     *
+     * @return The explanation, or {@code null} if this {@code NilReason} is not of kind
{@link #OTHER}.
+     */
+    public String getExplanation() {
+        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}.
+     *
+     * @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:text"},
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
enum.
+     * This method returns an object which implement the given interface together with the
+     * {@link NilObject} interface. The {@link NilObject#getNilReason()} method will return
+     * this enum, and all other methods (except the ones inherited from the {@link Object}
class)
+     * will return {@code null} or an empty collection as appropriate.
+     *
+     * @param  <T> The compile-time type of the {@code type} argument.
+     * @param  type The object type as an <strong>interface</strong>.
+     *         This is usually a <a href="http://www.geoapi.org">GeoAPI</a> interface.
+     * @return An {@link NilObject} of the given type.
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T createNilObject(final Class<T> type) {
+        ArgumentChecks.ensureNonNull("type", type);
+        if (NilObjectHandler.isIgnoredInterface(type)) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"type", type));
+        }
+        InvocationHandler h;
+        synchronized (this) {
+            if ((h = handler) == null) {
+                handler = h = new NilObjectHandler(this);
+            }
+        }
+        return (T) Proxy.newProxyInstance(NilReason.class.getClassLoader(),
+                new Class<?>[] {type, NilObject.class, LenientComparable.class}, h);
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1415260&r1=1415259&r2=1415260&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
Thu Nov 29 16:36:09 2012
@@ -72,6 +72,7 @@ import org.junit.runners.Suite;
 
   // XML most basic types.
   org.apache.sis.xml.XLinkTest.class,
+  org.apache.sis.xml.NilReasonTest.class,
   org.apache.sis.internal.jaxb.IdentifierMapAdapterTest.class,
   org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCasesTest.class
 })

Added: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java?rev=1415260&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java (added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java Thu
Nov 29 16:36:09 2012
@@ -0,0 +1,138 @@
+/*
+ * 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.net.URISyntaxException;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Arrays;
+import org.apache.sis.test.TestCase;
+import org.junit.*;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests the {@link NilReason}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ */
+public final strictfp class NilReasonTest extends TestCase {
+    /**
+     * Tests the {@link NilReason#valueOf(String)} method on constants.
+     *
+     * @throws URISyntaxException Should never happen.
+     */
+    @Test
+    public void testValueOfConstant() throws URISyntaxException {
+        assertSame(NilReason.TEMPLATE, NilReason.valueOf("template"));
+        assertSame(NilReason.MISSING,  NilReason.valueOf("missing"));
+        assertSame(NilReason.TEMPLATE, NilReason.valueOf("TEMPLATE"));
+        assertSame(NilReason.MISSING,  NilReason.valueOf("  missing "));
+
+        final NilReason[] reasons = NilReason.values();
+        assertTrue(Arrays.contains(reasons, NilReason.TEMPLATE));
+        assertTrue(Arrays.contains(reasons, NilReason.MISSING));
+    }
+
+    /**
+     * Tests the {@link NilReason#valueOf(String)} method on "other".
+     *
+     * @throws URISyntaxException Should never happen.
+     */
+    @Test
+    public void testValueOfOther() throws URISyntaxException {
+        assertSame(NilReason.OTHER, NilReason.valueOf("other"));
+        final NilReason other = NilReason.valueOf("other:myReason");
+        assertSame(other, NilReason.valueOf("  OTHER : myReason "));
+        assertNotSame("Expected a new instance.", NilReason.OTHER, other);
+        assertFalse  ("NilReason.equals(Object)", NilReason.OTHER.equals(other));
+        assertEquals ("NilReason.getExplanation()", "myReason", other.getExplanation());
+        assertNull   ("NilReason.getURI()", other.getURI());
+
+        final NilReason[] reasons = NilReason.values();
+        assertTrue(Arrays.contains(reasons, NilReason.TEMPLATE));
+        assertTrue(Arrays.contains(reasons, NilReason.MISSING));
+        assertTrue(Arrays.contains(reasons, other));
+    }
+
+    /**
+     * Tests the {@link NilReason#valueOf(String)} method on a URI.
+     *
+     * @throws URISyntaxException Should never happen.
+     */
+    @Test
+    public void testValueOfURI() throws URISyntaxException {
+        final NilReason other = NilReason.valueOf("http://www.nilreasons.org");
+        assertSame(other, NilReason.valueOf("  http://www.nilreasons.org  "));
+        assertNull  ("NilReason.getExplanation()", other.getExplanation());
+        assertEquals("NilReason.getURI()", "http://www.nilreasons.org", String.valueOf(other.getURI()));
+
+        final NilReason[] reasons = NilReason.values();
+        assertTrue(Arrays.contains(reasons, NilReason.TEMPLATE));
+        assertTrue(Arrays.contains(reasons, NilReason.MISSING));
+        assertTrue(Arrays.contains(reasons, other));
+    }
+
+    /**
+     * Tests the creation of {@link NilObject} instances.
+     */
+    @Test
+    public void testCreateNilObject() {
+        final Citation citation = NilReason.TEMPLATE.createNilObject(Citation.class);
+        assertInstanceOf("Unexpected proxy.", NilObject.class, citation);
+        assertNull(citation.getTitle());
+        assertTrue(citation.getDates().isEmpty());
+        assertEquals("NilObject.toString()", "Citation[template]", citation.toString());
+    }
+
+    /**
+     * Tests the comparison of {@link NilObject} instances.
+     */
+    @Test
+    public void testNilObjectComparison() {
+        final Citation e1 = NilReason.TEMPLATE.createNilObject(Citation.class);
+        final Citation e2 = NilReason.MISSING .createNilObject(Citation.class);
+        final Citation e3 = NilReason.TEMPLATE.createNilObject(Citation.class);
+        assertEquals("NilObject.hashCode()", e1.hashCode(), e3.hashCode());
+        assertFalse ("NilObject.hashCode()", e1.hashCode() == e2.hashCode());
+        assertEquals("NilObject.equals(Object)", e1, e3);
+        assertFalse ("NilObject.equals(Object)", e1.equals(e2));
+
+        assertInstanceOf("e1", LenientComparable.class, e1);
+        final LenientComparable c = (LenientComparable) e1;
+        assertTrue (c.equals(e3, ComparisonMode.STRICT));
+        assertFalse(c.equals(e2, ComparisonMode.STRICT));
+        assertFalse(c.equals(e2, ComparisonMode.BY_CONTRACT));
+        assertTrue (c.equals(e2, ComparisonMode.IGNORE_METADATA));
+        assertTrue (c.equals(e2, ComparisonMode.APPROXIMATIVE));
+        assertTrue (c.equals(e2, ComparisonMode.DEBUG));
+
+        // Following object should alway be different because it does not implement the same
interface.
+        final ResponsibleParty r1 = NilReason.TEMPLATE.createNilObject(ResponsibleParty.class);
+        assertFalse(c.equals(r1, ComparisonMode.STRICT));
+        assertFalse(c.equals(r1, ComparisonMode.BY_CONTRACT));
+        assertFalse(c.equals(r1, ComparisonMode.IGNORE_METADATA));
+        assertFalse(c.equals(r1, ComparisonMode.APPROXIMATIVE));
+        assertFalse(c.equals(r1, ComparisonMode.DEBUG));
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt?rev=1415260&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt (added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt Thu Nov 29 16:36:09
2012
@@ -0,0 +1,2 @@
+Most tests are actually performed in the sis-metadata module,
+since we need some concrete classes for running the tests.

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/xml/readme.txt
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message