Package org.apache.sis.util.iso

Source Code of org.apache.sis.util.iso.Types$Description

/*
* 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.util.iso;

import java.util.Map;
import java.util.HashMap;
import java.util.SortedMap;
import java.util.Collection;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import org.opengis.annotation.UML;
import org.opengis.util.CodeList;
import org.opengis.util.NameFactory;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.apache.sis.util.Static;
import org.apache.sis.util.Locales;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.internal.system.DefaultFactories;


/**
* Static methods working on GeoAPI types and {@link CodeList} values.
* This class provides:
*
* <ul>
*   <li>{@link #toInternationalString(CharSequence)} and {@link #toGenericName(Object, NameFactory)}
*       for creating name-related objects from various objects.</li>
*   <li>{@link #getStandardName(Class)}, {@link #getListName(CodeList)} and {@link #getCodeName(CodeList)}
*       for fetching ISO names if possible.</li>
*   <li>{@link #getCodeTitle(CodeList)}, {@link #getDescription(CodeList)} and
*       {@link #getDescription(Class)} for fetching human-readable descriptions.</li>
*   <li>{@link #forStandardName(String)} and {@link #forCodeName(Class, String, boolean)} for
*       fetching an instance from a name (converse of above {@code get} methods).</li>
* </ul>
*
* @author  Martin Desruisseaux (IRD, Geomatys)
* @since   0.3 (derived from geotk-3.19)
* @version 0.4
* @module
*/
public final class Types extends Static {
    /**
     * The separator character between class name and attribute name in resource files.
     */
    private static final char SEPARATOR = '.';

    /**
     * The types for ISO 19115 UML identifiers. The keys are UML identifiers. Values
     * are either class names as {@link String} objects, or the {@link Class} instances.
     * This map will be built only when first needed.
     *
     * @see #forName(String)
     */
    private static Map<Object,Object> typeForNames;

    /**
     * Do not allow instantiation of this class.
     */
    private Types() {
    }

    /**
     * Returns the ISO name for the given class, or {@code null} if none.
     * This method can be used for GeoAPI interfaces or {@link CodeList}.
     * Examples:
     *
     * <ul>
     *   <li><code>getStandardName({@linkplain org.opengis.metadata.citation.Citation}.class)</code>
     *       (an interface) returns {@code "CI_Citation"}.</li>
     *   <li><code>getStandardName({@linkplain org.opengis.referencing.cs.AxisDirection}.class)</code>
     *       (a code list) returns {@code "CS_AxisDirection"}.</li>
     * </ul>
     *
     * This method looks for the {@link UML} annotation on the given type. It does not search for
     * parent classes or interfaces if the given type is not directly annotated (i.e. {@code @UML}
     * annotations are not inherited). If no annotation is found, then this method does not fallback
     * on the Java name since, as the name implies, this method is about standard names.
     *
     * @param  type The GeoAPI interface or code list from which to get the ISO name, or {@code null}.
     * @return The ISO name for the given type, or {@code null} if none or if the given type is {@code null}.
     *
     * @see #forStandardName(String)
     */
    public static String getStandardName(final Class<?> type) {
        if (type != null) {
            final UML uml = type.getAnnotation(UML.class);
            if (uml != null) {
                final String id = uml.identifier();
                if (id != null && !id.isEmpty()) {
                    /*
                     * Workaround: I though that annotation strings were interned like any other constants,
                     * but it does not seem to be the case as of JDK7.  To verify if this explicit call to
                     * String.intern() is still needed in a future JDK release, see the workaround comment
                     * in the org.apache.sis.metadata.PropertyAccessor.name(…) method.
                     */
                    return id.intern();
                }
            }
        }
        return null;
    }

