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.HashMap; 036 037 038/** 039 * Departures in GeoAPI interfaces compared to OGC/ISO schemas. 040 * Each {@code Departures} instance is initialized with a default set of departure information. 041 * More departure can be added by calls to {@code add(…)} methods. 042 * 043 * @author Martin Desruisseaux (Geomatys) 044 * @since 3.1 045 * @version 3.1 046 */ 047public class Departures { 048 /** 049 * ISO 19115-2 classes merged with ISO 19115-1 classes. For example ISO 19115-2 defines {@code MI_Band} 050 * as an extension of ISO 19115-1 {@code MD_Band}, but GeoAPI merges those two types in a single interface 051 * for simplicity. 052 * 053 * <p>Keys or extension types (e.g. the {@code "MI_*"} types defined by the metadata extension for imagery) 054 * and values are the base types in which the extension has been merged (e.g. the {@code "MD_*"} types defined 055 * by metadata fundamentals).</p> 056 */ 057 private final Map<String,String> mergedTypes; 058 059 /** 060 * Changes in the spelling of an identifier. The differences may be for historical reasons, 061 * or in a few cases because of a misspelling in the XSD file compared to the UML. 062 * 063 * <p>Keys are the spellings used in GeoAPI {@link org.opengis.annotation.UML} annotations 064 * and values are the spellings used in XSD files.</p> 065 */ 066 final Map<String,String> spellingChanges; 067 068 /** 069 * Creates new collections of departure information. All maps in public fields ({@link #mergedTypes}, 070 * <i>etc.</i>) are initialized with new instances and populated. 071 */ 072 public Departures() { 073 Map<String,String> m = new HashMap<>(12); 074 // ………Merge what…………………………………………………………Into…………………………………………… 075 m.put("MI_Band_Type", "MD_Band_Type"); 076 m.put("MI_CoverageDescription_Type", "MD_CoverageDescription_Type"); 077 m.put("MI_Georectified_Type", "MD_Georectified_Type"); 078 m.put("MI_Georeferenceable_Type", "MD_Georeferenceable_Type"); 079 m.put("LE_Source_Type", "LI_Source_Type"); 080 m.put("LE_ProcessStep_Type", "LI_ProcessStep_Type"); 081 m.put("AbstractMX_File_Type", "MX_DataFile_Type"); 082 m.put("Abstract_DataQuality_Type", "DQ_DataQuality_Type"); 083 m.put("Abstract_QualityElement_Type", "AbstractDQ_Element_Type"); 084 mergedTypes = m; 085 086 m = new HashMap<>(12); 087 m.put("MI_EnvironmentalRecord.meteorologicalConditions", "meterologicalConditions"); // Misspelling in ISO 19115-3:2016 088 m.put("MI_Requirement.satisfiedPlan", "satisifiedPlan"); // Misspelling in ISO 19115-3:2016 089 m.put("LI_ProcessStep.stepDateTime", "dateTime"); // Spelling change in XSD files 090 m.put("DQ_Result.valueType", "valueRecordType"); // TODO: verify in ISO 19157 091 spellingChanges = m; 092 } 093 094 /** 095 * Adds a class to be retrofitted into another class. For example ISO 19115-2 defines {@code MI_Band} as 096 * an extension of ISO 19115-1 {@code MD_Band}, but GeoAPI merges those two types in a single interface 097 * for simplicity. 098 * 099 * @param toRetrofit name of the type to retrofit into another type. Example: {@code MI_Band}. 100 * @param target name of a type which will receive the properties of the retrofitted type. 101 * Example: {@code MD_Band}. 102 */ 103 public void addMergedType(final String toRetrofit, final String target) { 104 if (mergedTypes.putIfAbsent(toRetrofit, target) != null) { 105 throw new IllegalArgumentException(toRetrofit + " is already retrofitted."); 106 } 107 } 108 109 /** 110 * Changes the spelling of an identifier. The differences may be for historical reasons, 111 * or in a few cases because of a misspelling in the XSD file compared to the UML. 112 * 113 * @param uml the spelling in the UML, including class name. Example: {@code "LI_ProcessStep.stepDateTime"}. 114 * @param xsd the spelling in the XSD file. Example: {@code "dateTime"}. 115 */ 116 public void addSpellingChange(final String uml, final String xsd) { 117 if (spellingChanges.put(uml, xsd) != null) { 118 throw new IllegalArgumentException("A spelling change is already declared for " + uml); 119 } 120 } 121 122 /** 123 * Returns the name of a class merging the given class and its (usually) parent class. 124 * For example {@code "MI_Band_Type"} is renamed as {@code "MD_Band_Type"}. 125 * We do that because we use only one class for representing those two distinct ISO types. 126 * Note that not all ISO 19115-2 types extend an ISO 19115-1 type, so we need to apply a case-by-case approach. 127 * If there is no merge to apply, then this method returns the given name unchanged. 128 * 129 * @param name name of a class to potentially merge with its parent class. 130 * @return the merged class name (may be the given name) together with other information. 131 */ 132 final MergeInfo nameOfMergedType(final String name) { 133 String target = mergedTypes.remove(name); 134 if (target == null) { 135 return new MergeInfo(name, false); 136 } else { 137 return new MergeInfo(target, name.startsWith("Abstract")); 138 } 139 } 140 141 /** 142 * Information about a type that may have been retrofitted into another type. 143 * For example ISO 19115-2 defines {@code MI_Band} as an extension of ISO 19115-1 {@code MD_Band}, 144 * but GeoAPI merges those two types in a single interface for simplicity. Sometime the merge also 145 * implies to change properties order. 146 */ 147 static final class MergeInfo { 148 /** 149 * Name of the merged type. 150 */ 151 final String typeName; 152 153 /** 154 * Whether we will need to reorder properties. Reordering will be needed if the properties of 155 * parent type are retrofitted into the properties of the child type instead than the converse. 156 * We identifies this situation by the {@code "Abstract"} prefix in type to retrofit. 157 */ 158 private final boolean needToReorderProperties; 159 160 /** 161 * Names of properties to keep last, or {@code null} if none. 162 * This is set to a non-null value only if {@link #needToReorderProperties} is {@code true}. 163 */ 164 private String[] propertiesToKeepLast; 165 166 /** 167 * Creates information for a type. 168 */ 169 private MergeInfo(final String typeName, final boolean reorder) { 170 this.typeName = typeName; 171 needToReorderProperties = reorder; 172 } 173 174 /** 175 * Invoked before properties are added in the given map. This method does nothing 176 * in the common case where there is no merge operation to prepare. 177 */ 178 final void beforeAddProperties(final Map<String,?> properties) { 179 if (needToReorderProperties) { 180 propertiesToKeepLast = properties.keySet().toArray(new String[properties.size()]); 181 } 182 } 183 184 /** 185 * Invoked after properties are added in the given map. If a merge operation has been applied, 186 * then this method may reorder entries in the given map by moving last the properties recorded 187 * in this {@code MergeInfo}. 188 */ 189 final <E> void afterAddProperties(final Map<String,E> properties) { 190 if (propertiesToKeepLast != null) { 191 for (final String p : propertiesToKeepLast) { 192 if (p != null) { 193 final E e = properties.remove(p); 194 if (e == null) { 195 throw new IllegalArgumentException("Missing property for " + p); 196 } 197 properties.put(p, e); 198 } 199 } 200 } 201 } 202 } 203}