001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2018-2024 Open Geospatial Consortium, Inc.
004 *    http://www.geoapi.org
005 *
006 *    Licensed under the Apache License, Version 2.0 (the "License");
007 *    you may not use this file except in compliance with the License.
008 *    You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *    Unless required by applicable law or agreed to in writing, software
013 *    distributed under the License is distributed on an "AS IS" BASIS,
014 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *    See the License for the specific language governing permissions and
016 *    limitations under the License.
017 */
018package org.opengis.geoapi.schema;
019
020import java.util.Map;
021import java.util.Set;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Iterator;
025import javax.xml.namespace.QName;
026import org.opengis.annotation.UML;
027
028
029/**
030 * Mapping from XML prefixes or Java types to programmatic namespaces (modules or packages).
031 * There is not necessarily a one-to-one relationship between XML namespaces, Java packages
032 * or Python modules. For example, we may merge some XML namespaces in a single programmatic
033 * namespace if keeping them separated would result in modules or packages with few classes.
034 *
035 * @author  Martin Desruisseaux (Geomatys)
036 * @since   3.1
037 * @version 3.1
038 */
039public final class NameSpaces {
040    /**
041     * Modifiable mapping from XML prefixes to packages (not necessarily Java packages).
042     * Keys are usually three-letters prefixes like {@code "cit"} for citations.
043     * Values are {@code "module/package"} strings, for example {@code "metadata/citation"}.
044     * Two keys may map to the same package if GeoAPI decides to merge some packages together.
045     */
046    private final Map<String,String> prefixesToPackages;
047
048    /**
049     * Modifiable mapping from Java interfaces to packages (not necessarily Java packages).
050     * This is used for special cases before to test for {@link #prefixesToPackages}.
051     */
052    private final Map<Class<?>,String> typesToPackages;
053
054    /**
055     * Creates a new mapping from XML namespaces (identified by prefixes) to programmatic namespaces.
056     */
057    public NameSpaces() {
058        final Map<String,String> m = new HashMap<>(32);
059        m.put("gco", "metadata/naming");
060        m.put("lan", "metadata/language");
061        m.put("mcc", "metadata/maintenance");   // Default destination for commonClasses.xsd types not listed in 'typesToPackages'.
062        m.put("gex", "metadata/extent");
063        m.put("cit", "metadata/citation");
064        m.put("mmi", "metadata/maintenance");
065        m.put("mrd", "metadata/distribution");
066        m.put("mdt", "metadata/distribution");              // "transfer" merged with distribution.
067        m.put("mco", "metadata/constraints");
068        m.put("mri", "metadata/identification");
069        m.put("srv", "metadata/service");
070        m.put("mac", "metadata/acquisition");
071        m.put("mrc", "metadata/content");
072        m.put("mrl", "metadata/lineage");
073        m.put("mdq", "metadata/quality");                   // "dataQuality" simplified to "quality"
074        m.put("mrs", "metadata/representation");            // "referenceSystem" merged with "spatialRepresentation"
075        m.put("msr", "metadata/representation");            // "spatialRepresentation" simplified to "representation"
076        m.put("mas", "metadata/extension");                 // "applicationSchema" merged with "extension".
077        m.put("mex", "metadata/extension");
078        m.put("mpc", "metadata/base");                      // "portrayalCatalog" merged with "base".
079        m.put("mdb", "metadata/base");
080        prefixesToPackages = m;
081        /*
082         * Types defined in "commonClasses.xsd". Types not listed below will go to "metadata/maintenance"
083         */
084        final Map<Class<?>,String> t = new HashMap<>(8);
085        t.put(org.opengis.metadata.Identifier.class,                        "metadata/citation");
086        t.put(org.opengis.metadata.identification.Progress.class,           "metadata/identification");
087        t.put(org.opengis.metadata.identification.BrowseGraphic.class,      "metadata/identification");
088        t.put(org.opengis.metadata.spatial.SpatialRepresentationType.class, "metadata/representation");
089        /*
090         * Types having a different name in "dataQuality.xsd" because we have not yet updated that part.
091         */
092        t.put(org.opengis.metadata.quality.TemporalAccuracy.class, "metadata/quality");
093        typesToPackages = t;
094    }
095
096    /**
097     * Excludes the namespaces identified by the given prefixes. Calls to {@link #name(Class, Map)}
098     * for a type in the namespace identified by one of the given prefixes will return {@code null}.
099     *
100     * @param  prefixes  identifications of the namespaces to exclude.
101     */
102    public void exclude(final String... prefixes) {
103        for (final String prefix : prefixes) {
104            final String ns = prefixesToPackages.put(prefix, null);
105            if (ns != null) {
106                for (final Iterator<String> it = typesToPackages.values().iterator(); it.hasNext();) {
107                    if (ns.equals(it.next())) it.remove();
108                }
109            }
110        }
111    }
112
113    /**
114     * Returns the OGC/ISO name of the given type together with its XML prefix and pseudo-namespace, or {@code null}.
115     * Note that while we use the {@link QName} object for convenience, this is <strong>not</strong> the XML name:
116     *
117     * <ul>
118     *   <li>{@link QName#getLocalPart()} will be the OGC/ISO name,
119     *        which is usually the same as the XML local part but not always.</li>
120     *   <li>{@link QName#getPrefix()} will be the XML prefix if known, or the UML prefix otherwise.
121     *       May be empty is no prefix can be inferred.</li>
122     *   <li>{@link QName#getNamespaceURI()} will be the programmatic namespace.
123     *       This may be a fragment of the XML namespace but never the full URI.</li>
124     * </ul>
125     *
126     * @param  type        the type for which to get the namespace, or {@code null}.
127     * @param  definition  value of {@link SchemaInformation#getTypeDefinition(Class)} for the given {@code type},
128     *                     or {@code null} if unknown.
129     * @return the OGC/ISO name, prefix and pseudo-namespace for the given type, or {@code null} if none.
130     */
131    public QName name(final Class<?> type, final Map<String, SchemaInformation.Element> definition) {
132        if (type != null) {
133            final UML uml = type.getAnnotation(UML.class);
134            if (uml != null) {
135                String prefix = null;
136                if (definition != null) {
137                    SchemaInformation.Element def = definition.get(null);
138                    if (def != null) {
139                        prefix = def.prefix();
140                    }
141                }
142                String typeName = uml.identifier();
143                final int splitAt = typeName.indexOf('_');
144                if (prefix == null) {
145                    if (splitAt > 0) {
146                        prefix = typeName.substring(0, splitAt);
147                    } else {
148                        switch (type.getPackage().getName()) {
149                            case "org.opengis.util":                  prefix = "gco"; break;
150                            case "org.opengis.feature":               prefix = "GF";  break;
151                            case "org.opengis.coordinate":            // Fall through
152                            case "org.opengis.referencing":           prefix = "RS"; break;
153                            case "org.opengis.referencing.cs":        prefix = "CS"; break;
154                            case "org.opengis.referencing.crs":       prefix = "SC"; break;
155                            case "org.opengis.referencing.datum":     prefix = "CD"; break;
156                            case "org.opengis.referencing.operation": // Fall through
157                            case "org.opengis.parameter":             prefix = "CC"; break;
158                            case "org.opengis.filter":
159                            case "org.opengis.filter.capability": return null;
160                            default: {
161                                switch (typeName) {
162                                    case "DirectPosition": prefix = "GM"; break;
163                                    default: prefix = ""; break;
164                                }
165                            }
166                        }
167                    }
168                }
169                typeName = typeName.substring(splitAt + 1);
170                if (!typeName.isEmpty()) {                          // Paranoiac check (should never happen).
171                    String pkg = typesToPackages.get(type);
172                    if (pkg == null) {
173                        pkg = prefixesToPackages.get(prefix);
174                        if (pkg == null) {
175                            if (prefixesToPackages.containsKey(prefix)) {
176                                return null;                        // Type explicitly excluded.
177                            }
178                            pkg = prefix;
179                        }
180                    }
181                    return new QName(pkg, typeName, prefix);
182                }
183            }
184        }
185        return null;
186    }
187
188    /**
189     * Returns all namespace values that may be returned by {@link #name(Class, Map)}.
190     * This method returns a modifiable set. Modifications to the returned set will
191     * not affect this {@code NameSpaces} instance.
192     *
193     * @return all package names known to this {@code NameSpaces} instance.
194     */
195    public Set<String> packages() {
196        final Set<String> pkg = new HashSet<>(prefixesToPackages.values());
197        pkg.remove(null);
198        return pkg;
199    }
200}