    /**
     * Returns the ISO classname (if available) or the Java classname (as a fallback)
     * of the given code. This method uses the {@link UML} annotation if it exists, or
     * fallback on the {@linkplain Class#getSimpleName() simple class name} otherwise.
     * Examples:
     *
     * <ul>
     *   <li>{@code getListName(AxisDirection.NORTH)} returns {@code "CS_AxisDirection"}.</li>
     *   <li>{@code getListName(CharacterSet.UTF_8)} returns {@code "MD_CharacterSetCode"}.</li>
     *   <li>{@code getListName(ImagingCondition.BLURRED_IMAGE)} returns {@code "MD_ImagingConditionCode"}.</li>
     * </ul>
     *
     * @param  code The code for which to get the class name, or {@code null}.
     * @return The ISO (preferred) or Java (fallback) class name, or {@code null} if the given code is null.
     */
    public static String getListName(final CodeList<?> code) {
        if (code == null) {
            return null;
        }
        final Class<?> type = code.getClass();
        final String id = getStandardName(type);
        return (id != null) ? id : type.getSimpleName();
    }

    /**
     * Returns the ISO name (if available) or the Java name (as a fallback) of the given code.
     * If the code has no {@link UML} identifier, then the programmatic name is used as a fallback.
     * Examples:
     *
     * <ul>
     *   <li>{@code getCodeName(AxisDirection.NORTH)} returns {@code "north"}.</li>
     *   <li>{@code getCodeName(CharacterSet.UTF_8)} returns {@code "utf8"}.</li>
     *   <li>{@code getCodeName(ImagingCondition.BLURRED_IMAGE)} returns {@code "blurredImage"}.</li>
     * </ul>
     *
     * @param  code The code for which to get the name, or {@code null}.
     * @return The UML identifiers or programmatic name for the given code,
     *         or {@code null} if the given code is null.
     *
     * @see #getCodeLabel(CodeList)
     * @see #getCodeTitle(CodeList)
     * @see #getDescription(CodeList)
     * @see #forCodeName(Class, String, boolean)
     */
    public static String getCodeName(final CodeList<?> code) {
        if (code == null) {
            return null;
        }
        final String id = code.identifier();
        return (id != null && !id.isEmpty()) ? id : code.name();
    }

    /**
     * Returns a unlocalized title for the given code.
     * This method builds a title using heuristics rules, which should give reasonable
     * results without the need of resource bundles. For better results, consider using
     * {@link #getCodeTitle(CodeList)} instead.
     *
     * <p>The current heuristic implementation iterates over {@linkplain CodeList#names() all code names},
     * selects the longest one excluding the {@linkplain CodeList#name() field name} if possible, then
     * {@linkplain CharSequences#camelCaseToSentence(CharSequence) makes a sentence} from that name.
     * Examples:</p>
     *
     * <ul>
     *   <li>{@code getCodeLabel(AxisDirection.NORTH)} returns {@code "North"}.</li>
     *   <li>{@code getCodeLabel(CharacterSet.UTF_8)} returns {@code "UTF-8"}.</li>
     *   <li>{@code getCodeLabel(ImagingCondition.BLURRED_IMAGE)} returns {@code "Blurred image"}.</li>
     * </ul>
     *
     * @param  code The code from which to get a title, or {@code null}.
     * @return A unlocalized title for the given code, or {@code null} if the given code is null.
     *
     * @see #getCodeName(CodeList)
     * @see #getCodeTitle(CodeList)
     * @see #getDescription(CodeList)
     */
    public static String getCodeLabel(final CodeList<?> code) {
        if (code == null) {
            return null;
        }
        String id = code.identifier();
        final String name = code.name();
        if (id == null) {
            id = name;
        }
        for (final String candidate : code.names()) {
            if (!candidate.equals(name) && candidate.length() >= id.length()) {
                id = candidate;
            }
        }
        return CharSequences.camelCaseToSentence(id).toString();
    }

    /**
     * Returns the title of the given code. Title are usually much shorter than descriptions.
     * English titles are often the same than the {@linkplain #getCodeLabel(CodeList) code labels}.
     *
     * @param  code The code for which to get the title, or {@code null}.
     * @return The title, or {@code null} if the given code is null.
     *
     * @see #getDescription(CodeList)
     */
    public static InternationalString getCodeTitle(final CodeList<?> code) {
        return (code != null) ? new CodeTitle(code) : null;
    }

