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}