/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.referencing.operation.provider; import java.util.*; import org.opengis.util.GenericName; import org.opengis.util.FactoryException; import org.opengis.metadata.citation.Citation; import org.opengis.metadata.Identifier; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.SingleOperation; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Deprecable; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.factory.AuthorityFactoryFinder; import org.geotoolkit.metadata.Citations; import org.geotoolkit.test.TestBase; import org.junit.*; import static org.junit.Assert.*; import static org.junit.Assume.*; /** * Compares the hard-coded codes declared in the {@code provider} package with the codes found * in an authority factory. This is primarily used for comparing the EPSG codes against those * declared in the EPSG database, but this could be extended to other authorities as well. * * @author Martin Desruisseaux (Geomatys) * @version 3.20 * * @since 3.03 */ public final strictfp class ConformanceTest extends TestBase { /** * Deprecated method names to ignore. */ private static final String[] IGNORE = { "Krovak Oblique Conic Conformal" // Since EPSG 7.6, the name is only "Krovak". }; /** * Tests the conformance of EPSG codes. * * @throws FactoryException If an error occurred while querying the database. */ @Test public void testEPSG() throws FactoryException { assumeTrue(false /*isEpsgFactoryAvailable()*/); assumeTrue(false /*isEpsgDatabaseUpToDate()*/); run(Citations.EPSG, AuthorityFactoryFinder.getCoordinateOperationAuthorityFactory("EPSG", null)); } /** * Tests the conformance of GeoTIFF codes. * * @throws FactoryException If an error occurred while querying the database. */ @Test public void testGeoTIFF() throws FactoryException { assumeTrue(false /*isEpsgFactoryAvailable()*/); run(Citations.GEOTIFF, null); } /** * Tests the conformance with the given authority. * * @param authority The authority to test. * @param factory The factory for the operation method, or {@code null} if none. * @throws FactoryException If an error occurred while querying the database. */ private static void run(final Citation authority, final CoordinateOperationAuthorityFactory factory) throws FactoryException { /* * Get all known names and codes for the given authority. For * each name or code, we remember the method that declare it. * * Only one OperationMethod can be associated to a given numerical identifier, * but many OperationMethods can be associated to the same name or alias because * some are ambiguous (e.g. "Bursa-Wolf"). */ final Map<String, OperationMethod> codes = new LinkedHashMap<>(); final Map<String, Map<OperationMethod,OperationMethod>> names = new LinkedHashMap<>(); final MathTransformFactory mtFactory = FactoryFinder.getMathTransformFactory(null); skip: for (final OperationMethod method : mtFactory.getAvailableMethods(SingleOperation.class)) { if (method.getClass().getName().endsWith("Mock")) { continue; // Skip mock providers defined in sis-referencing test jar. } for (final Identifier id : method.getIdentifiers()) { if (org.apache.sis.metadata.iso.citation.Citations.identifierMatches(authority, id.getAuthority())) { final String code = id.getCode().trim(); if (code.equals("9602") && codes.containsKey(code)) { /* * Exclude this special case because EPSG defines a single OperationMethod * for both "Ellipsoid_To_Geocentric" and "Geocentric_To_Ellipsoid", while * OGC and Geotk define two distinct operations. This name appears twice. */ continue skip; } if (code.equals("1051")) { continue skip; // TODO: Lambert Conic Conformal (2SP Michigan) } assertTrue("Not a code: " + code, isNumber(code)); assertNull("Defined twice:" + code, codes.put(code, method)); } } final Collection<GenericName> aliases = method.getAlias(); // assertTrue("In Geotk implementation, the aliases shall contain the primary name as a " + // "GenericName in addition of Identifier", aliases.contains(method.getName())); for (final GenericName alias : aliases) { if (org.apache.sis.metadata.iso.citation.Citations.identifierMatches(authority, alias.head().toString())) { final String name = alias.tip().toString().trim(); if (ArraysExt.contains(IGNORE, name)) { assertTrue(name, ((Deprecable) alias).isDeprecated()); continue; } assertFalse("Not a name: " + name, isNumber(name)); Map<OperationMethod,OperationMethod> methods = names.get(name); if (methods == null) { methods = new IdentityHashMap<>(); assertNull(name, names.put(name, methods)); } assertNull("MathTransformFactory.getAvailableMethods(...) did not retuned a Set." + " Duplicated OperationMethod is: " + name, methods.put(method, method)); } } } assertFalse(codes.isEmpty()); assertFalse(names.isEmpty()); assertTrue(Collections.disjoint(codes.keySet(), names.keySet())); if (factory == null) { return; } /* * Now compares the method names with the one declared in the EPSG database. * We will run this block twice. This first execution performs the test without * changing the map (because we need to read some informations more than once). * The second execution remove the entries that we have processed, in order to * see if there is any left at the end of this block. */ boolean clean = false; do { for (final Map.Entry<String,OperationMethod> entry : codes.entrySet()) { final OperationMethod epsgMethod, geotkMethod; final String code, name, message; code = entry.getKey(); geotkMethod = entry.getValue(); epsgMethod = factory.createOperationMethod(code); name = epsgMethod.getName().getCode(); message = "Name \"" + name + "\" for code " + code; assertNameIsKnown(message, geotkMethod, names.get(name), clean); /* * Checks the aliases. This is basically the same test that the one * we did for the primary name. */ for (final GenericName alias : epsgMethod.getAlias()) { final String epsgAlias = alias.tip().toString(); assertNameIsKnown(message + " alias \"" + epsgAlias + '"', geotkMethod, names.get(epsgAlias), clean); } } } while ((clean = !clean) == true); /* * Clean and ensure there is no remaining name. */ for (final Iterator<Map<OperationMethod,OperationMethod>> it=names.values().iterator(); it.hasNext();) { if (it.next().isEmpty()) { it.remove(); } } // assertTrue("Unknown names: " + names.keySet(), names.isEmpty()); } /** * Returns {@code true} if the given string is a number. */ private static boolean isNumber(final String code) { for (int i=code.length(); --i>=0;) { final char c = code.charAt(i); if ((c < '0' || c > '9') && c!='-' && c!='+' && c!='.') { return false; } } return true; } /** * Asserts that a name is declared for the given operation method. * * @param message The message to emit in case of failure. * @param geotkMethod The operation method which is expected to have a name. * @param sameNames The set of operations having the expected name. * @param clean {@code true} if this method is invoked in the cleaning phase. */ private static void assertNameIsKnown(final String message, final OperationMethod geotkMethod, final Map<OperationMethod,OperationMethod> operationsForName, final boolean clean) { // Temporary patch because Apache SIS does not duplicate name in aliases anymore. if (operationsForName == null) return; assertNotNull(message + ": name is undeclared.", operationsForName); final OperationMethod opForName; if (clean) { opForName = operationsForName.remove(geotkMethod); if (opForName == null) { return; // This is normal during the cleaning phase. } } else { opForName = operationsForName.get(geotkMethod); } assertNotNull(message + ": name is declared for another operation.", opForName); assertSame(message + ": inconsistency in this test suite.", geotkMethod, opForName); } }