    /**
     * Returns the description of the given code, or {@code null} if none.
     * For a description of the code list as a whole instead than a particular code,
     * see {@link Types#getDescription(Class)}.
     *
     * @param  code The code for which to get the localized description, or {@code null}.
     * @return The description, or {@code null} if none or if the given code is null.
     *
     * @see #getCodeTitle(CodeList)
     * @see #getDescription(Class)
     */
    public static InternationalString getDescription(final CodeList<?> code) {
        if (code != null) {
            final String resources = getResources(code.getClass().getName());
            if (resources != null) {
                return new Description(resources, resourceKey(code));
            }
        }
        return null;
    }

    /**
     * Returns a description for the given class, or {@code null} if none.
     * This method can be used for GeoAPI interfaces or {@link CodeList}.
     *
     * @param  type The GeoAPI interface or code list from which to get the description, or {@code null}.
     * @return The description, or {@code null} if none or if the given type is {@code null}.
     *
     * @see #getDescription(CodeList)
     */
    public static InternationalString getDescription(final Class<?> type) {
        final String name = getStandardName(type);
        if (name != null) {
            final String resources = getResources(type.getName());
            if (resources != null) {
                return new Description(resources, name);
            }
        }
        return null;
    }

    /**
     * Returns a description for the given property, or {@code null} if none.
     * The given type shall be a GeoAPI interface, and the given property shall
     * be a UML identifier. If any of the input argument is {@code null}, then
     * this method returns {@code null}.
     *
     * @param  type     The GeoAPI interface from which to get the description of a property, or {@code null}.
     * @param  property The ISO name of the property for which to get the description, or {@code null}.
     * @return The description, or {@code null} if none or if the given type or property name is {@code null}.
     */
    public static InternationalString getDescription(final Class<?> type, final String property) {
        if (property != null) {
            final String name = getStandardName(type);
            if (name != null) {
                final String resources = getResources(type.getName());
                if (resources != null) {
                    return new Description(resources, name + SEPARATOR + property);
                }
            }
        }
        return null;
    }

    /**
     * The {@link InternationalString} returned by the {@code Types.getDescription(…)} methods.
     *
     * @author  Martin Desruisseaux (Geomatys)
     * @since   0.3
     * @version 0.3
     * @module
     */
    private static class Description extends ResourceInternationalString {
        /**
         * For cross-version compatibility.
         */
        private static final long serialVersionUID = -6202647167398898834L;

        /**
         * The class loader to use for fetching GeoAPI resources.
         * Since the resources are bundled in the GeoAPI JAR file,
         * we use the instance that loaded GeoAPI for more determinist behavior.
         */
        private static final ClassLoader CLASSLOADER = UML.class.getClassLoader();

        /**
         * Creates a new international string from the specified resource bundle and key.
         *
         * @param resources The name of the resource bundle, as a fully qualified class name.
         * @param key       The key for the resource to fetch.
         */
        Description(final String resources, final String key) {
            super(resources, key);
        }

        /**
         * Loads the resources using the class loader used for loading GeoAPI interfaces.
         */
        @Override
        protected final ResourceBundle getBundle(final Locale locale) {
            return ResourceBundle.getBundle(resources, locale, CLASSLOADER);
        }

        /**
         * Returns the description for the given locale, or fallback on a default description
         * if no resources exist for that locale.
         */
        @Override
        public final String toString(final Locale locale) {
            try {
                return super.toString(locale);
            } catch (MissingResourceException e) {
                Logging.recoverableException(ResourceInternationalString.class, "toString", e);
                return fallback();
            }
        }

        /**
         * Returns a fallback if no resource is found.
         */
        String fallback() {
            return CharSequences.camelCaseToSentence(key.substring(key.lastIndexOf(SEPARATOR) + 1)).toString();
        }
    }

