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; 019 020import java.util.BitSet; 021import java.util.Objects; 022import java.util.Optional; 023import java.util.Collection; 024import java.util.function.BiConsumer; 025import java.util.logging.Logger; 026import org.opengis.annotation.Obligation; 027 028import static org.junit.jupiter.api.Assertions.*; 029 030 031/** 032 * Base class of all GeoAPI validators. Validators can be configured on a case-by-case basis by 033 * changing the values of non-final public fields. If the same configuration needs to be applied 034 * on all validators, then {@link ValidatorContainer#all} provides a convenient way to make such 035 * change in a loop. 036 * 037 * <p>Configurations available in this class and some subclasses are:</p> 038 * <ul> 039 * <li>{@link #requireMandatoryAttributes} - controls whether unexpected null values can be tolerated.</li> 040 * <li>{@link #enforceForbiddenAttributes} - controls whether unexpected non-null values can be tolerated.</li> 041 * <li>{@link org.opengis.test.referencing.CRSValidator#enforceStandardNames} - controls whether axis 042 * names shall be restricted to ISO standards.</li> 043 * </ul> 044 * 045 * <p>Once the configuration is finished, all validators provided in GeoAPI are thread-safe 046 * provided that their configuration is not modified.</p> 047 * 048 * @author Martin Desruisseaux (Geomatys) 049 * @version 4.0 050 * @since 2.2 051 */ 052public abstract class Validator { 053 /** 054 * The default tolerance value for comparisons of floating point numbers in validators. 055 * The current value is {@value}. This value is relatively large because some implementations 056 * may store their values as {@code float} numbers instead of {@code double}. 057 * 058 * <p>Note that {@link TestCase} subclasses use smaller tolerance thresholds, typically centimetric. 059 * Test cases are stricter then validators because the tests control the objects they create, 060 * while validators need to work reasonably well for arbitrary objects.</p> 061 * 062 * @see org.opengis.test.geometry.GeometryValidator#tolerance 063 */ 064 public static final double DEFAULT_TOLERANCE = 1E-6; 065 066 /** 067 * The validators to use for every validations not defined in the concrete subclass. 068 * For example if {@link org.opengis.test.referencing.CRSValidator} needs to validate 069 * a datum, it will use the {@link org.opengis.test.referencing.DatumValidator} instance 070 * defined in this container. 071 * 072 * <p>The container may contain this validator instance. For example if this validator 073 * is an instance of {@link org.opengis.test.referencing.CRSValidator}, then the 074 * {@link ValidatorContainer#crs} field may be set to {@code this}. Doing so ensure 075 * that the proper {@code validate(…)} methods will be invoked in case of callback.</p> 076 * 077 * <p><b>Tip:</b> if the other validators are not expected to callback the {@code validate} 078 * methods defined in this {@code Validator} instance (for example a datum has no reason 079 * to validate a CRS), then it is safe to set this field to {@link Validators#DEFAULT}.</p> 080 */ 081 protected final ValidatorContainer container; 082 083 /** 084 * The logger for reporting non-fatal warnings. 085 * This logger is determined by the package name given at construction time. 086 */ 087 protected final Logger logger; 088 089 /** 090 * {@code true} if mandatory attributes are required to be non-null, or {@code false} 091 * for tolerating null values. ISO specifications flags some attributes as mandatory, 092 * while some other are optional. Optional attributes are allowed to be null at any time, 093 * but mandatory attributes shall never be null - in theory. However, implementers may 094 * choose to return {@code null} on a temporary basis while they are developing their 095 * library. If this field is set to {@code false}, then missing mandatory attributes 096 * will be logged as warnings instead of causing a failure. 097 * 098 * <p>The default value is {@code true}.</p> 099 * 100 * @see #mandatory(Object, String) 101 */ 102 public boolean requireMandatoryAttributes = true; 103 104 /** 105 * {@code true} if forbidden attributes are required to be null, or {@code false} for 106 * tolerating non-null values. In ISO specifications, some attributes are declared as 107 * optional in parent class and specialized in subclasses, either as mandatory or as 108 * forbidden. If this field is set to {@code false}, then forbidden attributes will 109 * be logged as warnings instead of causing a failure. 110 * 111 * <p>The default value is {@code true}.</p> 112 * 113 * @see #forbidden(Object, String) 114 */ 115 public boolean enforceForbiddenAttributes = true; 116 117 /** 118 * Creates a new validator instance. 119 * 120 * @param container the set of validators to use for validating other kinds of objects 121 * (see {@linkplain #container field javadoc}). 122 * @param packageName the name of the package containing the classes to be validated. 123 */ 124 protected Validator(final ValidatorContainer container, final String packageName) { 125 this.container = Objects.requireNonNull(container, "ValidatorContainer shall not be null."); 126 this.logger = Logger.getLogger(packageName); 127 } 128 129 /** 130 * Returns {@code true} if the given object is an empty collection. 131 * 132 * @param value the object to test, or {@code null}. 133 * @return {@code true} if the given object is a non-null empty collection. 134 */ 135 private static boolean isEmptyCollection(final Object value) { 136 return (value instanceof Collection<?>) && ((Collection<?>) value).isEmpty(); 137 } 138 139 /** 140 * Invoked when the existence of a mandatory attribute needs to be verified. 141 * If the given value is {@code null} or is an {@linkplain Collection#isEmpty() 142 * empty collection}, then there is a choice: 143 * 144 * <ul> 145 * <li>If {@link #requireMandatoryAttributes} is {@code true} (which is the default), 146 * then the test fails with the given message.</li> 147 * <li>Otherwise, the message is logged as a warning and the test continues.</li> 148 * </ul> 149 * 150 * Subclasses can override this method if they want more control. 151 * 152 * @param value the value to test for non-nullity. 153 * @param message the message to send in case of failure. 154 * 155 * @see #requireMandatoryAttributes 156 * @see Obligation#MANDATORY 157 */ 158 protected void mandatory(final Object value, final String message) { 159 assertFalse(value instanceof Optional, "This method should not be invoked for instances of `Optional`."); 160 if (requireMandatoryAttributes) { 161 assertNotNull(value, message); 162 assertFalse(isEmptyCollection(value), message); 163 } else if (value == null || isEmptyCollection(value)) { 164 WarningMessage.log(logger, message, true); 165 } 166 } 167 168 /** 169 * Invoked when the existence of an optional attribute needs to be verified. 170 * This method is invoked when an attribute is optional in a parent interface, 171 * but become mandatory in a sub-interface. 172 * 173 * @param value the value to test for non-nullity. 174 * @param message the message to send in case of failure. 175 */ 176 protected final void mandatory(final Optional<?> value, final String message) { 177 assertNotNull(value, "`Optional` cannot be null."); 178 mandatory(value.orElse(null), message); 179 } 180 181 /** 182 * Invoked when the existence of a forbidden attribute needs to be checked. 183 * If the given value is non-null and is not an {@linkplain Collection#isEmpty() 184 * empty collection}, then there is a choice: 185 * 186 * <ul> 187 * <li>If {@link #enforceForbiddenAttributes} is {@code true} (which is the default), 188 * then the test fails with the given message.</li> 189 * <li>Otherwise, the message is logged as a warning and the test continues.</li> 190 * </ul> 191 * 192 * Subclasses can override this method if they want more control. 193 * 194 * @param value the value to test for nullity. 195 * @param message the message to send in case of failure. 196 * 197 * @see #enforceForbiddenAttributes 198 * @see Obligation#FORBIDDEN 199 */ 200 protected void forbidden(final Object value, final String message) { 201 assertFalse(value instanceof Optional, "This method should not be invoked for instances of `Optional`."); 202 if (enforceForbiddenAttributes) { 203 if (value instanceof Collection<?>) { 204 assertTrue(((Collection<?>) value).isEmpty(), message); 205 } else { 206 assertNull(value, message); 207 } 208 } else if (value != null && !isEmptyCollection(value)) { 209 WarningMessage.log(logger, message, false); 210 } 211 } 212 213 /** 214 * Invoked when the existence of a forbidden attribute needs to be verified. 215 * This method is invoked when an attribute is optional in a parent interface, 216 * but become forbidden in a sub-interface. 217 * 218 * @param value the value to test for nullity. 219 * @param message the message to send in case of failure. 220 */ 221 protected final void forbidden(final Optional<?> value, final String message) { 222 assertNotNull(value, "`Optional` cannot be null."); 223 forbidden(value.orElse(null), message); 224 } 225 226 /** 227 * Ensures that the elements in the given collection are compliant 228 * with the {@code equals(Object)} and {@code hashCode()} contract. 229 * This method ensures that the {@code equals(Object)} methods implement 230 * <i>reflexive</i>, <i>symmetric</i> and <i>transitive</i> relations. 231 * It also ensures that if {@code A.equals(B)}, then {@code A.hashCode() == B.hashCode()}. 232 * 233 * <p>If the given collection is null, then this method does nothing. 234 * If the given collection contains null elements, then those elements are ignored.</p> 235 * 236 * <p>This method does not invoke any other {@code validate} method on collection elements. 237 * It is caller responsibility to validates elements according their types.</p> 238 * 239 * @param collection the collection of elements to validate, or {@code null}. 240 * 241 * @since 3.1 242 */ 243 @SuppressWarnings("ObjectEqualsNull") 244 protected void validate(final Collection<?> collection) { 245 if (collection == null) { 246 return; 247 } 248 /* 249 * Get an array with null elements omitted. 250 */ 251 int count = 0; 252 final Object[] elements = collection.toArray(); 253 for (final Object element : elements) { 254 if (element != null) { 255 elements[count++] = element; 256 } 257 } 258 /* 259 * Store the hash code before to do any comparison 260 * in order to detect unexpected changes. 261 */ 262 final int[] hashCodes = new int[count]; 263 for (int i=0; i<count; i++) { 264 hashCodes[i] = elements[i].hashCode(); 265 } 266 /* 267 * Marks every objects that are equal. 268 */ 269 final BitSet[] equalMasks = new BitSet[count]; 270 for (int i=0; i<count; i++) { 271 final Object toCompare = elements [i]; 272 final int hashCode = hashCodes [i]; 273 final BitSet equalMask = equalMasks[i] = new BitSet(count); 274 for (int j=0; j<count; j++) { 275 final Object candidate = elements[j]; 276 if (toCompare.equals(candidate)) { 277 assertEquals(hashCode, candidate.hashCode(), "Inconsistent hash codes."); 278 equalMask.set(j); 279 } 280 } 281 assertFalse(toCompare.equals(null), "equals(null):"); 282 } 283 /* 284 * Now compare the sets of objects marked as equal. 285 */ 286 for (int i=0; i<count; i++) { 287 final BitSet equalMask = equalMasks[i]; 288 assertTrue(equalMask.get(i), "equals(this) shall be reflexive."); 289 for (int j=0; (j = equalMask.nextSetBit(j)) >= 0; j++) { 290 assertEquals(equalMask, equalMasks[j], "A.equals(B) shall be symmetric and transitive."); 291 } 292 assertEquals(hashCodes[i], elements[i].hashCode(), "The hash code value has changed."); 293 } 294 } 295 296 /** 297 * Validates the given collection, then validates each element in that collection. 298 * This method invokes {@link #validate(Collection)} and adds the restriction that 299 * the collection and all its element shall be non-null. 300 * Then the given validate function is invoked for each element. 301 * Example: 302 * 303 * {@snippet lang="java" : 304 * validate("identifiers", object.getIdentifiers(), ValidatorContainer::validate, false); 305 * } 306 * 307 * @param <E> type of elements to validate. 308 * @param property name of the property to validate. 309 * @param values values of the property to validate. 310 * @param validator the function to invoke for validating each element. 311 * @param mandatory whether at least one element shall be present. 312 * 313 * @since 3.1 314 */ 315 protected <E> void validate(final String property, 316 final Collection<? extends E> values, 317 final BiConsumer<ValidatorContainer,E> validator, 318 final boolean mandatory) 319 { 320 assertNotNull(values, () -> property + " shall not be null. " 321 + (mandatory ? "Expected a collection with at least one element." 322 : "If absent, it should be an empty collection instead.")); 323 validate(values); 324 boolean isPresent = false; 325 for (final E element : values) { 326 assertNotNull(element, () -> property + " shall not contain null elements."); 327 validator.accept(container, element); 328 isPresent = true; 329 } 330 if (mandatory && requireMandatoryAttributes) { 331 assertTrue(isPresent, () -> " is mandatory. The collection shall not be empty."); 332 } 333 } 334}