Source Code Cross Referenced for DirectEpsgFactory.java in  » GIS » GeoTools-2.4.1 » org » geotools » referencing » factory » epsg » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.referencing.factory.epsg 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2005-2006, GeoTools Project Managment Committee (PMC)
0005:         *    (C) 2005, Institut de Recherche pour le Développement
0006:         *   
0007:         *    This library is free software; you can redistribute it and/or
0008:         *    modify it under the terms of the GNU Lesser General Public
0009:         *    License as published by the Free Software Foundation;
0010:         *    version 2.1 of the License.
0011:         *
0012:         *    This library is distributed in the hope that it will be useful,
0013:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015:         *    Lesser General Public License for more details.
0016:         */
0017:        package org.geotools.referencing.factory.epsg;
0018:
0019:        // J2SE dependencies and extensions
0020:        import java.util.*;
0021:        import java.io.File;
0022:        import java.net.URI;
0023:        import java.net.URISyntaxException;
0024:        import java.sql.Date;
0025:        import java.sql.Connection;
0026:        import java.sql.PreparedStatement;
0027:        import java.sql.Statement;
0028:        import java.sql.ResultSet;
0029:        import java.sql.DatabaseMetaData;
0030:        import java.sql.ResultSetMetaData;
0031:        import java.sql.SQLException;
0032:        import java.lang.ref.Reference;
0033:        import java.lang.ref.WeakReference;
0034:        import java.lang.ref.SoftReference;
0035:        import java.util.logging.Level;
0036:        import javax.units.NonSI;
0037:        import javax.units.Unit;
0038:        import javax.units.SI;
0039:
0040:        // OpenGIS dependencies
0041:        import org.opengis.metadata.Identifier;
0042:        import org.opengis.metadata.extent.Extent;
0043:        import org.opengis.metadata.citation.Citation;
0044:        import org.opengis.metadata.quality.EvaluationMethodType;
0045:        import org.opengis.metadata.quality.PositionalAccuracy;
0046:        import org.opengis.parameter.*;
0047:        import org.opengis.referencing.*;
0048:        import org.opengis.referencing.cs.*;
0049:        import org.opengis.referencing.crs.*;
0050:        import org.opengis.referencing.datum.*;
0051:        import org.opengis.referencing.operation.*;
0052:        import org.opengis.util.GenericName;
0053:        import org.opengis.util.InternationalString;
0054:
0055:        // Geotools dependencies
0056:        import org.geotools.factory.Hints;
0057:        import org.geotools.measure.Units;
0058:        import org.geotools.metadata.iso.citation.Citations;
0059:        import org.geotools.metadata.iso.citation.CitationImpl;
0060:        import org.geotools.metadata.iso.extent.ExtentImpl;
0061:        import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
0062:        import org.geotools.metadata.iso.quality.QuantitativeResultImpl;
0063:        import org.geotools.metadata.iso.quality.AbsoluteExternalPositionalAccuracyImpl;
0064:        import org.geotools.parameter.DefaultParameterDescriptor;
0065:        import org.geotools.parameter.DefaultParameterDescriptorGroup;
0066:        import org.geotools.referencing.AbstractIdentifiedObject;
0067:        import org.geotools.referencing.factory.AbstractAuthorityFactory;
0068:        import org.geotools.referencing.factory.DirectAuthorityFactory;
0069:        import org.geotools.referencing.factory.IdentifiedObjectFinder;
0070:        import org.geotools.referencing.NamedIdentifier;
0071:        import org.geotools.referencing.datum.DefaultGeodeticDatum;
0072:        import org.geotools.referencing.datum.BursaWolfParameters;
0073:        import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
0074:        import org.geotools.referencing.operation.DefaultConcatenatedOperation;
0075:        import org.geotools.referencing.operation.DefaultOperationMethod;
0076:        import org.geotools.referencing.operation.DefaultOperation;
0077:        import org.geotools.referencing.operation.DefiningConversion;
0078:        import org.geotools.resources.Utilities;
0079:        import org.geotools.resources.CRSUtilities;
0080:        import org.geotools.resources.i18n.Errors;
0081:        import org.geotools.resources.i18n.ErrorKeys;
0082:        import org.geotools.resources.i18n.Logging;
0083:        import org.geotools.resources.i18n.LoggingKeys;
0084:        import org.geotools.resources.i18n.Vocabulary;
0085:        import org.geotools.resources.i18n.VocabularyKeys;
0086:        import org.geotools.io.TableWriter;
0087:        import org.geotools.util.LocalName;
0088:        import org.geotools.util.SimpleInternationalString;
0089:        import org.geotools.util.ScopedName;
0090:        import org.geotools.util.Version;
0091:
0092:        /**
0093:         * A coordinate reference system factory backed by the EPSG database tables.
0094:         * <p>
0095:         * The EPSG database is freely available at <A HREF="http://www.epsg.org">http://www.epsg.org</a>.
0096:         * Current version of this class requires EPSG database version 6.6 or above.
0097:         * <p>
0098:         * This factory doesn't cache any result. Any call to a {@code createFoo} method will send a new
0099:         * query to the EPSG database. For caching, this factory should be wrapped in some buffered factory
0100:         * like {@link ThreadedEpsgFactory}.
0101:         * <p>
0102:         * This class is abstract - please see the subclasses for dialect specific implementations:
0103:         * <ul>
0104:         *   <li>{@link AccessDialectEpsgFactory}</li>
0105:         *   <li>{@link AnsiDialectEpsgFactory}</li>
0106:         *   <li>{@link OracleDialectEpsgFactory}</li>
0107:         * </ul>
0108:         *
0109:         * These factories accepts names as well as numerical identifiers. For example
0110:         * "<cite>NTF (Paris) / France I</cite>" and {@code "27581"} both fetchs the same object.
0111:         * However, names may be ambiguous since the same name may be used for more than one object.
0112:         * This is the case of "WGS 84" for example. If such an ambiguity is found, an exception
0113:         * will be thrown. If names are not wanted as a legal EPSG code, subclasses can override the
0114:         * {@link #isPrimaryKey} method.
0115:         *
0116:         * @since 2.4
0117:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/epsg/DirectEpsgFactory.java $
0118:         * @version $Id: DirectEpsgFactory.java 28264 2007-12-05 21:53:08Z desruisseaux $
0119:         * @author Yann Cézard
0120:         * @author Martin Desruisseaux
0121:         * @author Rueben Schulz
0122:         * @author Matthias Basler
0123:         * @author Andrea Aime
0124:         */
0125:        public abstract class DirectEpsgFactory extends DirectAuthorityFactory
0126:                implements  CRSAuthorityFactory, CSAuthorityFactory,
0127:                DatumAuthorityFactory, CoordinateOperationAuthorityFactory {
0128:            //////////////////////////////////////////////////////////////////////////////////////////////
0129:            //////                                                                                 ///////
0130:            //////   HARD CODED VALUES (other than SQL statements) RELATIVE TO THE EPSG DATABASE   ///////
0131:            //////                                                                                 ///////
0132:            //////////////////////////////////////////////////////////////////////////////////////////////
0133:            /**
0134:             * Returns a hard-coded unit from an EPSG code. We do not need to provide all units here,
0135:             * but we must at least provide all base units declared in the [TARGET_UOM_CODE] column
0136:             * of table [Unit of Measure]. Other units will be derived automatically if they are not
0137:             * listed here.
0138:             *
0139:             * @param  code The code.
0140:             * @return The unit, or {@code null} if the code is unrecognized.
0141:             */
0142:            private static Unit getUnit(final int code) {
0143:                switch (code) {
0144:                case 9001:
0145:                    return SI.METER;
0146:                case 9002:
0147:                    return NonSI.FOOT;
0148:                case 9030:
0149:                    return NonSI.NAUTICAL_MILE;
0150:                case 9036:
0151:                    return SI.KILO(SI.METER);
0152:                case 9101:
0153:                    return SI.RADIAN;
0154:                case 9122: // Fall through
0155:                case 9102:
0156:                    return NonSI.DEGREE_ANGLE;
0157:                case 9103:
0158:                    return NonSI.MINUTE_ANGLE;
0159:                case 9104:
0160:                    return NonSI.SECOND_ANGLE;
0161:                case 9105:
0162:                    return NonSI.GRADE;
0163:                case 9107:
0164:                    return Units.DEGREE_MINUTE_SECOND;
0165:                case 9108:
0166:                    return Units.DEGREE_MINUTE_SECOND;
0167:                case 9109:
0168:                    return SI.MICRO(SI.RADIAN);
0169:                case 9110:
0170:                    return Units.SEXAGESIMAL_DMS;
0171:                    //TODO      case 9111: return NonSI.SEXAGESIMAL_DM;
0172:                case 9203: // Fall through
0173:                case 9201:
0174:                    return Unit.ONE;
0175:                case 9202:
0176:                    return Units.PPM;
0177:                default:
0178:                    return null;
0179:                }
0180:            }
0181:
0182:            /**
0183:             * Set a Bursa-Wolf parameter from an EPSG parameter.
0184:             *
0185:             * @param  parameters The Bursa-Wolf parameters to modify.
0186:             * @param  code       The EPSG code for a parameter   from [PARAMETER_CODE]  column.
0187:             * @param  value      The value of the parameter      from [PARAMETER_VALUE] column.
0188:             * @param  unit       The unit of the parameter value from [UOM_CODE]        column.
0189:             * @throws FactoryException if the code is unrecognized.
0190:             */
0191:            private static void setBursaWolfParameter(
0192:                    final BursaWolfParameters parameters, final int code,
0193:                    double value, final Unit unit) throws FactoryException {
0194:                Unit target = unit;
0195:                if (code >= 8605) {
0196:                    if (code <= 8607)
0197:                        target = SI.METER;
0198:                    else if (code <= 8710)
0199:                        target = NonSI.SECOND_ANGLE;
0200:                    else if (code == 8611)
0201:                        target = Units.PPM;
0202:                }
0203:                if (target != unit) {
0204:                    value = unit.getConverterTo(target).convert(value);
0205:                }
0206:                switch (code) {
0207:                case 8605:
0208:                    parameters.dx = value;
0209:                    break;
0210:                case 8606:
0211:                    parameters.dy = value;
0212:                    break;
0213:                case 8607:
0214:                    parameters.dz = value;
0215:                    break;
0216:                case 8608:
0217:                    parameters.ex = value;
0218:                    break;
0219:                case 8609:
0220:                    parameters.ey = value;
0221:                    break;
0222:                case 8610:
0223:                    parameters.ez = value;
0224:                    break;
0225:                case 8611:
0226:                    parameters.ppm = value;
0227:                    break;
0228:                default:
0229:                    throw new FactoryException(Errors.format(
0230:                            ErrorKeys.UNEXPECTED_PARAMETER_$1,
0231:                            new Integer(code)));
0232:                }
0233:            }
0234:
0235:            /// Datum shift operation methods
0236:            /** First Bursa-Wolf method.   */
0237:            private static final int BURSA_WOLF_MIN_CODE = 9603;
0238:            /**  Last Bursa-Wolf method.   */
0239:            private static final int BURSA_WOLF_MAX_CODE = 9607;
0240:            /**   Rotation frame method.   */
0241:            private static final int ROTATION_FRAME_CODE = 9607;
0242:            /** Dummy operation to ignore. */
0243:            private static final int DUMMY_OPERATION = 1;
0244:
0245:            /**
0246:             * List of tables and columns to test for codes values.
0247:             * This table is used by the {@link #createObject} method in order to detect
0248:             * which of the following methods should be invoked for a given code:
0249:             *
0250:             * {@link #createCoordinateReferenceSystem}
0251:             * {@link #createCoordinateSystem}
0252:             * {@link #createDatum}
0253:             * {@link #createEllipsoid}
0254:             * {@link #createUnit}
0255:             *
0256:             * The order is significant: it is the key for a {@code switch} statement.
0257:             *
0258:             * @see #createObject
0259:             * @see #lastObjectType
0260:             */
0261:            private static final TableInfo[] TABLES_INFO = {
0262:                    new TableInfo(CoordinateReferenceSystem.class,
0263:                            "[Coordinate Reference System]",
0264:                            "COORD_REF_SYS_CODE", "COORD_REF_SYS_NAME",
0265:                            "COORD_REF_SYS_KIND", new Class[] {
0266:                                    ProjectedCRS.class, GeographicCRS.class,
0267:                                    GeocentricCRS.class }, new String[] {
0268:                                    "projected", "geographic", "geocentric" }),
0269:
0270:                    new TableInfo(CoordinateSystem.class,
0271:                            "[Coordinate System]", "COORD_SYS_CODE",
0272:                            "COORD_SYS_NAME", "COORD_SYS_TYPE", new Class[] {
0273:                                    CartesianCS.class, EllipsoidalCS.class,
0274:                                    SphericalCS.class, VerticalCS.class },
0275:                            new String[] { "Cartesian", "ellipsoidal",
0276:                                    "spherical", "vertical" }),
0277:
0278:                    new TableInfo(
0279:                            CoordinateSystemAxis.class,
0280:                            "[Coordinate Axis] AS CA INNER JOIN [Coordinate Axis Name] AS CAN"
0281:                                    + " ON CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
0282:                            "COORD_AXIS_CODE", "COORD_AXIS_NAME"),
0283:
0284:                    new TableInfo(Datum.class, "[Datum]", "DATUM_CODE",
0285:                            "DATUM_NAME", "DATUM_TYPE", new Class[] {
0286:                                    GeodeticDatum.class, VerticalDatum.class,
0287:                                    EngineeringDatum.class }, new String[] {
0288:                                    "geodetic", "vertical", "engineering" }),
0289:
0290:                    new TableInfo(Ellipsoid.class, "[Ellipsoid]",
0291:                            "ELLIPSOID_CODE", "ELLIPSOID_NAME"),
0292:
0293:                    new TableInfo(PrimeMeridian.class, "[Prime Meridian]",
0294:                            "PRIME_MERIDIAN_CODE", "PRIME_MERIDIAN_NAME"),
0295:
0296:                    new TableInfo(CoordinateOperation.class,
0297:                            "[Coordinate_Operation]", "COORD_OP_CODE",
0298:                            "COORD_OP_NAME", "COORD_OP_TYPE", new Class[] {
0299:                                    Projection.class, Conversion.class,
0300:                                    Transformation.class }, new String[] {
0301:                                    "conversion", "conversion",
0302:                                    "transformation" }),
0303:                    // Note: Projection is handle in a special way.
0304:
0305:                    new TableInfo(OperationMethod.class,
0306:                            "[Coordinate_Operation Method]",
0307:                            "COORD_OP_METHOD_CODE", "COORD_OP_METHOD_NAME"),
0308:
0309:                    new TableInfo(ParameterDescriptor.class,
0310:                            "[Coordinate_Operation Parameter]",
0311:                            "PARAMETER_CODE", "PARAMETER_NAME"),
0312:
0313:                    new TableInfo(Unit.class, "[Unit of Measure]", "UOM_CODE",
0314:                            "UNIT_OF_MEAS_NAME") };
0315:
0316:            ///////////////////////////////////////////////////////////////////////////////
0317:            ////////                                                               ////////
0318:            ////////        E N D   O F   H A R D   C O D E D   V A L U E S        ////////
0319:            ////////                                                               ////////
0320:            ////////    NOTE: 'createFoo(...)' methods may still have hard-coded   ////////
0321:            ////////    values (others than SQL statements) in 'equalsIgnoreCase'  ////////
0322:            ////////    expressions.                                               ////////
0323:            ///////////////////////////////////////////////////////////////////////////////
0324:            /**
0325:             * The name for the transformation accuracy metadata.
0326:             */
0327:            private static final InternationalString TRANSFORMATION_ACCURACY = Vocabulary
0328:                    .formatInternational(VocabularyKeys.TRANSFORMATION_ACCURACY);
0329:
0330:            /**
0331:             * The name of the thread to execute at JVM shutdown. This thread will be created
0332:             * by {@link ThreadedEpsgFactory} on registration. It will be checked by {@link #dispose}
0333:             * in order to determine if we are in the process for shutting down the database engine.
0334:             */
0335:            static final String SHUTDOWN_THREAD = "EPSG factory shutdown";
0336:
0337:            /**
0338:             * The authority for this database. Will be created only when first needed.
0339:             * This authority will contains the database version in the {@linkplain Citation#getEdition
0340:             * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
0341:             */
0342:            private transient Citation authority;
0343:
0344:            /**
0345:             * Last object type returned by {@link #createObject}, or -1 if none.
0346:             * This type is an index in the {@link #TABLES_INFO} array and is
0347:             * strictly for {@link #createObject} internal use.
0348:             */
0349:            private int lastObjectType = -1;
0350:
0351:            /**
0352:             * The last table in which object name were looked for. This is for internal use
0353:             * by {@link #toPrimaryKey} only.
0354:             */
0355:            private transient String lastTableForName;
0356:
0357:            /**
0358:             * The calendar instance for creating {@link java.util.Date} objects from a year
0359:             * (the "epoch" in datum definition). We use the local timezone, which may not be
0360:             * quite accurate. But there is no obvious timezone for "epoch", and the "epoch"
0361:             * is approximative anyway.
0362:             */
0363:            private final Calendar calendar = Calendar.getInstance();
0364:
0365:            /**
0366:             * A pool of prepared statements. Key are {@link String} object related to their
0367:             * originating method name (for example "Ellipsoid" for {@link #createEllipsoid},
0368:             * while values are {@link PreparedStatement} objects.
0369:             * <p>
0370:             * <strong>Note:</strong> It is okay to use {@link IdentityHashMap} instead of {@link HashMap}
0371:             * because the keys will always be the exact same object, namely the hard-coded argument given
0372:             * to calls to {@link #prepareStatement} in this class.
0373:             */
0374:            private final Map/*<String,PreparedStatement>*/statements = new IdentityHashMap();
0375:
0376:            /**
0377:             * The set of authority codes for different types. This map is used by the
0378:             * {@link #getAuthorityCodes} method as a cache for returning the set created
0379:             * in a previous call.
0380:             * <p>
0381:             * Note that this {@code DirectEpsgFactory} can not be disposed as long as this map is not
0382:             * empty, sinces {@link AuthorityCodes} cache some SQL statements and concequently require
0383:             * the {@linkplain #connection} to be open. This is why we use soft references rather than
0384:             * hard ones, in order to know when no {@link AuthorityCodes} are still in use.
0385:             * <p>
0386:             * The {@link AuthorityCodes#finalize} methods take care of closing the stamenents used by
0387:             * the sets. The {@link AuthorityCodes} reference in this map is then cleared by the garbage
0388:             * collector. The {@link #canDispose} method checks if there is any remaining live reference
0389:             * in this map, and returns {@code false} if some are found (thus blocking the call to
0390:             * {@link #dispose} by the {@link ThreadedEpsgFactory} timer).
0391:             */
0392:            private final Map/*<Class,Reference<AuthorityCodes>>*/authorityCodes = new HashMap();
0393:
0394:            /**
0395:             * Cache for axis names. This service is not provided by {@link BufferedAuthorityFactory}
0396:             * since {@link AxisName} object are particular to the EPSG database.
0397:             *
0398:             * @see #getAxisName
0399:             */
0400:            private final Map/*<String,AxisName>*/axisNames = new HashMap();
0401:
0402:            /**
0403:             * Cache for axis numbers. This service is not provided by {@link BufferedAuthorityFactory}
0404:             * since the number of axis is used internally in this class.
0405:             *
0406:             * @see #getDimensionForCRS
0407:             */
0408:            private final Map/*<String,Short>*/axisCounts = new HashMap();
0409:
0410:            /**
0411:             * Cache for projection checks. This service is not provided by {@link BufferedAuthorityFactory}
0412:             * since the check that a transformation is a projection is used internally in this class.
0413:             *
0414:             * @see #isProjection
0415:             */
0416:            private final Map/*<String,Boolean>*/codeProjection = new HashMap();
0417:
0418:            /**
0419:             * Pool of naming systems, used for caching.
0420:             * There is usually few of them (about 15).
0421:             */
0422:            private final Map scopes = new HashMap();
0423:
0424:            /**
0425:             * The properties to be given the objects to construct.
0426:             * Reused every time {@link #createProperties} is invoked.
0427:             */
0428:            private final Map properties = new HashMap();
0429:
0430:            /**
0431:             * A safety guard for preventing never-ending loops in recursive calls to
0432:             * {@link #createDatum}. This is used by {@link #createBursaWolfParameters},
0433:             * which need to create a target datum. The target datum could have its own
0434:             * Bursa-Wolf parameters, with one of them pointing again to the source datum.
0435:             */
0436:            private final Set safetyGuard = new HashSet();
0437:
0438:            /**
0439:             * The buffered authority factory, or {@code this} if none. This field is set
0440:             * to a different value by {@link ThreadedEpsgFactory} only, which will point toward a
0441:             * buffered factory wrapping this {@code DirectEpsgFactory} for efficienty.
0442:             */
0443:            AbstractAuthorityFactory buffered = this ;
0444:
0445:            /**
0446:             * The connection to the EPSG database.
0447:             */
0448:            protected final Connection connection;
0449:
0450:            /**
0451:             * Constructs an authority factory using the specified connection.
0452:             *
0453:             * @param userHints The underlying factories used for objects creation.
0454:             * @param connection The connection to the underlying EPSG database.
0455:             */
0456:            public DirectEpsgFactory(final Hints userHints,
0457:                    final Connection connection) {
0458:                super (userHints, MAXIMUM_PRIORITY - 20);
0459:                // The following hints have no effect on this class behaviour,
0460:                // but tell to the user what this factory do about axis order.
0461:                hints
0462:                        .put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
0463:                                Boolean.FALSE);
0464:                hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS, Boolean.FALSE);
0465:                hints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean.FALSE);
0466:                this .connection = connection;
0467:                ensureNonNull("connection", connection);
0468:            }
0469:
0470:            /**
0471:             * Returns the authority for this EPSG database.
0472:             * This authority will contains the database version in the {@linkplain Citation#getEdition
0473:             * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
0474:             */
0475:            public synchronized Citation getAuthority() {
0476:                if (authority == null)
0477:                    try {
0478:                        final String query = adaptSQL("SELECT VERSION_NUMBER, VERSION_DATE FROM [Version History]"
0479:                                + " ORDER BY VERSION_DATE DESC");
0480:                        final DatabaseMetaData metadata = connection
0481:                                .getMetaData();
0482:                        final Statement statement = connection
0483:                                .createStatement();
0484:                        final ResultSet result = statement.executeQuery(query);
0485:                        if (result.next()) {
0486:                            final String version = result.getString(1);
0487:                            final Date date = result.getDate(2);
0488:                            final String engine = metadata
0489:                                    .getDatabaseProductName();
0490:                            final CitationImpl c = new CitationImpl(
0491:                                    Citations.EPSG);
0492:                            c.getAlternateTitles().add(
0493:                                    Vocabulary.formatInternational(
0494:                                            VocabularyKeys.DATA_BASE_$3,
0495:                                            "EPSG", version, engine));
0496:                            c
0497:                                    .setEdition(new SimpleInternationalString(
0498:                                            version));
0499:                            c.setEditionDate(date);
0500:                            authority = (Citation) c.unmodifiable();
0501:                            hints.put(Hints.VERSION, new Version(version)); // For getImplementationHints()
0502:                        } else {
0503:                            authority = Citations.EPSG;
0504:                        }
0505:                        result.close();
0506:                        statement.close();
0507:                    } catch (SQLException exception) {
0508:                        org.geotools.util.logging.Logging.unexpectedException(
0509:                                LOGGER.getName(), DirectEpsgFactory.class,
0510:                                "getAuthority", exception);
0511:                        return Citations.EPSG;
0512:                    }
0513:                return authority;
0514:            }
0515:
0516:            /**
0517:             * Returns a description of the database engine.
0518:             *
0519:             * @throws FactoryException if the database's metadata can't be fetched.
0520:             */
0521:            public synchronized String getBackingStoreDescription()
0522:                    throws FactoryException {
0523:                final Citation authority = getAuthority();
0524:                final TableWriter table = new TableWriter(null, " ");
0525:                final Vocabulary resources = Vocabulary.getResources(null);
0526:                CharSequence cs;
0527:                if ((cs = authority.getEdition()) != null) {
0528:                    table.write(resources.getString(
0529:                            VocabularyKeys.VERSION_OF_$1, "EPSG"));
0530:                    table.write(':');
0531:                    table.nextColumn();
0532:                    table.write(cs.toString());
0533:                    table.nextLine();
0534:                }
0535:                try {
0536:                    String s;
0537:                    final DatabaseMetaData metadata = connection.getMetaData();
0538:                    if ((s = metadata.getDatabaseProductName()) != null) {
0539:                        table.write(resources
0540:                                .getLabel(VocabularyKeys.DATABASE_ENGINE));
0541:                        table.nextColumn();
0542:                        table.write(s);
0543:                        if ((s = metadata.getDatabaseProductVersion()) != null) {
0544:                            table.write(' ');
0545:                            table.write(resources.getString(
0546:                                    VocabularyKeys.VERSION_$1, s));
0547:                        }
0548:                        table.nextLine();
0549:                    }
0550:                    if ((s = metadata.getURL()) != null) {
0551:                        table.write(resources
0552:                                .getLabel(VocabularyKeys.DATABASE_URL));
0553:                        table.nextColumn();
0554:                        table.write(s);
0555:                        table.nextLine();
0556:                    }
0557:                } catch (SQLException exception) {
0558:                    throw new FactoryException(exception);
0559:                }
0560:                return table.toString();
0561:            }
0562:
0563:            /**
0564:             * Returns the implementation hints for this factory. The returned map contains all the
0565:             * values specified in {@linkplain DirectAuthorityFactory#getImplementationHints subclass},
0566:             * with the addition of {@link Hints#VERSION VERSION}.
0567:             */
0568:            // @Override
0569:            public Map getImplementationHints() {
0570:                if (authority == null) {
0571:                    // For the computation of Hints.VERSION.
0572:                    getAuthority();
0573:                }
0574:                return super .getImplementationHints();
0575:            }
0576:
0577:            /**
0578:             * Returns the set of authority codes of the given type.
0579:             * <p>
0580:             * <strong>NOTE:</strong> This method returns a living connection to the underlying database.
0581:             * This means that the returned set can executes efficiently idioms like the following one:
0582:             *
0583:             * <blockquote>
0584:             * <pre>getAuthorityCodes(<var>type</var).containsAll(<var>others</var>)</pre>
0585:             * </blockquote>
0586:             *
0587:             * But do not keep the returned reference for a long time. The returned set should stay valid
0588:             * even if retained for a long time (as long as this factory has not been {@linkplain #dispose
0589:             * disposed}), but the existence of those long-living connections may prevent this factory to
0590:             * release some resources. If the set of codes is needed for a long time, copy their values in
0591:             * an other collection object.
0592:             *
0593:             * @param  type The spatial reference objects type (may be {@code Object.class}).
0594:             * @return The set of authority codes for spatial reference objects of the given type.
0595:             *         If this factory doesn't contains any object of the given type, then this method
0596:             *         returns an {@linkplain java.util.Collections#EMPTY_SET empty set}.
0597:             * @throws FactoryException if access to the underlying database failed.
0598:             */
0599:            public Set/*<String>*/getAuthorityCodes(final Class type)
0600:                    throws FactoryException {
0601:                return getAuthorityCodes0(type);
0602:            }
0603:
0604:            /**
0605:             * Implementation of {@link #getAuthorityCodes} as a private method, for protecting
0606:             * {@link #getDescriptionText} from user overriding of {@link #getAuthorityCodes}.
0607:             */
0608:            private synchronized Set/*<String>*/getAuthorityCodes0(
0609:                    final Class type) throws FactoryException {
0610:                /*
0611:                 * If the set were already requested previously for the given type, returns it.
0612:                 * Otherwise, a new one will be created (but will not use the database connection yet).
0613:                 */
0614:                Reference reference = (Reference) authorityCodes.get(type);
0615:                AuthorityCodes candidate = (reference != null) ? (AuthorityCodes) reference
0616:                        .get()
0617:                        : null;
0618:                if (candidate != null) {
0619:                    return candidate;
0620:                }
0621:                Set result = Collections.EMPTY_SET;
0622:                for (int i = 0; i < TABLES_INFO.length; i++) {
0623:                    final TableInfo table = TABLES_INFO[i];
0624:                    /*
0625:                     * We test 'isAssignableFrom' in the two ways, which may seems strange but try
0626:                     * to catch the following use cases:
0627:                     *
0628:                     *  - table.type.isAssignableFrom(type)
0629:                     *    is for the case where a table is for CoordinateReferenceSystem while the user
0630:                     *    type is some subtype like GeographicCRS. The GeographicCRS need to be queried
0631:                     *    into the CoordinateReferenceSystem table. An additional filter will be applied
0632:                     *    inside the AuthorityCodes class implementation.
0633:                     *
0634:                     *  - type.isAssignableFrom(table.type)
0635:                     *    is for the case where the user type is IdentifiedObject or Object, in which
0636:                     *    case we basically want to iterate through every tables.
0637:                     */
0638:                    if (table.type.isAssignableFrom(type)
0639:                            || type.isAssignableFrom(table.type)) {
0640:                        /*
0641:                         * Maybe an instance already existed but was not found above because the user
0642:                         * specified some implementation class instead of an interface class. Before
0643:                         * to return the newly created set, check again in the cached sets using the
0644:                         * type computed by AuthorityCodes itself.
0645:                         */
0646:                        final AuthorityCodes codes;
0647:                        codes = new AuthorityCodes(connection, TABLES_INFO[i],
0648:                                type, this );
0649:                        reference = (Reference) authorityCodes.get(codes.type);
0650:                        candidate = (reference != null) ? (AuthorityCodes) reference
0651:                                .get()
0652:                                : null;
0653:                        final boolean cache;
0654:                        if (candidate == null) {
0655:                            candidate = codes;
0656:                            cache = true;
0657:                        } else {
0658:                            // We will reuse the existing 'candidate' instead of the newly created 'codes'.
0659:                            assert candidate.sqlAll.equals(codes.sqlAll) : codes.type;
0660:                            cache = !(reference instanceof  SoftReference);
0661:                        }
0662:                        if (cache) {
0663:                            reference = new SoftReference(candidate);
0664:                            authorityCodes.put(codes.type, reference);
0665:                        }
0666:                        /*
0667:                         * We now have the codes for a single type.  Append with the codes of previous
0668:                         * types, if any. This usually happen only if the user asked for the Object or
0669:                         * IdentifiedObject type.
0670:                         */
0671:                        if (result.isEmpty()) {
0672:                            result = candidate;
0673:                        } else {
0674:                            if (result instanceof  AuthorityCodes) {
0675:                                result = new LinkedHashSet(result);
0676:                            }
0677:                            result.addAll(candidate);
0678:                        }
0679:                    }
0680:                }
0681:                return result;
0682:            }
0683:
0684:            /**
0685:             * Gets a description of the object corresponding to a code.
0686:             *
0687:             * @param  code Value allocated by authority.
0688:             * @return A description of the object, or {@code null} if the object
0689:             *         corresponding to the specified {@code code} has no description.
0690:             * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
0691:             * @throws FactoryException if the query failed for some other reason.
0692:             */
0693:            public InternationalString getDescriptionText(final String code)
0694:                    throws FactoryException {
0695:                final String primaryKey = trimAuthority(code);
0696:                for (int i = 0; i < TABLES_INFO.length; i++) {
0697:                    final Set codes = getAuthorityCodes0(TABLES_INFO[i].type);
0698:                    if (codes instanceof  AuthorityCodes) {
0699:                        final String text = (String) ((AuthorityCodes) codes)
0700:                                .asMap().get(primaryKey);
0701:                        if (text != null) {
0702:                            return new SimpleInternationalString(text);
0703:                        }
0704:                    }
0705:                }
0706:                /*
0707:                 * Maybe the user overridden some object creation
0708:                 * methods with a value for the supplied code.
0709:                 */
0710:                final Identifier identifier = createObject(code).getName();
0711:                if (identifier instanceof  GenericName) {
0712:                    return ((GenericName) identifier).toInternationalString();
0713:                }
0714:                return new SimpleInternationalString(identifier.getCode());
0715:            }
0716:
0717:            /**
0718:             * Returns a prepared statement for the specified name. Most {@link PreparedStatement}
0719:             * creations are performed through this method, except {@link #getNumericalIdentifier}
0720:             * and {@link #createObject}.
0721:             *
0722:             * @param  key A key uniquely identifying the caller
0723:             *         (e.g. {@code "Ellipsoid"} for {@link #createEllipsoid}).
0724:             * @param  sql The SQL statement to use if for creating the {@link PreparedStatement}
0725:             *         object. Will be used only if no prepared statement was already created for
0726:             *         the specified key.
0727:             * @return The prepared statement.
0728:             * @throws SQLException if the prepared statement can't be created.
0729:             */
0730:            private PreparedStatement prepareStatement(final String key,
0731:                    final String sql) throws SQLException {
0732:                assert Thread.holdsLock(this );
0733:                PreparedStatement stmt = (PreparedStatement) statements
0734:                        .get(key);
0735:                if (stmt == null) {
0736:                    stmt = connection.prepareStatement(adaptSQL(sql));
0737:                    statements.put(key, stmt);
0738:                }
0739:                return stmt;
0740:            }
0741:
0742:            /**
0743:             * Gets the string from the specified {@link ResultSet}.
0744:             * The string is required to be non-null. A null string
0745:             * will throw an exception.
0746:             *
0747:             * @param  result The result set to fetch value from.
0748:             * @param  columnIndex The column index (1-based).
0749:             * @param  code The identifier of the record where the string was found.
0750:             * @return The string at the specified column.
0751:             * @throws SQLException if a SQL error occured.
0752:             * @throws FactoryException If a null value was found.
0753:             */
0754:            private static String getString(final ResultSet result,
0755:                    final int columnIndex, final String code)
0756:                    throws SQLException, FactoryException {
0757:                final String value = result.getString(columnIndex);
0758:                ensureNonNull(result, columnIndex, code);
0759:                return value.trim();
0760:            }
0761:
0762:            /**
0763:             * Same as {@link #getString(ResultSet,int,String)}, but report the fault on an alternative
0764:             * column if the value is null.
0765:             */
0766:            private static String getString(final ResultSet result,
0767:                    final int columnIndex, final String code,
0768:                    final int columnFault) throws SQLException,
0769:                    FactoryException {
0770:                final String str = result.getString(columnIndex);
0771:                if (result.wasNull()) {
0772:                    final ResultSetMetaData metadata = result.getMetaData();
0773:                    final String column = metadata.getColumnName(columnFault);
0774:                    final String table = metadata.getTableName(columnFault);
0775:                    result.close();
0776:                    throw new FactoryException(Errors.format(
0777:                            ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column,
0778:                            table));
0779:                }
0780:                return str.trim();
0781:            }
0782:
0783:            /**
0784:             * Gets the value from the specified {@link ResultSet}.
0785:             * The value is required to be non-null. A null value
0786:             * (i.e. blank) will throw an exception.
0787:             *
0788:             * @param  result The result set to fetch value from.
0789:             * @param  columnIndex The column index (1-based).
0790:             * @param  code The identifier of the record where the string was found.
0791:             * @return The double at the specified column.
0792:             * @throws SQLException if a SQL error occured.
0793:             * @throws FactoryException If a null value was found.
0794:             */
0795:            private static double getDouble(final ResultSet result,
0796:                    final int columnIndex, final String code)
0797:                    throws SQLException, FactoryException {
0798:                final double value = result.getDouble(columnIndex);
0799:                ensureNonNull(result, columnIndex, code);
0800:                return value;
0801:            }
0802:
0803:            /**
0804:             * Gets the value from the specified {@link ResultSet}.
0805:             * The value is required to be non-null. A null value
0806:             * (i.e. blank) will throw an exception.
0807:             *
0808:             * @param  result The result set to fetch value from.
0809:             * @param  columnIndex The column index (1-based).
0810:             * @param  code The identifier of the record where the string was found.
0811:             * @return The integer at the specified column.
0812:             * @throws SQLException if a SQL error occured.
0813:             * @throws FactoryException If a null value was found.
0814:             */
0815:            private static int getInt(final ResultSet result,
0816:                    final int columnIndex, final String code)
0817:                    throws SQLException, FactoryException {
0818:                final int value = result.getInt(columnIndex);
0819:                ensureNonNull(result, columnIndex, code);
0820:                return value;
0821:            }
0822:
0823:            /**
0824:             * Make sure that the last result was non-null. Used for {@code getString}, {@code getDouble}
0825:             * and {@code getInt} methods only.
0826:             */
0827:            private static void ensureNonNull(final ResultSet result,
0828:                    final int columnIndex, final String code)
0829:                    throws SQLException, FactoryException {
0830:                if (result.wasNull()) {
0831:                    final ResultSetMetaData metadata = result.getMetaData();
0832:                    final String column = metadata.getColumnName(columnIndex);
0833:                    final String table = metadata.getTableName(columnIndex);
0834:                    result.close();
0835:                    throw new FactoryException(Errors.format(
0836:                            ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column,
0837:                            table));
0838:                }
0839:            }
0840:
0841:            /**
0842:             * Converts a code from an arbitrary name to the numerical identifier (the primary key).
0843:             * If the supplied code is already a numerical value, then it is returned unchanged.
0844:             * If the code is not found in the name column, it is returned unchanged as well so that
0845:             * the caller will produces an appropriate "Code not found" error message. If the code
0846:             * is found more than once, then an exception is thrown.
0847:             * <p>
0848:             * Note that this method includes a call to {@link #trimAuthority}, so there is no need to
0849:             * call it before or after this method.
0850:             *
0851:             * @param  type       The type of object to create.
0852:             * @param  code       The code to check.
0853:             * @param  table      The table where the code should appears.
0854:             * @param  codeColumn The column name for the code.
0855:             * @param  nameColumn The column name for the name.
0856:             * @return The numerical identifier (i.e. the table primary key value).
0857:             * @throws SQLException if an error occured while reading the database.
0858:             */
0859:            private String toPrimaryKey(final Class type, final String code,
0860:                    final String table, final String codeColumn,
0861:                    final String nameColumn) throws SQLException,
0862:                    FactoryException {
0863:                assert Thread.holdsLock(this );
0864:                String identifier = trimAuthority(code);
0865:                if (!isPrimaryKey(identifier)) {
0866:                    /*
0867:                     * The character is not the numerical code. Search the value in the database.
0868:                     * If a prepared statement is already available, reuse it providing that it was
0869:                     * created for the current table. Otherwise, we will create a new statement.
0870:                     */
0871:                    final String KEY = "NumericalIdentifier";
0872:                    PreparedStatement statement = (PreparedStatement) statements
0873:                            .get(KEY);
0874:                    if (statement != null) {
0875:                        if (!table.equals(lastTableForName)) {
0876:                            statements.remove(KEY);
0877:                            statement.close();
0878:                            statement = null;
0879:                            lastTableForName = null;
0880:                        }
0881:                    }
0882:                    if (statement == null) {
0883:                        final String query = "SELECT " + codeColumn + " FROM "
0884:                                + table + " WHERE " + nameColumn + " = ?";
0885:                        statement = connection
0886:                                .prepareStatement(adaptSQL(query));
0887:                        statements.put(KEY, statement);
0888:                    }
0889:                    statement.setString(1, identifier);
0890:                    identifier = null;
0891:                    final ResultSet result = statement.executeQuery();
0892:                    while (result.next()) {
0893:                        identifier = (String) ensureSingleton(result
0894:                                .getString(1), identifier, code);
0895:                    }
0896:                    result.close();
0897:                    if (identifier == null) {
0898:                        throw noSuchAuthorityCode(type, code);
0899:                    }
0900:                }
0901:                return identifier;
0902:            }
0903:
0904:            /**
0905:             * Make sure that an object constructed from the database is not incoherent.
0906:             * If the code supplied to a {@code createFoo} method exists in the database,
0907:             * then we should find only one record. However, we will do a paranoiac check and
0908:             * verify if there is more records, using a {@code while (results.next())}
0909:             * loop instead of {@code if (results.next())}. This method is invoked in
0910:             * the loop for making sure that, if there is more than one record (which should
0911:             * never happen), at least they have identical contents.
0912:             *
0913:             * @param  newValue The newly constructed object.
0914:             * @param  oldValue The object previously constructed, or {@code null} if none.
0915:             * @param  code The EPSG code (for formatting error message).
0916:             * @throws FactoryException if a duplication has been detected.
0917:             *
0918:             * @todo Use generic type when we will be allowed to compile for J2SE 1.5.
0919:             */
0920:            private static Object ensureSingleton(final Object newValue,
0921:                    final Object oldValue, final String code)
0922:                    throws FactoryException {
0923:                if (oldValue == null) {
0924:                    return newValue;
0925:                }
0926:                if (oldValue.equals(newValue)) {
0927:                    return oldValue;
0928:                }
0929:                throw new FactoryException(Errors.format(
0930:                        ErrorKeys.DUPLICATED_VALUES_$1, code));
0931:            }
0932:
0933:            /**
0934:             * Returns the name for the {@link IdentifiedObject} to construct.
0935:             * This method also search for alias.
0936:             *
0937:             * @param  name The name for the {@link IndentifiedObject} to construct.
0938:             * @param  code The EPSG code of the object to construct.
0939:             * @param  remarks Remarks, or {@code null} if none.
0940:             * @return The name together with a set of properties.
0941:             */
0942:            private Map createProperties(final String name, final String code,
0943:                    String remarks) throws SQLException, FactoryException {
0944:                properties.clear();
0945:                final Citation authority = getAuthority();
0946:                if (name != null) {
0947:                    properties.put(IdentifiedObject.NAME_KEY,
0948:                            new NamedIdentifier(authority, name.trim()));
0949:                }
0950:                if (code != null) {
0951:                    final InternationalString edition = authority.getEdition();
0952:                    final String version = (edition != null) ? edition
0953:                            .toString() : null;
0954:                    properties
0955:                            .put(IdentifiedObject.IDENTIFIERS_KEY,
0956:                                    new NamedIdentifier(authority, code.trim(),
0957:                                            version));
0958:                }
0959:                if (remarks != null && (remarks = remarks.trim()).length() != 0) {
0960:                    properties.put(IdentifiedObject.REMARKS_KEY, remarks);
0961:                }
0962:                /*
0963:                 * Search for alias.
0964:                 */
0965:                List alias = null;
0966:                final PreparedStatement stmt;
0967:                stmt = prepareStatement("Alias",
0968:                        "SELECT NAMING_SYSTEM_NAME, ALIAS"
0969:                                + " FROM [Alias] INNER JOIN [Naming System]"
0970:                                + " ON [Alias].NAMING_SYSTEM_CODE ="
0971:                                + " [Naming System].NAMING_SYSTEM_CODE"
0972:                                + " WHERE OBJECT_CODE = ?");
0973:                stmt.setString(1, code);
0974:                final ResultSet result = stmt.executeQuery();
0975:                while (result.next()) {
0976:                    final String scope = result.getString(1);
0977:                    final String local = getString(result, 2, code);
0978:                    final GenericName generic;
0979:                    if (scope == null) {
0980:                        generic = new LocalName(local);
0981:                    } else {
0982:                        LocalName cached = (LocalName) scopes.get(scope);
0983:                        if (cached == null) {
0984:                            cached = new LocalName(scope);
0985:                            scopes.put(scope, cached);
0986:                        }
0987:                        generic = new ScopedName(cached, local);
0988:                    }
0989:                    if (alias == null) {
0990:                        alias = new ArrayList();
0991:                    }
0992:                    alias.add(generic);
0993:                }
0994:                result.close();
0995:                if (alias != null) {
0996:                    properties.put(IdentifiedObject.ALIAS_KEY,
0997:                            (GenericName[]) alias.toArray(new GenericName[alias
0998:                                    .size()]));
0999:                }
1000:                return properties;
1001:            }
1002:
1003:            /**
1004:             * Returns the name for the {@link IdentifiedObject} to construct.
1005:             * This method also search for alias.
1006:             *
1007:             * @param  name  The name for the {@link IndentifiedObject} to construct.
1008:             * @param  code  The EPSG code of the object to construct.
1009:             * @param  area  The area of use, or {@code null} if none.
1010:             * @param  scope The scope, or {@code null} if none.
1011:             * @param  remarks Remarks, or {@code null} if none.
1012:             * @return The name together with a set of properties.
1013:             */
1014:            private Map createProperties(final String name, final String code,
1015:                    String area, String scope, String remarks)
1016:                    throws SQLException, FactoryException {
1017:                final Map properties = createProperties(name, code, remarks);
1018:                if (area != null && (area = area.trim()).length() != 0) {
1019:                    final Extent extent = buffered.createExtent(area);
1020:                    properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, extent);
1021:                }
1022:                if (scope != null && (scope = scope.trim()).length() != 0) {
1023:                    properties.put(Datum.SCOPE_KEY, scope);
1024:                }
1025:                return properties;
1026:            }
1027:
1028:            /**
1029:             * Returns an arbitrary object from a code.
1030:             * The default implementation invokes one of {@link #createCoordinateReferenceSystem},
1031:             * {@link #createCoordinateSystem}, {@link #createDatum}, {@link #createEllipsoid}, or
1032:             * {@link #createUnit} methods according the object type.
1033:             *
1034:             * @param  code The EPSG value.
1035:             * @return The object.
1036:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1037:             * @throws FactoryException if some other kind of failure occured in the backing
1038:             *         store. This exception usually have {@link SQLException} as its cause.
1039:             */
1040:            public synchronized IdentifiedObject createObject(final String code)
1041:                    throws FactoryException {
1042:                ensureNonNull("code", code);
1043:                final String KEY = "IdentifiedObject";
1044:                PreparedStatement stmt = (PreparedStatement) statements
1045:                        .get(KEY); // Null allowed.
1046:                StringBuffer query = null; // Will be created only if the last statement doesn't suit.
1047:                /*
1048:                 * Iterates through all tables listed in TABLES_INFO, starting with the table used during
1049:                 * the last call to 'createObject(code)'.  This approach assumes that two consecutive calls
1050:                 * will often return the same type of object.  If the object type changed, then this method
1051:                 * will have to discard the old prepared statement and prepare a new one, which may be a
1052:                 * costly operation. Only the last successful prepared statement is cached, in order to keep
1053:                 * the amount of statements low. Unsuccessful statements are immediately disposed.
1054:                 */
1055:                final String epsg = trimAuthority(code);
1056:                final boolean isPrimaryKey = isPrimaryKey(epsg);
1057:                final int tupleToSkip = isPrimaryKey ? lastObjectType : -1;
1058:                int index = -1;
1059:                for (int i = -1; i < TABLES_INFO.length; i++) {
1060:                    if (i == tupleToSkip) {
1061:                        // Avoid to test the same table twice.  Note that this test also avoid a
1062:                        // NullPointerException if 'stmt' is null, since 'lastObjectType' should
1063:                        // be -1 in this case.
1064:                        continue;
1065:                    }
1066:                    try {
1067:                        if (i >= 0) {
1068:                            final TableInfo table = TABLES_INFO[i];
1069:                            final String column = isPrimaryKey ? table.codeColumn
1070:                                    : table.nameColumn;
1071:                            if (column == null) {
1072:                                continue;
1073:                            }
1074:                            if (query == null) {
1075:                                query = new StringBuffer("SELECT ");
1076:                            }
1077:                            query.setLength(7); // 7 is the length of "SELECT " in the line above.
1078:                            query.append(table.codeColumn);
1079:                            query.append(" FROM ");
1080:                            query.append(table.table);
1081:                            query.append(" WHERE ");
1082:                            query.append(column);
1083:                            query.append(" = ?");
1084:                            if (isPrimaryKey) {
1085:                                assert !statements.containsKey(KEY) : table;
1086:                                stmt = prepareStatement(KEY, query.toString());
1087:                            } else {
1088:                                // Do not cache the statement for names.
1089:                                stmt = connection
1090:                                        .prepareStatement(adaptSQL(query
1091:                                                .toString()));
1092:                            }
1093:                        }
1094:                        /*
1095:                         * Checks if at least one record is found for the code. If the code is the primary
1096:                         * key, then we will stop at the first table found since a well-formed EPSG database
1097:                         * should not contains any duplicate identifiers. In the code is a name, then search
1098:                         * in all tables since duplicate names exist.
1099:                         */
1100:                        stmt.setString(1, epsg);
1101:                        final ResultSet result = stmt.executeQuery();
1102:                        final boolean present = result.next();
1103:                        result.close();
1104:                        if (present) {
1105:                            if (index >= 0) {
1106:                                throw new FactoryException(Errors.format(
1107:                                        ErrorKeys.DUPLICATED_VALUES_$1, code));
1108:                            }
1109:                            index = (i < 0) ? lastObjectType : i;
1110:                            if (isPrimaryKey) {
1111:                                // Don't scan other tables, since primary keys should be unique.
1112:                                // Note that names may be duplicated, so we don't stop for names.
1113:                                break;
1114:                            }
1115:                        }
1116:                        if (isPrimaryKey) {
1117:                            if (statements.remove(KEY) == null) {
1118:                                throw new AssertionError(code); // Should never happen.
1119:                            }
1120:                        }
1121:                        stmt.close();
1122:                    } catch (SQLException exception) {
1123:                        throw databaseFailure(IdentifiedObject.class, code,
1124:                                exception);
1125:                    }
1126:                }
1127:                /*
1128:                 * If a record has been found in one table, then delegates to the appropriate method.
1129:                 */
1130:                if (isPrimaryKey) {
1131:                    lastObjectType = index;
1132:                }
1133:                if (index >= 0) {
1134:                    switch (index) {
1135:                    case 0:
1136:                        return buffered.createCoordinateReferenceSystem(code);
1137:                    case 1:
1138:                        return buffered.createCoordinateSystem(code);
1139:                    case 2:
1140:                        return buffered.createCoordinateSystemAxis(code);
1141:                    case 3:
1142:                        return buffered.createDatum(code);
1143:                    case 4:
1144:                        return buffered.createEllipsoid(code);
1145:                    case 5:
1146:                        return buffered.createPrimeMeridian(code);
1147:                    case 6:
1148:                        return buffered.createCoordinateOperation(code);
1149:                    case 7:
1150:                        return buffered.createOperationMethod(code);
1151:                    case 8:
1152:                        return buffered.createParameterDescriptor(code);
1153:                    case 9:
1154:                        break; // Can't cast Unit to IdentifiedObject
1155:                    default:
1156:                        throw new AssertionError(index); // Should not happen
1157:                    }
1158:                }
1159:                return super .createObject(code);
1160:            }
1161:
1162:            /**
1163:             * Returns an unit from a code.
1164:             *
1165:             * @param  code Value allocated by authority.
1166:             * @return The unit object.
1167:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1168:             * @throws FactoryException if some other kind of failure occured in the backing
1169:             *         store. This exception usually have {@link SQLException} as its cause.
1170:             */
1171:            public synchronized Unit createUnit(final String code)
1172:                    throws FactoryException {
1173:                ensureNonNull("code", code);
1174:                Unit returnValue = null;
1175:                try {
1176:                    final String primaryKey = toPrimaryKey(Unit.class, code,
1177:                            "[Unit of Measure]", "UOM_CODE",
1178:                            "UNIT_OF_MEAS_NAME");
1179:                    final PreparedStatement stmt;
1180:                    stmt = prepareStatement("Unit", "SELECT UOM_CODE,"
1181:                            + " FACTOR_B," + " FACTOR_C," + " TARGET_UOM_CODE"
1182:                            + " FROM [Unit of Measure]" + " WHERE UOM_CODE = ?");
1183:                    stmt.setString(1, primaryKey);
1184:                    final ResultSet result = stmt.executeQuery();
1185:                    while (result.next()) {
1186:                        final int source = getInt(result, 1, code);
1187:                        final double b = result.getDouble(2);
1188:                        final double c = result.getDouble(3);
1189:                        final int target = getInt(result, 4, code);
1190:                        final Unit base = getUnit(target);
1191:                        if (base == null) {
1192:                            throw noSuchAuthorityCode(Unit.class, String
1193:                                    .valueOf(target));
1194:                        }
1195:                        Unit unit = getUnit(source);
1196:                        if (unit != null) {
1197:                            // TODO: check unit consistency here.
1198:                        } else if (b != 0 && c != 0) {
1199:                            unit = (b == c) ? base : base.multiply(b / c);
1200:                        } else {
1201:                            // TODO: provide a localized message.
1202:                            throw new FactoryException("Unsupported unit: "
1203:                                    + code);
1204:                        }
1205:                        returnValue = (Unit) ensureSingleton(unit, returnValue,
1206:                                code);
1207:                    }
1208:                    result.close();
1209:                } catch (SQLException exception) {
1210:                    throw databaseFailure(Unit.class, code, exception);
1211:                }
1212:                if (returnValue == null) {
1213:                    throw noSuchAuthorityCode(Unit.class, code);
1214:                }
1215:                return returnValue;
1216:            }
1217:
1218:            /**
1219:             * Returns an ellipsoid from a code.
1220:             *
1221:             * @param  code The EPSG value.
1222:             * @return The ellipsoid object.
1223:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1224:             * @throws FactoryException if some other kind of failure occured in the backing
1225:             *         store. This exception usually have {@link SQLException} as its cause.
1226:             */
1227:            public synchronized Ellipsoid createEllipsoid(final String code)
1228:                    throws FactoryException {
1229:                ensureNonNull("code", code);
1230:                Ellipsoid returnValue = null;
1231:                try {
1232:                    final String primaryKey = toPrimaryKey(Ellipsoid.class,
1233:                            code, "[Ellipsoid]", "ELLIPSOID_CODE",
1234:                            "ELLIPSOID_NAME");
1235:                    final PreparedStatement stmt;
1236:                    stmt = prepareStatement("Ellipsoid",
1237:                            "SELECT ELLIPSOID_CODE," + " ELLIPSOID_NAME,"
1238:                                    + " SEMI_MAJOR_AXIS," + " INV_FLATTENING,"
1239:                                    + " SEMI_MINOR_AXIS," + " UOM_CODE,"
1240:                                    + " REMARKS" + " FROM [Ellipsoid]"
1241:                                    + " WHERE ELLIPSOID_CODE = ?");
1242:                    stmt.setString(1, primaryKey);
1243:                    final ResultSet result = stmt.executeQuery();
1244:                    while (result.next()) {
1245:                        /*
1246:                         * One of 'semiMinorAxis' and 'inverseFlattening' values can be NULL in
1247:                         * the database. Consequently, we don't use 'getString(ResultSet, int)'
1248:                         * because we don't want to thrown an exception if a NULL value is found.
1249:                         */
1250:                        final String epsg = getString(result, 1, code);
1251:                        final String name = getString(result, 2, code);
1252:                        final double semiMajorAxis = getDouble(result, 3, code);
1253:                        final double inverseFlattening = result.getDouble(4);
1254:                        final double semiMinorAxis = result.getDouble(5);
1255:                        final String unitCode = getString(result, 6, code);
1256:                        final String remarks = result.getString(7);
1257:                        final Unit unit = buffered.createUnit(unitCode);
1258:                        final Map properties = createProperties(name, epsg,
1259:                                remarks);
1260:                        final Ellipsoid ellipsoid;
1261:                        if (inverseFlattening == 0) {
1262:                            if (semiMinorAxis == 0) {
1263:                                // Both are null, which is not allowed.
1264:                                final String column = result.getMetaData()
1265:                                        .getColumnName(3);
1266:                                result.close();
1267:                                throw new FactoryException(Errors.format(
1268:                                        ErrorKeys.NULL_VALUE_IN_TABLE_$3, code,
1269:                                        column));
1270:                            } else {
1271:                                // We only have semiMinorAxis defined -> it's OK
1272:                                ellipsoid = factories.getDatumFactory()
1273:                                        .createEllipsoid(properties,
1274:                                                semiMajorAxis, semiMinorAxis,
1275:                                                unit);
1276:                            }
1277:                        } else {
1278:                            if (semiMinorAxis != 0) {
1279:                                // Both 'inverseFlattening' and 'semiMinorAxis' are defined.
1280:                                // Log a warning and create the ellipsoid using the inverse flattening.
1281:                                LOGGER.log(Logging.format(Level.WARNING,
1282:                                        LoggingKeys.AMBIGUOUS_ELLIPSOID));
1283:                            }
1284:                            ellipsoid = factories.getDatumFactory()
1285:                                    .createFlattenedSphere(properties,
1286:                                            semiMajorAxis, inverseFlattening,
1287:                                            unit);
1288:                        }
1289:                        /*
1290:                         * Now that we have built an ellipsoid, compare
1291:                         * it with the previous one (if any).
1292:                         */
1293:                        returnValue = (Ellipsoid) ensureSingleton(ellipsoid,
1294:                                returnValue, code);
1295:                    }
1296:                    result.close();
1297:                } catch (SQLException exception) {
1298:                    throw databaseFailure(Ellipsoid.class, code, exception);
1299:                }
1300:                if (returnValue == null) {
1301:                    throw noSuchAuthorityCode(Ellipsoid.class, code);
1302:                }
1303:                return returnValue;
1304:            }
1305:
1306:            /**
1307:             * Returns a prime meridian, relative to Greenwich.
1308:             *
1309:             * @param  code Value allocated by authority.
1310:             * @return The prime meridian object.
1311:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1312:             * @throws FactoryException if some other kind of failure occured in the backing
1313:             *         store. This exception usually have {@link SQLException} as its cause.
1314:             */
1315:            public synchronized PrimeMeridian createPrimeMeridian(
1316:                    final String code) throws FactoryException {
1317:                ensureNonNull("code", code);
1318:                PrimeMeridian returnValue = null;
1319:                try {
1320:                    final String primaryKey = toPrimaryKey(PrimeMeridian.class,
1321:                            code, "[Prime Meridian]", "PRIME_MERIDIAN_CODE",
1322:                            "PRIME_MERIDIAN_NAME");
1323:                    final PreparedStatement stmt;
1324:                    stmt = prepareStatement("PrimeMeridian",
1325:                            "SELECT PRIME_MERIDIAN_CODE,"
1326:                                    + " PRIME_MERIDIAN_NAME,"
1327:                                    + " GREENWICH_LONGITUDE," + " UOM_CODE,"
1328:                                    + " REMARKS" + " FROM [Prime Meridian]"
1329:                                    + " WHERE PRIME_MERIDIAN_CODE = ?");
1330:                    stmt.setString(1, primaryKey);
1331:                    final ResultSet result = stmt.executeQuery();
1332:                    while (result.next()) {
1333:                        final String epsg = getString(result, 1, code);
1334:                        final String name = getString(result, 2, code);
1335:                        final double longitude = getDouble(result, 3, code);
1336:                        final String unit_code = getString(result, 4, code);
1337:                        final String remarks = result.getString(5);
1338:                        final Unit unit = buffered.createUnit(unit_code);
1339:                        final Map properties = createProperties(name, epsg,
1340:                                remarks);
1341:                        PrimeMeridian primeMeridian = factories
1342:                                .getDatumFactory().createPrimeMeridian(
1343:                                        properties, longitude, unit);
1344:                        returnValue = (PrimeMeridian) ensureSingleton(
1345:                                primeMeridian, returnValue, code);
1346:                    }
1347:                    result.close();
1348:                } catch (SQLException exception) {
1349:                    throw databaseFailure(PrimeMeridian.class, code, exception);
1350:                }
1351:                if (returnValue == null) {
1352:                    throw noSuchAuthorityCode(PrimeMeridian.class, code);
1353:                }
1354:                return returnValue;
1355:            }
1356:
1357:            /**
1358:             * Returns an area of use.
1359:             *
1360:             * @param  code Value allocated by authority.
1361:             * @return The area of use.
1362:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1363:             * @throws FactoryException if some other kind of failure occured in the backing
1364:             *         store. This exception usually have {@link SQLException} as its cause.
1365:             */
1366:            public synchronized Extent createExtent(final String code)
1367:                    throws FactoryException {
1368:                ensureNonNull("code", code);
1369:                Extent returnValue = null;
1370:                try {
1371:                    final String primaryKey = toPrimaryKey(Extent.class, code,
1372:                            "[Area]", "AREA_CODE", "AREA_NAME");
1373:                    final PreparedStatement stmt;
1374:                    stmt = prepareStatement("Area", "SELECT AREA_OF_USE,"
1375:                            + " AREA_SOUTH_BOUND_LAT,"
1376:                            + " AREA_NORTH_BOUND_LAT,"
1377:                            + " AREA_WEST_BOUND_LON," + " AREA_EAST_BOUND_LON"
1378:                            + " FROM [Area]" + " WHERE AREA_CODE = ?");
1379:                    stmt.setString(1, primaryKey);
1380:                    final ResultSet result = stmt.executeQuery();
1381:                    while (result.next()) {
1382:                        ExtentImpl extent = null;
1383:                        final String description = result.getString(1);
1384:                        if (description != null) {
1385:                            extent = new ExtentImpl();
1386:                            extent
1387:                                    .setDescription(new SimpleInternationalString(
1388:                                            description));
1389:                        }
1390:                        final double ymin = result.getDouble(2);
1391:                        if (!result.wasNull()) {
1392:                            final double ymax = result.getDouble(3);
1393:                            if (!result.wasNull()) {
1394:                                final double xmin = result.getDouble(4);
1395:                                if (!result.wasNull()) {
1396:                                    final double xmax = result.getDouble(5);
1397:                                    if (!result.wasNull()) {
1398:                                        if (extent == null) {
1399:                                            extent = new ExtentImpl();
1400:                                        }
1401:                                        extent
1402:                                                .setGeographicElements(Collections
1403:                                                        .singleton(new GeographicBoundingBoxImpl(
1404:                                                                xmin, xmax,
1405:                                                                ymin, ymax)));
1406:                                    }
1407:                                }
1408:                            }
1409:                        }
1410:                        if (extent != null) {
1411:                            returnValue = (Extent) ensureSingleton(extent
1412:                                    .unmodifiable(), returnValue, code);
1413:                        }
1414:                    }
1415:                    result.close();
1416:                } catch (SQLException exception) {
1417:                    throw databaseFailure(Extent.class, code, exception);
1418:                }
1419:                if (returnValue == null) {
1420:                    throw noSuchAuthorityCode(Extent.class, code);
1421:                }
1422:                return returnValue;
1423:            }
1424:
1425:            /** 
1426:             * Returns Bursa-Wolf parameters for a geodetic datum. If the specified datum has
1427:             * no conversion informations, then this method will returns {@code null}.
1428:             *  
1429:             * @param  code The EPSG code of the {@link GeodeticDatum}.
1430:             * @param  toClose The result set to close if this method is going to invokes
1431:             *         {@link #createDatum} recursively. This hack is necessary because many
1432:             *         JDBC drivers do not support multiple result sets for the same statement.
1433:             *         The result set is closed if an only if this method returns a non-null value.
1434:             * @return an array of Bursa-Wolf parameters (in which case {@code toClose} has
1435:             *         been closed), or {@code null} (in which case {@code toClose} has
1436:             *         <strong>not</strong> been closed).
1437:             */
1438:            private BursaWolfParameters[] createBursaWolfParameters(
1439:                    final String code, final ResultSet toClose)
1440:                    throws SQLException, FactoryException {
1441:                if (safetyGuard.contains(code)) {
1442:                    /*
1443:                     * Do not try to create Bursa-Wolf parameters if the datum is already
1444:                     * in process of being created. This check avoid never-ending loops in
1445:                     * recursive call to 'createDatum'.
1446:                     */
1447:                    return null;
1448:                }
1449:                PreparedStatement stmt;
1450:                stmt = prepareStatement(
1451:                        "BursaWolfParametersSet",
1452:                        "SELECT CO.COORD_OP_CODE,"
1453:                                + " CO.COORD_OP_METHOD_CODE,"
1454:                                + " CRS2.DATUM_CODE"
1455:                                + " FROM [Coordinate_Operation] AS CO"
1456:                                + " INNER JOIN [Coordinate Reference System] AS CRS2"
1457:                                + " ON CO.TARGET_CRS_CODE = CRS2.COORD_REF_SYS_CODE"
1458:                                + " WHERE CO.COORD_OP_METHOD_CODE >= "
1459:                                + BURSA_WOLF_MIN_CODE
1460:                                + " AND CO.COORD_OP_METHOD_CODE <= "
1461:                                + BURSA_WOLF_MAX_CODE
1462:                                + " AND CO.COORD_OP_CODE <> "
1463:                                + DUMMY_OPERATION // GEOT-1008
1464:                                + " AND CO.SOURCE_CRS_CODE IN ("
1465:                                + " SELECT CRS1.COORD_REF_SYS_CODE " // GEOT-1129
1466:                                + " FROM [Coordinate Reference System] AS CRS1 "
1467:                                + " WHERE CRS1.DATUM_CODE = ?)"
1468:                                + " ORDER BY CRS2.DATUM_CODE,"
1469:                                + " ABS(CO.DEPRECATED), CO.COORD_OP_ACCURACY,"
1470:                                + " CO.COORD_OP_CODE DESC"); // GEOT-846 fix
1471:                stmt.setString(1, code);
1472:                ResultSet result = stmt.executeQuery();
1473:                List bwInfos = null;
1474:                while (result.next()) {
1475:                    final String operation = getString(result, 1, code);
1476:                    final int method = getInt(result, 2, code);
1477:                    final String datum = getString(result, 3, code);
1478:                    if (bwInfos == null) {
1479:                        bwInfos = new ArrayList();
1480:                    }
1481:                    bwInfos.add(new BursaWolfInfo(operation, method, datum));
1482:                }
1483:                result.close();
1484:                if (bwInfos == null) {
1485:                    // Don't close the connection here.
1486:                    return null;
1487:                }
1488:                toClose.close();
1489:                /*
1490:                 * Sorts the infos in preference order. The "ORDER BY" clause above was not enough;
1491:                 * we also need to take the "supersession" table in account. Once the sorting is done,
1492:                 * keep only one Bursa-Wolf parameters for each datum.
1493:                 */
1494:                int size = bwInfos.size();
1495:                if (size > 1) {
1496:                    final BursaWolfInfo[] codes = (BursaWolfInfo[]) bwInfos
1497:                            .toArray(new BursaWolfInfo[size]);
1498:                    sort(codes);
1499:                    bwInfos.clear();
1500:                    final Set added = new HashSet();
1501:                    for (int i = 0; i < codes.length; i++) {
1502:                        final BursaWolfInfo candidate = codes[i];
1503:                        if (added.add(candidate.target)) {
1504:                            bwInfos.add(candidate);
1505:                        }
1506:                    }
1507:                    size = bwInfos.size();
1508:                }
1509:                /*
1510:                 * We got all the needed informations before to built Bursa-Wolf parameters because the
1511:                 * 'createDatum(...)' call below may invokes 'createBursaWolfParameters(...)' recursively,
1512:                 * and not all JDBC drivers supported multi-result set for the same statement. Now, iterate
1513:                 * throw the results and fetch the parameter values for each BursaWolfParameters object.
1514:                 */
1515:                stmt = prepareStatement(
1516:                        "BursaWolfParameters",
1517:                        "SELECT PARAMETER_CODE,"
1518:                                + " PARAMETER_VALUE,"
1519:                                + " UOM_CODE"
1520:                                + " FROM [Coordinate_Operation Parameter Value]"
1521:                                + " WHERE COORD_OP_CODE = ?"
1522:                                + " AND COORD_OP_METHOD_CODE = ?");
1523:                for (int i = 0; i < size; i++) {
1524:                    final BursaWolfInfo info = (BursaWolfInfo) bwInfos.get(i);
1525:                    final GeodeticDatum datum;
1526:                    try {
1527:                        safetyGuard.add(code);
1528:                        datum = buffered.createGeodeticDatum(info.target);
1529:                    } finally {
1530:                        safetyGuard.remove(code);
1531:                    }
1532:                    final BursaWolfParameters parameters = new BursaWolfParameters(
1533:                            datum);
1534:                    stmt.setString(1, info.operation);
1535:                    stmt.setInt(2, info.method);
1536:                    result = stmt.executeQuery();
1537:                    while (result.next()) {
1538:                        setBursaWolfParameter(parameters, getInt(result, 1,
1539:                                info.operation), getDouble(result, 2,
1540:                                info.operation), buffered.createUnit(getString(
1541:                                result, 3, info.operation)));
1542:                    }
1543:                    result.close();
1544:                    if (info.method == ROTATION_FRAME_CODE) {
1545:                        // Coordinate frame rotation (9607): same as 9606,
1546:                        // except for the sign of rotation parameters.
1547:                        parameters.ex = -parameters.ex;
1548:                        parameters.ey = -parameters.ey;
1549:                        parameters.ey = -parameters.ey;
1550:                    }
1551:                    bwInfos.set(i, parameters);
1552:                }
1553:                return (BursaWolfParameters[]) bwInfos
1554:                        .toArray(new BursaWolfParameters[size]);
1555:            }
1556:
1557:            /**
1558:             * Returns a datum from a code.
1559:             *
1560:             * @param  code Value allocated by authority.
1561:             * @return The datum object.
1562:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1563:             * @throws FactoryException if some other kind of failure occured in the backing
1564:             *         store. This exception usually have {@link SQLException} as its cause.
1565:             *
1566:             * @todo Current implementation maps all "vertical" datum to
1567:             *       {@link VerticalDatumType#GEOIDAL}. We don't know yet how
1568:             *       to maps the exact vertical datum type from the EPSG database.
1569:             */
1570:            public synchronized Datum createDatum(final String code)
1571:                    throws FactoryException {
1572:                ensureNonNull("code", code);
1573:                Datum returnValue = null;
1574:                try {
1575:                    final String primaryKey = toPrimaryKey(Datum.class, code,
1576:                            "[Datum]", "DATUM_CODE", "DATUM_NAME");
1577:                    final PreparedStatement stmt;
1578:                    stmt = prepareStatement("Datum", "SELECT DATUM_CODE,"
1579:                            + " DATUM_NAME," + " DATUM_TYPE,"
1580:                            + " ORIGIN_DESCRIPTION," + " REALIZATION_EPOCH,"
1581:                            + " AREA_OF_USE_CODE," + " DATUM_SCOPE,"
1582:                            + " REMARKS," + " ELLIPSOID_CODE," // Only for geodetic type
1583:                            + " PRIME_MERIDIAN_CODE" // Only for geodetic type
1584:                            + " FROM [Datum]" + " WHERE DATUM_CODE = ?");
1585:                    stmt.setString(1, primaryKey);
1586:                    ResultSet result = stmt.executeQuery();
1587:                    while (result.next()) {
1588:                        final String epsg = getString(result, 1, code);
1589:                        final String name = getString(result, 2, code);
1590:                        final String type = getString(result, 3, code).trim()
1591:                                .toLowerCase();
1592:                        final String anchor = result.getString(4);
1593:                        final String epoch = result.getString(5);
1594:                        final String area = result.getString(6);
1595:                        final String scope = result.getString(7);
1596:                        final String remarks = result.getString(8);
1597:                        Map properties = createProperties(name, epsg, area,
1598:                                scope, remarks);
1599:                        if (anchor != null) {
1600:                            properties.put(Datum.ANCHOR_POINT_KEY, anchor);
1601:                        }
1602:                        if (epoch != null && epoch.length() != 0)
1603:                            try {
1604:                                calendar.clear();
1605:                                calendar.set(Integer.parseInt(epoch), 0, 1);
1606:                                properties.put(Datum.REALIZATION_EPOCH_KEY,
1607:                                        calendar.getTime());
1608:                            } catch (NumberFormatException exception) {
1609:                                // Not a fatal error...
1610:                                org.geotools.util.logging.Logging
1611:                                        .unexpectedException(LOGGER.getName(),
1612:                                                DirectEpsgFactory.class,
1613:                                                "createDatum", exception);
1614:                            }
1615:                        final DatumFactory factory = factories
1616:                                .getDatumFactory();
1617:                        final Datum datum;
1618:                        /*
1619:                         * Now build datum according their datum type. Constructions are straightforward,
1620:                         * except for the "geodetic" datum type which need some special processing:
1621:                         *
1622:                         *   - Because it invokes again 'createProperties' indirectly (through calls to
1623:                         *     'createEllipsoid' and 'createPrimeMeridian'), it must protect 'properties'
1624:                         *     from changes.
1625:                         *
1626:                         *   - Because 'createBursaWolfParameters' may invokes 'createDatum' recursively,
1627:                         *     we must close the result set if Bursa-Wolf parameters are found. In this
1628:                         *     case, we lost our paranoiac check for duplication.
1629:                         */
1630:                        if (type.equals("geodetic")) {
1631:                            properties = new HashMap(properties); // Protect from changes
1632:                            final Ellipsoid ellipsoid = buffered
1633:                                    .createEllipsoid(getString(result, 9, code));
1634:                            final PrimeMeridian meridian = buffered
1635:                                    .createPrimeMeridian(getString(result, 10,
1636:                                            code));
1637:                            final BursaWolfParameters[] param = createBursaWolfParameters(
1638:                                    primaryKey, result);
1639:                            if (param != null) {
1640:                                result = null; // Already closed by createBursaWolfParameters
1641:                                properties.put(
1642:                                        DefaultGeodeticDatum.BURSA_WOLF_KEY,
1643:                                        param);
1644:                            }
1645:                            datum = factory.createGeodeticDatum(properties,
1646:                                    ellipsoid, meridian);
1647:                        } else if (type.equals("vertical")) {
1648:                            // TODO: Find the right datum type.
1649:                            datum = factory.createVerticalDatum(properties,
1650:                                    VerticalDatumType.GEOIDAL);
1651:                        } else if (type.equals("engineering")) {
1652:                            datum = factory.createEngineeringDatum(properties);
1653:                        } else {
1654:                            result.close();
1655:                            throw new FactoryException(Errors.format(
1656:                                    ErrorKeys.UNKNOW_TYPE_$1, type));
1657:                        }
1658:                        returnValue = (Datum) ensureSingleton(datum,
1659:                                returnValue, code);
1660:                        if (result == null) {
1661:                            // Bypass the 'result.close()' line below:
1662:                            // the ResultSet has already been closed.
1663:                            return returnValue;
1664:                        }
1665:                    }
1666:                    result.close();
1667:                } catch (SQLException exception) {
1668:                    throw databaseFailure(Datum.class, code, exception);
1669:                }
1670:                if (returnValue == null) {
1671:                    throw noSuchAuthorityCode(Datum.class, code);
1672:                }
1673:                return returnValue;
1674:            }
1675:
1676:            /**
1677:             * Returns the name and description for the specified {@linkplain CoordinateSystemAxis
1678:             * coordinate system axis} code. Many axis share the same name and description, so it
1679:             * is worth to cache them.
1680:             */
1681:            private AxisName getAxisName(final String code)
1682:                    throws FactoryException {
1683:                assert Thread.holdsLock(this );
1684:                AxisName returnValue = (AxisName) axisNames.get(code);
1685:                if (returnValue == null)
1686:                    try {
1687:                        final PreparedStatement stmt;
1688:                        stmt = prepareStatement("AxisName",
1689:                                "SELECT COORD_AXIS_NAME, DESCRIPTION, REMARKS"
1690:                                        + " FROM [Coordinate Axis Name]"
1691:                                        + " WHERE COORD_AXIS_NAME_CODE = ?");
1692:                        stmt.setString(1, code);
1693:                        ResultSet result = stmt.executeQuery();
1694:                        while (result.next()) {
1695:                            final String name = getString(result, 1, code);
1696:                            String description = result.getString(2);
1697:                            String remarks = result.getString(3);
1698:                            if (description == null) {
1699:                                description = remarks;
1700:                            } else if (remarks != null) {
1701:                                description += System.getProperty(
1702:                                        "line.separator", "\n")
1703:                                        + remarks;
1704:                            }
1705:                            final AxisName axis = new AxisName(name,
1706:                                    description);
1707:                            returnValue = (AxisName) ensureSingleton(axis,
1708:                                    returnValue, code);
1709:                        }
1710:                        result.close();
1711:                        if (returnValue == null) {
1712:                            throw noSuchAuthorityCode(AxisName.class, code);
1713:                        }
1714:                        axisNames.put(code, returnValue);
1715:                    } catch (SQLException exception) {
1716:                        throw databaseFailure(AxisName.class, code, exception);
1717:                    }
1718:                return returnValue;
1719:            }
1720:
1721:            /**
1722:             * Returns a {@linkplain CoordinateSystemAxis coordinate system axis} from a code.
1723:             *
1724:             * @param  code Value allocated by authority.
1725:             * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
1726:             * @throws FactoryException if the object creation failed for some other reason.
1727:             */
1728:            public synchronized CoordinateSystemAxis createCoordinateSystemAxis(
1729:                    final String code) throws FactoryException {
1730:                ensureNonNull("code", code);
1731:                CoordinateSystemAxis returnValue = null;
1732:                try {
1733:                    final String primaryKey = trimAuthority(code);
1734:                    final PreparedStatement stmt;
1735:                    stmt = prepareStatement("Axis", "SELECT COORD_AXIS_CODE,"
1736:                            + " COORD_AXIS_NAME_CODE,"
1737:                            + " COORD_AXIS_ORIENTATION,"
1738:                            + " COORD_AXIS_ABBREVIATION," + " UOM_CODE"
1739:                            + " FROM [Coordinate Axis]"
1740:                            + " WHERE COORD_AXIS_CODE = ?");
1741:                    stmt.setString(1, code);
1742:                    ResultSet result = stmt.executeQuery();
1743:                    while (result.next()) {
1744:                        final String epsg = getString(result, 1, code);
1745:                        final String nameCode = getString(result, 2, code);
1746:                        final String orientation = getString(result, 3, code);
1747:                        final String abbreviation = getString(result, 4, code);
1748:                        final String unit = getString(result, 5, code);
1749:                        AxisDirection direction;
1750:                        try {
1751:                            direction = DefaultCoordinateSystemAxis
1752:                                    .getDirection(orientation);
1753:                        } catch (NoSuchElementException exception) {
1754:                            if (orientation
1755:                                    .equalsIgnoreCase("Geocentre > equator/PM")) {
1756:                                direction = AxisDirection.OTHER; // TODO: can we choose a more accurate direction?
1757:                            } else if (orientation
1758:                                    .equalsIgnoreCase("Geocentre > equator/90dE")) {
1759:                                direction = AxisDirection.EAST;
1760:                            } else if (orientation
1761:                                    .equalsIgnoreCase("Geocentre > north pole")) {
1762:                                direction = AxisDirection.NORTH;
1763:                            } else {
1764:                                throw new FactoryException(exception);
1765:                            }
1766:                        }
1767:                        final AxisName an = getAxisName(nameCode);
1768:                        final Map properties = createProperties(an.name, epsg,
1769:                                an.description);
1770:                        final CSFactory factory = factories.getCSFactory();
1771:                        final CoordinateSystemAxis axis = factory
1772:                                .createCoordinateSystemAxis(properties,
1773:                                        abbreviation, direction, buffered
1774:                                                .createUnit(unit));
1775:                        returnValue = (CoordinateSystemAxis) ensureSingleton(
1776:                                axis, returnValue, code);
1777:                    }
1778:                    result.close();
1779:                } catch (SQLException exception) {
1780:                    throw databaseFailure(CoordinateSystemAxis.class, code,
1781:                            exception);
1782:                }
1783:                if (returnValue == null) {
1784:                    throw noSuchAuthorityCode(CoordinateSystemAxis.class, code);
1785:                }
1786:                return returnValue;
1787:            }
1788:
1789:            /**
1790:             * Returns the coordinate system axis from an EPSG code for a {@link CoordinateSystem}.
1791:             * <p>
1792:             * <strong>WARNING:</strong> The EPSG database uses "{@code ORDER}" as a column name.
1793:             * This is tolerated by Access, but MySQL doesn't accept this name.
1794:             *
1795:             * @param  code the EPSG code for coordinate system owner.
1796:             * @param  dimension of the coordinate system, which is also the size of the returned array.
1797:             * @return An array of coordinate system axis.
1798:             * @throws SQLException if an error occured during database access.
1799:             * @throws FactoryException if the code has not been found.
1800:             */
1801:            private CoordinateSystemAxis[] createAxesForCoordinateSystem(
1802:                    final String code, final int dimension)
1803:                    throws SQLException, FactoryException {
1804:                assert Thread.holdsLock(this );
1805:                final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[dimension];
1806:                final PreparedStatement stmt;
1807:                stmt = prepareStatement("AxisOrder", "SELECT COORD_AXIS_CODE"
1808:                        + " FROM [Coordinate Axis]"
1809:                        + " WHERE COORD_SYS_CODE = ?" + " ORDER BY [ORDER]");
1810:                // WARNING: Be careful about the column name :
1811:                //          MySQL rejects ORDER as a column name !!!
1812:                stmt.setString(1, code);
1813:                final ResultSet result = stmt.executeQuery();
1814:                int i = 0;
1815:                while (result.next()) {
1816:                    final String axisCode = getString(result, 1, code);
1817:                    if (i < axis.length) {
1818:                        // If 'i' is out of bounds, an exception will be thrown after the loop.
1819:                        // We don't want to thrown an ArrayIndexOutOfBoundsException here.
1820:                        axis[i] = buffered.createCoordinateSystemAxis(axisCode);
1821:                    }
1822:                    ++i;
1823:                }
1824:                result.close();
1825:                if (i != axis.length) {
1826:                    throw new FactoryException(Errors.format(
1827:                            ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
1828:                                    axis.length), new Integer(i)));
1829:                }
1830:                return axis;
1831:            }
1832:
1833:            /**
1834:             * Returns a coordinate system from a code.
1835:             *
1836:             * @param  code Value allocated by authority.
1837:             * @return The coordinate system object.
1838:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1839:             * @throws FactoryException if some other kind of failure occured in the backing
1840:             *         store. This exception usually have {@link SQLException} as its cause.
1841:             */
1842:            public synchronized CoordinateSystem createCoordinateSystem(
1843:                    final String code) throws FactoryException {
1844:                ensureNonNull("code", code);
1845:                CoordinateSystem returnValue = null;
1846:                final PreparedStatement stmt;
1847:                try {
1848:                    final String primaryKey = toPrimaryKey(
1849:                            CoordinateSystem.class, code,
1850:                            "[Coordinate System]", "COORD_SYS_CODE",
1851:                            "COORD_SYS_NAME");
1852:                    stmt = prepareStatement("CoordinateSystem",
1853:                            "SELECT COORD_SYS_CODE," + " COORD_SYS_NAME,"
1854:                                    + " COORD_SYS_TYPE," + " DIMENSION,"
1855:                                    + " REMARKS" + " FROM [Coordinate System]"
1856:                                    + " WHERE COORD_SYS_CODE = ?");
1857:                    stmt.setString(1, primaryKey);
1858:                    final ResultSet result = stmt.executeQuery();
1859:                    while (result.next()) {
1860:                        final String epsg = getString(result, 1, code);
1861:                        final String name = getString(result, 2, code);
1862:                        final String type = getString(result, 3, code).trim()
1863:                                .toLowerCase();
1864:                        final int dimension = getInt(result, 4, code);
1865:                        final String remarks = result.getString(5);
1866:                        final CoordinateSystemAxis[] axis = createAxesForCoordinateSystem(
1867:                                primaryKey, dimension);
1868:                        final Map properties = createProperties(name, epsg,
1869:                                remarks); // Must be after axis
1870:                        final CSFactory factory = factories.getCSFactory();
1871:                        CoordinateSystem cs = null;
1872:                        if (type.equals("ellipsoidal")) {
1873:                            switch (dimension) {
1874:                            case 2:
1875:                                cs = factory.createEllipsoidalCS(properties,
1876:                                        axis[0], axis[1]);
1877:                                break;
1878:                            case 3:
1879:                                cs = factory.createEllipsoidalCS(properties,
1880:                                        axis[0], axis[1], axis[2]);
1881:                                break;
1882:                            }
1883:                        } else if (type.equals("cartesian")) {
1884:                            switch (dimension) {
1885:                            case 2:
1886:                                cs = factory.createCartesianCS(properties,
1887:                                        axis[0], axis[1]);
1888:                                break;
1889:                            case 3:
1890:                                cs = factory.createCartesianCS(properties,
1891:                                        axis[0], axis[1], axis[2]);
1892:                                break;
1893:                            }
1894:                        } else if (type.equals("spherical")) {
1895:                            switch (dimension) {
1896:                            case 3:
1897:                                cs = factory.createSphericalCS(properties,
1898:                                        axis[0], axis[1], axis[2]);
1899:                                break;
1900:                            }
1901:                        } else if (type.equals("vertical")
1902:                                || type.equals("gravity-related")) {
1903:                            switch (dimension) {
1904:                            case 1:
1905:                                cs = factory.createVerticalCS(properties,
1906:                                        axis[0]);
1907:                                break;
1908:                            }
1909:                        } else if (type.equals("linear")) {
1910:                            switch (dimension) {
1911:                            case 1:
1912:                                cs = factory
1913:                                        .createLinearCS(properties, axis[0]);
1914:                                break;
1915:                            }
1916:                        } else if (type.equals("polar")) {
1917:                            switch (dimension) {
1918:                            case 2:
1919:                                cs = factory.createPolarCS(properties, axis[0],
1920:                                        axis[1]);
1921:                                break;
1922:                            }
1923:                        } else if (type.equals("cylindrical")) {
1924:                            switch (dimension) {
1925:                            case 3:
1926:                                cs = factory.createCylindricalCS(properties,
1927:                                        axis[0], axis[1], axis[2]);
1928:                                break;
1929:                            }
1930:                        } else if (type.equals("affine")) {
1931:                            switch (dimension) {
1932:                            case 2:
1933:                                cs = factory.createAffineCS(properties,
1934:                                        axis[0], axis[1]);
1935:                                break;
1936:                            case 3:
1937:                                cs = factory.createAffineCS(properties,
1938:                                        axis[0], axis[1], axis[2]);
1939:                                break;
1940:                            }
1941:                        } else {
1942:                            result.close();
1943:                            throw new FactoryException(Errors.format(
1944:                                    ErrorKeys.UNKNOW_TYPE_$1, type));
1945:                        }
1946:                        if (cs == null) {
1947:                            result.close();
1948:                            throw new FactoryException(Errors.format(
1949:                                    ErrorKeys.UNEXPECTED_DIMENSION_FOR_CS_$1,
1950:                                    type));
1951:                        }
1952:                        returnValue = (CoordinateSystem) ensureSingleton(cs,
1953:                                returnValue, code);
1954:                    }
1955:                    result.close();
1956:                } catch (SQLException exception) {
1957:                    throw databaseFailure(CoordinateSystem.class, code,
1958:                            exception);
1959:                }
1960:                if (returnValue == null) {
1961:                    throw noSuchAuthorityCode(CoordinateSystem.class, code);
1962:                }
1963:                return returnValue;
1964:
1965:            }
1966:
1967:            /**
1968:             * Returns the primary key for a coordinate reference system name.
1969:             * This method is used both by {@link #createCoordinateReferenceSystem}
1970:             * and {@link #createFromCoordinateReferenceSystemCodes}
1971:             */
1972:            private String toPrimaryKeyCRS(final String code)
1973:                    throws SQLException, FactoryException {
1974:                return toPrimaryKey(CoordinateReferenceSystem.class, code,
1975:                        "[Coordinate Reference System]", "COORD_REF_SYS_CODE",
1976:                        "COORD_REF_SYS_NAME");
1977:            }
1978:
1979:            /**
1980:             * Returns a coordinate reference system from a code.
1981:             *
1982:             * @param  code Value allocated by authority.
1983:             * @return The coordinate reference system object.
1984:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1985:             * @throws FactoryException if some other kind of failure occured in the backing
1986:             *         store. This exception usually have {@link SQLException} as its cause.
1987:             */
1988:            public synchronized CoordinateReferenceSystem createCoordinateReferenceSystem(
1989:                    final String code) throws FactoryException {
1990:                ensureNonNull("code", code);
1991:                CoordinateReferenceSystem returnValue = null;
1992:                try {
1993:                    final String primaryKey = toPrimaryKeyCRS(code);
1994:                    final PreparedStatement stmt;
1995:                    stmt = prepareStatement("CoordinateReferenceSystem",
1996:                            "SELECT COORD_REF_SYS_CODE,"
1997:                                    + " COORD_REF_SYS_NAME,"
1998:                                    + " AREA_OF_USE_CODE," + " CRS_SCOPE,"
1999:                                    + " REMARKS,"
2000:                                    + " COORD_REF_SYS_KIND,"
2001:                                    + " COORD_SYS_CODE," // Null for CompoundCRS
2002:                                    + " DATUM_CODE," // Null for ProjectedCRS
2003:                                    + " SOURCE_GEOGCRS_CODE," // For ProjectedCRS
2004:                                    + " PROJECTION_CONV_CODE," // For ProjectedCRS
2005:                                    + " CMPD_HORIZCRS_CODE," // For CompoundCRS only
2006:                                    + " CMPD_VERTCRS_CODE" // For CompoundCRS only
2007:                                    + " FROM [Coordinate Reference System]"
2008:                                    + " WHERE COORD_REF_SYS_CODE = ?");
2009:                    stmt.setString(1, primaryKey);
2010:                    ResultSet result = stmt.executeQuery();
2011:                    while (result.next()) {
2012:                        final String epsg = getString(result, 1, code);
2013:                        final String name = getString(result, 2, code);
2014:                        final String area = result.getString(3);
2015:                        final String scope = result.getString(4);
2016:                        final String remarks = result.getString(5);
2017:                        final String type = getString(result, 6, code);
2018:                        // Note: Do not invoke 'createProperties' now, even if we have all required
2019:                        //       informations, because the 'properties' map is going to overwritten
2020:                        //       by calls to 'createDatum', 'createCoordinateSystem', etc.
2021:                        final CRSFactory factory = factories.getCRSFactory();
2022:                        final CoordinateReferenceSystem crs;
2023:                        /* ----------------------------------------------------------------------
2024:                         *   GEOGRAPHIC CRS
2025:                         *
2026:                         *   NOTE: 'createProperties' MUST be invoked after any call to an other
2027:                         *         'createFoo' method. Consequently, do not factor out.
2028:                         * ---------------------------------------------------------------------- */
2029:                        if (type.equalsIgnoreCase("geographic 2D")
2030:                                || type.equalsIgnoreCase("geographic 3D")) {
2031:                            final String csCode = getString(result, 7, code);
2032:                            final String dmCode = result.getString(8);
2033:                            final EllipsoidalCS cs = buffered
2034:                                    .createEllipsoidalCS(csCode);
2035:                            final GeodeticDatum datum;
2036:                            if (dmCode != null) {
2037:                                datum = buffered.createGeodeticDatum(dmCode);
2038:                            } else {
2039:                                final String geoCode = getString(result, 9,
2040:                                        code, 8);
2041:                                result.close(); // Must be close before createGeographicCRS
2042:                                result = null;
2043:                                final GeographicCRS baseCRS = buffered
2044:                                        .createGeographicCRS(geoCode);
2045:                                datum = (GeodeticDatum) baseCRS.getDatum(); // TODO: remove cast with J2SE 1.5.
2046:                            }
2047:                            final Map properties = createProperties(name, epsg,
2048:                                    area, scope, remarks);
2049:                            crs = factory.createGeographicCRS(properties,
2050:                                    datum, cs);
2051:                        }
2052:                        /* ----------------------------------------------------------------------
2053:                         *   PROJECTED CRS
2054:                         *
2055:                         *   NOTE: This method invokes itself indirectly, through createGeographicCRS.
2056:                         *         Consequently, we can't use 'result' anymore. We must close it here.
2057:                         * ---------------------------------------------------------------------- */
2058:                        else if (type.equalsIgnoreCase("projected")) {
2059:                            final String csCode = getString(result, 7, code);
2060:                            final String geoCode = getString(result, 9, code);
2061:                            final String opCode = getString(result, 10, code);
2062:                            result.close(); // Must be close before createGeographicCRS
2063:                            result = null;
2064:                            final CartesianCS cs = buffered
2065:                                    .createCartesianCS(csCode);
2066:                            final GeographicCRS baseCRS = buffered
2067:                                    .createGeographicCRS(geoCode);
2068:                            final CoordinateOperation op = buffered
2069:                                    .createCoordinateOperation(opCode);
2070:                            if (op instanceof  Conversion) {
2071:                                final Map properties = createProperties(name,
2072:                                        epsg, area, scope, remarks);
2073:                                crs = factories.createProjectedCRS(properties,
2074:                                        baseCRS, (Conversion) op, cs);
2075:                            } else {
2076:                                throw noSuchAuthorityCode(Projection.class,
2077:                                        opCode);
2078:                            }
2079:                        }
2080:                        /* ----------------------------------------------------------------------
2081:                         *   VERTICAL CRS
2082:                         * ---------------------------------------------------------------------- */
2083:                        else if (type.equalsIgnoreCase("vertical")) {
2084:                            final String csCode = getString(result, 7, code);
2085:                            final String dmCode = getString(result, 8, code);
2086:                            final VerticalCS cs = buffered
2087:                                    .createVerticalCS(csCode);
2088:                            final VerticalDatum datum = buffered
2089:                                    .createVerticalDatum(dmCode);
2090:                            final Map properties = createProperties(name, epsg,
2091:                                    area, scope, remarks);
2092:                            crs = factory.createVerticalCRS(properties, datum,
2093:                                    cs);
2094:                        }
2095:                        /* ----------------------------------------------------------------------
2096:                         *   COMPOUND CRS
2097:                         *
2098:                         *   NOTE: This method invokes itself recursively.
2099:                         *         Consequently, we can't use 'result' anymore.
2100:                         * ---------------------------------------------------------------------- */
2101:                        else if (type.equalsIgnoreCase("compound")) {
2102:                            final String code1 = getString(result, 11, code);
2103:                            final String code2 = getString(result, 12, code);
2104:                            result.close();
2105:                            result = null;
2106:                            final CoordinateReferenceSystem crs1, crs2;
2107:                            if (!safetyGuard.add(epsg)) {
2108:                                throw recursiveCall(CompoundCRS.class, epsg);
2109:                            }
2110:                            try {
2111:                                crs1 = buffered
2112:                                        .createCoordinateReferenceSystem(code1);
2113:                                crs2 = buffered
2114:                                        .createCoordinateReferenceSystem(code2);
2115:                            } finally {
2116:                                safetyGuard.remove(epsg);
2117:                            }
2118:                            // Note: Don't invoke 'createProperties' sooner.
2119:                            final Map properties = createProperties(name, epsg,
2120:                                    area, scope, remarks);
2121:                            crs = factory.createCompoundCRS(properties,
2122:                                    new CoordinateReferenceSystem[] { crs1,
2123:                                            crs2 });
2124:                        }
2125:                        /* ----------------------------------------------------------------------
2126:                         *   GEOCENTRIC CRS
2127:                         * ---------------------------------------------------------------------- */
2128:                        else if (type.equalsIgnoreCase("geocentric")) {
2129:                            final String csCode = getString(result, 7, code);
2130:                            final String dmCode = getString(result, 8, code);
2131:                            final CoordinateSystem cs = buffered
2132:                                    .createCoordinateSystem(csCode);
2133:                            final GeodeticDatum datum = buffered
2134:                                    .createGeodeticDatum(dmCode);
2135:                            final Map properties = createProperties(name, epsg,
2136:                                    area, scope, remarks);
2137:                            if (cs instanceof  CartesianCS) {
2138:                                crs = factory.createGeocentricCRS(properties,
2139:                                        datum, (CartesianCS) cs);
2140:                            } else if (cs instanceof  SphericalCS) {
2141:                                crs = factory.createGeocentricCRS(properties,
2142:                                        datum, (SphericalCS) cs);
2143:                            } else {
2144:                                result.close();
2145:                                throw new FactoryException(
2146:                                        Errors
2147:                                                .format(
2148:                                                        ErrorKeys.ILLEGAL_COORDINATE_SYSTEM_FOR_CRS_$2,
2149:                                                        Utilities
2150:                                                                .getShortClassName(cs),
2151:                                                        Utilities
2152:                                                                .getShortName(GeocentricCRS.class)));
2153:                            }
2154:                        }
2155:                        /* ----------------------------------------------------------------------
2156:                         *   ENGINEERING CRS
2157:                         * ---------------------------------------------------------------------- */
2158:                        else if (type.equalsIgnoreCase("engineering")) {
2159:                            final String csCode = getString(result, 7, code);
2160:                            final String dmCode = getString(result, 8, code);
2161:                            final CoordinateSystem cs = buffered
2162:                                    .createCoordinateSystem(csCode);
2163:                            final EngineeringDatum datum = buffered
2164:                                    .createEngineeringDatum(dmCode);
2165:                            final Map properties = createProperties(name, epsg,
2166:                                    area, scope, remarks);
2167:                            crs = factory.createEngineeringCRS(properties,
2168:                                    datum, cs);
2169:                        }
2170:                        /* ----------------------------------------------------------------------
2171:                         *   UNKNOW CRS
2172:                         * ---------------------------------------------------------------------- */
2173:                        else {
2174:                            result.close();
2175:                            throw new FactoryException(Errors.format(
2176:                                    ErrorKeys.UNKNOW_TYPE_$1, type));
2177:                        }
2178:                        returnValue = (CoordinateReferenceSystem) ensureSingleton(
2179:                                crs, returnValue, code);
2180:                        if (result == null) {
2181:                            // Bypass the 'result.close()' line below:
2182:                            // the ResultSet has already been closed.
2183:                            return returnValue;
2184:                        }
2185:                    }
2186:                    result.close();
2187:                } catch (SQLException exception) {
2188:                    throw databaseFailure(CoordinateReferenceSystem.class,
2189:                            code, exception);
2190:                }
2191:                if (returnValue == null) {
2192:                    throw noSuchAuthorityCode(CoordinateReferenceSystem.class,
2193:                            code);
2194:                }
2195:                return returnValue;
2196:            }
2197:
2198:            /**
2199:             * Returns a parameter descriptor from a code.
2200:             *
2201:             * @param  code The parameter descriptor code allocated by EPSG authority.
2202:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2203:             * @throws FactoryException if some other kind of failure occured in the backing
2204:             *         store. This exception usually have {@link SQLException} as its cause.
2205:             */
2206:            public synchronized ParameterDescriptor createParameterDescriptor(
2207:                    final String code) throws FactoryException {
2208:                ensureNonNull("code", code);
2209:                ParameterDescriptor returnValue = null;
2210:                final PreparedStatement stmt;
2211:                try {
2212:                    final String primaryKey = toPrimaryKey(
2213:                            ParameterDescriptor.class, code,
2214:                            "[Coordinate_Operation Parameter]",
2215:                            "PARAMETER_CODE", "PARAMETER_NAME");
2216:                    stmt = prepareStatement("ParameterDescriptor", // Must be singular form.
2217:                            "SELECT PARAMETER_CODE," + " PARAMETER_NAME,"
2218:                                    + " DESCRIPTION"
2219:                                    + " FROM [Coordinate_Operation Parameter]"
2220:                                    + " WHERE PARAMETER_CODE = ?");
2221:                    stmt.setString(1, primaryKey);
2222:                    ResultSet result = stmt.executeQuery();
2223:                    while (result.next()) {
2224:                        final String epsg = getString(result, 1, code);
2225:                        final String name = getString(result, 2, code);
2226:                        final String remarks = result.getString(3);
2227:                        final Unit unit;
2228:                        final Class type;
2229:                        /*
2230:                         * Search for units. We will choose the most commonly used one in parameter values.
2231:                         * If the parameter appears to have at least one non-null value in the "Parameter
2232:                         * File Name" column, then the type is assumed to be URI. Otherwise, the type is a
2233:                         * floating point number.
2234:                         */
2235:                        final PreparedStatement units = prepareStatement(
2236:                                "ParameterUnit",
2237:                                "SELECT MIN(UOM_CODE) AS UOM,"
2238:                                        + " MIN(PARAM_VALUE_FILE_REF) AS FILEREF"
2239:                                        + " FROM [Coordinate_Operation Parameter Value]"
2240:                                        + " WHERE (PARAMETER_CODE = ?)"
2241:                                        + " GROUP BY UOM_CODE"
2242:                                        + " ORDER BY COUNT(UOM_CODE) DESC");
2243:                        units.setString(1, epsg);
2244:                        final ResultSet resultUnits = units.executeQuery();
2245:                        if (resultUnits.next()) {
2246:                            String element = resultUnits.getString(1);
2247:                            unit = (element != null) ? buffered
2248:                                    .createUnit(element) : null;
2249:                            element = resultUnits.getString(2);
2250:                            type = (element != null && element.trim().length() != 0) ? URI.class
2251:                                    : double.class;
2252:                        } else {
2253:                            unit = null;
2254:                            type = double.class;
2255:                        }
2256:                        resultUnits.close();
2257:                        /*
2258:                         * Now creates the parameter descriptor.
2259:                         */
2260:                        final ParameterDescriptor descriptor;
2261:                        final Map properties = createProperties(name, epsg,
2262:                                remarks);
2263:                        descriptor = new DefaultParameterDescriptor(properties,
2264:                                type, null, null, null, null, unit, true);
2265:                        returnValue = (ParameterDescriptor) ensureSingleton(
2266:                                descriptor, returnValue, code);
2267:                    }
2268:                } catch (SQLException exception) {
2269:                    throw databaseFailure(OperationMethod.class, code,
2270:                            exception);
2271:                }
2272:                if (returnValue == null) {
2273:                    throw noSuchAuthorityCode(OperationMethod.class, code);
2274:                }
2275:                return returnValue;
2276:            }
2277:
2278:            /**
2279:             * Returns all parameter descriptors for the specified method.
2280:             *
2281:             * @param  method The operation method code.
2282:             * @return The parameter descriptors.
2283:             * @throws SQLException if a SQL statement failed.
2284:             */
2285:            private ParameterDescriptor[] createParameterDescriptors(
2286:                    final String method) throws FactoryException, SQLException {
2287:                final PreparedStatement stmt;
2288:                stmt = prepareStatement(
2289:                        "ParameterDescriptors", // Must be plural form.
2290:                        "SELECT PARAMETER_CODE"
2291:                                + " FROM [Coordinate_Operation Parameter Usage]"
2292:                                + " WHERE COORD_OP_METHOD_CODE = ?"
2293:                                + " ORDER BY SORT_ORDER");
2294:                stmt.setString(1, method);
2295:                final ResultSet results = stmt.executeQuery();
2296:                final List descriptors = new ArrayList();
2297:                while (results.next()) {
2298:                    final String param = getString(results, 1, method);
2299:                    descriptors.add(buffered.createParameterDescriptor(param));
2300:                }
2301:                results.close();
2302:                return (ParameterDescriptor[]) descriptors
2303:                        .toArray(new ParameterDescriptor[descriptors.size()]);
2304:            }
2305:
2306:            /**
2307:             * Fill parameter values in the specified group.
2308:             *
2309:             * @param  method    The EPSG code for the operation method.
2310:             * @param  operation The EPSG code for the operation (conversion or transformation).
2311:             * @param  value     The parameter values to fill.
2312:             * @throws SQLException if a SQL statement failed.
2313:             */
2314:            private void fillParameterValues(final String method,
2315:                    final String operation, final ParameterValueGroup parameters)
2316:                    throws FactoryException, SQLException {
2317:                final PreparedStatement stmt;
2318:                stmt = prepareStatement(
2319:                        "ParameterValues",
2320:                        "SELECT CP.PARAMETER_NAME,"
2321:                                + " CV.PARAMETER_VALUE,"
2322:                                + " CV.PARAM_VALUE_FILE_REF,"
2323:                                + " CV.UOM_CODE"
2324:                                + " FROM ([Coordinate_Operation Parameter Value] AS CV"
2325:                                + " INNER JOIN [Coordinate_Operation Parameter] AS CP"
2326:                                + " ON CV.PARAMETER_CODE = CP.PARAMETER_CODE)"
2327:                                + " INNER JOIN [Coordinate_Operation Parameter Usage] AS CU"
2328:                                + " ON (CP.PARAMETER_CODE = CU.PARAMETER_CODE)"
2329:                                + " AND (CV.COORD_OP_METHOD_CODE = CU.COORD_OP_METHOD_CODE)"
2330:                                + " WHERE CV.COORD_OP_METHOD_CODE = ?"
2331:                                + " AND CV.COORD_OP_CODE = ?"
2332:                                + " ORDER BY CU.SORT_ORDER");
2333:                stmt.setString(1, method);
2334:                stmt.setString(2, operation);
2335:                final ResultSet result = stmt.executeQuery();
2336:                while (result.next()) {
2337:                    final String name = getString(result, 1, operation);
2338:                    final double value = result.getDouble(2);
2339:                    final Unit unit;
2340:                    Object reference;
2341:                    if (result.wasNull()) {
2342:                        /*
2343:                         * If no numeric values were provided in the database, then the values must
2344:                         * appears in some external file. It may be a file to download from FTP.
2345:                         */
2346:                        reference = getString(result, 3, operation);
2347:                        try {
2348:                            reference = new URI((String) reference);
2349:                        } catch (URISyntaxException exception) {
2350:                            // Ignore: we will stores the reference as a file.
2351:                            reference = new File((String) reference);
2352:                        }
2353:                        unit = null;
2354:                    } else {
2355:                        reference = null;
2356:                        final String unitCode = result.getString(4);
2357:                        unit = (unitCode != null) ? buffered
2358:                                .createUnit(unitCode) : null;
2359:                    }
2360:                    final ParameterValue param;
2361:                    try {
2362:                        param = parameters.parameter(name);
2363:                    } catch (ParameterNotFoundException exception) {
2364:                        /*
2365:                         * Wraps the unchecked ParameterNotFoundException into the checked
2366:                         * NoSuchIdentifierException, which is a FactoryException subclass.
2367:                         * Note that in theory, NoSuchIdentifierException is for MathTransforms rather
2368:                         * than parameters.  However, we are close in spirit here since we are setting
2369:                         * up MathTransform's parameters. Using NoSuchIdentifierException allows users
2370:                         * (including CoordinateOperationSet) to know that the failure is probably
2371:                         * caused by a MathTransform not yet supported in Geotools (or only partially
2372:                         * supported) rather than some more serious failure in the database side.
2373:                         * CoordinateOperationSet uses this information in order to determine if it
2374:                         * should try the next coordinate operation or propagate the exception.
2375:                         */
2376:                        final NoSuchIdentifierException e = new NoSuchIdentifierException(
2377:                                Errors.format(
2378:                                        ErrorKeys.CANT_SET_PARAMETER_VALUE_$1,
2379:                                        name), name);
2380:                        e.initCause(exception);
2381:                        throw e;
2382:                    }
2383:                    try {
2384:                        if (reference != null) {
2385:                            param.setValue(reference);
2386:                        } else if (unit != null) {
2387:                            param.setValue(value, unit);
2388:                        } else {
2389:                            param.setValue(value);
2390:                        }
2391:                    } catch (InvalidParameterValueException exception) {
2392:                        throw new FactoryException(Errors.format(
2393:                                ErrorKeys.CANT_SET_PARAMETER_VALUE_$1, name),
2394:                                exception);
2395:                    }
2396:                }
2397:                result.close();
2398:            }
2399:
2400:            /**
2401:             * Returns an operation method from a code.
2402:             *
2403:             * @param  code The operation method code allocated by EPSG authority.
2404:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2405:             * @throws FactoryException if some other kind of failure occured in the backing
2406:             *         store. This exception usually have {@link SQLException} as its cause.
2407:             */
2408:            public synchronized OperationMethod createOperationMethod(
2409:                    final String code) throws FactoryException {
2410:                ensureNonNull("code", code);
2411:                OperationMethod returnValue = null;
2412:                final PreparedStatement stmt;
2413:                try {
2414:                    final String primaryKey = toPrimaryKey(
2415:                            OperationMethod.class, code,
2416:                            "[Coordinate_Operation Method]",
2417:                            "COORD_OP_METHOD_CODE", "COORD_OP_METHOD_NAME");
2418:                    stmt = prepareStatement("OperationMethod",
2419:                            "SELECT COORD_OP_METHOD_CODE,"
2420:                                    + " COORD_OP_METHOD_NAME," + " FORMULA,"
2421:                                    + " REMARKS"
2422:                                    + " FROM [Coordinate_Operation Method]"
2423:                                    + " WHERE COORD_OP_METHOD_CODE = ?");
2424:                    stmt.setString(1, primaryKey);
2425:                    final ResultSet result = stmt.executeQuery();
2426:                    OperationMethod method = null;
2427:                    while (result.next()) {
2428:                        final String epsg = getString(result, 1, code);
2429:                        final String name = getString(result, 2, code);
2430:                        final String formula = result.getString(3);
2431:                        final String remarks = result.getString(4);
2432:                        final int encoded = getDimensionsForMethod(epsg);
2433:                        final int sourceDimensions = encoded >>> 16;
2434:                        final int targetDimensions = encoded & 0xFFFF;
2435:                        final ParameterDescriptor[] descriptors = createParameterDescriptors(epsg);
2436:                        final Map properties = createProperties(name, epsg,
2437:                                remarks);
2438:                        if (formula != null) {
2439:                            properties
2440:                                    .put(OperationMethod.FORMULA_KEY, formula);
2441:                        }
2442:                        method = new DefaultOperationMethod(properties,
2443:                                sourceDimensions, targetDimensions,
2444:                                new DefaultParameterDescriptorGroup(properties,
2445:                                        descriptors));
2446:                        returnValue = (OperationMethod) ensureSingleton(method,
2447:                                returnValue, code);
2448:                    }
2449:                } catch (SQLException exception) {
2450:                    throw databaseFailure(OperationMethod.class, code,
2451:                            exception);
2452:                }
2453:                if (returnValue == null) {
2454:                    throw noSuchAuthorityCode(OperationMethod.class, code);
2455:                }
2456:                return returnValue;
2457:            }
2458:
2459:            /**
2460:             * Returns the must common source and target dimensions for the specified method.
2461:             * Source dimension is encoded in the 16 highest bits and target dimension is encoded
2462:             * in the 16 lowest bits. If this method can't infers the dimensions from the "Coordinate
2463:             * Operation" table, then the operation method is probably a projection, which always have
2464:             * (2,2) dimensions in the EPSG database.
2465:             */
2466:            private int getDimensionsForMethod(final String code)
2467:                    throws SQLException {
2468:                final PreparedStatement stmt;
2469:                stmt = prepareStatement("MethodDimensions",
2470:                        "SELECT SOURCE_CRS_CODE," + " TARGET_CRS_CODE"
2471:                                + " FROM [Coordinate_Operation]"
2472:                                + " WHERE COORD_OP_METHOD_CODE = ?"
2473:                                + " AND SOURCE_CRS_CODE IS NOT NULL"
2474:                                + " AND TARGET_CRS_CODE IS NOT NULL");
2475:                stmt.setString(1, code);
2476:                final ResultSet result = stmt.executeQuery();
2477:                final Map dimensions = new HashMap();
2478:                final Dimensions temp = new Dimensions((2 << 16) | 2); // Default to (2,2) dimensions.
2479:                Dimensions max = temp;
2480:                while (result.next()) {
2481:                    final short sourceDimensions = getDimensionForCRS(result
2482:                            .getString(1));
2483:                    final short targetDimensions = getDimensionForCRS(result
2484:                            .getString(2));
2485:                    temp.encoded = (sourceDimensions << 16)
2486:                            | (targetDimensions);
2487:                    Dimensions candidate = (Dimensions) dimensions.get(temp);
2488:                    if (candidate == null) {
2489:                        candidate = new Dimensions(temp.encoded);
2490:                        dimensions.put(candidate, candidate);
2491:                    }
2492:                    if (++candidate.occurences > max.occurences) {
2493:                        max = candidate;
2494:                    }
2495:                }
2496:                result.close();
2497:                return max.encoded;
2498:            }
2499:
2500:            /** A counter for source and target dimensions (to be kept together). */
2501:            private static final class Dimensions {
2502:                /** The dimensions as an encoded value. */
2503:                int encoded;
2504:                /** The occurences of this dimensions.  */
2505:                int occurences;
2506:
2507:                Dimensions(final int e) {
2508:                    encoded = e;
2509:                }
2510:
2511:                public int hashCode() {
2512:                    return encoded;
2513:                }
2514:
2515:                public boolean equals(final Object object) { // MUST ignore 'occurences'.
2516:                    return (object instanceof  Dimensions)
2517:                            && ((Dimensions) object).encoded == encoded;
2518:                }
2519:
2520:                public String toString() {
2521:                    return "[(" + (encoded >>> 16) + ',' + (encoded & 0xFFFF)
2522:                            + ")\u00D7" + occurences + ']';
2523:                }
2524:            }
2525:
2526:            /**
2527:             * Returns the dimension of the specified CRS. If the CRS is not found (which should not
2528:             * happen, but we don't need to be strict here), then this method assumes a two-dimensional
2529:             * CRS.
2530:             */
2531:            private short getDimensionForCRS(final String code)
2532:                    throws SQLException {
2533:                final PreparedStatement stmt;
2534:                final Short cached = (Short) axisCounts.get(code);
2535:                final short dimension;
2536:                if (cached == null) {
2537:                    stmt = prepareStatement(
2538:                            "Dimension",
2539:                            "  SELECT COUNT(COORD_AXIS_CODE)"
2540:                                    + " FROM [Coordinate Axis]"
2541:                                    + " WHERE COORD_SYS_CODE = (SELECT COORD_SYS_CODE "
2542:                                    + " FROM [Coordinate Reference System]"
2543:                                    + " WHERE COORD_REF_SYS_CODE = ?)");
2544:                    stmt.setString(1, code);
2545:                    final ResultSet result = stmt.executeQuery();
2546:                    dimension = result.next() ? result.getShort(1) : 2;
2547:                    axisCounts.put(code, new Short(dimension));
2548:                    result.close();
2549:                } else {
2550:                    dimension = cached.shortValue();
2551:                }
2552:                return dimension;
2553:            }
2554:
2555:            /**
2556:             * Returns {@code true} if the {@linkplain CoordinateOperation coordinate operation} for the
2557:             * specified code is a {@linkplain Projection projection}. The caller must have ensured that
2558:             * the designed operation is a {@linkplain Conversion conversion} before to invoke this method.
2559:             */
2560:            final boolean isProjection(final String code) throws SQLException {
2561:                final PreparedStatement stmt;
2562:                Boolean projection = (Boolean) codeProjection.get(code);
2563:                if (projection == null) {
2564:                    stmt = prepareStatement(
2565:                            "isProjection",
2566:                            "SELECT COORD_REF_SYS_CODE"
2567:                                    + " FROM [Coordinate Reference System]"
2568:                                    + " WHERE PROJECTION_CONV_CODE = ?"
2569:                                    + " AND COORD_REF_SYS_KIND LIKE 'projected%'");
2570:                    stmt.setString(1, code);
2571:                    final ResultSet result = stmt.executeQuery();
2572:                    final boolean found = result.next();
2573:                    result.close();
2574:                    projection = Boolean.valueOf(found);
2575:                    codeProjection.put(code, projection);
2576:                }
2577:                return projection.booleanValue();
2578:            }
2579:
2580:            /**
2581:             * Returns a coordinate operation from a code.
2582:             * The returned object will either be a {@linkplain Conversion conversion} or a
2583:             * {@linkplain Transformation transformation}, depending on the code.
2584:             *
2585:             * @param  code Value allocated by authority.
2586:             * @return The coordinate operation object.
2587:             * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2588:             * @throws FactoryException if some other kind of failure occured in the backing
2589:             *         store. This exception usually have {@link SQLException} as its cause.
2590:             */
2591:            public synchronized CoordinateOperation createCoordinateOperation(
2592:                    final String code) throws FactoryException {
2593:                ensureNonNull("code", code);
2594:                CoordinateOperation returnValue = null;
2595:                try {
2596:                    final String primaryKey = toPrimaryKey(
2597:                            CoordinateOperation.class, code,
2598:                            "[Coordinate_Operation]", "COORD_OP_CODE",
2599:                            "COORD_OP_NAME");
2600:                    final PreparedStatement stmt;
2601:                    stmt = prepareStatement("CoordinateOperation",
2602:                            "SELECT COORD_OP_CODE," + " COORD_OP_NAME,"
2603:                                    + " COORD_OP_TYPE," + " SOURCE_CRS_CODE,"
2604:                                    + " TARGET_CRS_CODE,"
2605:                                    + " COORD_OP_METHOD_CODE,"
2606:                                    + " COORD_TFM_VERSION,"
2607:                                    + " COORD_OP_ACCURACY,"
2608:                                    + " AREA_OF_USE_CODE," + " COORD_OP_SCOPE,"
2609:                                    + " REMARKS"
2610:                                    + " FROM [Coordinate_Operation]"
2611:                                    + " WHERE COORD_OP_CODE = ?");
2612:                    stmt.setString(1, primaryKey);
2613:                    ResultSet result = stmt.executeQuery();
2614:                    while (result.next()) {
2615:                        final String epsg = getString(result, 1, code);
2616:                        final String name = getString(result, 2, code);
2617:                        final String type = getString(result, 3, code).trim()
2618:                                .toLowerCase();
2619:                        final boolean isTransformation = type
2620:                                .equals("transformation");
2621:                        final boolean isConversion = type.equals("conversion");
2622:                        final boolean isConcatenated = type
2623:                                .equals("concatenated operation");
2624:                        final String sourceCode, targetCode, methodCode;
2625:                        if (isConversion) {
2626:                            // Optional for conversions, mandatory for all others.
2627:                            sourceCode = result.getString(4);
2628:                            targetCode = result.getString(5);
2629:                        } else {
2630:                            sourceCode = getString(result, 4, code);
2631:                            targetCode = getString(result, 5, code);
2632:                        }
2633:                        if (isConcatenated) {
2634:                            // Not applicable to concatenated operation, mandatory for all others.
2635:                            methodCode = result.getString(6);
2636:                        } else {
2637:                            methodCode = getString(result, 6, code);
2638:                        }
2639:                        String version = result.getString(7);
2640:                        double accuracy = result.getDouble(8);
2641:                        if (result.wasNull())
2642:                            accuracy = Double.NaN;
2643:                        String area = result.getString(9);
2644:                        String scope = result.getString(10);
2645:                        String remarks = result.getString(11);
2646:                        /*
2647:                         * Gets the source and target CRS. They are mandatory for transformations (it
2648:                         * was checked above in this method) and optional for conversions. Conversions
2649:                         * are usually "defining conversions" and don't define source and target CRS.
2650:                         * In EPSG database 6.7, all defining conversions are projections and their
2651:                         * dimensions are always 2. However, this is not generalizable to other kind
2652:                         * of operation methods. For example the "Geocentric translation" operation
2653:                         * method has 3-dimensional source and target.
2654:                         */
2655:                        final int sourceDimensions, targetDimensions;
2656:                        final CoordinateReferenceSystem sourceCRS, targetCRS;
2657:                        if (sourceCode != null) {
2658:                            sourceCRS = buffered
2659:                                    .createCoordinateReferenceSystem(sourceCode);
2660:                            sourceDimensions = sourceCRS.getCoordinateSystem()
2661:                                    .getDimension();
2662:                        } else {
2663:                            sourceCRS = null;
2664:                            sourceDimensions = 2; // Acceptable default for projections only.
2665:                        }
2666:                        if (targetCode != null) {
2667:                            targetCRS = buffered
2668:                                    .createCoordinateReferenceSystem(targetCode);
2669:                            targetDimensions = targetCRS.getCoordinateSystem()
2670:                                    .getDimension();
2671:                        } else {
2672:                            targetCRS = null;
2673:                            targetDimensions = 2; // Acceptable default for projections only.
2674:                        }
2675:                        /*
2676:                         * Gets the operation method. This is mandatory for conversions and transformations
2677:                         * (it was checked above in this method) but optional for concatenated operations.
2678:                         * Fetching parameter values is part of this block.
2679:                         */
2680:                        final boolean isBursaWolf;
2681:                        OperationMethod method;
2682:                        final ParameterValueGroup parameters;
2683:                        if (methodCode == null) {
2684:                            isBursaWolf = false;
2685:                            method = null;
2686:                            parameters = null;
2687:                        } else {
2688:                            final int num;
2689:                            try {
2690:                                num = Integer.parseInt(methodCode);
2691:                            } catch (NumberFormatException exception) {
2692:                                result.close();
2693:                                throw new FactoryException(exception);
2694:                            }
2695:                            isBursaWolf = (num >= BURSA_WOLF_MIN_CODE && num <= BURSA_WOLF_MAX_CODE);
2696:                            // Reminder: The source and target dimensions MUST be computed when
2697:                            //           the information is available. Dimension is not always 2!!
2698:                            method = buffered.createOperationMethod(methodCode);
2699:                            if (method.getSourceDimensions() != sourceDimensions
2700:                                    || method.getTargetDimensions() != targetDimensions) {
2701:                                method = new DefaultOperationMethod(method,
2702:                                        sourceDimensions, targetDimensions);
2703:                            }
2704:                            /*
2705:                             * Note that some parameters required for MathTransform creation are implicit in
2706:                             * the EPSG database (e.g. semi-major and semi-minor axis length in the case of
2707:                             * map projections). We ask the parameter value group straight from the math
2708:                             * transform factory instead of from the operation method in order to get all
2709:                             * required parameter descriptors, including implicit ones.
2710:                             */
2711:                            final String classe = method.getName().getCode();
2712:                            parameters = factories.getMathTransformFactory()
2713:                                    .getDefaultParameters(classe);
2714:                            fillParameterValues(methodCode, epsg, parameters);
2715:                        }
2716:                        /*
2717:                         * Creates common properties. The 'version' and 'accuracy' are usually defined
2718:                         * for transformations only. However, we check them for all kind of operations
2719:                         * (including conversions) and copy the information inconditionnaly if present.
2720:                         *
2721:                         * NOTE: This block must be executed last before object creations below, because
2722:                         *       methods like createCoordinateReferenceSystem and createOperationMethod
2723:                         *       overwrite the properties map.
2724:                         */
2725:                        final Map properties = createProperties(name, epsg,
2726:                                area, scope, remarks);
2727:                        if (version != null
2728:                                && (version = version.trim()).length() != 0) {
2729:                            properties.put(
2730:                                    CoordinateOperation.OPERATION_VERSION_KEY,
2731:                                    version);
2732:                        }
2733:                        if (!Double.isNaN(accuracy)) {
2734:                            final QuantitativeResultImpl accuracyResult;
2735:                            final AbsoluteExternalPositionalAccuracyImpl accuracyElement;
2736:                            accuracyResult = new QuantitativeResultImpl(
2737:                                    new double[] { accuracy });
2738:                            // TODO: Need to invoke something equivalent to:
2739:                            // accuracyResult.setValueType(Float.class);
2740:                            // This is the type declared in the MS-Access database.
2741:                            accuracyResult.setValueUnit(SI.METER); // In meters by definition in the EPSG database.
2742:                            accuracyElement = new AbsoluteExternalPositionalAccuracyImpl(
2743:                                    accuracyResult);
2744:                            accuracyElement
2745:                                    .setMeasureDescription(TRANSFORMATION_ACCURACY);
2746:                            accuracyElement
2747:                                    .setEvaluationMethodType(EvaluationMethodType.DIRECT_EXTERNAL);
2748:                            properties
2749:                                    .put(
2750:                                            CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
2751:                                            new PositionalAccuracy[] { (PositionalAccuracy) accuracyElement
2752:                                                    .unmodifiable() });
2753:                        }
2754:                        /*
2755:                         * Creates the operation. Conversions should be the only operations allowed to
2756:                         * have null source and target CRS. In such case, the operation is a defining
2757:                         * conversion (usually to be used later as part of a ProjectedCRS creation),
2758:                         * and always a projection in the specific case of the EPSG database (which
2759:                         * allowed us to assume 2-dimensional operation method in the code above for
2760:                         * this specific case - not to be generalized to the whole EPSG database).
2761:                         */
2762:                        final CoordinateOperation operation;
2763:                        if (isConversion
2764:                                && (sourceCRS == null || targetCRS == null)) {
2765:                            // Note: we usually can't resolve sourceCRS and targetCRS because there
2766:                            // is many of them for the same coordinate operation (projection) code.
2767:                            operation = new DefiningConversion(properties,
2768:                                    method, parameters);
2769:                        } else if (isConcatenated) {
2770:                            /*
2771:                             * Concatenated operation: we need to close the current result set, because
2772:                             * we are going to invoke this method recursively in the following lines.
2773:                             *
2774:                             * Note: we instantiate directly the Geotools's implementation of
2775:                             * ConcatenatedOperation instead of using CoordinateOperationFactory in order
2776:                             * to avoid loading the quite large Geotools's implementation of this factory,
2777:                             * and also because it is not part of FactoryGroup anyway.
2778:                             */
2779:                            result.close();
2780:                            result = null;
2781:                            final PreparedStatement cstmt = prepareStatement(
2782:                                    "ConcatenatedOperation",
2783:                                    "SELECT SINGLE_OPERATION_CODE"
2784:                                            + " FROM [Coordinate_Operation Path]"
2785:                                            + " WHERE (CONCAT_OPERATION_CODE = ?)"
2786:                                            + " ORDER BY OP_PATH_STEP");
2787:                            cstmt.setString(1, epsg);
2788:                            final ResultSet cr = cstmt.executeQuery();
2789:                            final List codes = new ArrayList();
2790:                            while (cr.next()) {
2791:                                codes.add(cr.getString(1));
2792:                            }
2793:                            cr.close();
2794:                            final CoordinateOperation[] operations = new CoordinateOperation[codes
2795:                                    .size()];
2796:                            if (!safetyGuard.add(epsg)) {
2797:                                throw recursiveCall(
2798:                                        ConcatenatedOperation.class, epsg);
2799:                            }
2800:                            try {
2801:                                for (int i = 0; i < operations.length; i++) {
2802:                                    operations[i] = buffered
2803:                                            .createCoordinateOperation((String) codes
2804:                                                    .get(i));
2805:                                }
2806:                            } finally {
2807:                                safetyGuard.remove(epsg);
2808:                            }
2809:                            try {
2810:                                return new DefaultConcatenatedOperation(
2811:                                        properties, operations);
2812:                            } catch (IllegalArgumentException exception) {
2813:                                // May happen if there is less than 2 operations to concatenate.
2814:                                // It happen for some deprecated CRS like 8658 for example.
2815:                                throw new FactoryException(exception);
2816:                            }
2817:                        } else {
2818:                            /*
2819:                             * Needs to create a math transform. A special processing is performed for
2820:                             * datum shift methods, since the conversion from ellipsoid to geocentric
2821:                             * for "geocentric translations" is implicit in the EPSG database. Even in
2822:                             * the case of Molodenski transforms, the axis length to set are the same.
2823:                             */
2824:                            if (isBursaWolf)
2825:                                try {
2826:                                    Ellipsoid ellipsoid = CRSUtilities
2827:                                            .getHeadGeoEllipsoid(sourceCRS);
2828:                                    if (ellipsoid != null) {
2829:                                        final Unit axisUnit = ellipsoid
2830:                                                .getAxisUnit();
2831:                                        parameters
2832:                                                .parameter("src_semi_major")
2833:                                                .setValue(
2834:                                                        ellipsoid
2835:                                                                .getSemiMajorAxis(),
2836:                                                        axisUnit);
2837:                                        parameters
2838:                                                .parameter("src_semi_minor")
2839:                                                .setValue(
2840:                                                        ellipsoid
2841:                                                                .getSemiMinorAxis(),
2842:                                                        axisUnit);
2843:                                        parameters
2844:                                                .parameter("src_dim")
2845:                                                .setValue(
2846:                                                        sourceCRS
2847:                                                                .getCoordinateSystem()
2848:                                                                .getDimension());
2849:                                    }
2850:                                    ellipsoid = CRSUtilities
2851:                                            .getHeadGeoEllipsoid(targetCRS);
2852:                                    if (ellipsoid != null) {
2853:                                        final Unit axisUnit = ellipsoid
2854:                                                .getAxisUnit();
2855:                                        parameters
2856:                                                .parameter("tgt_semi_major")
2857:                                                .setValue(
2858:                                                        ellipsoid
2859:                                                                .getSemiMajorAxis(),
2860:                                                        axisUnit);
2861:                                        parameters
2862:                                                .parameter("tgt_semi_minor")
2863:                                                .setValue(
2864:                                                        ellipsoid
2865:                                                                .getSemiMinorAxis(),
2866:                                                        axisUnit);
2867:                                        parameters
2868:                                                .parameter("tgt_dim")
2869:                                                .setValue(
2870:                                                        targetCRS
2871:                                                                .getCoordinateSystem()
2872:                                                                .getDimension());
2873:                                    }
2874:                                } catch (ParameterNotFoundException exception) {
2875:                                    result.close();
2876:                                    throw new FactoryException(
2877:                                            Errors
2878:                                                    .format(
2879:                                                            ErrorKeys.GEOTOOLS_EXTENSION_REQUIRED_$1,
2880:                                                            method.getName()
2881:                                                                    .getCode(),
2882:                                                            exception));
2883:                                }
2884:                            /*
2885:                             * At this stage, the parameters are ready for use. Creates the math transform
2886:                             * and wraps it in the final operation (a Conversion or a Transformation).
2887:                             */
2888:                            final Class expected;
2889:                            if (isTransformation) {
2890:                                expected = Transformation.class;
2891:                            } else if (isConversion) {
2892:                                expected = Conversion.class;
2893:                            } else {
2894:                                result.close();
2895:                                throw new FactoryException(Errors.format(
2896:                                        ErrorKeys.UNKNOW_TYPE_$1, type));
2897:                            }
2898:                            final MathTransform mt = factories
2899:                                    .createBaseToDerived(sourceCRS, parameters,
2900:                                            targetCRS.getCoordinateSystem());
2901:                            // TODO: uses GeoAPI factory method once available.
2902:                            operation = DefaultOperation.create(properties,
2903:                                    sourceCRS, targetCRS, mt, method, expected);
2904:                        }
2905:                        returnValue = (CoordinateOperation) ensureSingleton(
2906:                                operation, returnValue, code);
2907:                        if (result == null) {
2908:                            // Bypass the 'result.close()' line below:
2909:                            // the ResultSet has already been closed.
2910:                            return returnValue;
2911:                        }
2912:                    }
2913:                    result.close();
2914:                } catch (SQLException exception) {
2915:                    throw databaseFailure(CoordinateOperation.class, code,
2916:                            exception);
2917:                }
2918:                if (returnValue == null) {
2919:                    throw noSuchAuthorityCode(CoordinateOperation.class, code);
2920:                }
2921:                return returnValue;
2922:            }
2923:
2924:            /**
2925:             * Creates operations from coordinate reference system codes.
2926:             * The returned set is ordered with the most accurate operations first.
2927:             *
2928:             * @param sourceCode Coded value of source coordinate reference system.
2929:             * @param targetCode Coded value of target coordinate reference system.
2930:             * @throws FactoryException if the object creation failed.
2931:             *
2932:             * @todo The ordering is not consistent among all database software, because the "accuracy"
2933:             *       column may contains null values. When used in an "ORDER BY" clause, PostgreSQL put
2934:             *       null values last, while Access and HSQL put them first. The PostgreSQL's behavior is
2935:             *       better for what we want (put operations with unknow accuracy last). Unfortunatly,
2936:             *       I don't know yet how to instruct Access to put null values last using standard SQL
2937:             *       ("IIF" is not standard, and Access doesn't seem to understand "CASE ... THEN" clauses).
2938:             */
2939:            public synchronized Set createFromCoordinateReferenceSystemCodes(
2940:                    final String sourceCode, final String targetCode)
2941:                    throws FactoryException {
2942:                ensureNonNull("sourceCode", sourceCode);
2943:                ensureNonNull("targetCode", targetCode);
2944:                final String pair = sourceCode + " \u21E8 " + targetCode;
2945:                final CoordinateOperationSet set = new CoordinateOperationSet(
2946:                        buffered);
2947:                try {
2948:                    final String sourceKey = toPrimaryKeyCRS(sourceCode);
2949:                    final String targetKey = toPrimaryKeyCRS(targetCode);
2950:                    boolean searchTransformations = false;
2951:                    do {
2952:                        /*
2953:                         * This 'do' loop is executed twice: the first time for searching defining
2954:                         * conversions, and the second time for searching all other kind of operations.
2955:                         * Defining conversions are searched first because they are, by definition, the
2956:                         * most accurate operations.
2957:                         */
2958:                        final String key, sql;
2959:                        if (searchTransformations) {
2960:                            key = "TransformationFromCRS";
2961:                            sql = "SELECT COORD_OP_CODE"
2962:                                    + " FROM [Coordinate_Operation]"
2963:                                    + " WHERE SOURCE_CRS_CODE = ?"
2964:                                    + " AND TARGET_CRS_CODE = ?"
2965:                                    + " ORDER BY ABS(DEPRECATED), COORD_OP_ACCURACY";
2966:                        } else {
2967:                            key = "ConversionFromCRS";
2968:                            sql = "SELECT PROJECTION_CONV_CODE"
2969:                                    + " FROM [Coordinate Reference System]"
2970:                                    + " WHERE SOURCE_GEOGCRS_CODE = ?"
2971:                                    + " AND COORD_REF_SYS_CODE = ?";
2972:                        }
2973:                        final PreparedStatement stmt = prepareStatement(key,
2974:                                sql);
2975:                        stmt.setString(1, sourceKey);
2976:                        stmt.setString(2, targetKey);
2977:                        final ResultSet result = stmt.executeQuery();
2978:                        while (result.next()) {
2979:                            final String code = getString(result, 1, pair);
2980:                            set.addAuthorityCode(code,
2981:                                    searchTransformations ? null : targetKey);
2982:                        }
2983:                        result.close();
2984:                    } while ((searchTransformations = !searchTransformations) == true);
2985:                    /*
2986:                     * Search finished. We may have a lot of coordinate operations
2987:                     * (e.g. about 40 for "ED50" (EPSG:4230) to "WGS 84" (EPSG:4326)).
2988:                     * Alter the ordering using the information supplied in the supersession table.
2989:                     */
2990:                    final String[] codes = set.getAuthorityCodes();
2991:                    sort(codes);
2992:                    set.setAuthorityCodes(codes);
2993:                } catch (SQLException exception) {
2994:                    throw databaseFailure(CoordinateOperation.class, pair,
2995:                            exception);
2996:                }
2997:                /*
2998:                 * Before to return the set, tests the creation of 1 object in order to report early
2999:                 * (i.e. now) any problems with SQL statements. Remaining operations will be created
3000:                 * only when first needed.
3001:                 */
3002:                set.resolve(1);
3003:                return set;
3004:            }
3005:
3006:            /**
3007:             * Sorts an array of codes in preference order. This method orders pairwise the codes according
3008:             * the information provided in the supersession table. If the same object is superseded by more
3009:             * than one object, then the most recent one is inserted first. Except for the codes moved as a
3010:             * result of pairwise ordering, this method try to preserve the old ordering of the supplied
3011:             * codes (since deprecated operations should already be last). The ordering is performed in
3012:             * place.
3013:             *
3014:             * @param codes The codes, usually as an array of {@link String}. If the array do not contains
3015:             *              string objects, then the {@link Object#toString} method must returns the code
3016:             *              for each element.
3017:             */
3018:            // TODO: Use generic type for "Object[] codes" with J2SE 1.5.
3019:            private void sort(final Object[] codes) throws SQLException,
3020:                    FactoryException {
3021:                if (codes.length <= 1) {
3022:                    return; // Nothing to sort.
3023:                }
3024:                final PreparedStatement stmt;
3025:                stmt = prepareStatement("Supersession", "SELECT SUPERSEDED_BY"
3026:                        + " FROM [Supersession]" + " WHERE OBJECT_CODE = ?"
3027:                        + " ORDER BY SUPERSESSION_YEAR DESC");
3028:                int maxIterations = 15; // For avoiding never-ending loop.
3029:                do {
3030:                    boolean changed = false;
3031:                    for (int i = 0; i < codes.length; i++) {
3032:                        final String code = codes[i].toString();
3033:                        stmt.setString(1, code);
3034:                        final ResultSet result = stmt.executeQuery();
3035:                        while (result.next()) {
3036:                            final String replacement = getString(result, 1,
3037:                                    code);
3038:                            for (int j = i + 1; j < codes.length; j++) {
3039:                                final Object candidate = codes[j];
3040:                                if (replacement.equals(candidate.toString())) {
3041:                                    /*
3042:                                     * Found a code to move in front of the superceded one.
3043:                                     */
3044:                                    System.arraycopy(codes, i, codes, i + 1, j
3045:                                            - i);
3046:                                    codes[i++] = candidate;
3047:                                    changed = true;
3048:                                }
3049:                            }
3050:                        }
3051:                        result.close();
3052:                    }
3053:                    if (!changed) {
3054:                        return;
3055:                    }
3056:                } while (--maxIterations != 0);
3057:                LOGGER.finer("Possible recursivity in supersessions.");
3058:            }
3059:
3060:            /**
3061:             * Returns a finder which can be used for looking up unidentified objects.
3062:             *
3063:             * @param  type The type of objects to look for.
3064:             * @return A finder to use for looking up unidentified objects.
3065:             * @throws FactoryException if the finder can not be created.
3066:             */
3067:            //@Override
3068:            public IdentifiedObjectFinder getIdentifiedObjectFinder(
3069:                    final Class/*<? extends IdentifiedObject>*/type)
3070:                    throws FactoryException {
3071:                return new Finder(buffered, type);
3072:            }
3073:
3074:            /**
3075:             * An implementation of {@link IdentifiedObjectFinder} which scans over a smaller set
3076:             * of authority codes.
3077:             * <p>
3078:             * <b>Implementation note:</b> Since this method may be invoked indirectly by
3079:             * {@link LongitudeFirstFactory}, it must be insensitive to axis order.
3080:             */
3081:            private final class Finder extends IdentifiedObjectFinder {
3082:                /**
3083:                 * Creates a new finder backed by the specified <em>buffered</em> authority factory.
3084:                 */
3085:                Finder(final AbstractAuthorityFactory buffered,
3086:                        final Class/*<? extends IdentifiedObject>*/type) {
3087:                    super (buffered, type);
3088:                }
3089:
3090:                /**
3091:                 * Returns a set of authority codes that <strong>may</strong> identify the same object
3092:                 * than the specified one. This implementation tries to get a smaller set than what
3093:                 * {@link DirectEpsgFactory#getAuthorityCodes} would produce.
3094:                 */
3095:                //@Override
3096:                protected Set getCodeCandidates(final IdentifiedObject object)
3097:                        throws FactoryException {
3098:                    String select = "COORD_REF_SYS_CODE";
3099:                    String from = "[Coordinate Reference System]";
3100:                    String where, code;
3101:                    if (object instanceof  Ellipsoid) {
3102:                        select = "ELLIPSOID_CODE";
3103:                        from = "[Ellipsoid]";
3104:                        where = "SEMI_MAJOR_AXIS";
3105:                        code = Double.toString(((Ellipsoid) object)
3106:                                .getSemiMajorAxis());
3107:                    } else {
3108:                        IdentifiedObject dependency;
3109:                        if (object instanceof  GeneralDerivedCRS) {
3110:                            dependency = ((GeneralDerivedCRS) object)
3111:                                    .getBaseCRS();
3112:                            where = "SOURCE_GEOGCRS_CODE";
3113:                        } else if (object instanceof  SingleCRS) {
3114:                            dependency = ((SingleCRS) object).getDatum();
3115:                            where = "DATUM_CODE";
3116:                        } else if (object instanceof  GeodeticDatum) {
3117:                            dependency = ((GeodeticDatum) object)
3118:                                    .getEllipsoid();
3119:                            select = "DATUM_CODE";
3120:                            from = "[Datum]";
3121:                            where = "ELLIPSOID_CODE";
3122:                        } else {
3123:                            return super .getCodeCandidates(object);
3124:                        }
3125:                        dependency = buffered.getIdentifiedObjectFinder(
3126:                                dependency.getClass()).find(dependency);
3127:                        Identifier id = AbstractIdentifiedObject.getIdentifier(
3128:                                dependency, getAuthority());
3129:                        if (id == null || (code = id.getCode()) == null) {
3130:                            return super .getCodeCandidates(object);
3131:                        }
3132:                    }
3133:                    String sql = "SELECT " + select + " FROM " + from
3134:                            + " WHERE " + where + "='" + code + '\'';
3135:                    sql = adaptSQL(sql);
3136:                    final Set/*<String>*/result = new LinkedHashSet();
3137:                    try {
3138:                        final Statement s = connection.createStatement();
3139:                        final ResultSet r = s.executeQuery(sql);
3140:                        while (r.next()) {
3141:                            result.add(r.getString(1));
3142:                        }
3143:                        r.close();
3144:                        s.close();
3145:                    } catch (SQLException exception) {
3146:                        throw databaseFailure(Identifier.class, code, exception);
3147:                    }
3148:                    return result;
3149:                }
3150:            }
3151:
3152:            /**
3153:             * Constructs an exception for recursive calls.
3154:             */
3155:            private static FactoryException recursiveCall(final Class type,
3156:                    final String code) {
3157:                return new FactoryException(Errors.format(
3158:                        ErrorKeys.RECURSIVE_CALL_$2, Utilities
3159:                                .getShortName(type), code));
3160:            }
3161:
3162:            /**
3163:             * Constructs an exception for a database failure.
3164:             */
3165:            private static FactoryException databaseFailure(final Class type,
3166:                    final String code, final SQLException cause) {
3167:                return new FactoryException(Errors.format(
3168:                        ErrorKeys.DATABASE_FAILURE_$2, Utilities
3169:                                .getShortName(type), code), cause);
3170:            }
3171:
3172:            /**
3173:             * Invoked when a new {@link PreparedStatement} is about to be created from a SQL string.
3174:             * Since the <A HREF="http://www.epsg.org">EPSG database</A> is available mainly in MS-Access
3175:             * format, SQL statements are formatted using some syntax specific to this particular database
3176:             * software (for example "<code>SELECT * FROM [Coordinate Reference System]</code>"). When
3177:             * providing subclass targeting another database vendor, then this method should be overridden
3178:             * in order to adapt the local SQL syntax.
3179:             * <p>
3180:             * For example a subclass connecting to a <cite>PostgreSQL</cite> database could replace
3181:             * all spaces ("&nbsp;") between watching braces ("[" and "]") by underscore ("_").
3182:             *
3183:             * @param  statement The statement in MS-Access syntax.
3184:             * @return The SQL statement to use. The default implementation returns the string unchanged.
3185:             */
3186:            protected abstract String adaptSQL(final String statement);
3187:
3188:            /**
3189:             * Returns {@code true} if the specified code may be a primary key in some table. This method
3190:             * do not needs to checks any entry in the database. It should just checks from the syntax if
3191:             * the code looks like a valid EPSG identifier. The default implementation returns {@code true}
3192:             * if all non-space characters are {@linkplain Character#isDigit(char) digits}.
3193:             * <p>
3194:             * When this method returns {@code false}, some {@code createFoo(...)} methods look for the
3195:             * code in the name column instead of the primary key column. This allows to accept the
3196:             * "<cite>NTF (Paris) / France I</cite>" string (for example) in addition to the {@code "27581"}
3197:             * primary key. Both should fetch the same object.
3198:             * <p>
3199:             * If this method returns {@code true} in all cases, then this factory never search for matching
3200:             * names. In such case, an appropriate exception will be thrown in {@code createFoo(...)}
3201:             * methods if the code is not found in the primary key column. Subclasses can overrides this
3202:             * method that way if this is the intended behavior.
3203:             *
3204:             * @param  code The code the inspect.
3205:             * @return {@code true} if the code is probably a primary key.
3206:             * @throws FactoryException if an unexpected error occured while inspecting the code.
3207:             */
3208:            protected boolean isPrimaryKey(final String code)
3209:                    throws FactoryException {
3210:                final int length = code.length();
3211:                for (int i = 0; i < length; i++) {
3212:                    final char c = code.charAt(i);
3213:                    if (!Character.isDigit(c) && !Character.isSpaceChar(c)) {
3214:                        return false;
3215:                    }
3216:                }
3217:                return true;
3218:            }
3219:
3220:            /**
3221:             * Returns {@code true} if it is safe to dispose this factory. This method is invoked indirectly
3222:             * by {@link ThreadedEpsgFactory} after some timeout in order to release resources. This method will
3223:             * block the disposal if some {@linkplain #getAuthorityCodes set of authority codes} are still
3224:             * in use.
3225:             */
3226:            final synchronized boolean canDispose() {
3227:                boolean can = true;
3228:                Map pool/*<SoftReference,WeakReference>*/= null;
3229:                for (final Iterator it = authorityCodes.entrySet().iterator(); it
3230:                        .hasNext();) {
3231:                    final Map.Entry entry = (Map.Entry) it.next();
3232:                    final Reference reference = (Reference) entry.getValue();
3233:                    final AuthorityCodes codes = (AuthorityCodes) reference
3234:                            .get();
3235:                    if (codes == null) {
3236:                        it.remove();
3237:                        continue;
3238:                    }
3239:                    /*
3240:                     * A set of authority codes is still in use. We can't dispose this factory.
3241:                     * But maybe the set was retained only by soft references... So we continue
3242:                     * the iteration anyway and replace all soft references by weak ones, in order
3243:                     * to get more chances to be garbage-collected before the next disposal cycle.
3244:                     */
3245:                    can = false;
3246:                    if (reference instanceof  SoftReference) {
3247:                        // Each reference appears twice (once with the type key, and once under the SQL
3248:                        // statement as key). So we need to manage a pool of references for avoiding
3249:                        // duplication.
3250:                        if (pool == null) {
3251:                            pool = new IdentityHashMap();
3252:                        }
3253:                        WeakReference weak = (WeakReference) pool
3254:                                .get(reference);
3255:                        if (weak == null) {
3256:                            weak = new WeakReference(codes);
3257:                            pool.put(reference, weak);
3258:                        }
3259:                        entry.setValue(weak);
3260:                    }
3261:                }
3262:                return can;
3263:            }
3264:
3265:            /**
3266:             * Disposes any resources hold by this object.
3267:             *
3268:             * @throws FactoryException if an error occured while closing the connection.
3269:             */
3270:            public synchronized void dispose() throws FactoryException {
3271:                final boolean shutdown = SHUTDOWN_THREAD.equals(Thread
3272:                        .currentThread().getName());
3273:                final boolean isClosed;
3274:                try {
3275:                    isClosed = connection.isClosed();
3276:                    for (final Iterator it = authorityCodes.values().iterator(); it
3277:                            .hasNext();) {
3278:                        final AuthorityCodes set = (AuthorityCodes) ((Reference) it
3279:                                .next()).get();
3280:                        if (set != null) {
3281:                            set.finalize();
3282:                        }
3283:                        it.remove();
3284:                    }
3285:                    for (final Iterator it = statements.values().iterator(); it
3286:                            .hasNext();) {
3287:                        ((PreparedStatement) it.next()).close();
3288:                        it.remove();
3289:                    }
3290:                    if (shutdown) {
3291:                        shutdown(true);
3292:                    }
3293:                    connection.close();
3294:                } catch (SQLException exception) {
3295:                    throw new FactoryException(exception);
3296:                }
3297:                super .dispose();
3298:                if (shutdown)
3299:                    try {
3300:                        shutdown(false);
3301:                    } catch (SQLException exception) {
3302:                        throw new FactoryException(exception);
3303:                    }
3304:                if (!isClosed) {
3305:                    /*
3306:                     * The above code was run inconditionnaly as a safety, even if the connection
3307:                     * was already closed. However we will log a message only if we actually closed
3308:                     * the connection, otherwise the log records are a little bit misleading.
3309:                     */
3310:                    LOGGER.log(Logging.format(Level.FINE,
3311:                            LoggingKeys.CLOSED_EPSG_DATABASE));
3312:                }
3313:            }
3314:
3315:            /**
3316:             * Shutdown the database engine. This method is invoked twice by {@link ThreadedEpsgFactory}
3317:             * at JVM shutdown: one time before the {@linkplain #connection} is closed, and a second
3318:             * time after. This shutdown hook is usefull for <cite>embedded</cite> database engine
3319:             * starting a server process in addition to the client process. Just closing the connection
3320:             * is not enough for them. Example:
3321:             * <P>
3322:             * <UL>
3323:             *   <LI>HSQL database engine needs to execute a {@code "SHUTDOWN"} statement using the
3324:             *      {@linkplain #connection} before it is closed.</LI>
3325:             *   <LI>Derby database engine needs to instruct the {@linkplain java.sql.DriverManager driver
3326:             *       manager} after all connections have been closed.</LI>
3327:             * </UL>
3328:             * <P>
3329:             * The default implementation does nothing, which is suffisient for implementations
3330:             * connecting to a distant server (i.e. non-embedded database engine), for example
3331:             * {@linkplain AccessDataSource MS-Access} or {@linkplain PostgreDataSource PostgreSQL}.
3332:             *
3333:             * @param active {@code true} if the {@linkplain #connection} is alive, or {@code false}
3334:             *        otherwise. This method is invoked first with {@code active} set to {@code true},
3335:             *        then a second time with {@code active} set to {@code false}.
3336:             * @throws SQLException if this method failed to shutdown the database engine.
3337:             */
3338:            protected void shutdown(final boolean active) throws SQLException {
3339:            }
3340:
3341:            /**
3342:             * Invokes {@link #dispose} when this factory is garbage collected.
3343:             *
3344:             * @throws Throwable if an error occured while closing the connection.
3345:             */
3346:            protected final void finalize() throws Throwable {
3347:                dispose();
3348:                super.finalize();
3349:            }
3350:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.