    /**
     * The {@link InternationalString} returned by the {@code Types.getCodeTitle(…)} method.
     * The code below is a duplicated - in a different way - of {@code CodeListProxy(CodeList)}
     * constructor ({@link org.apache.sis.internal.jaxb.code package}). This duplication exists
     * because {@code CodeListProxy} constructor stores more information in an opportunist way.
     * If this method is updated, please update {@code CodeListProxy(CodeList)} accordingly.
     *
     * @author  Martin Desruisseaux (Geomatys)
     * @since   0.3
     * @version 0.3
     * @module
     */
    private static final class CodeTitle extends Description {
        /**
         * For cross-version compatibility.
         */
        private static final long serialVersionUID = 3306532357801489365L;

        /**
         * The code list for which to create a title.
         */
        private final CodeList<?> code;

        /**
         * Creates a new international string for the given code list element.
         *
         * @param code The code list for which to create a title.
         */
        CodeTitle(final CodeList<?> code) {
            super("org.opengis.metadata.CodeLists", resourceKey(code));
            this.code = code;
        }

        /**
         * Returns a fallback if no resource is found.
         */
        @Override
        String fallback() {
            return getCodeLabel(code);
        }
    }

    /**
     * Returns the resource name for the given GeoAPI type, or {@code null} if none.
     *
     * @param  classname The fully qualified name of the GeoAPI type.
     * @return The resource bundle to load, or {@code null} if none.
     */
    static String getResources(final String classname) {
        String resources = "org.opengis.metadata.Descriptions";
        if (classname.regionMatches(0, resources, 0, 21)) { // 21 is the location after the last dot.
            return resources;
        }
        // Add more checks here (maybe in a loop) if there is more resource candidates.
        return null;
    }

    /**
     * Returns the resource key for the given code list.
     */
    static String resourceKey(final CodeList<?> code) {
        String key = getCodeName(code);
        if (key.indexOf(SEPARATOR) < 0) {
            key = getListName(code) + SEPARATOR + key;
        }
        return key;
    }

    /**
     * Returns all known values for the given type of code list.
     * Note that the size of the returned array may growth between different invocations of this method,
     * since users can add their own codes to an existing list.
     *
     * @param <T> The compile-time type given as the {@code codeType} parameter.
     * @param codeType The type of code list.
     * @return The list of values for the given code list, or an empty array if none.
     */
    @SuppressWarnings("unchecked")
    public static <T extends CodeList<?>> T[] getCodeValues(final Class<T> codeType) {
        Object values;
        try {
            values = codeType.getMethod("values", (Class<?>[]) null).invoke(null, (Object[]) null);
        } catch (InvocationTargetException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new UndeclaredThrowableException(cause);
        } catch (NoSuchMethodException e) {
            values = Array.newInstance(codeType, 0);
        } catch (IllegalAccessException e) {
            values = Array.newInstance(codeType, 0);
        }
        return (T[]) values;
    }

    /**
     * Returns the GeoAPI interface for the given ISO name, or {@code null} if none.
     * The identifier argument shall be the value documented in the {@link UML#identifier()}
     * annotation associated with the GeoAPI interface.
     * Examples:
     *
     * <ul>
     *   <li>{@code forStandardName("CI_Citation")}      returns <code>{@linkplain org.opengis.metadata.citation.Citation}.class</code></li>
     *   <li>{@code forStandardName("CS_AxisDirection")} returns <code>{@linkplain org.opengis.referencing.cs.AxisDirection}.class</code></li>
     * </ul>
     *
     * Only identifiers for the stable part of GeoAPI are recognized. This method does not handle
     * the identifiers for the {@code geoapi-pending} module.
     *
     * @param  identifier The ISO {@linkplain UML} identifier, or {@code null}.
     * @return The GeoAPI interface, or {@code null} if the given identifier is {@code null} or unknown.
     */
    public static synchronized Class<?> forStandardName(final String identifier) {
        if (identifier == null) {
            return null;
        }
        if (typeForNames == null) {
            final Class<?> c = Types.class;
            final InputStream in = c.getResourceAsStream("class-index.properties");
            if (in == null) {
                throw new MissingResourceException("class-index.properties", c.getName(), identifier);
            }
            final Properties props = new Properties();
            try {
                props.load(in);
                in.close();
            } catch (IOException e) {
                throw new BackingStoreException(e);
            } catch (IllegalArgumentException e) {
                throw new BackingStoreException(e);
            }
            typeForNames = new HashMap<Object,Object>(props);
        }
        final Object value = typeForNames.get(identifier);
        if (value == null || value instanceof Class<?>) {
            return (Class<?>) value;
        }
        final Class<?> type;
        try {
            type = Class.forName((String) value);
        } catch (ClassNotFoundException e) {
            throw new TypeNotPresentException((String) value, e);
        }
        typeForNames.put(identifier, type);
        return type;
    }

