Author: desruisseaux Date: Fri Nov 4 15:48:28 2016 New Revision: 1768057 URL: http://svn.apache.org/viewvc?rev=1768057&view=rev Log: Performance improvement: when searching for a CRS in the database matching a given CRS, filter better the EPSG codes by IdentifiedObject sub-type before to instantiate the object. The filtering that existed before this commit was doing only part of the work and was not sufficient.
Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java?rev=1768057&r1=1768056&r2=1768057&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java [UTF-8] Fri Nov 4 15:48:28 2016 @@ -19,6 +19,8 @@ package org.apache.sis.referencing.facto import java.util.Set; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.logging.Level; +import java.util.logging.LogRecord; import org.opengis.util.GenericName; import org.opengis.util.FactoryException; import org.opengis.metadata.Identifier; @@ -165,7 +167,7 @@ public class IdentifiedObjectFinder { * this constructor is protected because instances of this class should not be created directly. * Use {@link GeodeticAuthorityFactory#newIdentifiedObjectFinder()} instead.</div> * - * @param factory The factory to scan for the identified objects. + * @param factory the factory to scan for the identified objects. * * @see GeodeticAuthorityFactory#newIdentifiedObjectFinder() */ @@ -182,7 +184,7 @@ public class IdentifiedObjectFinder { * <p>This method also copies the configuration of the given finder, thus providing a central place * where to add calls to setters methods if such methods are added in a future SIS version.</p> * - * @param other The cache or the adapter wrapping this finder. + * @param other the cache or the adapter wrapping this finder. */ final void setWrapper(final IdentifiedObjectFinder other) { wrapper = other; @@ -197,7 +199,7 @@ public class IdentifiedObjectFinder { * * <p>The default value is {@link Domain#VALID_DATASET}.</p> * - * @return The domain of the search. + * @return the domain of the search. */ public Domain getSearchDomain() { return domain; @@ -207,7 +209,7 @@ public class IdentifiedObjectFinder { * Sets the domain of the search (for example whether to include deprecated objects in the search). * If this method is never invoked, then the default value is {@link Domain#VALID_DATASET}. * - * @param domain The domain of the search. + * @param domain the domain of the search. */ public void setSearchDomain(final Domain domain) { ArgumentChecks.ensureNonNull("domain", domain); @@ -231,7 +233,7 @@ public class IdentifiedObjectFinder { * method may return a Coordinate Reference System object with (<var>latitude</var>, <var>longitude</var>) * axes even if the given object had (<var>longitude</var>, <var>latitude</var>) axes. * - * @param ignore {@code true} if the search should ignore coordinate system axes. + * @param ignore {@code true} if the search should ignore coordinate system axes. */ public void setIgnoringAxes(final boolean ignore) { ignoreAxes = ignore; @@ -259,7 +261,7 @@ public class IdentifiedObjectFinder { * This method will be invoked by {@link #find(IdentifiedObject)} * only if {@link #getSearchDomain()} is not {@link Domain#DECLARATION}. * - * @return The given {@code result}, or another set equal to the result if it has been computed + * @return the given {@code result}, or another set equal to the result if it has been computed * concurrently in another thread. */ Set<IdentifiedObject> cache(final IdentifiedObject object, Set<IdentifiedObject> result) { @@ -288,8 +290,8 @@ public class IdentifiedObjectFinder { * The created objects which are equal to the specified object in the * the sense of {@link ComparisonMode#APPROXIMATIVE} are returned. * - * @param object The object looked up. - * @return The identified objects, or an empty set if not found. + * @param object the object looked up. + * @return the identified objects, or an empty set if not found. * @throws FactoryException if an error occurred while creating an object. */ public Set<IdentifiedObject> find(final IdentifiedObject object) throws FactoryException { @@ -348,8 +350,8 @@ public class IdentifiedObjectFinder { * <li>Otherwise this method considers that there is ambiguity and returns {@code null}.</li> * </ul> * - * @param object The object looked up. - * @return The identified object, or {@code null} if none or ambiguous. + * @param object the object looked up. + * @return the identified object, or {@code null} if none or ambiguous. * @throws FactoryException if an error occurred while creating an object. */ public IdentifiedObject findSingleton(final IdentifiedObject object) throws FactoryException { @@ -385,8 +387,8 @@ public class IdentifiedObjectFinder { * * <p>This method may be used in order to get a fully identified object from a partially identified one.</p> * - * @param object The object looked up. - * @return The identified object, or {@code null} if not found. + * @param object the object looked up. + * @return the identified object, or {@code null} if not found. * @throws FactoryException if an error occurred while creating an object. * * @see #createFromCodes(IdentifiedObject) @@ -427,8 +429,8 @@ public class IdentifiedObjectFinder { * implementations like the one backed by the EPSG database, which are capable to find an object * from its name when the identifier is unknown.</p> * - * @param object The object looked up. - * @return The identified object, or {@code null} if not found. + * @param object the object looked up. + * @return the identified object, or {@code null} if not found. * @throws FactoryException if an error occurred while creating an object. * * @see #createFromCodes(IdentifiedObject) @@ -482,8 +484,8 @@ public class IdentifiedObjectFinder { * <code>{@linkplain #createFromNames createFromNames}(object)</code> before to fallback * on this method.</p> * - * @param object The object looked up. - * @return The identified object, or {@code null} if not found. + * @param object the object looked up. + * @return the identified object, or {@code null} if not found. * @throws FactoryException if an error occurred while scanning through authority codes. * * @see #createFromIdentifiers(IdentifiedObject) @@ -511,8 +513,8 @@ public class IdentifiedObjectFinder { * method implementation of for each code returned by the {@link #getCodeCandidates(IdentifiedObject)} method, * in iteration order. * - * @param code The authority code for which to create an object. - * @return The identified object for the given code, or {@code null} to stop attempts. + * @param code the authority code for which to create an object. + * @return the identified object for the given code, or {@code null} to stop attempts. * @throws FactoryException if an error occurred while creating the object. */ private IdentifiedObject create(final String code) throws FactoryException { @@ -537,8 +539,8 @@ public class IdentifiedObjectFinder { * where {@code type} is the interface specified at construction type. * Subclasses should override this method in order to return a smaller set, if they can. * - * @param object The object looked up. - * @return A set of code candidates. + * @param object the object looked up. + * @return a set of code candidates. * @throws FactoryException if an error occurred while fetching the set of code candidates. */ protected Set<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException { @@ -549,6 +551,8 @@ public class IdentifiedObjectFinder { * Invoked when an exception occurred during the creation of a candidate from a code. */ private static void exceptionOccurred(final FactoryException exception) { - Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY), IdentifiedObjectFinder.class, "find", exception); + final LogRecord record = new LogRecord(Level.FINER, exception.getLocalizedMessage()); + record.setLoggerName(Loggers.CRS_FACTORY); + Logging.log(IdentifiedObjectFinder.class, "find", record); } } Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java?rev=1768057&r1=1768056&r2=1768057&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java [UTF-8] Fri Nov 4 15:48:28 2016 @@ -48,7 +48,7 @@ import org.apache.sis.util.Debug; * * @author Martin Desruisseaux (IRD, Geomatys) * @since 0.7 - * @version 0.7 + * @version 0.8 * @module */ final class AuthorityCodes extends AbstractMap<String,String> implements Serializable { @@ -124,10 +124,10 @@ final class AuthorityCodes extends Abstr /** * Creates a new map of authority codes for the specified type. * - * @param connection The connection to the EPSG database. - * @param table The table to query. - * @param type The type to query. - * @param factory The factory originator. + * @param connection the connection to the EPSG database. + * @param table the table to query. + * @param type the type to query. + * @param factory the factory originator. */ AuthorityCodes(final Connection connection, final TableInfo table, final Class<?> type, final EPSGDataAccess factory) throws SQLException @@ -142,21 +142,7 @@ final class AuthorityCodes extends Abstr final int columnNameStart = buffer.append("SELECT ").length(); final int columnNameEnd = buffer.append(table.codeColumn).length(); buffer.append(" FROM ").append(table.table); - boolean hasWhere = false; - Class<?> tableType = table.type; - if (table.typeColumn != null) { - for (int i=0; i<table.subTypes.length; i++) { - final Class<?> candidate = table.subTypes[i]; - if (candidate.isAssignableFrom(type)) { - buffer.append(" WHERE (CAST(").append(table.typeColumn).append(" AS ").append(TableInfo.ENUM_REPLACEMENT) - .append(") LIKE '").append(table.typeNames[i]).append("%')"); - hasWhere = true; - tableType = candidate; - break; - } - } - } - buffer.append(hasWhere ? " AND " : " WHERE "); + final Class<?> tableType = table.where(type, buffer); final int conditionStart = buffer.length(); if (table.showColumn != null) { buffer.append(table.showColumn).append("<>0 AND "); @@ -204,8 +190,8 @@ final class AuthorityCodes extends Abstr /** * Returns the code at the given index, or -1 if the index is out of bounds. * - * @param index index of the code to fetch. - * @return The code at the given index, or -1 if out of bounds. + * @param index index of the code to fetch. + * @return the code at the given index, or -1 if out of bounds. * @throws SQLException if an error occurred while querying the database. */ private int getCodeAt(final int index) throws SQLException { @@ -273,8 +259,8 @@ final class AuthorityCodes extends Abstr * If there is no name for the {@linkplain #type} of object being queried, then this method * returns the code itself. * - * @param code The code for which to get the description. May be a string or an integer. - * @return The description for the given code, or {@code null} if none. + * @param code the code for which to get the description. May be a string or an integer. + * @return the description for the given code, or {@code null} if none. */ @Override public String get(final Object code) { Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java?rev=1768057&r1=1768056&r2=1768057&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] Fri Nov 4 15:48:28 2016 @@ -3079,11 +3079,13 @@ next: while (r.next()) { String from = "Coordinate Reference System"; final String where; final Set<Number> codes; + final TableInfo table; boolean isFloat = false; if (object instanceof Ellipsoid) { select = "ELLIPSOID_CODE"; from = "Ellipsoid"; where = "SEMI_MAJOR_AXIS"; + table = TableInfo.ELLIPSOID; codes = Collections.singleton(((Ellipsoid) object).getSemiMajorAxis()); isFloat = true; } else { @@ -3091,14 +3093,17 @@ next: while (r.next()) { if (object instanceof GeneralDerivedCRS) { dependency = ((GeneralDerivedCRS) object).getBaseCRS(); where = "SOURCE_GEOGCRS_CODE"; + table = TableInfo.CRS; } else if (object instanceof SingleCRS) { dependency = ((SingleCRS) object).getDatum(); where = "DATUM_CODE"; + table = TableInfo.CRS; } else if (object instanceof GeodeticDatum) { dependency = ((GeodeticDatum) object).getEllipsoid(); select = "DATUM_CODE"; from = "Datum"; where = "ELLIPSOID_CODE"; + table = TableInfo.DATUM; } else { // Not a supported type. Returns all codes. return super.getCodeCandidates(object); @@ -3128,7 +3133,6 @@ next: while (r.next()) { Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY), Finder.class, "getCodeCandidates", e); } } - codes.remove(null); // Paranoiac safety. if (codes.isEmpty()) { // Dependency not found. return Collections.emptySet(); @@ -3141,8 +3145,10 @@ next: while (r.next()) { * - If EPSG code, there is only one parameter which is the code to search. * - If numeric, there is 3 parameters: lower value, upper value, exact value to search. */ - final StringBuilder buffer = new StringBuilder(60); - buffer.append("SELECT ").append(select).append(" FROM [").append(from).append("] WHERE ").append(where); + final StringBuilder buffer = new StringBuilder(200); + buffer.append("SELECT ").append(select).append(" FROM [").append(from).append(']'); + table.where(object.getClass(), buffer); + buffer.append(where); if (isFloat) { buffer.append(">=? AND ").append(where).append("<=?"); } else { Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java?rev=1768057&r1=1768056&r2=1768057&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java [UTF-8] Fri Nov 4 15:48:28 2016 @@ -29,13 +29,28 @@ import org.apache.sis.internal.metadata. * Information about a specific table. The MS-Access dialect of SQL is assumed; * it will be translated into ANSI SQL later by {@link SQLTranslator#apply(String)} if needed. * - * @author Martin Desruisseaux (IRD) + * @author Martin Desruisseaux (IRD, Geomatys) * @since 0.7 - * @version 0.7 + * @version 0.8 * @module */ final class TableInfo { /** + * The {@link EPSG} item used for coordinate reference systems. + */ + static final TableInfo CRS; + + /** + * The {@link EPSG} item used for datums. + */ + static final TableInfo DATUM; + + /** + * The {@link EPSG} item used for ellipsoids. + */ + static final TableInfo ELLIPSOID; + + /** * List of tables and columns to test for codes values. * Those tables are used by the {@link EPSGDataAccess#createObject(String)} method * in order to detect which of the following methods should be invoked for a given code: @@ -49,23 +64,23 @@ final class TableInfo { * The order is significant: it is the key for a {@code switch} statement. */ static final TableInfo[] EPSG = { - new TableInfo(CoordinateReferenceSystem.class, + CRS = new TableInfo(CoordinateReferenceSystem.class, "[Coordinate Reference System]", "COORD_REF_SYS_CODE", "COORD_REF_SYS_NAME", "COORD_REF_SYS_KIND", new Class<?>[] { ProjectedCRS.class, GeographicCRS.class, GeocentricCRS.class, - VerticalCRS.class, CompoundCRS.class, EngineeringCRS.class}, - // TemporalCRS.class, ParametricCRS.class (See comment below) + VerticalCRS.class, CompoundCRS.class, EngineeringCRS.class, + DerivedCRS.class, TemporalCRS.class, ParametricCRS.class}, // See comment below new String[] {"projected", "geographic", "geocentric", - "vertical", "compound", "engineering"}, - // "temporal", "parametric" + "vertical", "compound", "engineering", + "derived", "temporal", "parametric"}, // See comment below "SHOW_CRS"), /* - * Above declaration omitted Temporal and Parametric cases because they are not defined - * by the EPSG registry (at least as of version 8.9). In particular, we are not sure if - * EPSG would chose to use "time" or "temporal". Omitting those types for now does not - * prevent SIS to find CRS of those types; the operation will only be more costly. + * Above declaration could omit Derived, Temporal and Parametric cases since they are not defined + * by the EPSG registry (at least as of version 8.9). In particular we are not sure if EPSG would + * chose to use "time" or "temporal". However omitting those types slow down a lot the search for + * CRS matching an existing one (even if it still work). */ new TableInfo(CoordinateSystem.class, @@ -74,11 +89,11 @@ final class TableInfo { "COORD_SYS_NAME", "COORD_SYS_TYPE", new Class<?>[] {CartesianCS.class, EllipsoidalCS.class, VerticalCS.class, LinearCS.class, - SphericalCS.class, PolarCS.class, CylindricalCS.class}, - // TimeCS.class, ParametricCS.class, AffineCS.class (see above comment) + SphericalCS.class, PolarCS.class, CylindricalCS.class, + TimeCS.class, ParametricCS.class, AffineCS.class}, new String[] {WKTKeywords.Cartesian, WKTKeywords.ellipsoidal, WKTKeywords.vertical, WKTKeywords.linear, - WKTKeywords.spherical, WKTKeywords.polar, WKTKeywords.cylindrical}, - // WKTKeywords.temporal, WKTKeywords.parametric, WKTKeywords.affine + WKTKeywords.spherical, WKTKeywords.polar, WKTKeywords.cylindrical, + WKTKeywords.temporal, WKTKeywords.parametric, WKTKeywords.affine}, // Same comment than in the CRS case above. null), new TableInfo(CoordinateSystemAxis.class, @@ -88,18 +103,18 @@ final class TableInfo { "COORD_AXIS_NAME", null, null, null, null), - new TableInfo(Datum.class, + DATUM = new TableInfo(Datum.class, "[Datum]", "DATUM_CODE", "DATUM_NAME", "DATUM_TYPE", - new Class<?>[] { GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class}, - // TemporalDatum.class, ParametricDatum.class (see above comment), - new String[] {"geodetic", "vertical", "engineering"}, - // "temporal", "parametric", + new Class<?>[] { GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class, + TemporalDatum.class, ParametricDatum.class}, + new String[] {"geodetic", "vertical", "engineering", + "temporal", "parametric"}, // Same comment than in the CRS case above. null), - new TableInfo(Ellipsoid.class, + ELLIPSOID = new TableInfo(Ellipsoid.class, "[Ellipsoid]", "ELLIPSOID_CODE", "ELLIPSOID_NAME", @@ -165,7 +180,7 @@ final class TableInfo { * {@link EPSGDataAccess} and {@link AuthorityCodes} assumes that values in this column * have the maximal length described in the {@value #ENUM_REPLACEMENT} statement. */ - final String typeColumn; + private final String typeColumn; /** * The SQL type to use as a replacement for enumerated values on databases that do not support enumerations. @@ -175,12 +190,12 @@ final class TableInfo { /** * Sub-interfaces of {@link #type} to handle, or {@code null} if none. */ - final Class<?>[] subTypes; + private final Class<?>[] subTypes; /** * Names of {@link #subTypes} in the database, or {@code null} if none. */ - final String[] typeNames; + private final String[] typeNames; /** * The column that specify if the object should be shown, or {@code null} if none. @@ -204,4 +219,38 @@ final class TableInfo { this.typeNames = typeNames; this.showColumn = showColumn; } + + /** + * Appends a {@code WHERE} clause together with a condition for searching the most specific subtype, + * if such condition can be added. The clause appended by this method looks like the following example + * (details may vary because of enumeration values): + * + * {@preformat sql + * WHERE COORD_REF_SYS_KIND LIKE 'geographic%' AND + * } + * + * In any case, the caller shall add at least one condition after this method call. + * + * @param userType the type specified by the user. + * @param buffer where to append the {@code WHERE} clause. + * @return the subtype, or {@link #type} if no subtype was found. + */ + final Class<?> where(final Class<?> userType, final StringBuilder buffer) { + buffer.append(" WHERE "); + if (typeColumn != null) { + for (int i=0; i<subTypes.length; i++) { + final Class<?> candidate = subTypes[i]; + if (candidate.isAssignableFrom(userType)) { + if (ENUM_REPLACEMENT != null) { + buffer.append("CAST(").append(typeColumn).append(" AS ").append(ENUM_REPLACEMENT).append(')'); + } else { + buffer.append(typeColumn); + } + buffer.append(" LIKE '").append(typeNames[i]).append("%' AND "); + return candidate; + } + } + } + return type; + } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java?rev=1768057&r1=1768056&r2=1768057&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java [UTF-8] Fri Nov 4 15:48:28 2016 @@ -253,8 +253,8 @@ public final strictfp class EPSGFactoryT /** * Verifies the parameter values of the given Universal Transverse Mercator projection. * - * @param parameters The parameter value to verify. - * @param cm The expected central meridian value. + * @param parameters the parameter value to verify. + * @param cm the expected central meridian value. */ private static void verifyTransverseMercatorParmeters(final ParameterValueGroup parameters, final double cm) { assertEquals("Transverse Mercator", parameters.getDescriptor().getName().getCode()); @@ -667,6 +667,7 @@ public final strictfp class EPSGFactoryT final Set<String> geographicCRS = factory.getAuthorityCodes(GeographicCRS.class); assertFalse("GeographicCRS not found.", geographicCRS.isEmpty()); assertTrue ("Shall contain WGS84.", geographicCRS.contains("4326")); + assertFalse("Shall not contain geocentric CRS.", geographicCRS.contains("4978")); assertFalse("Shall not contain projected CRS.", geographicCRS.contains("3395")); if (RUN_EXTENSIVE_TESTS) { assertTrue ("Check size() consistency.", geographicCRS.size() >= 468);