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