    /**
     * Returns the code of the given type that matches the given name, or optionally returns a new
     * one if none match it. This method performs the same work than the GeoAPI {@code valueOf(…)}
     * method, except that this method is more tolerant on string comparisons when looking for an
     * existing code:
     *
     * <ul>
     *   <li>Name comparisons are case-insensitive.</li>
     *   <li>Only {@linkplain Character#isLetterOrDigit(int) letter and digit} characters are compared.
     *       Spaces and punctuation characters like {@code '_'} and {@code '-'} are ignored.</li>
     * </ul>
     *
     * If no match is found, then a new code is created only if the {@code canCreate} argument is
     * {@code true}. Otherwise this method returns {@code null}.
     *
     * @param <T>        The compile-time type given as the {@code codeType} parameter.
     * @param codeType   The type of code list.
     * @param name       The name of the code to obtain, or {@code null}.
     * @param canCreate  {@code true} if this method is allowed to create new code.
     * @return A code matching the given name, or {@code null} if the name is null
     *         or if no matching code is found and {@code canCreate} is {@code false}.
     *
     * @see CodeList#valueOf(Class, String)
     */
    public static <T extends CodeList<T>> T forCodeName(final Class<T> codeType, String name, final boolean canCreate) {
        name = CharSequences.trimWhitespaces(name);
        if (name == null || name.isEmpty()) {
            return null;
        }
        // -------- Begin workaround for GeoAPI 3.0 (TODO: remove after upgrade to GeoAPI 3.1) ------------
        final String typeName = codeType.getName();
        try {
            // Forces initialization of the given class in order
            // to register its list of static final constants.
            Class.forName(typeName, true, codeType.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new TypeNotPresentException(typeName, e); // Should never happen.
        }
        // -------- End workaround ------------------------------------------------------------------------
        return CodeList.valueOf(codeType, new CodeListFilter(name, canCreate));
    }

    /**
     * Returns the given characters sequence as an international string. If the given sequence is
     * null or an instance of {@link InternationalString}, this this method returns it unchanged.
     * Otherwise, this method copies the {@link InternationalString#toString()} value in a new
     * {@link SimpleInternationalString} instance and returns it.
     *
     * @param  string The characters sequence to convert, or {@code null}.
     * @return The given sequence as an international string,
     *         or {@code null} if the given sequence was null.
     *
     * @see DefaultNameFactory#createInternationalString(Map)
     */
    public static InternationalString toInternationalString(final CharSequence string) {
        if (string == null || string instanceof InternationalString) {
            return (InternationalString) string;
        }
        return new SimpleInternationalString(string.toString());
    }

    /**
     * Returns an international string for the values in the given properties map, or {@code null} if none.
     * This method is used when a property in a {@link java.util.Map} may have many localized variants.
     * For example the given map may contains a {@code "remarks"} property defined by values associated to
     * the {@code "remarks_en"} and {@code "remarks_fr"} keys, for English and French locales respectively.
     *
     * <p>If the given map is {@code null}, then this method returns {@code null}.
     * Otherwise this method iterates over the entries having a key that starts with the specified prefix,
     * followed by the {@code '_'} character. For each such key:</p>
     *
     * <ul>
     *   <li>If the key is exactly equals to {@code prefix}, selects {@link Locale#ROOT}.</li>
     *   <li>Otherwise the characters after {@code '_'} are parsed as an ISO language and country code
     *       by the {@link Locales#parse(String, int)} method. Note that 3-letters codes are replaced
     *       by their 2-letters counterparts on a <cite>best effort</cite> basis.</li>
     *   <li>The value for the decoded locale is added in the international string to be returned.</li>
     * </ul>
     *
     * @param  properties The map from which to get the string values for an international string, or {@code null}.
     * @param  prefix     The prefix of keys to use for creating the international string.
     * @return The international string, or {@code null} if the given map is null or does not contain values
     *         associated to keys starting with the given prefix.
     * @throws IllegalArgumentException If a key starts by the given prefix and:
     *         <ul>
     *           <li>The key suffix is an illegal {@link Locale} code,</li>
     *           <li>or the value associated to that key is a not a {@link CharSequence}.</li>
     *         </ul>
     *
     * @see Locales#parse(String, int)
     * @see DefaultInternationalString#DefaultInternationalString(Map)
     *
     * @since 0.4
     */
    public static InternationalString toInternationalString(Map<String,?> properties, final String prefix)
            throws IllegalArgumentException
    {
        ArgumentChecks.ensureNonEmpty("prefix", prefix);
        if (properties == null) {
            return null;
        }
        /*
         * If the given map is an instance of SortedMap using the natural ordering of keys,
         * we can skip all keys that lexicographically precedes the given prefix.
         */
        boolean isSorted = false;
        if (properties instanceof SortedMap<?,?>) {
            final SortedMap<String,?> sorted = (SortedMap<String,?>) properties;
            if (sorted.comparator() == null) { // We want natural ordering.
                properties = sorted.tailMap(prefix);
                isSorted = true;
            }
        }
        /*
         * Now iterates over the map entry and lazily create the InternationalString
         * only when first needed. In most cases, we have 0 or 1 matching entry.
         */
        CharSequence i18n = null;
        Locale firstLocale = null;
        DefaultInternationalString dis = null;
        final int offset = prefix.length();
        for (final Map.Entry<String,?> entry : properties.entrySet()) {
            final String key = entry.getKey();
            if (key == null) {
                continue; // Tolerance for Map that accept null keys.
            }
            if (!key.startsWith(prefix)) {
                if (isSorted) break; // If the map is sorted, there is no need to check next entries.
                continue;
            }
            final Locale locale;
            if (key.length() == offset) {
                locale = Locale.ROOT;
            } else {
                final char c = key.charAt(offset);
                if (c != '_') {
                    if (isSorted && c > '_') break;
                    continue;
                }
                final int s = offset + 1;
                try {
                    locale = Locales.parse(key, s);
                } catch (RuntimeException e) { // IllformedLocaleException on the JDK7 branch.
                    throw new IllegalArgumentException(Errors.getResources(properties).getString(
                            Errors.Keys.IllegalLanguageCode_1, '(' + key.substring(0, s) + ')' + key.substring(s), e));
                }
            }
            final Object value = entry.getValue();
            if (value != null) {
                if (!(value instanceof CharSequence)) {
                    throw new IllegalArgumentException(Errors.getResources(properties)
                            .getString(Errors.Keys.IllegalPropertyClass_2, key, value.getClass()));
                }
                if (i18n == null) {
                    i18n = (CharSequence) value;
                    firstLocale = locale;
                } else {
                    if (dis == null) {
                        dis = new DefaultInternationalString();
                        dis.add(firstLocale, i18n);
                        i18n = dis;
                    }
                    dis.add(locale, (CharSequence) value);
                }
            }
        }
        return toInternationalString(i18n);
    }

    /**
     * Returns the given array of {@code CharSequence}s as an array of {@code InternationalString}s.
     * If the given array is null or an instance of {@code InternationalString[]}, then this method
     * returns it unchanged. Otherwise a new array of type {@code InternationalString[]} is created
     * and every elements from the given array is copied or
     * {@linkplain #toInternationalString(CharSequence) converted} in the new array.
     *
     * <p>If a defensive copy of the {@code strings} array is wanted, then the caller needs to check
     * if the returned array is the same instance than the one given in argument to this method.</p>
     *
     * @param  strings The characters sequences to convert, or {@code null}.
     * @return The given array as an array of type {@code InternationalString[]},
     *         or {@code null} if the given array was null.
     */
    public static InternationalString[] toInternationalStrings(final CharSequence... strings) {
        if (strings == null || strings instanceof InternationalString[]) {
            return (InternationalString[]) strings;
        }
        final InternationalString[] copy = new InternationalString[strings.length];
        for (int i=0; i<strings.length; i++) {
            copy[i] = toInternationalString(strings[i]);
        }
        return copy;
    }

    /**
     * Converts the given value to an array of generic names. If the given value is an instance of
     * {@link GenericName}, {@link String} or any other type enumerated below, then it is converted
     * and returned in an array of length 1. If the given value is an array or a collection, then an
     * array of same length is returned where each element has been converted.
     *
     * <p>Allowed types or element types are:</p>
     * <ul>
     *   <li>{@link GenericName}, to be casted and returned as-is.</li>
     *   <li>{@link CharSequence} (usually a {@link String} or an {@link InternationalString}),
     *       to be parsed as a generic name using the {@link DefaultNameSpace#DEFAULT_SEPARATOR ':'} separator.</li>
     *   <li>{@link Identifier}, its {@linkplain Identifier#getCode() code} to be parsed as a generic name
     *       using the {@link DefaultNameSpace#DEFAULT_SEPARATOR ':'} separator.</li>
     * </ul>
     *
     * If {@code value} is an array or a collection containing {@code null} elements,
     * then the corresponding element in the returned array will also be {@code null}.
     *
     * @param  value The object to cast into an array of generic names, or {@code null}.
     * @param  factory The factory to use for creating names, or {@code null} for the default.
     * @return The generic names, or {@code null} if the given {@code value} was null.
     *         Note that it may be the {@code value} reference itself casted to {@code GenericName[]}.
     * @throws ClassCastException if {@code value} can't be casted.
     */
    public static GenericName[] toGenericNames(Object value, NameFactory factory) throws ClassCastException {
        if (value == null) {
            return null;
        }
        if (factory == null) {
            factory = DefaultFactories.NAMES;
        }
        GenericName name = toGenericName(value, factory);
        if (name != null) {
            return new GenericName[] {
                name
            };
        }
        /*
         * Above code checked for a singleton. Now check for a collection or an array.
         */
        final Object[] values;
        if (value instanceof Object[]) {
            values = (Object[]) value;
            if (values instanceof GenericName[]) {
                return (GenericName[]) values;
            }
        } else if (value instanceof Collection<?>) {
            values = ((Collection<?>) value).toArray();
        } else {
            throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_2,
                    "value", value.getClass()));
        }
        final GenericName[] names = new GenericName[values.length];
        for (int i=0; i<values.length; i++) {
            value = values[i];
            if (value != null) {
                name = toGenericName(value, factory);
                if (name == null) {
                    throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_2,
                            "value[" + i + ']', value.getClass()));
                }
                names[i] = name;
            }
        }
        return names;
    }

    /**
     * Creates a generic name from the given value. The value may be an instance of
     * {@link GenericName}, {@link Identifier} or {@link CharSequence}. If the given
     * object is not recognized, then this method returns {@code null}.
     *
     * @param  value The object to convert.
     * @param  factory The factory to use for creating names.
     * @return The converted object, or {@code null} if {@code value} is not convertible.
     */
    private static GenericName toGenericName(final Object value, final NameFactory factory) {
        if (value instanceof GenericName) {
            return (GenericName) value;
        }
        if (value instanceof Identifier) {
            return factory.parseGenericName(null, ((Identifier) value).getCode());
        }
        if (value instanceof CharSequence) {
            return factory.parseGenericName(null, (CharSequence) value);
        }
        return null;
    }
}
TOP

Related Classes of org.apache.sis.util.iso.Types$Description

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.