001    /*
002     *    GeoAPI - Java interfaces for OGC/ISO standards
003     *    http://www.geoapi.org
004     *
005     *    Copyright (C) 2008-2013 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     */
032    package org.opengis.test.util;
033    
034    import java.util.List;
035    import org.opengis.util.*;
036    import org.opengis.test.Validator;
037    import org.opengis.test.ValidatorContainer;
038    import 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     */
052    public 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                // Following test is a consequence of the previous one, so we check the scope first in
111                // order to report the error as a bad scope before to reference this GeoAPI extension.
112                assertSame("NameSpace: the identifier shall be fully qualified.", name, name.toFullyQualifiedName());
113            }
114            // Do not validate global namespaces because their name could be anything including
115            // an empty name, and the 'validate' method below does not accept empty collections.
116            if (!object.isGlobal()) {
117                validate(name, name.getParsedNames());
118            }
119        }
120    
121        /**
122         * For each interface implemented by the given object, invokes the corresponding
123         * {@code validate(...)} method defined in this class (if any).
124         *
125         * @param  object The object to dispatch to {@code validate(...)} methods, or {@code null}.
126         * @return Number of {@code validate(...)} methods invoked in this class for the given object.
127         */
128        public int dispatch(final GenericName object) {
129            int n = 0;
130            if (object != null) {
131                if (object instanceof LocalName)  {validate((LocalName)  object); n++;}
132                if (object instanceof ScopedName) {validate((ScopedName) object); n++;}
133            }
134            return n;
135        }
136    
137        /**
138         * Performs some tests that are common to all subclasses of {@link GenericName}. This method
139         * shall not invokes {@link #validate(LocalName)} or {@link #validate(ScopedName)} in order
140         * to avoid never-ending loop.
141         *
142         * <p>This method shall not validate the scope, since it could leads to a never-ending loop.</p>
143         */
144        private void validate(final GenericName object, final List<? extends LocalName> parsedNames) {
145            mandatory("GenericName: getParsedNames() shall not return null.", parsedNames);
146            if (parsedNames != null) {
147                validate(parsedNames);
148                assertFalse("GenericName: getParsedNames() shall not return an empty list.", parsedNames.isEmpty());
149                final int size = parsedNames.size();
150                assertEquals("GenericName: getParsedNames() list size shall be equal to depth().",
151                        size, object.depth());
152                assertEquals("GenericName: head() shall be the first element in getParsedNames() list.",
153                        parsedNames.get(0), object.head());
154                assertEquals("GenericName: tip() shall be the last element in getParsedNames() list.",
155                        parsedNames.get(size-1), object.tip());
156            }
157            /*
158             * Validates fully qualified name.
159             */
160            final GenericName fullyQualified = object.toFullyQualifiedName();
161            mandatory("GenericName: toFullyQualifiedName() shall not return null.", fullyQualified);
162            if (fullyQualified != null) {
163                assertEquals("GenericName: toFullyQualifiedName() inconsistent with the global scope status.",
164                        object.scope().isGlobal(), fullyQualified == object);
165            }
166            /*
167             * Validates string representations.
168             */
169            final String unlocalized = object.toString();
170            mandatory("GenericName: toString() shall never returns null.", unlocalized);
171            if (unlocalized != null && fullyQualified != null) {
172                assertTrue("GenericName: fully qualified name shall end with the name.",
173                        fullyQualified.toString().endsWith(unlocalized));
174            }
175            final InternationalString localized = object.toInternationalString();
176            validate(localized);
177            if (localized != null && fullyQualified != null) {
178                assertTrue("GenericName: fully qualified name shall end with the name (localized version).",
179                        fullyQualified.toInternationalString().toString().endsWith(localized.toString()));
180            }
181            /*
182             * Validates comparisons.
183             */
184            assertEquals("GenericName: shall be equal to itself.", object, object);
185            assertEquals("GenericName: shall be comparable to itself.", 0, object.compareTo(object));
186        }
187    
188        /**
189         * Ensures that ISO 19103 or GeoAPI restrictions apply.
190         *
191         * @param object The object to validate, or {@code null}.
192         */
193        public void validate(final LocalName object) {
194            if (object == null) {
195                return;
196            }
197            validate(object.scope());
198            final List<? extends LocalName> parsedNames = object.getParsedNames();
199            validate(object, parsedNames);
200            if (parsedNames != null) {
201                assertEquals("LocalName: shall have exactly one parsed name.", 1, parsedNames.size());
202                assertSame("LocalName: the parsed name element shall be the enclosing local name.",
203                        object, parsedNames.get(0));
204            }
205        }
206    
207        /**
208         * Ensures that ISO 19103 or GeoAPI restrictions apply.
209         *
210         * @param object The object to validate, or {@code null}.
211         */
212        public void validate(final ScopedName object) {
213            if (object == null) {
214                return;
215            }
216            final List<? extends LocalName> parsedNames = object.getParsedNames();
217            validate(object, parsedNames);
218            final NameSpace scope = object.scope();
219            validate(scope);
220            if (scope != null) {
221                assertEquals("ScopedName: head.scope shall be equal to the scope.", scope, object.head().scope());
222            }
223            if (parsedNames != null) {
224                boolean global = scope.isGlobal();
225                for (final LocalName name : parsedNames) {
226                    assertNotNull("ScopedName: getParsedNames() can not contain null element.", name);
227                    assertNotSame("ScopedName: the enclosing scoped name can not be in any parsed name.", object, name);
228                    assertEquals("ScopedName: inconsistent value of isGlobal().", global, name.scope().isGlobal());
229                    global = false; // Only the first name may be global.
230                    validate(name);
231                }
232            }
233            /*
234             * Validates tail.
235             */
236            final int depth = object.depth();
237            final GenericName tail = object.tail();
238            mandatory("ScopedName: tail() shall not return null.", tail);
239            if (tail != null) {
240                assertEquals("ScopedName: tail() shall have one less element than the enclosing scoped name.",
241                        depth-1, tail.depth());
242                assertEquals("ScopedName: tip().toString() and tail.tip().toString() shall be equal.",
243                        object.tip(), tail.tip());
244                if (parsedNames != null) {
245                    assertEquals("ScopedName: tail() shall be defined as subList(1, depth).",
246                            parsedNames.subList(1, depth), tail.getParsedNames());
247                }
248            }
249            /*
250             * Validates path.
251             */
252            final GenericName path = object.path();
253            mandatory("ScopedName: the path shall not be null.", path);
254            if (path != null) {
255                assertEquals("ScopedName: path() shall have one less element than the enclosing scoped name.",
256                        depth-1, path.depth());
257                assertEquals("ScopedName: head() and path.head() shall be equal.",
258                        object.head(), path.head());
259                if (parsedNames != null) {
260                    assertEquals("ScopedName: path() shall be defined as subList(0, depth-1).",
261                            parsedNames.subList(0, depth-1), path.getParsedNames());
262                }
263            }
264        }
265    }