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