Package org.geotools.referencing.factory.epsg

Source Code of org.geotools.referencing.factory.epsg.AbstractEpsgFactory$AuthorityCodeSet$Map

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation;
*    version 2.1 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*/
package org.geotools.referencing.factory.epsg;

import java.io.File;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.awt.RenderingHints;

import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;

import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.io.TableWriter;
import org.geotools.measure.Units;
import org.geotools.metadata.iso.citation.CitationImpl;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.metadata.iso.extent.ExtentImpl;
import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
import org.geotools.metadata.iso.quality.AbsoluteExternalPositionalAccuracyImpl;
import org.geotools.metadata.iso.quality.QuantitativeResultImpl;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.parameter.DefaultParameterDescriptorGroup;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.NamedIdentifier;
import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
import org.geotools.referencing.datum.BursaWolfParameters;
import org.geotools.referencing.datum.DefaultGeodeticDatum;
import org.geotools.referencing.factory.AbstractCachedAuthorityFactory;
import org.geotools.referencing.factory.BufferedAuthorityFactory;
import org.geotools.referencing.factory.DirectAuthorityFactory;
import org.geotools.referencing.factory.IdentifiedObjectFinder;
import org.geotools.referencing.operation.DefaultConcatenatedOperation;
import org.geotools.referencing.operation.DefaultOperation;
import org.geotools.referencing.operation.DefaultOperationMethod;
import org.geotools.referencing.operation.DefiningConversion;
import org.geotools.resources.CRSUtilities;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.util.LocalName;
import org.geotools.util.ObjectCache;
import org.geotools.util.ScopedName;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.Version;
import org.geotools.util.logging.Logging;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.quality.EvaluationMethodType;
import org.opengis.metadata.quality.PositionalAccuracy;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.NoSuchIdentifierException;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeneralDerivedCRS;
import org.opengis.referencing.crs.GeocentricCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CSFactory;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.SphericalCS;
import org.opengis.referencing.cs.VerticalCS;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.datum.DatumFactory;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.EngineeringDatum;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.VerticalDatumType;
import org.opengis.referencing.operation.ConcatenatedOperation;
import org.opengis.referencing.operation.Conversion;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.referencing.operation.Projection;
import org.opengis.referencing.operation.Transformation;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;


/**
* A coordinate reference system factory backed by the EPSG database tables.
* <p>
* The EPSG database is freely available at <A HREF="http://www.epsg.org">http://www.epsg.org</a>.
* Current version of this class requires EPSG database version 6.6 or above.
* <p>
* This factory makes use of a provided {@link ObjectCache}, and may be deployed in stand aline fashion;
* or as a worker for a {@link MultiEpsgFactory}.
* <p>
* This class is abstract - please see the subclasses for dialect specific implementations:
* <ul>
*   <li>{@link AccessDialectEpsgFactory}</li>
*   <li>{@link AnsiDialectEpsgFactory}</li>
*   <li>{@link OracleDialectEpsgFactory}</li>
* </ul>
*
* These factories accepts names as well as numerical identifiers. For example
* "<cite>NTF (Paris) / France I</cite>" and {@code "27581"} both fetchs the same object.
* However, names may be ambiguous since the same name may be used for more than one object.
* This is the case of "WGS 84" for example. If such an ambiguity is found, an exception
* will be thrown. If names are not wanted as a legal EPSG code, subclasses can override the
* {@link #isPrimaryKey} method.
*
* @since 2.4
*
*
* @source $URL$
* @version $Id$
* @author Yann Cézard
* @author Martin Desruisseaux (IRD)
* @author Rueben Schulz
* @author Matthias Basler
* @author Andrea Aime
*/
public abstract class AbstractEpsgFactory extends AbstractCachedAuthorityFactory {
    /// Datum shift operation methods
    /** First Bursa-Wolf method.   */
    private static final int BURSA_WOLF_MIN_CODE = 9603;

    /** Last Bursa-Wolf method.   */
    private static final int BURSA_WOLF_MAX_CODE = 9607;

    /** Rotation frame method.   */
    private static final int ROTATION_FRAME_CODE = 9607;

    /** Dummy operation to ignore. */
    private static final int DUMMY_OPERATION     =    1;

    /**
     * The name for the transformation accuracy metadata.
     */
    private static final InternationalString TRANSFORMATION_ACCURACY =
            Vocabulary.formatInternational(VocabularyKeys.TRANSFORMATION_ACCURACY);

    /**
     * The calendar instance for creating {@link java.util.Date} objects from a year
     * (the "epoch" in datum definition). We use the local timezone, which may not be
     * quite accurate. But there is no obvious timezone for "epoch", and the "epoch"
     * is approximative anyway.
     */
    private final Calendar calendar = Calendar.getInstance(); // FIXME: move into factories?

    /**
     * The authority for this database. Will be created only when first needed.
     * This authority will contains the database version in the {@linkplain Citation#getEdition
     * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
     */
    private transient Citation authority; //FIXME: always EPSG

    /**
     * A DataSource to the EPSG database being used.
     */
    protected javax.sql.DataSource dataSource;

    /**
     * The connection to the EPSG database - this is create in a lazy manner from the DataSource.
     * <p>
     * This field is managed as part of our connection lifecycle.
     */
    private Connection connection;

    /**
     * A pool of prepared statements. Key are {@link String} object related to their
     * originating method name (for example "Ellipsoid" for {@link #createEllipsoid},
     * while values are {@link PreparedStatement} objects.
     * <p>
     * <strong>Note:</strong> It is okay to use {@link IdentityHashMap} instead of {@link HashMap}
     * because the keys will always be the exact same object, namely the hard-coded argument given
     * to calls to {@link #prepareStatement} in this class.
     * <p>
     * This field is managed as part of our connection lifecycle.
     */
    private final Map<String,PreparedStatement> statements = new IdentityHashMap<String,PreparedStatement>();

    /**
     * Last object type returned by {@link #createObject}, or -1 if none.
     * This type is an index in the {@link #TABLES_INFO} array and is
     * strictly for {@link #createObject} internal use.
     */
    private int lastObjectType = -1;

    /**
     * The last table in which object name were looked for. This is for internal use
     * by {@link #toPrimaryKey} only.
     */
    private transient String lastTableForName;

    /**
     * Cache for axis names. This service is not provided by {@link BufferedAuthorityFactory}
     * since {@link AxisName} object are particular to the EPSG database.
     *
     * @see #getAxisName
     */
    private final Map<String,AxisName> axisNames = new HashMap<String,AxisName>();

    /**
     * Cache for axis numbers. This service is not provided by {@link BufferedAuthorityFactory}
     * since the number of axis is used internally in this class.
     *
     * @see #getDimensionForCRS
     */
    private final Map<String,Short> axisCounts = new HashMap<String,Short>();

    /**
     * Cache for projection checks. This service is not provided by {@link BufferedAuthorityFactory}
     * since the check that a transformation is a projection is used internally in this class.
     *
     * @see #isProjection
     */
    private final Map<String,Boolean> codeProjection = new HashMap<String,Boolean>();

    /**
     * Pool of naming systems, used for caching.
     * There is usually few of them (about 15).
     */
    private final Map<String,LocalName> scopes = new HashMap<String,LocalName>();

    /**
     * The properties to be given the objects to construct.
     * Reused every time {@link #createProperties} is invoked.
     */
    private final Map<String,Object> properties = new HashMap<String,Object>();

    /**
     * A safety guard for preventing never-ending loops in recursive calls to
     * {@link #createDatum}. This is used by {@link #createBursaWolfParameters},
     * which need to create a target datum. The target datum could have its own
     * Bursa-Wolf parameters, with one of them pointing again to the source datum.
     */
    private final Set<String> safetyGuard = new HashSet<String>();

    public AbstractEpsgFactory(final Hints userHints ) throws FactoryException {
        super( MAXIMUM_PRIORITY-20 );
        // The following hints have no effect on this class behaviour,
        // but tell to the user what this factory do about axis order.
        hints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS,   Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_UNITS,        Boolean.FALSE);

