001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * Copyright © 2008-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.test.util; 019 020import java.util.List; 021import org.opengis.util.*; 022import org.opengis.test.Validator; 023import org.opengis.test.ValidatorContainer; 024import static org.junit.jupiter.api.Assertions.*; 025 026 027/** 028 * Validates {@link GenericName} and related objects from the {@code org.opengis.util} package. 029 * 030 * <p>This class is provided for users wanting to override the validation methods. When the default 031 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 032 * convenient way to validate various kinds of objects.</p> 033 * 034 * @author Martin Desruisseaux (Geomatys) 035 * @version 3.1 036 * @since 2.2 037 */ 038public class NameValidator extends Validator { 039 /** 040 * Creates a new validator instance. 041 * 042 * @param container the set of validators to use for validating other kinds of objects 043 * (see {@linkplain #container field javadoc}). 044 */ 045 public NameValidator(final ValidatorContainer container) { 046 super(container, "org.opengis.util"); 047 } 048 049 /** 050 * Ensures that the {@link CharSequence} methods are consistent with the {@code toString()} value. 051 * 052 * @param object the object to validate, or {@code null}. 053 */ 054 public void validate(final InternationalString object) { 055 if (object == null) { 056 return; 057 } 058 final int length = object.length(); 059 final String s = object.toString(); 060 mandatory(s, "CharSequence: toString() shall never returns null."); 061 if (s != null) { 062 assertEquals(s.length(), length, "CharSequence: length is inconsistent with toString() length."); 063 boolean expectLowSurrogate = false; 064 for (int i=0; i<length; i++) { 065 final char c = s.charAt(i); 066 assertEquals(c, object.charAt(i), "CharSequence: character inconsistent with toString()."); 067 if (expectLowSurrogate) { 068 assertTrue(Character.isLowSurrogate(c), "CharSequence: High surrogate shall be followed by low surrogate."); 069 } 070 expectLowSurrogate = Character.isHighSurrogate(c); 071 } 072 assertFalse(expectLowSurrogate, "CharSequence: High surrogate shall be followed by low surrogate."); 073 } 074 mandatory(object.toString(null), "InternationalString: toString(Locale) shall not return null."); 075 assertEquals(object, object, "InternationalString: shall be equal to itself."); 076 assertEquals(0, object.compareTo(object), "InternationalString: shall be comparable to itself."); 077 } 078 079 /** 080 * Ensures that ISO 19103 or GeoAPI restrictions apply. 081 * 082 * @param object the object to validate, or {@code null}. 083 */ 084 public void validate(final NameSpace object) { 085 if (object == null) { 086 return; 087 } 088 final GenericName name = object.name(); 089 mandatory(name, "NameSpace: shall have a name."); 090 if (name != null) { 091 final NameSpace scope = name.scope(); 092 mandatory(scope, "NameSpace: identifier shall have a global scope."); 093 if (scope != null) { 094 assertTrue(scope.isGlobal(), "NameSpace: identifier scope shall be global."); 095 } 096 /* 097 * Following test is a consequence of the previous one, so we check the scope first in 098 * order to report the error as a bad scope before to reference this GeoAPI extension. 099 */ 100 assertSame(name, name.toFullyQualifiedName(), "NameSpace: the identifier shall be fully qualified."); 101 /* 102 * Do not validate global namespaces because their name could be anything including 103 * an empty name, and the 'validate' method below does not accept empty collections. 104 */ 105 if (!object.isGlobal()) { 106 validate(name, name.getParsedNames()); 107 } 108 } 109 } 110 111 /** 112 * For each interface implemented by the given object, invokes the corresponding 113 * {@code validate(…)} method defined in this class (if any). 114 * 115 * @param object the object to dispatch to {@code validate(…)} methods, or {@code null}. 116 * @return number of {@code validate(…)} methods invoked in this class for the given object. 117 */ 118 public int dispatch(final GenericName object) { 119 int n = 0; 120 if (object != null) { 121 if (object instanceof LocalName) {validate((LocalName) object); n++;} 122 if (object instanceof ScopedName) {validate((ScopedName) object); n++;} 123 } 124 return n; 125 } 126 127 /** 128 * Performs some tests that are common to all subclasses of {@link GenericName}. This method 129 * shall not invokes {@link #validate(LocalName)} or {@link #validate(ScopedName)} in order 130 * to avoid never-ending loop. 131 * 132 * <p>This method shall not validate the scope, since it could leads to a never-ending loop.</p> 133 * 134 * @param object the name to validate. 135 * @param parsedNames expected parsed names. 136 */ 137 private void validate(final GenericName object, final List<? extends LocalName> parsedNames) { 138 mandatory(parsedNames, "GenericName: getParsedNames() shall not return null."); 139 if (parsedNames != null) { 140 validate(parsedNames); 141 assertFalse(parsedNames.isEmpty(), 142 "GenericName: getParsedNames() shall not return an empty list."); 143 final int size = parsedNames.size(); 144 assertEquals(size, object.depth(), 145 "GenericName: getParsedNames() list size shall be equal to depth()."); 146 assertEquals(parsedNames.get(0), object.head(), 147 "GenericName: head() shall be the first element in getParsedNames() list."); 148 assertEquals(parsedNames.get(size-1), object.tip(), 149 "GenericName: tip() shall be the last element in getParsedNames() list."); 150 } 151 /* 152 * Validates fully qualified name. 153 */ 154 final GenericName fullyQualified = object.toFullyQualifiedName(); 155 mandatory(fullyQualified, "GenericName: toFullyQualifiedName() shall not return null."); 156 if (fullyQualified != null) { 157 assertEquals(object.scope().isGlobal(), fullyQualified == object, 158 "GenericName: toFullyQualifiedName() inconsistent with the global scope status."); 159 } 160 /* 161 * Validates string representations. 162 */ 163 final String unlocalized = object.toString(); 164 mandatory(unlocalized, "GenericName: toString() shall never returns null."); 165 if (unlocalized != null && fullyQualified != null) { 166 assertTrue(fullyQualified.toString().endsWith(unlocalized), 167 "GenericName: fully qualified name shall end with the name."); 168 } 169 final InternationalString localized = object.toInternationalString(); 170 validate(localized); 171 if (localized != null && fullyQualified != null) { 172 assertTrue(fullyQualified.toInternationalString().toString().endsWith(localized.toString()), 173 "GenericName: fully qualified name shall end with the name (localized version)."); 174 } 175 /* 176 * Validates comparisons. 177 */ 178 assertEquals(object, object, "GenericName: shall be equal to itself."); 179 assertEquals(0, object.compareTo(object), "GenericName: shall be comparable to itself."); 180 } 181 182 /** 183 * Ensures that ISO 19103 or GeoAPI restrictions apply. 184 * 185 * @param object the object to validate, or {@code null}. 186 */ 187 public void validate(final LocalName object) { 188 if (object == null) { 189 return; 190 } 191 validate(object.scope()); 192 final List<? extends LocalName> parsedNames = object.getParsedNames(); 193 validate(object, parsedNames); 194 if (parsedNames != null) { 195 assertEquals(1, parsedNames.size(), "LocalName: shall have exactly one parsed name."); 196 assertSame(object, parsedNames.get(0), 197 "LocalName: the parsed name element shall be the enclosing local name."); 198 } 199 } 200 201 /** 202 * Ensures that ISO 19103 or GeoAPI restrictions apply. 203 * 204 * @param object the object to validate, or {@code null}. 205 */ 206 public void validate(final ScopedName object) { 207 if (object == null) { 208 return; 209 } 210 final List<? extends LocalName> parsedNames = object.getParsedNames(); 211 validate(object, parsedNames); 212 final NameSpace scope = object.scope(); 213 validate(scope); 214 if (scope != null) { 215 assertEquals(scope, object.head().scope(), "ScopedName: head.scope shall be equal to the scope."); 216 } 217 if (parsedNames != null) { 218 boolean global = (scope != null) && scope.isGlobal(); 219 for (final LocalName name : parsedNames) { 220 assertNotNull(name, "ScopedName: getParsedNames() cannot contain null element."); 221 assertNotSame(object, name, "ScopedName: the enclosing scoped name cannot be in any parsed name."); 222 assertEquals(global, name.scope().isGlobal(), "ScopedName: inconsistent value of isGlobal()."); 223 global = false; // Only the first name may be global. 224 validate(name); 225 } 226 } 227 /* 228 * Validates tail. 229 */ 230 final int depth = object.depth(); 231 final GenericName tail = object.tail(); 232 mandatory(tail, "ScopedName: tail() shall not return null."); 233 if (tail != null) { 234 assertEquals(depth-1, tail.depth(), 235 "ScopedName: tail() shall have one less element than the enclosing scoped name."); 236 assertEquals(object.tip(), tail.tip(), 237 "ScopedName: tip().toString() and tail.tip().toString() shall be equal."); 238 if (parsedNames != null) { 239 assertEquals(parsedNames.subList(1, depth), tail.getParsedNames(), 240 "ScopedName: tail() shall be defined as subList(1, depth)."); 241 } 242 } 243 /* 244 * Validates path. 245 */ 246 final GenericName path = object.path(); 247 mandatory(path, "ScopedName: the path shall not be null."); 248 if (path != null) { 249 assertEquals(depth-1, path.depth(), 250 "ScopedName: path() shall have one less element than the enclosing scoped name."); 251 assertEquals(object.head(), path.head(), 252 "ScopedName: head() and path.head() shall be equal."); 253 if (parsedNames != null) { 254 assertEquals(parsedNames.subList(0, depth-1), path.getParsedNames(), 255 "ScopedName: path() shall be defined as subList(0, depth-1)."); 256 } 257 } 258 } 259}