001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2018-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.geoapi;
033
034import java.util.Map;
035import java.util.Set;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.Iterator;
039import javax.xml.namespace.QName;
040import org.opengis.annotation.UML;
041
042
043/**
044 * Mapping from XML prefixes or Java types to programmatic namespaces (modules or packages).
045 * There is not necessarily a one-to-one relationship between XML namespaces, Java packages
046 * or Python modules. For example we may merge some XML namespaces in a single programmatic
047 * namespace if keeping them separated would result in modules or packages with few classes.
048 *
049 * @author  Martin Desruisseaux (Geomatys)
050 * @since   3.1
051 * @version 3.1
052 */
053public final class NameSpaces {
054    /**
055     * Modifiable mapping from XML prefixes to packages (not necessarily Java packages).
056     * Keys are usually three-letters prefixes like {@code "cit"} for citations.
057     * Values are {@code "module/package"} strings, for example {@code "metadata/citation"}.
058     * Two keys may map to the same package if GeoAPI decides to merge some packages together.
059     */
060    private final Map<String,String> prefixesToPackages;
061
062    /**
063     * Modifiable mapping from Java interfaces to packages (not necessarily Java packages).
064     * This is used for special cases before to test for {@link #prefixesToPackages}.
065     */
066    private final Map<Class<?>,String> typesToPackages;
067
068    /**
069     * Creates a new mapping from XML namespaces (identified by prefixes) to programmatic namespaces.
070     */
071    public NameSpaces() {
072        final Map<String,String> m = new HashMap<>(32);
073        m.put("gco", "metadata/naming");
074        m.put("lan", "metadata/language");
075        m.put("mcc", "metadata/maintenance");   // Default destination for commonClasses.xsd types not listed in 'typesToPackages'.
076        m.put("gex", "metadata/extent");
077        m.put("cit", "metadata/citation");
078        m.put("mmi", "metadata/maintenance");
079        m.put("mrd", "metadata/distribution");
080        m.put("mdt", "metadata/distribution");              // "transfer" merged with distribution.
081        m.put("mco", "metadata/constraints");
082        m.put("mri", "metadata/identification");
083        m.put("srv", "metadata/service");
084        m.put("mac", "metadata/acquisition");
085        m.put("mrc", "metadata/content");
086        m.put("mrl", "metadata/lineage");
087        m.put("mdq", "metadata/quality");                   // "dataQuality" simplified to "quality"
088        m.put("mrs", "metadata/representation");            // "referenceSystem" merged with "spatialRepresentation"
089        m.put("msr", "metadata/representation");            // "spatialRepresentation" simplified to "representation"
090        m.put("mas", "metadata/extension");                 // "applicationSchema" merged with "extension".
091        m.put("mex", "metadata/extension");
092        m.put("mpc", "metadata/base");                      // "portrayalCatalog" merged with "base".
093        m.put("mdb", "metadata/base");
094        prefixesToPackages = m;
095        /*
096         * Types defined in "commonClasses.xsd". Types not listed below will go to "metadata/maintenance"
097         */
098        final Map<Class<?>,String> t = new HashMap<>(8);
099        t.put(org.opengis.metadata.Identifier.class,                        "metadata/citation");
100        t.put(org.opengis.metadata.identification.Progress.class,           "metadata/identification");
101        t.put(org.opengis.metadata.identification.BrowseGraphic.class,      "metadata/identification");
102        t.put(org.opengis.metadata.spatial.SpatialRepresentationType.class, "metadata/representation");
103        /*
104         * Types having a different name in "dataQuality.xsd" because we have not yet updated that part.
105         */
106        t.put(org.opengis.metadata.quality.TemporalAccuracy.class, "metadata/quality");
107        typesToPackages = t;
108    }
109
110    /**
111     * Excludes the namespaces identified by the given prefixes. Calls to {@link #name(Class, Map)}
112     * for a type in the namespace identified by one of the given prefixes will return {@code null}.
113     *
114     * @param  prefixes  identifications of the namespaces to exclude.
115     */
116    public void exclude(final String... prefixes) {
117        for (final String prefix : prefixes) {
118            final String ns = prefixesToPackages.put(prefix, null);
119            if (ns != null) {
120                for (final Iterator<String> it = typesToPackages.values().iterator(); it.hasNext();) {
121                    if (ns.equals(it.next())) it.remove();
122                }
123            }
124        }
125    }
126
127    /**
128     * Returns the OGC/ISO name of the given type together with its XML prefix and pseudo-namespace, or {@code null}.
129     * Note that while we use the {@link QName} object for convenience, this is <strong>not</strong> the XML name:
130     *
131     * <ul>
132     *   <li>{@link QName#getLocalPart()} will be the OGC/ISO name,
133     *        which is usually the same than the XML local part but not always.</li>
134     *   <li>{@link QName#getPrefix()} will be the XML prefix if known, or the UML prefix otherwise.
135     *       May be empty is no prefix can be inferred.</li>
136     *   <li>{@link QName#getNamespaceURI()} will be the programmatic namespace.
137     *       This may be a fragment of the XML namespace but never the full URI.</li>
138     * </ul>
139     *
140     * @param  type        the type for which to get the namespace, or {@code null}.
141     * @param  definition  value of {@link SchemaInformation#getTypeDefinition(Class)} for the given {@code type},
142     *                     or {@code null} if unknown.
143     * @return the OGC/ISO name, prefix and pseudo-namespace for the given type, or {@code null} if none.
144     */
145    public QName name(final Class<?> type, final Map<String, SchemaInformation.Element> definition) {
146        if (type != null) {
147            final UML uml = type.getAnnotation(UML.class);
148            if (uml != null) {
149                String prefix = null;
150                if (definition != null) {
151                    SchemaInformation.Element def = definition.get(null);
152                    if (def != null) {
153                        prefix = def.prefix();
154                    }
155                }
156                String typeName = uml.identifier();
157                final int splitAt = typeName.indexOf('_');
158                if (prefix == null) {
159                    if (splitAt > 0) {
160                        prefix = typeName.substring(0, splitAt);
161                    } else {
162                        switch (type.getPackage().getName()) {
163                            case "org.opengis.util":    prefix = "gco"; break;
164                            case "org.opengis.feature": prefix = "GF";  break;
165                            default: {
166                                switch (typeName) {
167                                    case "DirectPosition": prefix = "GM"; break;
168                                    default: prefix = ""; break;
169                                }
170                            }
171                        }
172                    }
173                }
174                typeName = typeName.substring(splitAt + 1);
175                if (!typeName.isEmpty()) {                          // Paranoiac check (should never happen).
176                    String pkg = typesToPackages.get(type);
177                    if (pkg == null) {
178                        pkg = prefixesToPackages.get(prefix);
179                        if (pkg == null) {
180                            if (prefixesToPackages.containsKey(prefix)) {
181                                return null;                        // Type explicitly excluded.
182                            }
183                            pkg = prefix;
184                        }
185                    }
186                    return new QName(pkg, typeName, prefix);
187                }
188            }
189        }
190        return null;
191    }
192
193    /**
194     * Returns all namespace values that may be returned by {@link #name(Class, Map)}.
195     * This method returns a modifiable set. Modifications to the returned set will
196     * not affect this {@code NameSpaces} instance.
197     *
198     * @return all package names known to this {@code NameSpaces} instance.
199     */
200    public Set<String> packages() {
201        final Set<String> pkg = new HashSet<>(prefixesToPackages.values());
202        pkg.remove(null);
203        return pkg;
204    }
205}