        //
        // We need to obtain our DataSource
        if (userHints != null) {
            Object hint = userHints.get(Hints.EPSG_DATA_SOURCE);
            if( hint instanceof String ){
                String name = (String) hint;
                try {
                    dataSource = (DataSource) GeoTools.getInitialContext(userHints).lookup(name);
                } catch (NamingException e) {
                    throw new FactoryException("A EPSG_DATA_SOURCE hint is required:"+e);
                }
                hints.put(Hints.EPSG_DATA_SOURCE, dataSource );
            }
            else if( hint instanceof DataSource ){
                dataSource = (DataSource) hint;
                hints.put(Hints.EPSG_DATA_SOURCE, dataSource );
            }
            else {
                throw new FactoryException("A EPSG_DATA_SOURCE hint is required.");
            }
        }
        else {
            throw new FactoryException("A EPSG_DATA_SOURCE hint is required.");
        }
    }

    public AbstractEpsgFactory(final Hints userHints, final javax.sql.DataSource dataSource) {
      super( MAXIMUM_PRIORITY-20 );

      this.dataSource = dataSource;
        // The following hints have no effect on this class behaviour,
        // but tell to the user what this factory do about axis order.
        hints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS,   Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_UNITS,        Boolean.FALSE);
        hints.put(Hints.EPSG_DATA_SOURCE, dataSource );
    }
    /**
     * Constructs an authority factory using the specified connection.
     *
     * @param userHints The underlying factories used for objects creation.
     * @param connection The connection to the underlying EPSG database.
     */
    public AbstractEpsgFactory(final Hints userHints, final Connection connection) {
        super(MAXIMUM_PRIORITY-20, userHints );
        // The following hints have no effect on this class behaviour,
        // but tell to the user what this factory do about axis order.
        hints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS,   Boolean.FALSE);
        hints.put(Hints.FORCE_STANDARD_AXIS_UNITS,        Boolean.FALSE);
        this.connection = connection;
        ensureNonNull("connection", connection);
    }

    /**
     * Returns the authority for this EPSG database.
     * This authority will contains the database version in the {@linkplain Citation#getEdition
     * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
     */
    public synchronized Citation getAuthority() {
        if (authority == null) try {
            final String query = adaptSQL("SELECT VERSION_NUMBER, VERSION_DATE FROM [Version History]" +
                                          " ORDER BY VERSION_DATE DESC");
            final DatabaseMetaData metadata  = getConnection().getMetaData();
            final Statement        statement = getConnection().createStatement();
            final ResultSet        result    = statement.executeQuery(query);
            if (result.next()) {
                final String version = result.getString(1);
                final Date   date    = result.getDate  (2);
                final String engine  = metadata.getDatabaseProductName();
                final CitationImpl c = new CitationImpl(Citations.EPSG);
                c.getAlternateTitles().add(Vocabulary.formatInternational(
                        VocabularyKeys.DATA_BASE_$3, "EPSG", version, engine));
                c.setEdition(new SimpleInternationalString(version));
                c.setEditionDate(date);
                authority = (Citation) c.unmodifiable();
                hints.put(Hints.VERSION, new Version(version))// For getImplementationHints()
            } else {
                authority = Citations.EPSG;
            }
            result.close();
            statement.close();
        } catch (SQLException exception) {
            Logging.unexpectedException(LOGGER, AbstractEpsgFactory.class, "getAuthority", exception);
            return Citations.EPSG;
        }
        return authority;
    }

    /**
     * Returns a description of the database engine.
     *
     * @throws FactoryException if the database's metadata can't be fetched.
     */
    @Override
    public synchronized String getBackingStoreDescription() throws FactoryException {
        final Citation   authority = getAuthority();
        final TableWriter    table = new TableWriter(null, " ");
        final Vocabulary resources = Vocabulary.getResources(null);
        CharSequence cs;
        if ((cs=authority.getEdition()) != null) {
            table.write(resources.getString(VocabularyKeys.VERSION_OF_$1, "EPSG"));
            table.write(':');
            table.nextColumn();
            table.write(cs.toString());
            table.nextLine();
        }
        try {
            String s;
            final DatabaseMetaData metadata = getConnection().getMetaData();
            if ((s=metadata.getDatabaseProductName()) != null) {
                table.write(resources.getLabel(VocabularyKeys.DATABASE_ENGINE));
                table.nextColumn();
                table.write(s);
                if ((s=metadata.getDatabaseProductVersion()) != null) {
                    table.write(' ');
                    table.write(resources.getString(VocabularyKeys.VERSION_$1, s));
                }
                table.nextLine();
            }
            if ((s=metadata.getURL()) != null) {
                table.write(resources.getLabel(VocabularyKeys.DATABASE_URL));
                table.nextColumn();
                table.write(s);
                table.nextLine();
            }
        } catch (SQLException exception) {
            throw new FactoryException(exception);
        }
        return table.toString();
    }

    /**
     * Returns the implementation hints for this factory. The returned map contains all the
     * values specified in {@linkplain DirectAuthorityFactory#getImplementationHints subclass},
     * with the addition of {@link Hints#VERSION VERSION}.
     */
    @Override
    public Map<RenderingHints.Key,?> getImplementationHints() {
        if (authority == null) {
            // For the computation of Hints.VERSION.
            getAuthority();
        }
        return super.getImplementationHints();
    }

    /**
     * Returns the set of authority codes of the given type.
     *
     * @param  type The spatial reference objects type (may be {@code Object.class}).
     * @return The set of authority codes for spatial reference objects of the given type.
     *         If this factory doesn't contains any object of the given type, then this method
     *         returns an empty set.
     * @throws FactoryException if access to the underlying database failed.
     */
    protected synchronized Set<String> generateAuthorityCodes(final Class type) throws FactoryException {
      Set<String> result = new HashSet<String>();
        for (int i=0; i<TABLES_INFO.length; i++) {
            final TableInfo table = TABLES_INFO[i];
            if (table.isTypeOf(type)) {
                final AuthorityCodeSet codes = new AuthorityCodeSet(TABLES_INFO[i], type);
                result.addAll(codes);
            }
        }
        return result;
    }

    /**
     * Gets a description of the object corresponding to a code.
     *
     * @param  code Value allocated by authority.
     * @return A description of the object, or {@code null} if the object
     *         corresponding to the specified {@code code} has no description.
     * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
     * @throws FactoryException if the query failed for some other reason.
     */
    public InternationalString getDescriptionText(final String code) throws FactoryException {
        IdentifiedObject identifiedObject = createObject(code);
    final Identifier identifier = identifiedObject.getName();
        if (identifier instanceof GenericName) {
            return ((GenericName) identifier).toInternationalString();
        }
        return new SimpleInternationalString( identifier.getCode() );
    }

    /**
     * Returns a prepared statement for the specified name. Most {@link PreparedStatement}
     * creations are performed through this method, except {@link #getNumericalIdentifier}
     * and {@link #createObject}.
     *
     * @param  key A key uniquely identifying the caller
     *         (e.g. {@code "Ellipsoid"} for {@link #createEllipsoid}).
     * @param  sql The SQL statement to use if for creating the {@link PreparedStatement}
     *         object. Will be used only if no prepared statement was already created for
     *         the specified key.
     * @return The prepared statement.
     * @throws SQLException if the prepared statement can't be created.
     */
    private PreparedStatement prepareStatement(final String key, final String sql)
            throws SQLException
    {
        assert Thread.holdsLock(this);
        PreparedStatement stmt = statements.get(key);
        if (stmt == null) {
            stmt = getConnection().prepareStatement(adaptSQL(sql));
            statements.put(key, stmt);
        }
        return stmt;
    }

    /**
     * Gets the string from the specified {@link ResultSet}.
     * The string is required to be non-null. A null string
     * will throw an exception.
     *
     * @param  result The result set to fetch value from.
     * @param  columnIndex The column index (1-based).
     * @param  code The identifier of the record where the string was found.
     * @return The string at the specified column.
     * @throws SQLException if a SQL error occured.
     * @throws FactoryException If a null value was found.
     */
    private static String getString(final ResultSet result, final int columnIndex, final String code)
            throws SQLException, FactoryException
    {
        final String value = result.getString(columnIndex);
        ensureNonNull(result, columnIndex, code);
        return value.trim();
    }

    /**
     * Same as {@link #getString(ResultSet,int,String)}, but report the fault on an alternative
     * column if the value is null.
     */
    private static String getString(final ResultSet result, final int columnIndex,
                                    final String    code,   final int columnFault)
            throws SQLException, FactoryException
    {
        final String str = result.getString(columnIndex);
        if (result.wasNull()) {
            final ResultSetMetaData metadata = result.getMetaData();
            final String column = metadata.getColumnName(columnFault);
            final String table  = metadata.getTableName (columnFault);
            result.close();
            throw new FactoryException(
                    Errors.format(ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column, table));
        }
        return str.trim();
    }

    /**
     * Gets the value from the specified {@link ResultSet}.
     * The value is required to be non-null. A null value
     * (i.e. blank) will throw an exception.
     *
     * @param  result The result set to fetch value from.
     * @param  columnIndex The column index (1-based).
     * @param  code The identifier of the record where the string was found.
     * @return The double at the specified column.
     * @throws SQLException if a SQL error occured.
     * @throws FactoryException If a null value was found.
     */
    private static double getDouble(final ResultSet result, final int columnIndex, final String code)
            throws SQLException, FactoryException
    {
        final double value = result.getDouble(columnIndex);
        ensureNonNull(result, columnIndex, code);
        return value;
    }

    /**
     * Gets the value from the specified {@link ResultSet}.
     * The value is required to be non-null. A null value
     * (i.e. blank) will throw an exception.
     *
     * @param  result The result set to fetch value from.
     * @param  columnIndex The column index (1-based).
     * @param  code The identifier of the record where the string was found.
     * @return The integer at the specified column.
     * @throws SQLException if a SQL error occured.
     * @throws FactoryException If a null value was found.
     */
    private static int getInt(final ResultSet result, final int columnIndex, final String code)
            throws SQLException, FactoryException
    {
        final int value = result.getInt(columnIndex);
        ensureNonNull(result, columnIndex, code);
        return value;
    }

    /**
     * Make sure that the last result was non-null. Used for {@code getString}, {@code getDouble}
     * and {@code getInt} methods only.
     */
    private static void ensureNonNull(final ResultSet result, final int columnIndex, final String code)
            throws SQLException, FactoryException
    {
        if (result.wasNull()) {
            final ResultSetMetaData metadata = result.getMetaData();
            final String column = metadata.getColumnName(columnIndex);
            final String table  = metadata.getTableName (columnIndex);
            result.close();
            throw new FactoryException(
                    Errors.format(ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column, table));
        }
    }

    /**
     * Converts a code from an arbitrary name to the numerical identifier (the primary key).
     * If the supplied code is already a numerical value, then it is returned unchanged.
     * If the code is not found in the name column, it is returned unchanged as well so that
     * the caller will produces an appropriate "Code not found" error message. If the code
     * is found more than once, then an exception is thrown.
     * <p>
     * Note that this method includes a call to {@link #trimAuthority}, so there is no need to
     * call it before or after this method.
     *
     * @param  type       The type of object to create.
     * @param  code       The code to check.
     * @param  table      The table where the code should appears.
     * @param  codeColumn The column name for the code.
     * @param  nameColumn The column name for the name.
     * @return The numerical identifier (i.e. the table primary key value).
     * @throws SQLException if an error occured while reading the database.
     */
    private String toPrimaryKey(final Class  type,
                                final String code,
                                final String table,
                                final String codeColumn,
                                final String nameColumn)
            throws SQLException, FactoryException
    {
        assert Thread.holdsLock(this);
        String identifier = trimAuthority(code);
        if (!isPrimaryKey(identifier)) {
            /*
             * The character is not the numerical code. Search the value in the database.
             * If a prepared statement is already available, reuse it providing that it was
             * created for the current table. Otherwise, we will create a new statement.
             */
            final String KEY = "NumericalIdentifier";
            PreparedStatement statement = statements.get(KEY);
            if (statement != null) {
                if (!table.equals(lastTableForName)) {
                    statements.remove(KEY);
                    statement.close();
                    statement        = null;
                    lastTableForName = null;
                }
            }
            if (statement == null) {
                final String query = "SELECT " + codeColumn + " FROM " + table +
                                     " WHERE " + nameColumn + " = ?";
                statement = getConnection().prepareStatement(adaptSQL(query));
                statements.put(KEY, statement);
            }
            statement.setString(1, identifier);
            identifier = null;
            final ResultSet result = statement.executeQuery();
            while (result.next()) {
                identifier = ensureSingleton(result.getString(1), identifier, code);
            }
            result.close();
            if (identifier == null) {
                throw noSuchAuthorityCode(type, code);
            }
        }
        return identifier;
    }

    /**
     * Make sure that an object constructed from the database is not incoherent.
     * If the code supplied to a {@code createFoo} method exists in the database,
     * then we should find only one record. However, we will do a paranoiac check and
     * verify if there is more records, using a {@code while (results.next())}
     * loop instead of {@code if (results.next())}. This method is invoked in
     * the loop for making sure that, if there is more than one record (which should
     * never happen), at least they have identical contents.
     *
     * @param  newValue The newly constructed object.
     * @param  oldValue The object previously constructed, or {@code null} if none.
     * @param  code The EPSG code (for formatting error message).
     * @throws FactoryException if a duplication has been detected.
     *
     * @todo Use generic type when we will be allowed to compile for J2SE 1.5.
     */
    private static <T> T ensureSingleton(final T newValue, final T oldValue, final String code)
            throws FactoryException
    {
        if (oldValue == null) {
            return newValue;
        }
        if (oldValue.equals(newValue)) {
            return oldValue;
        }
        throw new FactoryException(Errors.format(ErrorKeys.DUPLICATED_VALUES_$1, code));
    }

    /**
     * Returns the name for the {@link IdentifiedObject} to construct.
     * This method also search for alias.
     *
     * @param  name The name for the {@link IndentifiedObject} to construct.
     * @param  code The EPSG code of the object to construct.
     * @param  remarks Remarks, or {@code null} if none.
     * @return The name together with a set of properties.
     */
    private Map<String,Object> generateProperties(final String name, final String code, String remarks)
            throws SQLException, FactoryException
    {
        properties.clear();
        final Citation authority = getAuthority();
        if (name != null) {
            properties.put(IdentifiedObject.NAME_KEY,
                           new NamedIdentifier(authority, name.trim()));
        }
        if (code != null) {
            final InternationalString edition = authority.getEdition();
            final String version = (edition!=null) ? edition.toString() : null;
            properties.put(IdentifiedObject.IDENTIFIERS_KEY,
                           new NamedIdentifier(authority, code.trim(), version));
        }
        if (remarks!=null && (remarks=remarks.trim()).length()!=0) {
            properties.put(IdentifiedObject.REMARKS_KEY, remarks);
        }
        /*
         * Search for alias.
         */
        List<GenericName> alias = null;
        final PreparedStatement stmt;
        stmt = prepareStatement("Alias", "SELECT NAMING_SYSTEM_NAME, ALIAS"
                                       + " FROM [Alias] INNER JOIN [Naming System]"
                                       +   " ON [Alias].NAMING_SYSTEM_CODE ="
                                       +      " [Naming System].NAMING_SYSTEM_CODE"
                                       + " WHERE OBJECT_CODE = ?");
        stmt.setString(1, code);
        final ResultSet result = stmt.executeQuery();
        while (result.next()) {
            final String scope = result.getString(1);
            final String local = getString(result, 2, code);
            final GenericName generic;
            if (scope == null) {
                generic = new LocalName(local);
            } else {
                LocalName cached = scopes.get(scope);
                if (cached == null) {
                    cached = new LocalName(scope);
                    scopes.put(scope, cached);
                }
                generic = new ScopedName(cached, local);
            }
            if (alias == null) {
                alias = new ArrayList<GenericName>();
            }
            alias.add(generic);
        }
        result.close();
        if (alias != null) {
            properties.put(IdentifiedObject.ALIAS_KEY,
                           alias.toArray(new GenericName[alias.size()]));
        }
        return properties;
    }

    /**
     * Returns the name for the {@link IdentifiedObject} to construct.
     * This method also search for alias.
     *
     * @param  name  The name for the {@link IndentifiedObject} to construct.
     * @param  code  The EPSG code of the object to construct.
     * @param  area  The area of use, or {@code null} if none.
     * @param  scope The scope, or {@code null} if none.
     * @param  remarks Remarks, or {@code null} if none.
     * @return The name together with a set of properties.
     */
    private Map<String,Object> generateProperties(final String name, final String code,
            String area, String scope, String remarks)
            throws SQLException, FactoryException
    {
        final Map<String,Object> properties = generateProperties(name, code, remarks);
        if (area != null  &&  (area=area.trim()).length() != 0) {
            final Extent extent = generateExtent(area);
            properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, extent);
        }
        if (scope != null &&  (scope=scope.trim()).length() != 0) {
            properties.put(Datum.SCOPE_KEY, scope);
        }
        return properties;
    }

    /**
     * Returns an arbitrary object from a code.
     * The default implementation invokes one of {@link #createCoordinateReferenceSystem},
     * {@link #createCoordinateSystem}, {@link #createDatum}, {@link #createEllipsoid}, or
     * {@link #createUnit} methods according the object type.
     *
     * @param  code The EPSG value.
     * @return The object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized IdentifiedObject generateObject(final String code) throws FactoryException {
        ensureNonNull("code", code);
        final String       KEY = "IdentifiedObject";
        PreparedStatement stmt = statements.get(KEY); // Null allowed.
        StringBuilder    query = null; // Will be created only if the last statement doesn't suit.
        /*
         * Iterates through all tables listed in TABLES_INFO, starting with the table used during
         * the last call to 'createObject(code)'.  This approach assumes that two consecutive calls
         * will often return the same type of object.  If the object type changed, then this method
         * will have to discard the old prepared statement and prepare a new one, which may be a
         * costly operation. Only the last successful prepared statement is cached, in order to keep
         * the amount of statements low. Unsuccessful statements are immediately disposed.
         */
        final String  epsg         = trimAuthority(code);
        final boolean isPrimaryKey = isPrimaryKey(epsg);
        final int     tupleToSkip  = isPrimaryKey ? lastObjectType : -1;
        int index = -1;
        for (int i=-1; i<TABLES_INFO.length; i++) {
            if (i == tupleToSkip) {
                // Avoid to test the same table twice.  Note that this test also avoid a
                // NullPointerException if 'stmt' is null, since 'lastObjectType' should
                // be -1 in this case.
                continue;
            }
            try {
                if (i >= 0) {
                    final TableInfo table = TABLES_INFO[i];
                    final String column = isPrimaryKey ? table.codeColumn : table.nameColumn;
                    if (column == null) {
                        continue;
                    }
                    if (query == null) {
                        query = new StringBuilder("SELECT ");
                    }
                    query.setLength(7); // 7 is the length of "SELECT " in the line above.
                    query.append(table.codeColumn);
                    query.append(" FROM ");
                    query.append(table.table);
                    query.append(" WHERE ");
                    query.append(column);
                    query.append(" = ?");
                    if (isPrimaryKey) {
                        assert !statements.containsKey(KEY) : table;
                        stmt = prepareStatement(KEY, query.toString());
                    } else {
                        // Do not cache the statement for names.
                        stmt = getConnection().prepareStatement(adaptSQL(query.toString()));
                    }
                }
                /*
                 * Checks if at least one record is found for the code. If the code is the primary
                 * key, then we will stop at the first table found since a well-formed EPSG database
                 * should not contains any duplicate identifiers. In the code is a name, then search
                 * in all tables since duplicate names exist.
                 */
                stmt.setString(1, epsg);
                final ResultSet result = stmt.executeQuery();
                final boolean  present = result.next();
                result.close();
                if (present) {
                    if (index >= 0) {
                        throw new FactoryException(Errors.format(ErrorKeys.DUPLICATED_VALUES_$1, code));
                    }
                    index = (i < 0) ? lastObjectType : i;
                    if (isPrimaryKey) {
                        // Don't scan other tables, since primary keys should be unique.
                        // Note that names may be duplicated, so we don't stop for names.
                        break;
                    }
                }
                if (isPrimaryKey) {
                    if (statements.remove(KEY) == null) {
                        throw new AssertionError(code); // Should never happen.
                    }
                }
                stmt.close();
            } catch (SQLException exception) {
                throw databaseFailure(IdentifiedObject.class, code, exception);
            }
        }
        /*
         * If a record has been found in one table, then delegates to the appropriate method.
         */
        if (isPrimaryKey) {
            lastObjectType = index;
        }
        if (index >= 0) {
            switch (index) {
                case 0return createCoordinateReferenceSystem(code);
                case 1return createCoordinateSystem         (code);
                case 2return createCoordinateSystemAxis     (code);
                case 3return createDatum                    (code);
                case 4return createEllipsoid                (code);
                case 5return createPrimeMeridian            (code);
                case 6return createCoordinateOperation      (code);
                case 7return generateOperationMethod          (code);
                case 8return generateParameterDescriptor      (code);
                case 9break; // Can't cast Unit to IdentifiedObject
                default: throw new AssertionError(index); // Should not happen
            }
        }
        return super.createObject(code);
    }

    /**
     * Returns an unit from a code.
     *
     * @param  code Value allocated by authority.
     * @return The unit object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized Unit<?> generateUnit(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        Unit<?> returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(Unit.class, code,
                    "[Unit of Measure]", "UOM_CODE", "UNIT_OF_MEAS_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("Unit", "SELECT UOM_CODE,"
                                          +       " FACTOR_B,"
                                          +       " FACTOR_C,"
                                          +       " TARGET_UOM_CODE"
                                          + " FROM [Unit of Measure]"
                                          + " WHERE UOM_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final int source = getInt(result,   1, code);
                final double   b = result.getDouble(2);
                final double   c = result.getDouble(3);
                final int target = getInt(result,   4, code);
                final Unit<?> base = getUnit(target);
                if (base == null) {
                    throw noSuchAuthorityCode(Unit.class, String.valueOf(target));
                }
                Unit unit = getUnit(source);
                if (unit != null) {
                    // TODO: check unit consistency here.
                } else if (b!=0 && c!=0) {
                    unit = (b == c) ? base : base.times(b / c);
                } else {
                    // TODO: provide a localized message.
                    throw new FactoryException("Unsupported unit: " + code);
                }
                returnValue = ensureSingleton(unit, returnValue, code);
            }
            result.close();
        }
        catch (SQLException exception) {
            throw databaseFailure(Unit.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(Unit.class, code);
        }
        return returnValue;
    }

    /**
     * Returns an ellipsoid from a code.
     *
     * @param  code The EPSG value.
     * @return The ellipsoid object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized Ellipsoid generateEllipsoid(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        Ellipsoid returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(Ellipsoid.class, code,
                    "[Ellipsoid]", "ELLIPSOID_CODE", "ELLIPSOID_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("Ellipsoid", "SELECT ELLIPSOID_CODE,"
                                               +       " ELLIPSOID_NAME,"
                                               +       " SEMI_MAJOR_AXIS,"
                                               +       " INV_FLATTENING,"
                                               +       " SEMI_MINOR_AXIS,"
                                               +       " UOM_CODE,"
                                               +       " REMARKS"
                                               + " FROM [Ellipsoid]"
                                               + " WHERE ELLIPSOID_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            while (result.next()) {
                /*
                 * One of 'semiMinorAxis' and 'inverseFlattening' values can be NULL in
                 * the database. Consequently, we don't use 'getString(ResultSet, int)'
                 * because we don't want to thrown an exception if a NULL value is found.
                 */
                final String epsg              = getString(result, 1, code);
                final String name              = getString(result, 2, code);
                final double semiMajorAxis     = getDouble(result, 3, code);
                final double inverseFlattening = result.getDouble( 4);
                final double semiMinorAxis     = result.getDouble( 5);
                final String unitCode          = getString(result, 6, code);
                final String remarks           = result.getString( 7);
                final Unit   unit              = createUnit(unitCode);
                final Map<String,Object> properties = generateProperties(name, epsg, remarks);
                final Ellipsoid ellipsoid;
                if (inverseFlattening == 0) {
                    if (semiMinorAxis == 0) {
                        // Both are null, which is not allowed.
                        final String column = result.getMetaData().getColumnName(3);
                        result.close();
                        throw new FactoryException(Errors.format(ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column));
                    } else {
                        // We only have semiMinorAxis defined -> it's OK
                        ellipsoid = factories.getDatumFactory().createEllipsoid(
                                    properties, semiMajorAxis, semiMinorAxis, unit);
                    }
                } else {
                    if (semiMinorAxis != 0) {
                        // Both 'inverseFlattening' and 'semiMinorAxis' are defined.
                        // Log a warning and create the ellipsoid using the inverse flattening.
                        final LogRecord record = Loggings.format(Level.WARNING, LoggingKeys.AMBIGUOUS_ELLIPSOID,code);
                        record.setLoggerName(LOGGER.getName());
                        LOGGER.log(record);
                    }
                    ellipsoid = factories.getDatumFactory().createFlattenedSphere(
                                properties, semiMajorAxis, inverseFlattening, unit);
                }
                /*
                 * Now that we have built an ellipsoid, compare
                 * it with the previous one (if any).
                 */
                returnValue = ensureSingleton(ellipsoid, returnValue, code);
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(Ellipsoid.class, code, exception);
        }
        if (returnValue == null) {
             throw noSuchAuthorityCode(Ellipsoid.class, code);
        }
        return returnValue;
    }

    /**
     * Returns a prime meridian, relative to Greenwich.
     *
     * @param  code Value allocated by authority.
     * @return The prime meridian object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized PrimeMeridian generatePrimeMeridian(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        PrimeMeridian returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(PrimeMeridian.class, code,
                    "[Prime Meridian]", "PRIME_MERIDIAN_CODE", "PRIME_MERIDIAN_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("PrimeMeridian", "SELECT PRIME_MERIDIAN_CODE,"
                                                   +       " PRIME_MERIDIAN_NAME,"
                                                   +       " GREENWICH_LONGITUDE,"
                                                   +       " UOM_CODE,"
                                                   +       " REMARKS"
                                                   + " FROM [Prime Meridian]"
                                                   + " WHERE PRIME_MERIDIAN_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg      = getString(result, 1, code);
                final String name      = getString(result, 2, code);
                final double longitude = getDouble(result, 3, code);
                final String unit_code = getString(result, 4, code);
                final String remarks   = result.getString( 5);
                final Unit unit        = createUnit(unit_code);
                final Map<String,Object> properties = generateProperties(name, epsg, remarks);
                PrimeMeridian primeMeridian = factories.getDatumFactory().createPrimeMeridian(
                                              properties, longitude, unit);
                returnValue = ensureSingleton(primeMeridian, returnValue, code);
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(PrimeMeridian.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(PrimeMeridian.class, code);
        }
        return returnValue;
    }

    /**
     * Returns an area of use.
     *
     * @param  code Value allocated by authority.
     * @return The area of use.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized Extent generateExtent(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        Extent returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(Extent.class, code,
                    "[Area]", "AREA_CODE", "AREA_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("Area", "SELECT AREA_OF_USE,"
                                          +       " AREA_SOUTH_BOUND_LAT,"
                                          +       " AREA_NORTH_BOUND_LAT,"
                                          +       " AREA_WEST_BOUND_LON,"
                                          +       " AREA_EAST_BOUND_LON"
                                          + " FROM [Area]"
                                          + " WHERE AREA_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            while (result.next()) {
                ExtentImpl extent = null;
                final String description = result.getString(1);
                if (description != null) {
                    extent = new ExtentImpl();
                    extent.setDescription(new SimpleInternationalString(description));
                }
                final double ymin = result.getDouble(2);
                if (!result.wasNull()) {
                    final double ymax = result.getDouble(3);
                    if (!result.wasNull()) {
                        final double xmin = result.getDouble(4);
                        if (!result.wasNull()) {
                            final double xmax = result.getDouble(5);
                            if (!result.wasNull()) {
                                if (extent == null) {
                                    extent = new ExtentImpl();
                                }
                                extent.setGeographicElements(Collections.singleton(
                                        new GeographicBoundingBoxImpl(xmin, xmax, ymin, ymax)));
                            }
                        }
                    }
                }
                if (extent != null) {
                    returnValue = (Extent) ensureSingleton(extent.unmodifiable(), returnValue, code);
                }
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(Extent.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(Extent.class, code);
        }
        return returnValue;
    }

    /**
     * Returns Bursa-Wolf parameters for a geodetic datum. If the specified datum has
     * no conversion informations, then this method will returns {@code null}.
     *
     * @param  code The EPSG code of the {@link GeodeticDatum}.
     * @param  toClose The result set to close if this method is going to invokes
     *         {@link #createDatum} recursively. This hack is necessary because many
     *         JDBC drivers do not support multiple result sets for the same statement.
     *         The result set is closed if an only if this method returns a non-null value.
     * @return an array of Bursa-Wolf parameters (in which case {@code toClose} has
     *         been closed), or {@code null} (in which case {@code toClose} has
     *         <strong>not</strong> been closed).
     */
    private BursaWolfParameters[] generateBursaWolfParameters(final String    code,
                                                            final ResultSet toClose)
            throws SQLException, FactoryException
    {
        if (safetyGuard.contains(code)) {
            /*
             * Do not try to create Bursa-Wolf parameters if the datum is already
             * in process of being created. This check avoid never-ending loops in
             * recursive call to 'createDatum'.
             */
            return null;
        }
        PreparedStatement stmt;
        stmt = prepareStatement("BursaWolfParametersSet",
                                         "SELECT CO.COORD_OP_CODE,"
                                 +             " CO.COORD_OP_METHOD_CODE,"
                                 +             " CRS2.DATUM_CODE"
                                 +       " FROM [Coordinate_Operation] AS CO"
                                 + " INNER JOIN [Coordinate Reference System] AS CRS2"
                                 +          " ON CO.TARGET_CRS_CODE = CRS2.COORD_REF_SYS_CODE"
                                 +       " WHERE CO.COORD_OP_METHOD_CODE >= " + BURSA_WOLF_MIN_CODE
                                 +         " AND CO.COORD_OP_METHOD_CODE <= " + BURSA_WOLF_MAX_CODE
                                 +         " AND CO.COORD_OP_CODE <> " + DUMMY_OPERATION // GEOT-1008
                                 +         " AND CO.SOURCE_CRS_CODE IN ("
                                 +      " SELECT CRS1.COORD_REF_SYS_CODE " // GEOT-1129
                                 +        " FROM [Coordinate Reference System] AS CRS1 "
                                 +       " WHERE CRS1.DATUM_CODE = ?)"
                                 +    " ORDER BY CRS2.DATUM_CODE,"
                                 +             " ABS(CO.DEPRECATED), CO.COORD_OP_ACCURACY,"
                                 +             " CO.COORD_OP_CODE DESC"); // GEOT-846 fix
        stmt.setString(1, code);
        ResultSet result = stmt.executeQuery();
        List<Object> bwInfos = null;
        while (result.next()) {
            final String operation = getString(result, 1, code);
            final int    method    = getInt   (result, 2, code);
            final String datum     = getString(result, 3, code);
            if (bwInfos == null) {
                bwInfos = new ArrayList<Object>();
            }
            bwInfos.add(new BursaWolfInfo(operation, method, datum));
        }
        result.close();
        if (bwInfos == null) {
            // Don't close the connection here.
            return null;
        }
        toClose.close();
        /*
         * Sorts the infos in preference order. The "ORDER BY" clause above was not enough;
         * we also need to take the "supersession" table in account. Once the sorting is done,
         * keep only one Bursa-Wolf parameters for each datum.
         */
        int size = bwInfos.size();
        if (size > 1) {
            final BursaWolfInfo[] codes = bwInfos.toArray(new BursaWolfInfo[size]);
            sort(codes);
            bwInfos.clear();
            final Set<String> added = new HashSet<String>();
            for (int i=0; i<codes.length; i++) {
                final BursaWolfInfo candidate = codes[i];
                if (added.add(candidate.target)) {
                    bwInfos.add(candidate);
                }
            }
            size = bwInfos.size();
        }
        /*
         * We got all the needed informations before to built Bursa-Wolf parameters because the
         * 'createDatum(...)' call below may invokes 'createBursaWolfParameters(...)' recursively,
         * and not all JDBC drivers supported multi-result set for the same statement. Now, iterate
         * throw the results and fetch the parameter values for each BursaWolfParameters object.
         */
        stmt = prepareStatement("BursaWolfParameters", "SELECT PARAMETER_CODE,"
                                                     +       " PARAMETER_VALUE,"
                                                     +       " UOM_CODE"
                                                     + " FROM [Coordinate_Operation Parameter Value]"
                                                     + " WHERE COORD_OP_CODE = ?"
                                                     +   " AND COORD_OP_METHOD_CODE = ?");
        for (int i=0; i<size; i++) {
            final BursaWolfInfo info = (BursaWolfInfo) bwInfos.get(i);
            final GeodeticDatum datum;
            try {
                safetyGuard.add(code);
                datum = createGeodeticDatum(info.target);
            } finally {
                safetyGuard.remove(code);
            }
            final BursaWolfParameters parameters = new BursaWolfParameters(datum);
            stmt.setString(1, info.operation);
            stmt.setInt   (2, info.method);
            result = stmt.executeQuery();
            while (result.next()) {
                setBursaWolfParameter(parameters,
                                      getInt   (result, 1, info.operation),
                                      getDouble(result, 2, info.operation),
                  createUnit(getString(result, 3, info.operation)));
            }
            result.close();
            if (info.method == ROTATION_FRAME_CODE) {
                // Coordinate frame rotation (9607): same as 9606,
                // except for the sign of rotation parameters.
                parameters.ex = -parameters.ex;
                parameters.ey = -parameters.ey;
                parameters.ey = -parameters.ey;
            }
            bwInfos.set(i, parameters);
        }
        return bwInfos.toArray(new BursaWolfParameters[size]);
    }

    /**
     * Returns a datum from a code.
     *
     * @param  code Value allocated by authority.
     * @return The datum object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     *
     * @todo Current implementation maps all "vertical" datum to
     *       {@link VerticalDatumType#GEOIDAL}. We don't know yet how
     *       to maps the exact vertical datum type from the EPSG database.
     */
    public synchronized Datum generateDatum(final String code) throws FactoryException {
        ensureNonNull("code", code);
        Datum returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(Datum.class, code,
                    "[Datum]", "DATUM_CODE", "DATUM_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("Datum", "SELECT DATUM_CODE,"
                                           +       " DATUM_NAME,"
                                           +       " DATUM_TYPE,"
                                           +       " ORIGIN_DESCRIPTION,"
                                           +       " REALIZATION_EPOCH,"
                                           +       " AREA_OF_USE_CODE,"
                                           +       " DATUM_SCOPE,"
                                           +       " REMARKS,"
                                           +       " ELLIPSOID_CODE,"     // Only for geodetic type
                                           +       " PRIME_MERIDIAN_CODE" // Only for geodetic type
                                           + " FROM [Datum]"
                                           + " WHERE DATUM_CODE = ?");
            stmt.setString(1, primaryKey);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg    = getString(result, 1, code);
                final String name    = getString(result, 2, code);
                final String type    = getString(result, 3, code).trim().toLowerCase();
                final String anchor  = result.getString( 4);
                final String epoch   = result.getString( 5);
                final String area    = result.getString( 6);
                final String scope   = result.getString( 7);
                final String remarks = result.getString( 8);
                Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                if (anchor != null) {
                    properties.put(Datum.ANCHOR_POINT_KEY, anchor);
                }
                if (epoch!=null && epoch.length()!=0) try {
                    calendar.clear();
                    calendar.set(Integer.parseInt(epoch), 0, 1);
                    properties.put(Datum.REALIZATION_EPOCH_KEY, calendar.getTime());
                } catch (NumberFormatException exception) {
                    // Not a fatal error...
                    Logging.unexpectedException(LOGGER, AbstractEpsgFactory.class, "createDatum", exception);
                }
                final DatumFactory factory = factories.getDatumFactory();
                final Datum datum;
                /*
                 * Now build datum according their datum type. Constructions are straightforward,
                 * except for the "geodetic" datum type which need some special processing:
                 *
                 *   - Because it invokes again 'generateProperties' indirectly (through calls to
                 *     'createEllipsoid' and 'createPrimeMeridian'), it must protect 'properties'
                 *     from changes.
                 *
                 *   - Because 'createBursaWolfParameters' may invokes 'createDatum' recursively,
                 *     we must close the result set if Bursa-Wolf parameters are found. In this
                 *     case, we lost our paranoiac check for duplication.
                 */
                if (type.equals("geodetic")) {
                    properties = new HashMap<String,Object>(properties); // Protect from changes
                    final Ellipsoid         ellipsoid = createEllipsoid    (getString(result,  9, code));
                    final PrimeMeridian      meridian = createPrimeMeridian(getString(result, 10, code));
                    final BursaWolfParameters[] param = generateBursaWolfParameters(primaryKey, result);

                    if (param != null) {
                        result = null; // Already closed by createBursaWolfParameters
                        properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param);
                    }
                    datum = factory.createGeodeticDatum(properties, ellipsoid, meridian);
                } else if (type.equals("vertical")) {
                    // TODO: Find the right datum type.
                    datum = factory.createVerticalDatum(properties, VerticalDatumType.GEOIDAL);
                } else if (type.equals("engineering")) {
                    datum = factory.createEngineeringDatum(properties);
                } else {
                    result.close();
                    throw new FactoryException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type));
                }
                returnValue = ensureSingleton(datum, returnValue, code);
                if (result == null) {
                    // Bypass the 'result.close()' line below:
                    // the ResultSet has already been closed.
                    return returnValue;
                }
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(Datum.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(Datum.class, code);
        }
        return returnValue;
    }

    /**
     * Returns the name and description for the specified {@linkplain CoordinateSystemAxis
     * coordinate system axis} code. Many axis share the same name and description, so it
     * is worth to cache them.
     */
    private AxisName getAxisName(final String code) throws FactoryException {
        assert Thread.holdsLock(this);
        AxisName returnValue = axisNames.get(code);
        if (returnValue == null) try {
            final PreparedStatement stmt;
            stmt = prepareStatement("AxisName", "SELECT COORD_AXIS_NAME, DESCRIPTION, REMARKS"
                                    +       " FROM [Coordinate Axis Name]"
                                    +       " WHERE COORD_AXIS_NAME_CODE = ?");
            stmt.setString(1, code);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String name  = getString(result, 1, code);
                String description = result.getString (2);
                String remarks     = result.getString (3);
                if (description == null) {
                    description = remarks;
                } else if (remarks != null) {
                    description += System.getProperty("line.separator", "\n") + remarks;
                }
                final AxisName axis = new AxisName(name, description);
                returnValue = ensureSingleton(axis, returnValue, code);
            }
            result.close();
            if (returnValue == null) {
                throw noSuchAuthorityCode(AxisName.class, code);
            }
            axisNames.put(code, returnValue);
        } catch (SQLException exception) {
            throw databaseFailure(AxisName.class, code, exception);
        }
        return returnValue;
    }

    /**
     * Returns a {@linkplain CoordinateSystemAxis coordinate system axis} from a code.
     *
     * @param  code Value allocated by authority.
     * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
     * @throws FactoryException if the object creation failed for some other reason.
     */
    public synchronized CoordinateSystemAxis generateCoordinateSystemAxis(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        CoordinateSystemAxis returnValue = null;
        try {
            final String primaryKey = trimAuthority(code);
            final PreparedStatement stmt;
            stmt = prepareStatement("Axis", "SELECT COORD_AXIS_CODE,"
                                    +             " COORD_AXIS_NAME_CODE,"
                                    +             " COORD_AXIS_ORIENTATION,"
                                    +             " COORD_AXIS_ABBREVIATION,"
                                    +             " UOM_CODE"
                                    +       " FROM [Coordinate Axis]"
                                    +      " WHERE COORD_AXIS_CODE = ?");
            stmt.setString(1, code);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg         = getString(result, 1, code);
                final String nameCode     = getString(result, 2, code);
                final String orientation  = getString(result, 3, code);
                final String abbreviation = getString(result, 4, code);
                final String unit         = getString(result, 5, code);
                AxisDirection direction;
                try {
                    direction = DefaultCoordinateSystemAxis.getDirection(orientation);
                } catch (NoSuchElementException exception) {
                    if (orientation.equalsIgnoreCase("Geocentre > equator/PM")) {
                        direction = AxisDirection.OTHER; // TODO: can we choose a more accurate direction?
                    } else if (orientation.equalsIgnoreCase("Geocentre > equator/90dE")) {
                        direction = AxisDirection.EAST;
                    } else if (orientation.equalsIgnoreCase("Geocentre > north pole")) {
                        direction = AxisDirection.NORTH;
                    } else {
                        throw new FactoryException(exception);
                    }
                }
                final AxisName an = getAxisName(nameCode);
                final Map<String,Object> properties = generateProperties(an.name, epsg, an.description);
                final CSFactory factory = factories.getCSFactory();
                final CoordinateSystemAxis axis = factory.createCoordinateSystemAxis(
                        properties, abbreviation, direction, createUnit(unit));
                returnValue = ensureSingleton(axis, returnValue, code);
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(CoordinateSystemAxis.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(CoordinateSystemAxis.class, code);
        }
        return returnValue;
    }

    /**
     * Returns the coordinate system axis from an EPSG code for a {@link CoordinateSystem}.
     * <p>
     * <strong>WARNING:</strong> The EPSG database uses "{@code ORDER}" as a column name.
     * This is tolerated by Access, but MySQL doesn't accept this name.
     *
     * @param  code the EPSG code for coordinate system owner.
     * @param  dimension of the coordinate system, which is also the size of the returned array.
     * @return An array of coordinate system axis.
     * @throws SQLException if an error occured during database access.
     * @throws FactoryException if the code has not been found.
     */
    private CoordinateSystemAxis[] generateAxisForCoordinateSystem(final String code, final int dimension)
            throws SQLException, FactoryException
    {
        assert Thread.holdsLock(this);
        final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[dimension];
        final PreparedStatement stmt;
        stmt = prepareStatement("AxisOrder", "SELECT COORD_AXIS_CODE"
                                +             " FROM [Coordinate Axis]"
                                +            " WHERE COORD_SYS_CODE = ?"
                                +         " ORDER BY [ORDER]");
                                // WARNING: Be careful about the column name :
                                //          MySQL rejects ORDER as a column name !!!
        stmt.setString(1, code);
        final ResultSet result = stmt.executeQuery();
        int i = 0;
        while (result.next()) {
            final String axisCode = getString(result, 1, code);
            if (i < axis.length) {
                // If 'i' is out of bounds, an exception will be thrown after the loop.
                // We don't want to thrown an ArrayIndexOutOfBoundsException here.
                axis[i] = createCoordinateSystemAxis(axisCode);
            }
            ++i;
        }
        result.close();
        if (i != axis.length) {
            throw new FactoryException(Errors.format(
                    ErrorKeys.MISMATCHED_DIMENSION_$2, axis.length, i));
        }
        return axis;
    }

    /**
     * Returns a coordinate system from a code.
     *
     * @param  code Value allocated by authority.
     * @return The coordinate system object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized CoordinateSystem generateCoordinateSystem(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        CoordinateSystem returnValue = null;
        final PreparedStatement stmt;
        try {
            final String primaryKey = toPrimaryKey(CoordinateSystem.class, code,
                    "[Coordinate System]", "COORD_SYS_CODE", "COORD_SYS_NAME");
            stmt = prepareStatement("CoordinateSystem", "SELECT COORD_SYS_CODE,"
                                                      +       " COORD_SYS_NAME,"
                                                      +       " COORD_SYS_TYPE,"
                                                      +       " DIMENSION,"
                                                      +       " REMARKS"
                                                      + " FROM [Coordinate System]"
                                                      + " WHERE COORD_SYS_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String    epsg = getString(result, 1, code);
                final String    name = getString(result, 2, code);
                final String    type = getString(result, 3, code).trim().toLowerCase();
                final int  dimension = getInt   (result, 4, code);
                final String remarks = result.getString( 5);
                final CoordinateSystemAxis[] axis = generateAxisForCoordinateSystem(primaryKey, dimension);
                final Map<String,Object> properties = generateProperties(name, epsg, remarks); // Must be after axis
                final CSFactory factory = factories.getCSFactory();
                CoordinateSystem cs = null;
                if (type.equals("ellipsoidal")) {
                    switch (dimension) {
                        case 2: cs=factory.createEllipsoidalCS(properties, axis[0], axis[1]); break;
                        case 3: cs=factory.createEllipsoidalCS(properties, axis[0], axis[1], axis[2]); break;
                    }
                } else if (type.equals("cartesian")) {
                    switch (dimension) {
                        case 2: cs=factory.createCartesianCS(properties, axis[0], axis[1]); break;
                        case 3: cs=factory.createCartesianCS(properties, axis[0], axis[1], axis[2]); break;
                    }
                } else if (type.equals("spherical")) {
                    switch (dimension) {
                        case 3: cs=factory.createSphericalCS(properties, axis[0], axis[1], axis[2]); break;
                    }
                } else if (type.equals("vertical") || type.equals("gravity-related")) {
                    switch (dimension) {
                        case 1: cs=factory.createVerticalCS(properties, axis[0]); break;
                    }
                } else if (type.equals("linear")) {
                    switch (dimension) {
                        case 1: cs=factory.createLinearCS(properties, axis[0]); break;
                    }
                } else if (type.equals("polar")) {
                    switch (dimension) {
                        case 2: cs=factory.createPolarCS(properties, axis[0], axis[1]); break;
                    }
                } else if (type.equals("cylindrical")) {
                    switch (dimension) {
                        case 3: cs=factory.createCylindricalCS(properties, axis[0], axis[1], axis[2]); break;
                    }
                } else if (type.equals("affine")) {
                    switch (dimension) {
                        case 2: cs=factory.createAffineCS(properties, axis[0], axis[1]); break;
                        case 3: cs=factory.createAffineCS(properties, axis[0], axis[1], axis[2]); break;
                    }
                } else {
                    result.close();
                    throw new FactoryException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type));
                }
                if (cs == null) {
                    result.close();
                    throw new FactoryException(Errors.format(
                                               ErrorKeys.UNEXPECTED_DIMENSION_FOR_CS_$1, type));
                }
                returnValue = ensureSingleton(cs, returnValue, code);
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(CoordinateSystem.class, code, exception);
        }
        if (returnValue == null) {
            throw noSuchAuthorityCode(CoordinateSystem.class, code);
        }
        return returnValue;

    }

    /**
     * Returns the primary key for a coordinate reference system name.
     * This method is used both by {@link #createCoordinateReferenceSystem}
     * and {@link #createFromCoordinateReferenceSystemCodes}
     */
    private String toPrimaryKeyCRS(final String code) throws SQLException, FactoryException {
        return toPrimaryKey(CoordinateReferenceSystem.class, code,
                "[Coordinate Reference System]", "COORD_REF_SYS_CODE", "COORD_REF_SYS_NAME");
    }

    /**
     * Returns a coordinate reference system from a code.
     *
     * @param  code Value allocated by authority.
     * @return The coordinate reference system object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized CoordinateReferenceSystem generateCoordinateReferenceSystem(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        CoordinateReferenceSystem returnValue = null;
        try {
            final String primaryKey = toPrimaryKeyCRS(code);
            final PreparedStatement stmt;
            stmt = prepareStatement("CoordinateReferenceSystem",
                                            "SELECT COORD_REF_SYS_CODE,"
                                          +       " COORD_REF_SYS_NAME,"
                                          +       " AREA_OF_USE_CODE,"
                                          +       " CRS_SCOPE,"
                                          +       " REMARKS,"
                                          +       " COORD_REF_SYS_KIND,"
                                          +       " COORD_SYS_CODE,"       // Null for CompoundCRS
                                          +       " DATUM_CODE,"           // Null for ProjectedCRS
                                          +       " SOURCE_GEOGCRS_CODE,"  // For ProjectedCRS
                                          +       " PROJECTION_CONV_CODE," // For ProjectedCRS
                                          +       " CMPD_HORIZCRS_CODE,"   // For CompoundCRS only
                                          +       " CMPD_VERTCRS_CODE"     // For CompoundCRS only
                                          + " FROM [Coordinate Reference System]"
                                          + " WHERE COORD_REF_SYS_CODE = ?");
            stmt.setString(1, primaryKey);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg    = getString(result, 1, code);
                final String name    = getString(result, 2, code);
                final String area    = result.getString( 3);
                final String scope   = result.getString( 4);
                final String remarks = result.getString( 5);
                final String type    = getString(result, 6, code);
                // Note: Do not invoke 'generateProperties' now, even if we have all required
                //       informations, because the 'properties' map is going to overwritten
                //       by calls to 'createDatum', 'createCoordinateSystem', etc.
                final CRSFactory factory = factories.getCRSFactory();
                final CoordinateReferenceSystem crs;
                /* ----------------------------------------------------------------------
                 *   GEOGRAPHIC CRS
                 *
                 *   NOTE: 'generateProperties' MUST be invoked after any call to an other
                 *         'createFoo' method. Consequently, do not factor out.
                 * ---------------------------------------------------------------------- */
                if (type.equalsIgnoreCase("geographic 2D") ||
                    type.equalsIgnoreCase("geographic 3D"))
                {
                    final String csCode    = getString(result, 7, code);
                    final String dmCode    = result.getString( 8);
                    final EllipsoidalCS cs = createEllipsoidalCS(csCode);
                    final GeodeticDatum datum;
                    if (dmCode != null) {
                        datum = createGeodeticDatum(dmCode);
                    } else {
                        final String geoCode = getString(result, 9, code, 8);
                        result.close(); // Must be close before createGeographicCRS
                        result = null;
                        final GeographicCRS baseCRS = createGeographicCRS(geoCode);
                        datum = baseCRS.getDatum();
                    }
                    final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                    crs = factory.createGeographicCRS(properties, datum, cs);
                }
                /* ----------------------------------------------------------------------
                 *   PROJECTED CRS
                 *
                 *   NOTE: This method invokes itself indirectly, through createGeographicCRS.
                 *         Consequently, we can't use 'result' anymore. We must close it here.
                 * ---------------------------------------------------------------------- */
                else if (type.equalsIgnoreCase("projected")) {
                    final String csCode  = getString(result,  7, code);
                    final String geoCode = getString(result,  9, code);
                    final String opCode  = getString(result, 10, code);
                    result.close(); // Must be close before createGeographicCRS
                    result = null;
                    final CartesianCS         cs = createCartesianCS(csCode);
                    final GeographicCRS  baseCRS = createGeographicCRS(geoCode);
                    final CoordinateOperation op = createCoordinateOperation(opCode);
                    if (op instanceof Conversion) {
                        final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                        crs = factory.createProjectedCRS(properties, baseCRS, (Conversion)op, cs);
                    } else {
                         throw noSuchAuthorityCode(Projection.class, opCode);
                    }
                }
                /* ----------------------------------------------------------------------
                 *   VERTICAL CRS
                 * ---------------------------------------------------------------------- */
                else if (type.equalsIgnoreCase("vertical")) {
                    final String        csCode = getString(result, 7, code);
                    final String        dmCode = getString(result, 8, code);
                    final VerticalCS    cs     = createVerticalCS   (csCode);
                    final VerticalDatum datum  = createVerticalDatum(dmCode);
                    final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                    crs = factory.createVerticalCRS(properties, datum, cs);
                }
                /* ----------------------------------------------------------------------
                 *   COMPOUND CRS
                 *
                 *   NOTE: This method invokes itself recursively.
                 *         Consequently, we can't use 'result' anymore.
                 * ---------------------------------------------------------------------- */
                else if (type.equalsIgnoreCase("compound")) {
                    final String code1 = getString(result, 11, code);
                    final String code2 = getString(result, 12, code);
                    result.close();
                    result = null;
                    final CoordinateReferenceSystem crs1, crs2;
                    if (!safetyGuard.add(epsg)) {
                        throw recursiveCall(CompoundCRS.class, epsg);
                    } try {
                        crs1 = createCoordinateReferenceSystem(code1);
                        crs2 = createCoordinateReferenceSystem(code2);
                    } finally {
                        safetyGuard.remove(epsg);
                    }
                    // Note: Don't invoke 'generateProperties' sooner.
                    final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                    crs  = factory.createCompoundCRS(properties,
                           new CoordinateReferenceSystem[] {crs1, crs2});
                }
                /* ----------------------------------------------------------------------
                 *   GEOCENTRIC CRS
                 * ---------------------------------------------------------------------- */
                else if (type.equalsIgnoreCase("geocentric")) {
                    final String           csCode = getString(result, 7, code);
                    final String           dmCode = getString(result, 8, code);
                    final CoordinateSystem cs     = createCoordinateSystem(csCode);
                    final GeodeticDatum    datum  = createGeodeticDatum   (dmCode);
                    final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                    if (cs instanceof CartesianCS) {
                        crs = factory.createGeocentricCRS(properties, datum, (CartesianCS) cs);
                    } else if (cs instanceof SphericalCS) {
                        crs = factory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
                    } else {
                        result.close();
                        throw new FactoryException(Errors.format(
                                ErrorKeys.ILLEGAL_COORDINATE_SYSTEM_FOR_CRS_$2,
                                cs.getClass(), GeocentricCRS.class));
                    }
                }
                /* ----------------------------------------------------------------------
                 *   ENGINEERING CRS
                 * ---------------------------------------------------------------------- */
                else if (type.equalsIgnoreCase("engineering")) {
                    final String           csCode = getString(result, 7, code);
                    final String           dmCode = getString(result, 8, code);
                    final CoordinateSystem cs     = createCoordinateSystem(csCode);
                    final EngineeringDatum datum  = createEngineeringDatum(dmCode);
                    final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                    crs = factory.createEngineeringCRS(properties, datum, cs);
                }
                /* ----------------------------------------------------------------------
                 *   UNKNOW CRS
                 * ---------------------------------------------------------------------- */
                else {
                    result.close();
                    throw new FactoryException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type));
                }
                returnValue = ensureSingleton(crs, returnValue, code);
                if (result == null) {
                    // Bypass the 'result.close()' line below:
                    // the ResultSet has already been closed.
                    return returnValue;
                }
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(CoordinateReferenceSystem.class, code, exception);
        }
        if (returnValue == null) {
             throw noSuchAuthorityCode(CoordinateReferenceSystem.class, code);
        }
        return returnValue;
    }

    /**
     * Returns a parameter descriptor from a code.
     *
     * @param  code The parameter descriptor code allocated by EPSG authority.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized ParameterDescriptor generateParameterDescriptor(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        ParameterDescriptor returnValue = null;
        final PreparedStatement stmt;
        try {
            final String primaryKey = toPrimaryKey(ParameterDescriptor.class, code,
                    "[Coordinate_Operation Parameter]", "PARAMETER_CODE", "PARAMETER_NAME");
            stmt = prepareStatement("ParameterDescriptor", // Must be singular form.
                                        "SELECT PARAMETER_CODE,"
                                      +       " PARAMETER_NAME,"
                                      +       " DESCRIPTION"
                                      + " FROM [Coordinate_Operation Parameter]"
                                      + " WHERE PARAMETER_CODE = ?");
            stmt.setString(1, primaryKey);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg    = getString(result, 1, code);
                final String name    = getString(result, 2, code);
                final String remarks = result.getString( 3);
                final Unit   unit;
                final Class  type;
                /*
                 * Search for units. We will choose the most commonly used one in parameter values.
                 * If the parameter appears to have at least one non-null value in the "Parameter
                 * File Name" column, then the type is assumed to be URI. Otherwise, the type is a
                 * floating point number.
                 */
                final PreparedStatement units = prepareStatement("ParameterUnit",
                                                  "SELECT MIN(UOM_CODE) AS UOM,"
                                                 +      " MIN(PARAM_VALUE_FILE_REF) AS FILEREF"
                                                 +    " FROM [Coordinate_Operation Parameter Value]"
                                                 +   " WHERE (PARAMETER_CODE = ?)"
                                                 + " GROUP BY UOM_CODE"
                                                 + " ORDER BY COUNT(UOM_CODE) DESC");
                units.setString(1, epsg);
                final ResultSet resultUnits = units.executeQuery();
                if (resultUnits.next()) {
                    String element = resultUnits.getString(1);
                    unit = (element!=null) ? createUnit(element) : null;
                    element = resultUnits.getString(2);
                    type = (element!=null && element.trim().length()!=0) ? URI.class : double.class;
                } else {
                    unit = null;
                    type = double.class;
                }
                resultUnits.close();
                /*
                 * Now creates the parameter descriptor.
                 */
                final ParameterDescriptor descriptor;
                final Map<String,Object> properties = generateProperties(name, epsg, remarks);
                descriptor = new DefaultParameterDescriptor(properties, type,
                                    null, null, null, null, unit, true);
                returnValue = ensureSingleton(descriptor, returnValue, code);
            }
        } catch (SQLException exception) {
            throw databaseFailure(OperationMethod.class, code, exception);
        }
        if (returnValue == null) {
             throw noSuchAuthorityCode(OperationMethod.class, code);
        }
        return returnValue;
    }

    /**
     * Returns all parameter descriptors for the specified method.
     *
     * @param  method The operation method code.
     * @return The parameter descriptors.
     * @throws SQLException if a SQL statement failed.
     */
    private ParameterDescriptor[] generateParameterDescriptors(final String method)
            throws FactoryException, SQLException
    {
        final PreparedStatement stmt;
        stmt = prepareStatement("ParameterDescriptors", // Must be plural form.
                                        "SELECT PARAMETER_CODE"
                                   +    " FROM [Coordinate_Operation Parameter Usage]"
                                   +    " WHERE COORD_OP_METHOD_CODE = ?"
                                   + " ORDER BY SORT_ORDER");
        stmt.setString(1, method);
        final ResultSet results = stmt.executeQuery();
        final List<ParameterDescriptor> descriptors = new ArrayList<ParameterDescriptor>();
        while (results.next()) {
            final String param = getString(results, 1, method);
            descriptors.add(generateParameterDescriptor(param));
        }
        results.close();
        return descriptors.toArray(new ParameterDescriptor[descriptors.size()]);
    }

    /**
     * Fill parameter values in the specified group.
     *
     * @param  method    The EPSG code for the operation method.
     * @param  operation The EPSG code for the operation (conversion or transformation).
     * @param  value     The parameter values to fill.
     * @throws SQLException if a SQL statement failed.
     */
    private void fillParameterValues(final String method,
                                     final String operation,
                                     final ParameterValueGroup parameters)
            throws FactoryException, SQLException
    {
        final PreparedStatement stmt;
        stmt = prepareStatement("ParameterValues",
                                        "SELECT CP.PARAMETER_NAME,"
                                +             " CV.PARAMETER_VALUE,"
                                +             " CV.PARAM_VALUE_FILE_REF,"
                                +             " CV.UOM_CODE"
                                +      " FROM ([Coordinate_Operation Parameter Value] AS CV"
                                + " INNER JOIN [Coordinate_Operation Parameter] AS CP"
                                +          " ON CV.PARAMETER_CODE = CP.PARAMETER_CODE)"
                                + " INNER JOIN [Coordinate_Operation Parameter Usage] AS CU"
                                +         " ON (CP.PARAMETER_CODE = CU.PARAMETER_CODE)"
                                +        " AND (CV.COORD_OP_METHOD_CODE = CU.COORD_OP_METHOD_CODE)"
                                +       " WHERE CV.COORD_OP_METHOD_CODE = ?"
                                +         " AND CV.COORD_OP_CODE = ?"
                                +    " ORDER BY CU.SORT_ORDER");
        stmt.setString(1, method);
        stmt.setString(2, operation);
        final ResultSet result = stmt.executeQuery();
        while (result.next()) {
            final String name  = getString(result, 1, operation);
            final double value = result.getDouble( 2);
            final Unit   unit;
            Object reference;
            if (result.wasNull()) {
                /*
                 * If no numeric values were provided in the database, then the values must
                 * appears in some external file. It may be a file to download from FTP.
                 */
                reference = getString(result, 3, operation);
                try {
                    reference = new URI((String) reference);
                } catch (URISyntaxException exception) {
                    // Ignore: we will stores the reference as a file.
                    reference = new File((String) reference);
                }
                unit = null;
            } else {
                reference = null;
                final String unitCode = result.getString(4);
                unit = (unitCode!=null) ? createUnit(unitCode) : null;
            }
            final ParameterValue param;
            try {
                param = parameters.parameter(name);
            } catch (ParameterNotFoundException exception) {
                /*
                 * Wraps the unchecked ParameterNotFoundException into the checked
                 * NoSuchIdentifierException, which is a FactoryException subclass.
                 * Note that in theory, NoSuchIdentifierException is for MathTransforms rather
                 * than parameters.  However, we are close in spirit here since we are setting
                 * up MathTransform's parameters. Using NoSuchIdentifierException allows users
                 * (including CoordinateOperationSet) to know that the failure is probably
                 * caused by a MathTransform not yet supported in Geotools (or only partially
                 * supported) rather than some more serious failure in the database side.
                 * CoordinateOperationSet uses this information in order to determine if it
                 * should try the next coordinate operation or propagate the exception.
                 */
                final NoSuchIdentifierException e = new NoSuchIdentifierException(
                        Errors.format(ErrorKeys.CANT_SET_PARAMETER_VALUE_$1, name), name);
                e.initCause(exception);
                throw e;
            }
            try {
                if (reference != null) {
                    param.setValue(reference);
                } else if (unit != null) {
                    param.setValue(value, unit);
                } else {
                    param.setValue(value);
                }
            } catch (InvalidParameterValueException exception) {
                throw new FactoryException(
                          Errors.format(ErrorKeys.CANT_SET_PARAMETER_VALUE_$1, name), exception);
            }
        }
        result.close();
    }

    /**
     * Returns an operation method from a code.
     *
     * @param  code The operation method code allocated by EPSG authority.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized OperationMethod generateOperationMethod(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        OperationMethod returnValue = null;
        final PreparedStatement stmt;
        try {
            final String primaryKey = toPrimaryKey(OperationMethod.class, code,
                    "[Coordinate_Operation Method]", "COORD_OP_METHOD_CODE", "COORD_OP_METHOD_NAME");
            stmt = prepareStatement("OperationMethod", "SELECT COORD_OP_METHOD_CODE,"
                                                     +       " COORD_OP_METHOD_NAME,"
                                                     +       " FORMULA,"
                                                     +       " REMARKS"
                                                     " FROM [Coordinate_Operation Method]"
                                                     + " WHERE COORD_OP_METHOD_CODE = ?");
            stmt.setString(1, primaryKey);
            final ResultSet result = stmt.executeQuery();
            OperationMethod method = null;
            while (result.next()) {
                final String epsg    = getString(result, 1, code);
                final String name    = getString(result, 2, code);
                final String formula = result.getString( 3);
                final String remarks = result.getString( 4);
                final int encoded = getDimensionsForMethod(epsg);
                final int sourceDimensions = encoded >>> 16;
                final int targetDimensions = encoded & 0xFFFF;
                final ParameterDescriptor[] descriptors = generateParameterDescriptors(epsg);
                final Map<String,Object> properties = generateProperties(name, epsg, remarks);
                if (formula != null) {
                    properties.put(OperationMethod.FORMULA_KEY, formula);
                }
                method = new DefaultOperationMethod(properties, sourceDimensions, targetDimensions,
                         new DefaultParameterDescriptorGroup(properties, descriptors));
                returnValue = ensureSingleton(method, returnValue, code);
            }
        } catch (SQLException exception) {
            throw databaseFailure(OperationMethod.class, code, exception);
        }
        if (returnValue == null) {
             throw noSuchAuthorityCode(OperationMethod.class, code);
        }
        return returnValue;
    }

    /**
     * Returns the must common source and target dimensions for the specified method.
     * Source dimension is encoded in the 16 highest bits and target dimension is encoded
     * in the 16 lowest bits. If this method can't infers the dimensions from the "Coordinate
     * Operation" table, then the operation method is probably a projection, which always have
     * (2,2) dimensions in the EPSG database.
     */
    private int getDimensionsForMethod(final String code) throws SQLException {
        final PreparedStatement stmt;
        stmt = prepareStatement("MethodDimensions", "SELECT SOURCE_CRS_CODE,"
                                                +         " TARGET_CRS_CODE"
                                                " FROM [Coordinate_Operation]"
                                                + " WHERE COORD_OP_METHOD_CODE = ?"
                                                +   " AND SOURCE_CRS_CODE IS NOT NULL"
                                                +   " AND TARGET_CRS_CODE IS NOT NULL");
        stmt.setString(1, code);
        final ResultSet result = stmt.executeQuery();
        final Map<Dimensions,Dimensions> dimensions = new HashMap<Dimensions,Dimensions>();
        final Dimensions  temp = new Dimensions((2 << 16) | 2); // Default to (2,2) dimensions.
        Dimensions max = temp;
        while (result.next()) {
            final short sourceDimensions = getDimensionForCRS(result.getString(1));
            final short targetDimensions = getDimensionForCRS(result.getString(2));
            temp.encoded = (sourceDimensions << 16) | (targetDimensions);
            Dimensions candidate = dimensions.get(temp);
            if (candidate == null) {
                candidate = new Dimensions(temp.encoded);
                dimensions.put(candidate, candidate);
            }
            if (++candidate.occurences > max.occurences) {
                max = candidate;
            }
        }
        result.close();
        return max.encoded;
    }

    /** A counter for source and target dimensions (to be kept together). */
    private static final class Dimensions {
        /** The dimensions as an encoded value. */ int encoded;
        /** The occurences of this dimensions.  */ int occurences;
        Dimensions(final int e) {
            encoded = e;
        }
        @Override public int hashCode() {
            return encoded;
        }
        @Override public boolean equals(final Object object) { // MUST ignore 'occurences'.
            return (object instanceof Dimensions) && ((Dimensions) object).encoded == encoded;
        }
        @Override public String toString() {
            return "[(" + (encoded >>> 16) + ',' + (encoded & 0xFFFF) + ")\u00D7" + occurences + ']';
        }
    }

    /**
     * Returns the dimension of the specified CRS. If the CRS is not found (which should not
     * happen, but we don't need to be strict here), then this method assumes a two-dimensional
     * CRS.
     */
    private short getDimensionForCRS(final String code) throws SQLException {
        final PreparedStatement stmt;
        final Short cached = axisCounts.get(code);
        final short dimension;
        if (cached == null) {
          stmt = prepareStatement("Dimension",
                "  SELECT COUNT(COORD_AXIS_CODE)"
                    " FROM [Coordinate Axis]"
                    + " WHERE COORD_SYS_CODE = (SELECT COORD_SYS_CODE "
                    +                           " FROM [Coordinate Reference System]"
                    +                          " WHERE COORD_REF_SYS_CODE = ?)");
          stmt.setString(1, code);
          final ResultSet result = stmt.executeQuery();
          dimension = result.next() ? result.getShort(1) : 2;
          axisCounts.put(code, dimension);
          result.close();
        } else {
          dimension = cached.shortValue();
        }
        return dimension;
    }

    /**
     * Returns {@code true} if the {@linkplain CoordinateOperation coordinate operation} for the
     * specified code is a {@linkplain Projection projection}. The caller must have ensured that
     * the designed operation is a {@linkplain Conversion conversion} before to invoke this method.
     */
    final synchronized  boolean isProjection(final String code) throws SQLException {
        final PreparedStatement stmt;
        Boolean projection = (Boolean) codeProjection.get(code);
        if (projection == null) {
          stmt = prepareStatement("isProjection", "SELECT COORD_REF_SYS_CODE"
                                                " FROM [Coordinate Reference System]"
                                                + " WHERE PROJECTION_CONV_CODE = ?"
                                                +   " AND COORD_REF_SYS_KIND LIKE 'projected%'");
          stmt.setString(1, code);
          final ResultSet result = stmt.executeQuery();
          final boolean found = result.next();
          result.close();
          projection = Boolean.valueOf(found);
          codeProjection.put(code, projection);
        }
        return projection.booleanValue();
    }

    /**
     * Returns a coordinate operation from a code.
     * The returned object will either be a {@linkplain Conversion conversion} or a
     * {@linkplain Transformation transformation}, depending on the code.
     *
     * @param  code Value allocated by authority.
     * @return The coordinate operation object.
     * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
     * @throws FactoryException if some other kind of failure occured in the backing
     *         store. This exception usually have {@link SQLException} as its cause.
     */
    public synchronized CoordinateOperation generateCoordinateOperation(final String code)
            throws FactoryException
    {
        ensureNonNull("code", code);
        CoordinateOperation returnValue = null;
        try {
            final String primaryKey = toPrimaryKey(CoordinateOperation.class, code,
                    "[Coordinate_Operation]", "COORD_OP_CODE", "COORD_OP_NAME");
            final PreparedStatement stmt;
            stmt = prepareStatement("CoordinateOperation", "SELECT COORD_OP_CODE,"
                                                         +       " COORD_OP_NAME,"
                                                         +       " COORD_OP_TYPE,"
                                                         +       " SOURCE_CRS_CODE,"
                                                         +       " TARGET_CRS_CODE,"
                                                         +       " COORD_OP_METHOD_CODE,"
                                                         +       " COORD_TFM_VERSION,"
                                                         +       " COORD_OP_ACCURACY,"
                                                         +       " AREA_OF_USE_CODE,"
                                                         +       " COORD_OP_SCOPE,"
                                                         +       " REMARKS"
                                                         + " FROM [Coordinate_Operation]"
                                                         + " WHERE COORD_OP_CODE = ?");
            stmt.setString(1, primaryKey);
            ResultSet result = stmt.executeQuery();
            while (result.next()) {
                final String epsg = getString(result, 1, code);
                final String name = getString(result, 2, code);
                final String type = getString(result, 3, code).trim().toLowerCase();
                final boolean isTransformation = type.equals("transformation");
                final boolean isConversion     = type.equals("conversion");
                final boolean isConcatenated   = type.equals("concatenated operation");
                final String sourceCode, targetCode, methodCode;
                if (isConversion) {
                    // Optional for conversions, mandatory for all others.
                    sourceCode = result.getString(4);
                    targetCode = result.getString(5);
                } else {
                    sourceCode = getString(result, 4, code);
                    targetCode = getString(result, 5, code);
                }
                if (isConcatenated) {
                    // Not applicable to concatenated operation, mandatory for all others.
                    methodCode = result.getString(6);
                } else {
                    methodCode = getString(result, 6, code);
                }
                String version  = result.getString( 7);
                double accuracy = result.getDouble( 8); if (result.wasNull()) accuracy=Double.NaN;
                String area     = result.getString( 9);
                String scope    = result.getString(10);
                String remarks  = result.getString(11);
                /*
                 * Gets the source and target CRS. They are mandatory for transformations (it
                 * was checked above in this method) and optional for conversions. Conversions
                 * are usually "defining conversions" and don't define source and target CRS.
                 * In EPSG database 6.7, all defining conversions are projections and their
                 * dimensions are always 2. However, this is not generalizable to other kind
                 * of operation methods. For example the "Geocentric translation" operation
                 * method has 3-dimensional source and target.
                 */
                final int sourceDimensions, targetDimensions;
                final CoordinateReferenceSystem sourceCRS, targetCRS;
                if (sourceCode != null) {
                    sourceCRS = createCoordinateReferenceSystem(sourceCode);
                    sourceDimensions = sourceCRS.getCoordinateSystem().getDimension();
                } else {
                    sourceCRS = null;
                    sourceDimensions = 2; // Acceptable default for projections only.
                }
                if (targetCode != null) {
                    targetCRS = createCoordinateReferenceSystem(targetCode);
                    targetDimensions = targetCRS.getCoordinateSystem().getDimension();
                } else {
                    targetCRS = null;
                    targetDimensions = 2; // Acceptable default for projections only.
                }
                /*
                 * Gets the operation method. This is mandatory for conversions and transformations
                 * (it was checked above in this method) but optional for concatenated operations.
                 * Fetching parameter values is part of this block.
                 */
                final boolean             isBursaWolf;
                OperationMethod           method;
                final ParameterValueGroup parameters;
                if (methodCode == null) {
                    isBursaWolf = false;
                    method      = null;
                    parameters  = null;
                } else {
                    final int num;
                    try {
                        num = Integer.parseInt(methodCode);
                    } catch (NumberFormatException exception) {
                        result.close();
                        throw new FactoryException(exception);
                    }
                    isBursaWolf = (num>=BURSA_WOLF_MIN_CODE && num<=BURSA_WOLF_MAX_CODE);
                    // Reminder: The source and target dimensions MUST be computed when
                    //           the information is available. Dimension is not always 2!!
                    method = generateOperationMethod(methodCode);
                    if (method.getSourceDimensions() != sourceDimensions ||
                        method.getTargetDimensions() != targetDimensions)
                    {
                        method = new DefaultOperationMethod(method, sourceDimensions, targetDimensions);
                    }
                    /*
                     * Note that some parameters required for MathTransform creation are implicit in
                     * the EPSG database (e.g. semi-major and semi-minor axis length in the case of
                     * map projections). We ask the parameter value group straight from the math
                     * transform factory instead of from the operation method in order to get all
                     * required parameter descriptors, including implicit ones.
                     */
                    final String classe = method.getName().getCode();
                    parameters = factories.getMathTransformFactory().getDefaultParameters(classe);
                    fillParameterValues(methodCode, epsg, parameters);
                }
                /*
                 * Creates common properties. The 'version' and 'accuracy' are usually defined
                 * for transformations only. However, we check them for all kind of operations
                 * (including conversions) and copy the information inconditionnaly if present.
                 *
                 * NOTE: This block must be executed last before object creations below, because
                 *       methods like createCoordinateReferenceSystem and createOperationMethod
                 *       overwrite the properties map.
                 */
                final Map<String,Object> properties = generateProperties(name, epsg, area, scope, remarks);
                if (version!=null && (version=version.trim()).length()!=0) {
                    properties.put(CoordinateOperation.OPERATION_VERSION_KEY, version);
                }
                if (!Double.isNaN(accuracy)) {
                    final QuantitativeResultImpl                 accuracyResult;
                    final AbsoluteExternalPositionalAccuracyImpl accuracyElement;
                    accuracyResult = new QuantitativeResultImpl(new double[]{accuracy});
                    // TODO: Need to invoke something equivalent to:
                    // accuracyResult.setValueType(Float.class);
                    // This is the type declared in the MS-Access database.
                    accuracyResult.setValueUnit(SI.METER); // In meters by definition in the EPSG database.
                    accuracyElement = new AbsoluteExternalPositionalAccuracyImpl(accuracyResult);
                    accuracyElement.setMeasureDescription(TRANSFORMATION_ACCURACY);
                    accuracyElement.setEvaluationMethodType(EvaluationMethodType.DIRECT_EXTERNAL);
                    properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
                                   new PositionalAccuracy[] {
                                       (PositionalAccuracy)accuracyElement.unmodifiable()
                                   });
                }
                /*
                 * Creates the operation. Conversions should be the only operations allowed to
                 * have null source and target CRS. In such case, the operation is a defining
                 * conversion (usually to be used later as part of a ProjectedCRS creation),
                 * and always a projection in the specific case of the EPSG database (which
                 * allowed us to assume 2-dimensional operation method in the code above for
                 * this specific case - not to be generalized to the whole EPSG database).
                 */
                final CoordinateOperation operation;
                if (isConversion && (sourceCRS==null || targetCRS==null)) {
                    // Note: we usually can't resolve sourceCRS and targetCRS because there
                    // is many of them for the same coordinate operation (projection) code.
                    operation = new DefiningConversion(properties, method, parameters);
                } else if (isConcatenated) {
                    /*
                     * Concatenated operation: we need to close the current result set, because
                     * we are going to invoke this method recursively in the following lines.
                     *
                     * Note: we instantiate directly the Geotools's implementation of
                     * ConcatenatedOperation instead of using CoordinateOperationFactory in order
                     * to avoid loading the quite large Geotools's implementation of this factory,
                     * and also because it is not part of FactoryGroup anyway.
                     */
                    result.close();
                    result = null;
                    final PreparedStatement cstmt = prepareStatement("ConcatenatedOperation",
                                                        "SELECT SINGLE_OPERATION_CODE"
                                                   +    " FROM [Coordinate_Operation Path]"
                                                   +   " WHERE (CONCAT_OPERATION_CODE = ?)"
                                                   + " ORDER BY OP_PATH_STEP");
                    cstmt.setString(1, epsg);
                    final ResultSet cr = cstmt.executeQuery();
                    final List<String> codes = new ArrayList<String>();
                    while (cr.next()) {
                        codes.add(cr.getString(1));
                    }
                    cr.close();
                    final CoordinateOperation[] operations = new CoordinateOperation[codes.size()];
                    if (!safetyGuard.add(epsg)) {
                        throw recursiveCall(ConcatenatedOperation.class, epsg);
                    } try {
                        for (int i=0; i<operations.length; i++) {
                            operations[i] = createCoordinateOperation(codes.get(i));
                        }
                    } finally {
                        safetyGuard.remove(epsg);
                    }
                    try {
                        return new DefaultConcatenatedOperation(properties, operations);
                    } catch (IllegalArgumentException exception) {
                        // May happen if there is less than 2 operations to concatenate.
                        // It happen for some deprecated CRS like 8658 for example.
                        throw new FactoryException(exception);
                    }
                } else {
                    /*
                     * Needs to create a math transform. A special processing is performed for
                     * datum shift methods, since the conversion from ellipsoid to geocentric
                     * for "geocentric translations" is implicit in the EPSG database. Even in
                     * the case of Molodenski transforms, the axis length to set are the same.
                     */
                    if (isBursaWolf) try {
                        Ellipsoid ellipsoid = CRSUtilities.getHeadGeoEllipsoid(sourceCRS);
                        if (ellipsoid != null) {
                            final Unit axisUnit = ellipsoid.getAxisUnit();
                            parameters.parameter("src_semi_major").setValue(ellipsoid.getSemiMajorAxis(), axisUnit);
                            parameters.parameter("src_semi_minor").setValue(ellipsoid.getSemiMinorAxis(), axisUnit);
                            parameters.parameter("src_dim").setValue(sourceCRS.getCoordinateSystem().getDimension());
                        }
                        ellipsoid = CRSUtilities.getHeadGeoEllipsoid(targetCRS);
                        if (ellipsoid != null) {
                            final Unit axisUnit = ellipsoid.getAxisUnit();
                            parameters.parameter("tgt_semi_major").setValue(ellipsoid.getSemiMajorAxis(), axisUnit);
                            parameters.parameter("tgt_semi_minor").setValue(ellipsoid.getSemiMinorAxis(), axisUnit);
                            parameters.parameter("tgt_dim").setValue(targetCRS.getCoordinateSystem().getDimension());
                        }
                    } catch (ParameterNotFoundException exception) {
                        result.close();
                        throw new FactoryException(Errors.format(
                                ErrorKeys.GEOTOOLS_EXTENSION_REQUIRED_$1,
                                method.getName().getCode(), exception));
                    }
                    /*
                     * At this stage, the parameters are ready for use. Creates the math transform
                     * and wraps it in the final operation (a Conversion or a Transformation).
                     */
                    final Class expected;
                    if (isTransformation) {
                        expected = Transformation.class;
                    } else if (isConversion) {
                        expected = Conversion.class;
                    } else {
                        result.close();
                        throw new FactoryException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type));
                    }
                    final MathTransform mt = factories.getMathTransformFactory().createBaseToDerived(
                            sourceCRS, parameters, targetCRS.getCoordinateSystem());
                    // TODO: uses GeoAPI factory method once available.
                    operation = DefaultOperation.create(properties, sourceCRS, targetCRS,
                                                        mt, method, expected);
                }
                returnValue = ensureSingleton(operation, returnValue, code);
                if (result == null) {
                    // Bypass the 'result.close()' line below:
                    // the ResultSet has already been closed.
                    return returnValue;
                }
            }
            result.close();
        } catch (SQLException exception) {
            throw databaseFailure(CoordinateOperation.class, code, exception);
        }
        if (returnValue == null) {
             throw noSuchAuthorityCode(CoordinateOperation.class, code);
        }
        return returnValue;
    }

    /**
     * Creates operations from coordinate reference system codes.
     * The returned set is ordered with the most accurate operations first.
     *
     * @param sourceCode Coded value of source coordinate reference system.
     * @param targetCode Coded value of target coordinate reference system.
     * @throws FactoryException if the object creation failed.
     *
     * @todo The ordering is not consistent among all database software, because the "accuracy"
     *       column may contains null values. When used in an "ORDER BY" clause, PostgreSQL put
     *       null values last, while Access and HSQL put them first. The PostgreSQL's behavior is
     *       better for what we want (put operations with unknow accuracy last). Unfortunatly,
     *       I don't know yet how to instruct Access to put null values last using standard SQL
     *       ("IIF" is not standard, and Access doesn't seem to understand "CASE ... THEN" clauses).
     */
    public synchronized Set generateFromCoordinateReferenceSystemCodes(final String sourceCode,
                                                                     final String targetCode)
            throws FactoryException
    {
        ensureNonNull("sourceCode", sourceCode);
        ensureNonNull("targetCode", targetCode);
        final String pair = sourceCode + " \u21E8 " + targetCode;
        final CoordinateOperationSet set = new CoordinateOperationSet(this);
        try {
            final String sourceKey = toPrimaryKeyCRS(sourceCode);
            final String targetKey = toPrimaryKeyCRS(targetCode);
            boolean searchTransformations = false;
            do {
                /*
                 * This 'do' loop is executed twice: the first time for searching defining
                 * conversions, and the second time for searching all other kind of operations.
                 * Defining conversions are searched first because they are, by definition, the
                 * most accurate operations.
                 */
                final String key, sql;
                if (searchTransformations) {
                    key = "TransformationFromCRS";
                    sql = "SELECT COORD_OP_CODE"
                        +    " FROM [Coordinate_Operation]"
                        +    " WHERE SOURCE_CRS_CODE = ?"
                        +      " AND TARGET_CRS_CODE = ?"
                        + " ORDER BY ABS(DEPRECATED), COORD_OP_ACCURACY";
                } else {
                    key = "ConversionFromCRS";
                    sql = "SELECT PROJECTION_CONV_CODE"
                        +    " FROM [Coordinate Reference System]"
                        +    " WHERE SOURCE_GEOGCRS_CODE = ?"
                        +      " AND COORD_REF_SYS_CODE = ?";
                }
                final PreparedStatement stmt = prepareStatement(key, sql);
                stmt.setString(1, sourceKey);
                stmt.setString(2, targetKey);
                final ResultSet result = stmt.executeQuery();
                while (result.next()) {
                    final String code = getString(result, 1, pair);
                    set.addAuthorityCode(code, searchTransformations ? null : targetKey);
                }
                result.close();
            } while ((searchTransformations = !searchTransformations) == true);
            /*
             * Search finished. We may have a lot of coordinate operations
             * (e.g. about 40 for "ED50" (EPSG:4230) to "WGS 84" (EPSG:4326)).
             * Alter the ordering using the information supplied in the supersession table.
             */
            final String[] codes = set.getAuthorityCodes();
            sort(codes);
            set.setAuthorityCodes(codes);
        } catch (SQLException exception) {
            throw databaseFailure(CoordinateOperation.class, pair, exception);
        }
        /*
         * Before to return the set, tests the creation of 1 object in order to report early
         * (i.e. now) any problems with SQL statements. Remaining operations will be created
         * only when first needed.
         */
        set.resolve(1);
        return set;
    }

    /**
     * Sorts an array of codes in preference order. This method orders pairwise the codes according
     * the information provided in the supersession table. If the same object is superseded by more
     * than one object, then the most recent one is inserted first. Except for the codes moved as a
     * result of pairwise ordering, this method try to preserve the old ordering of the supplied
     * codes (since deprecated operations should already be last). The ordering is performed in
     * place.
     *
     * @param codes The codes, usually as an array of {@link String}. If the array do not contains
     *              string objects, then the {@link Object#toString} method must returns the code
     *              for each element.
     */
    // TODO: Use generic type for "Object[] codes" with J2SE 1.5.
    private void sort(final Object[] codes) throws SQLException, FactoryException {
        if (codes.length <= 1) {
            return; // Nothing to sort.
        }
        final PreparedStatement stmt;
        stmt = prepareStatement("Supersession", "SELECT SUPERSEDED_BY"
                                              + " FROM [Supersession]"
                                              + " WHERE OBJECT_CODE = ?"
                                              + " ORDER BY SUPERSESSION_YEAR DESC");
        int maxIterations = 15; // For avoiding never-ending loop.
        do {
            boolean changed = false;
            for (int i=0; i<codes.length; i++) {
                final String code = codes[i].toString();
                stmt.setString(1, code);
                final ResultSet result = stmt.executeQuery();
                while (result.next()) {
                    final String replacement = getString(result, 1, code);
                    for (int j=i+1; j<codes.length; j++) {
                        final Object candidate = codes[j];
                        if (replacement.equals(candidate.toString())) {
                            /*
                             * Found a code to move in front of the superceded one.
                             */
                            System.arraycopy(codes, i, codes, i+1, j-i);
                            codes[i++] = candidate;
                            changed = true;
                        }
                    }
                }
                result.close();
            }
            if (!changed) {
                return;
            }
        }
        while (--maxIterations != 0);
        LOGGER.finer("Possible recursivity in supersessions.");
    }

    /**
     * An implementation of {@link IdentifiedObjectFinder} which scans over a smaller set
     * of authority codes.
     * <p>
     * <b>Implementation note:</b> Since this method may be invoked indirectly by
     * {@link LongitudeFirstFactory}, it must be insensitive to axis order.
     */
    private final class Finder extends IdentifiedObjectFinder {
        /**
         * Creates a new finder backed by the specified <em>buffered</em> authority factory.
         */
        Finder( final Class/*<? extends IdentifiedObject>*/ type) {
            super( AbstractEpsgFactory.this, type);
        }

        /**
         * Returns a set of authority codes that <strong>may</strong> identify the same object
         * than the specified one. This implementation tries to get a smaller set than what
         * {@link AbstractEpsgFactory#getAuthorityCodes} would produce.
         */
        @Override
        protected Set getCodeCandidates(final IdentifiedObject object) throws FactoryException {
            String select = "COORD_REF_SYS_CODE";
            String from   = "[Coordinate Reference System]";
            String where, code;
            if (object instanceof Ellipsoid) {
                select = "ELLIPSOID_CODE";
                from   = "[Ellipsoid]";
                where  = "SEMI_MAJOR_AXIS";
                code   = Double.toString(((Ellipsoid) object).getSemiMajorAxis());
            } else {
                IdentifiedObject dependency;
                if (object instanceof GeneralDerivedCRS) {
                    dependency = ((GeneralDerivedCRS) object).getBaseCRS();
                    where      = "SOURCE_GEOGCRS_CODE";
                } else if (object instanceof SingleCRS) {
                    dependency = ((SingleCRS) object).getDatum();
                    where      = "DATUM_CODE";
                } else if (object instanceof GeodeticDatum) {
                    dependency = ((GeodeticDatum) object).getEllipsoid();
                    select     = "DATUM_CODE";
                    from       = "[Datum]";
                    where      = "ELLIPSOID_CODE";
                } else {
                    return super.getCodeCandidates(object);
                }
                dependency = getIdentifiedObjectFinder(dependency.getClass()).find(dependency);
                Identifier id = AbstractIdentifiedObject.getIdentifier(dependency, getAuthority());
                if (id == null || (code = id.getCode()) == null) {
                    return super.getCodeCandidates(object);
                }
            }
            String sql = "SELECT " + select + " FROM " + from + " WHERE " + where + "='" + code + '\'';
            sql = adaptSQL(sql);
            final Set<String> result = new LinkedHashSet<String>();
            try {
                final Statement s = getConnection().createStatement();
                final ResultSet r = s.executeQuery(sql);
                while (r.next()) {
                    result.add(r.getString(1));
                }
                r.close();
                s.close();
            } catch (SQLException exception) {
                throw databaseFailure(Identifier.class, code, exception);
            }
            return result;
        }
    }

    /**
     * Constructs an exception for recursive calls.
     */
    private static FactoryException recursiveCall(final Class type, final String code) {
        return new FactoryException(Errors.format(ErrorKeys.RECURSIVE_CALL_$2, type, code));
    }

    /**
     * Constructs an exception for a database failure.
     */
    private static FactoryException databaseFailure(final Class type, final String code,
                                                    final SQLException cause)
    {
        return new FactoryException(Errors.format(ErrorKeys.DATABASE_FAILURE_$2, type, code), cause);
    }

    /**
     * Invoked when a new {@link PreparedStatement} is about to be created from a SQL string.
     * Since the <A HREF="http://www.epsg.org">EPSG database</A> is available mainly in MS-Access
     * format, SQL statements are formatted using some syntax specific to this particular database
     * software (for example "<code>SELECT * FROM [Coordinate Reference System]</code>"). When
     * prociding subclass targeting another database vendor, then this method should be overridden
     * in order to adapt the local SQL syntax.
     * <p>
     * For example a subclass connecting to a <cite>PostgreSQL</cite> database could replace
     * all spaces ("&nbsp;") between watching braces ("[" and "]") by underscore ("_").
     *
     * @param  statement The statement in MS-Access syntax.
     * @return The SQL statement to use. The default implementation returns the string unchanged.
     */
    protected abstract String adaptSQL(final String statement);

    /**
     * Returns {@code true} if the specified code may be a primary key in some table. This method
     * do not needs to checks any entry in the database. It should just checks from the syntax if
     * the code looks like a valid EPSG identifier. The default implementation returns {@code true}
     * if all non-space characters are {@linkplain Character#isDigit(char) digits}.
     * <p>
     * When this method returns {@code false}, some {@code createFoo(...)} methods look for the
     * code in the name column instead of the primary key column. This allows to accept the
     * "<cite>NTF (Paris) / France I</cite>" string (for example) in addition to the {@code "27581"}
     * primary key. Both should fetch the same object.
     * <p>
     * If this method returns {@code true} in all cases, then this factory never search for matching
     * names. In such case, an appropriate exception will be thrown in {@code createFoo(...)}
     * methods if the code is not found in the primary key column. Subclasses can overrides this
     * method that way if this is the intended behavior.
     *
     * @param  code The code the inspect.
     * @return {@code true} if the code is probably a primary key.
     * @throws FactoryException if an unexpected error occured while inspecting the code.
     */
    protected boolean isPrimaryKey(final String code) throws FactoryException {
        final int length = code.length();
        for (int i=0; i<length; i++) {
            final char c = code.charAt(i);
            if (!Character.isDigit(c) && !Character.isSpaceChar(c)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns {@code true} if it is safe to dispose this factory. This method is invoked indirectly
     * by {@link ThreadedEpsgFactory} after some timeout in order to release resources. This method will
     * block the disposal if some {@linkplain #getAuthorityCodes set of authority codes} are still
     * in use.
     */
    final synchronized boolean canDispose() {
        return true;
    }

    /**
     * Disposes any resources hold by this object.
     *
     * @throws FactoryException if an error occurred while closing the connection.
     */
    @Override
    public synchronized void dispose() throws FactoryException {
        disconnect();
        super.dispose();
    }

    /**
     * Connect to the database in anticipation of of use.
     * @throws FactoryException
     */
    public void connect() throws FactoryException {
        try {
            getConnection();
        } catch (SQLException e) {
            throw new FactoryException(e);
        }
    }
    /**
     * Disconnect from the database, and remain idle. We will still keep our
     * internal data structures, we are not going to hold onto a database connection
     * unless we are going to be used.
     *
     * @throws FactoryException
     */
    public void disconnect() throws FactoryException {
        if (connection != null) {
            final boolean isClosed;
            try {
                isClosed = connection.isClosed();
                for (final Iterator it=statements.values().iterator(); it.hasNext();) {
                    ((PreparedStatement) it.next()).close();
                    it.remove();
                }
                connection.close();
            } catch (SQLException exception) {
                throw new FactoryException(exception);
            }
            if (!isClosed) {
                /*
                 * The above code was run unconditionally as a safety, even if the connection
                 * was already closed. However we will log a message only if we actually closed
                 * the connection, otherwise the log records are a little bit misleading.
                 */
                final LogRecord record = Loggings.format(Level.FINE, LoggingKeys.CLOSED_EPSG_DATABASE);
                record.setLoggerName(LOGGER.getName());
                LOGGER.log(record);
            }
            connection = null;
        }
    }

    /**
     * Access to the connection used by this EpsgFactory. The connection will
     * be created as needed.
     *
     * @return the connection
     */
    protected synchronized Connection getConnection() throws SQLException {
        if (connection == null) {
            connection = dataSource.getConnection();
        }
        return connection;
    }
    /**
     * Shutdown the database engine. This method is invoked twice by {@link ThreadedEpsgFactory}
     * at JVM shutdown: one time before the {@linkplain #connection} is closed, and a second
     * time after. This shutdown hook is useful for <cite>embedded</cite> database engine
     * starting a server process in addition to the client process. Just closing the connection
     * is not enough for them. Example:
     * <P>
     * <UL>
     *   <LI>HSQL database engine needs to execute a {@code "SHUTDOWN"} statement using the
     *      {@linkplain #connection} before it is closed.</LI>
     *   <LI>Derby database engine needs to instruct the {@linkplain java.sql.DriverManager driver
     *       manager} after all connections have been closed.</LI>
     * </UL>
     * <P>
     * The default implementation does nothing, which is sufficient for implementations
     * connecting to a distant server (i.e. non-embedded database engine), for example
     * {@linkplain AccessDataSource MS-Access} or {@linkplain PostgreDataSource PostgreSQL}.
     *
     * @param active {@code true} if the {@linkplain #connection} is alive, or {@code false}
     *        otherwise. This method is invoked first with {@code active} set to {@code true},
     *        then a second time with {@code active} set to {@code false}.
     * @throws SQLException if this method failed to shutdown the database engine.
     */
    protected void shutdown(final boolean active) throws SQLException {
    }

    /**
     * Invokes {@link #dispose} when this factory is garbage collected.
     *
     * @throws Throwable if an error occurred while closing the connection.
     */
    @Override
    protected final void finalize() throws Throwable {
        dispose();
        super.finalize();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    //////                                                                                 ///////
    //////   HARD CODED VALUES (other than SQL statements) RELATIVE TO THE EPSG DATABASE   ///////
    //////                                                                                 ///////
    //////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Returns a hard-coded unit from an EPSG code. We do not need to provide all units here,
     * but we must at least provide all base units declared in the [TARGET_UOM_CODE] column
     * of table [Unit of Measure]. Other units will be derived automatically if they are not
     * listed here.
     *
     * @param  code The code.
     * @return The unit, or {@code null} if the code is unrecognized.
     */
    private static Unit<?> getUnit(final int code) {
        switch (code) {
            case 9001: return    SI.METER;
            case 9002: return NonSI.FOOT;
            case 9030: return NonSI.NAUTICAL_MILE;
            case 9036: return    SI.KILO(SI.METER);
            case 9101: return    SI.RADIAN;
            case 9122: // Fall through
            case 9102: return NonSI.DEGREE_ANGLE;
            case 9103: return NonSI.MINUTE_ANGLE;
            case 9104: return NonSI.SECOND_ANGLE;
            case 9105: return NonSI.GRADE;
            case 9107: return Units.DEGREE_MINUTE_SECOND;
            case 9108: return Units.DEGREE_MINUTE_SECOND;
            case 9109: return    SI.MICRO(SI.RADIAN);
            case 9110: return Units.SEXAGESIMAL_DMS;
//TODO      case 9111: return NonSI.SEXAGESIMAL_DM;
            case 9203: // Fall through
            case 9201: return  Unit.ONE;
            case 9202: return Units.PPM;
            default  : return null;
        }
    }

    /**
     * Set a Bursa-Wolf parameter from an EPSG parameter.
     *
     * @param  parameters The Bursa-Wolf parameters to modify.
     * @param  code       The EPSG code for a parameter   from [PARAMETER_CODE]  column.
     * @param  value      The value of the parameter      from [PARAMETER_VALUE] column.
     * @param  unit       The unit of the parameter value from [UOM_CODE]        column.
     * @throws FactoryException if the code is unrecognized.
     */
    private static void setBursaWolfParameter(final BursaWolfParameters parameters,
                                              final int code, double value, final Unit<?> unit)
            throws FactoryException
    {
        Unit<?> target = unit;
        if (code >= 8605) {
            if      (code <= 8607) target = SI   .METER;
            else if (code <= 8710) target = NonSI.SECOND_ANGLE;
            else if (code == 8611) target = Units.PPM;
        }
        if (target != unit) {
            value = unit.getConverterTo(target).convert(value);
        }
        switch (code) {
            case 8605: parameters.dx  = value; break;
            case 8606: parameters.dy  = value; break;
            case 8607: parameters.dz  = value; break;
            case 8608: parameters.ex  = value; break;
            case 8609: parameters.ey  = value; break;
            case 8610: parameters.ez  = value; break;
            case 8611: parameters.ppm = value; break;
            default:   throw new FactoryException(Errors.format(ErrorKeys.UNEXPECTED_PARAMETER_$1, code));
        }
    }

    /**
     * List of tables and columns to test for codes values.
     * This table is used by the {@link #createObject} method in order to detect
     * which of the following methods should be invoked for a given code:
     *
     * {@link #createCoordinateReferenceSystem}
     * {@link #createCoordinateSystem}
     * {@link #createDatum}
     * {@link #createEllipsoid}
     * {@link #createUnit}
     *
     * The order is significant: it is the key for a {@code switch} statement.
     *
     * @see #createObject
     * @see #lastObjectType
     */
    private static final TableInfo[] TABLES_INFO = {
        new TableInfo(CoordinateReferenceSystem.class,
                      "[Coordinate Reference System]",
                      "COORD_REF_SYS_CODE",
                      "COORD_REF_SYS_NAME",
                      "COORD_REF_SYS_KIND",
         new Class[] { ProjectedCRS.class, GeographicCRS.class, GeocentricCRS.class},
         new String[] {"projected",       "geographic",        "geocentric"}),

        new TableInfo(CoordinateSystem.class,
                      "[Coordinate System]",
                      "COORD_SYS_CODE",
                      "COORD_SYS_NAME",
                      "COORD_SYS_TYPE",
         new Class[]  { CartesianCS.class, EllipsoidalCS.class, SphericalCS.class, VerticalCS.class},
         new String[] {"Cartesian",       "ellipsoidal",       "spherical",       "vertical"}),

        new TableInfo(CoordinateSystemAxis.class,
                      "[Coordinate Axis] AS CA INNER JOIN [Coordinate Axis Name] AS CAN"+
                                       " ON CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
                      "COORD_AXIS_CODE",
                      "COORD_AXIS_NAME"),

        new TableInfo(Datum.class,
                      "[Datum]",
                      "DATUM_CODE",
                      "DATUM_NAME",
                      "DATUM_TYPE",
         new Class[] { GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class},
         new String[]{"geodetic",          "vertical",          "engineering"}),

        new TableInfo(Ellipsoid.class,
                      "[Ellipsoid]",
                      "ELLIPSOID_CODE",
                      "ELLIPSOID_NAME"),

        new TableInfo(PrimeMeridian.class,
                      "[Prime Meridian]",
                      "PRIME_MERIDIAN_CODE",
                      "PRIME_MERIDIAN_NAME"),

        new TableInfo(CoordinateOperation.class,
                      "[Coordinate_Operation]",
                      "COORD_OP_CODE",
                      "COORD_OP_NAME",
                      "COORD_OP_TYPE",
         new Class[] { Projection.class, Conversion.class, Transformation.class},
         new String[]{"conversion",     "conversion",     "transformation"}),
                      // Note: Projection is handle in a special way.

        new TableInfo(OperationMethod.class,
                      "[Coordinate_Operation Method]",
                      "COORD_OP_METHOD_CODE",
                      "COORD_OP_METHOD_NAME"),

        new TableInfo(ParameterDescriptor.class,
                      "[Coordinate_Operation Parameter]",
                      "PARAMETER_CODE",
                      "PARAMETER_NAME"),

        new TableInfo(Unit.class,
                      "[Unit of Measure]",
                      "UOM_CODE",
                      "UNIT_OF_MEAS_NAME")
    };

    ///////////////////////////////////////////////////////////////////////////////
    ////////                                                               ////////
    ////////        E N D   O F   H A R D   C O D E D   V A L U E S        ////////
    ////////                                                               ////////
    ////////    NOTE: 'createFoo(...)' methods may still have hard-coded   ////////
    ////////    values (others than SQL statements) in 'equalsIgnoreCase'  ////////
    ////////    expressions.                                               ////////
    ///////////////////////////////////////////////////////////////////////////////
    /**
     * A set of EPSG authority codes. This set makes use of our connection to the EPSG database.
     * All {@link #iterator} method call creates a new {@link ResultSet} holding the codes. However,
     * call to {@link #contains} map directly to a SQL call.
     * <p>
     * Serialization of this class store a copy of all authority codes. The serialization
     * do not preserve any connection to the database.
     *
     * @since 2.2
     * @source $URL$
     * @version $Id$
     * @author Martin Desruisseaux (IRD)
     */
    final class AuthorityCodeSet extends AbstractSet<String> implements Serializable {
        /**
         * For compatibility with different versions.
         */
        private static final long serialVersionUID = 7105664579449680562L;

        /**
         * The type for this code set. This is translated to the most appropriate
         * interface type even if the user supplied an implementation type.
         */
        public final Class<?> type;

        /**
         * {@code true} if {@link #type} is assignable to {@link Projection}.
         */
        private final boolean isProjection;

        /**
         * A view of this set as a map with object's name as values, or {@code null} if none.
         * Will be created only when first needed.
         */
        private transient java.util.Map<String,String> asMap;

        /**
         * The SQL command to use for creating the {@code queryAll} statement.
         * Used for iteration over all codes.
         */
        final String sqlAll;

        /**
         * The SQL command to use for creating the {@code querySingle} statement.
         * Used for fetching the description from a code.
         */
        private final String sqlSingle;

        /**
         * The statement to use for querying all codes.
         * Will be created only when first needed.
         */
        private transient PreparedStatement queryAll;

        /**
         * The statement to use for querying a single code.
         * Will be created only when first needed.
         */
        private transient PreparedStatement querySingle;

        /**
         * The collection's size, or a negative value if not yet computed. The records will be counted
         * only when first needed. The special value -2 if set by {@link #isEmpty} if the size has not
         * yet been computed, but we know that the set is not empty.
         */
        private int size = -1;

        /**
         * Creates a new set of authority codes for the specified type.
         *
         * @param  connection The connection to the EPSG database.
         * @param  table      The table to query.
         * @param  type       The type to query.
         * @param  factory    The factory originator.
         */
        public AuthorityCodeSet(final TableInfo table,final Class type){
            final StringBuilder buffer = new StringBuilder("SELECT ");
            buffer.append(table.codeColumn);
            if (table.nameColumn != null) {
                buffer.append(", ").append(table.nameColumn);
            }
            buffer.append(" FROM ").append(table.table);
            boolean hasWhere = false;
            Class tableType = table.type;
            if (table.typeColumn != null) {
                for (int i=0; i<table.subTypes.length; i++) {
                    final Class<?> candidate = table.subTypes[i];
                    if (candidate.isAssignableFrom(type)) {
                        buffer.append(" WHERE (").append(table.typeColumn)
                              .append(" LIKE '").append(table.typeNames[i]).append("%'");
                        hasWhere = true;
                        tableType = candidate;
                        break;
                    }
                }
                if (hasWhere) {
                    buffer.append(')');
                }
            }
            this.type = tableType;
            isProjection = Projection.class.isAssignableFrom(tableType);
            final int length = buffer.length();
            buffer.append(" ORDER BY ").append(table.codeColumn);
            sqlAll = adaptSQL(buffer.toString());
            buffer.setLength(length);
            buffer.append(hasWhere ? " AND " : " WHERE ").append(table.codeColumn).append(" = ?");
            sqlSingle = adaptSQL(buffer.toString());
        }

        /**
         * Returns all codes.
         */
        private ResultSet getAll() throws SQLException {
            assert Thread.holdsLock(this);
            if (queryAll != null) {
                try {
                    return queryAll.executeQuery();
                } catch (SQLException ignore) {
                    /*
                     * Failed to reuse an existing statement. This problem occurs in some occasions
                     * with the JDBC-ODBC bridge in Java 6 (the error message is "Invalid handle").
                     * I'm not sure where the bug come from (didn't noticed it when using HSQL). We
                     * will try again with a new statement created in the code after this 'catch'
                     * clause. Note that we set 'queryAll' to null first in case of failure during
                     * the 'prepareStatement(...)' execution.
                     */
                    queryAll.close();
                    queryAll = null;
                    recoverableException("getAll", ignore);
                }
            }
            queryAll = getConnection().prepareStatement(sqlAll);
            return queryAll.executeQuery();
        }

        /**
         * Returns a single code.
         */
        private ResultSet getSingle(final Object code) throws SQLException {
            assert Thread.holdsLock(this);
            if (querySingle == null) {
                querySingle = getConnection().prepareStatement(sqlSingle);
            }
            querySingle.setString(1, code.toString());
            return querySingle.executeQuery();
        }

        /**
         * Returns {@code true} if the code in the specified result set is acceptable.
         * This method handle projections in a special way.
         */
        private boolean isAcceptable(final ResultSet results) throws SQLException {
            if (!isProjection) {
                return true;
            }
            final String code = results.getString(1);
            return isProjection(code);
        }

        /**
         * Returns {@code true} if the code in the specified code is acceptable.
         * This method handle projections in a special way.
         */
        private boolean isAcceptable(final String code) throws SQLException {
            if (!isProjection) {
                return true;
            }
            return isProjection(code);
        }

        /**
         * Returns {@code true} if this collection contains no elements.
         * This method fetch at most one row instead of counting all rows.
         */
        @Override
        public synchronized boolean isEmpty() {
            if (size != -1) {
                return size == 0;
            }
            boolean empty = true;
            try {
                final ResultSet results = getAll();
                while (results.next()) {
                    if (isAcceptable(results)) {
                        empty = false;
                        break;
                    }
                }
                results.close();
            } catch (SQLException exception) {
                unexpectedException("isEmpty", exception);
            }
            size = empty ? 0 : -2;
            return empty;
        }

        /**
         * Count the number of elements in the underlying result set.
         */
        public synchronized int size() {
            if (size >= 0) {
                return size;
            }
            int count = 0;
            try {
                final ResultSet results = getAll();
                while (results.next()) {
                    if (isAcceptable(results)) {
                        count++;
                    }
                }
                results.close();
            } catch (SQLException exception) {
                unexpectedException("size", exception);
            }
            size = count; // Stores only on success.
            return count;
        }

        /**
         * Returns {@code true} if this collection contains the specified element.
         */
        @Override
        public synchronized boolean contains(final Object code) {
            boolean exists = false;
            if (code != null) try {
                final ResultSet results = getSingle(code);
                while (results.next()) {
                    if (isAcceptable(results)) {
                        exists = true;
                        break;
                    }
                }
                results.close();
            } catch (SQLException exception) {
                unexpectedException("contains", exception);
            }
            return exists;
        }

        /**
         * Returns an iterator over the codes. The iterator is backed by a living {@link ResultSet},
         * which will be closed as soon as the iterator reach the last element.
         */
        public synchronized java.util.Iterator<String> iterator() {
            try {
                final Iterator iterator = new Iterator(getAll());
                /*
                 * Set the statement to null without closing it, in order to force a new statement
                 * creation if getAll() is invoked before the iterator finish its iteration.  This
                 * is needed because only one ResultSet is allowed for each Statement.
                 */
                queryAll = null;
                return iterator;
            } catch (SQLException exception) {
                unexpectedException("iterator", exception);
                final Set<String> empty = Collections.emptySet();
                return empty.iterator();
            }
        }

        /**
         * Returns a serializable copy of this set. This method is invoked automatically during
         * serialization. The serialised set of authority code is disconnected from the underlying
         * database.
         */
        protected Object writeReplace() throws ObjectStreamException {
            return new LinkedHashSet<String>(this);
        }

        /**
         * Closes the underlying statements. Note: this method is also invoked directly
         * by {@link DirectEpsgFactory#dispose}, which is okay in this particular case since
         * the implementation of this method can be executed an arbitrary amount of times.
         */
        @Override
        protected synchronized void finalize() throws SQLException {
            if (querySingle != null) {
                querySingle.close();
                querySingle = null;
            }
            if (queryAll != null) {
                queryAll.close();
                queryAll = null;
            }
        }

        /**
         * Invoked when an exception occured. This method just log a warning.
         */
        private void unexpectedException(final String       method,
                                                final SQLException exception)
        {
            unexpectedException(AuthorityCodes.class, method, exception);
        }

        /**
         * Invoked when an exception occured. This method just log a warning.
         */
        void unexpectedException(final Class        classe,
                                        final String       method,
                                        final SQLException exception)
        {
            Logging.unexpectedException(LOGGER, classe, method, exception);
        }

        /**
         * Invoked when a recoverable exception occured.
         */
        private void recoverableException(final String method, final SQLException exception) {
            // Uses the FINE level instead of WARNING because it may be a recoverable error.
            LogRecord record = Loggings.format(Level.FINE, LoggingKeys.UNEXPECTED_EXCEPTION);
            record.setSourceClassName(AuthorityCodes.class.getName());
            record.setSourceMethodName(method);
            record.setThrown(exception);
            record.setLoggerName(LOGGER.getName());
            LOGGER.log(record);
        }

        /**
         * The iterator over the codes. This inner class must kept a reference toward the enclosing
         * {@link AuthorityCodes} in order to prevent a call to {@link AuthorityCodes#finalize}
         * before the iteration is finished.
         */
        private final class Iterator implements java.util.Iterator<String> {
            /** The result set, or {@code null} if there is no more elements. */
            private ResultSet results;

            /** The next code. */
            private transient String next;

            /** Creates a new iterator for the specified result set. */
            Iterator(final ResultSet results) throws SQLException {
                assert Thread.holdsLock(AuthorityCodeSet.this);
                this.results = results;
                toNext();
            }

            /** Moves to the next element. */
            private void toNext() throws SQLException {
                while (results.next()) {
                    next = results.getString(1);
                    if (isAcceptable(next)) {
                        return;
                    }
                }
                finalize();
            }

            /** Returns {@code true} if there is more elements. */
            public boolean hasNext() {
                return results != null;
            }

            /** Returns the next element. */
            public String next() {
                if (results == null) {
                    throw new NoSuchElementException();
                }
                final String current = next;
                try {
                    toNext();
                } catch (SQLException exception) {
                    results = null;
                    unexpectedException(Iterator.class, "next", exception);
                }
                return current;
            }

            /** Always throws an exception, since this iterator is read-only. */
            public void remove() {
                throw new UnsupportedOperationException();
            }

            /** Closes the underlying result set. */
            @Override
            protected void finalize() throws SQLException {
                next = null;
                if (results != null) {
                    final PreparedStatement owner = (PreparedStatement) results.getStatement();
                    results.close();
                    results = null;
                    synchronized (AuthorityCodeSet.this) {
                        /*
                         * We don't need the statement anymore. Gives it back to the enclosing class
                         * in order to avoid creating a new one when AuthorityCodes.getAll() will be
                         * invoked again,  or closes the statement if getAll() already created a new
                         * statement anyway.
                         */
                        assert owner != queryAll;
                        if (queryAll == null) {
                            queryAll = owner;
                        } else {
                            owner.close();
                        }
                    }
                }
            }
        }

        /**
         * Returns a view of this set as a map with object's name as value, or {@code null} if none.
         */
        final java.util.Map<String,String> asMap() {
            if (asMap == null) {
                asMap = new Map();
            }
            return asMap;
        }

        /**
         * A view of {@link AuthorityCodes} as a map, with authority codes as key and
         * object names as values.
         */
        private final class Map extends AbstractMap<String,String> {
            /**
             * Returns the number of key-value mappings in this map.
             */
            @Override
            public int size() {
                return AuthorityCodeSet.this.size();
            }

            /**
             * Returns {@code true} if this map contains no key-value mappings.
             */
            @Override
            public boolean isEmpty() {
                return AuthorityCodeSet.this.isEmpty();
            }

            /**
             * Returns the description to which this map maps the specified EPSG code.
             */
            @Override
            public String get(final Object code) {
                String value = null;
                if (code != null) try {
                    synchronized (AuthorityCodeSet.this) {
                        final ResultSet results = getSingle(code);
                        while (results.next()) {
                            if (isAcceptable(results)) {
                                value = results.getString(2);
                                break;
                            }
                        }
                        results.close();
                    }
                } catch (SQLException exception) {
                    unexpectedException("get", exception);
                }
                return value;
            }

            /**
             * Returns {@code true} if this map contains a mapping for the specified EPSG code.
             */
            @Override
            public boolean containsKey(final Object key) {
                return contains(key);
            }

            /**
             * Returns a set view of the keys contained in this map.
             */
            @Override
            public Set<String> keySet() {
                return AuthorityCodeSet.this;
            }

            /**
             * Returns a set view of the mappings contained in this map.
             *
             * @todo Not yet implemented.
             */
            public Set<Map.Entry<String,String>> entrySet() {
                throw new UnsupportedOperationException();
            }
        }
    }
}
TOP

Related Classes of org.geotools.referencing.factory.epsg.AbstractEpsgFactory$AuthorityCodeSet$Map

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.