/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2004-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.
 *
 *    This package contains documentation from OpenGIS specifications.
 *    OpenGIS consortium's work is fully acknowledged here.
 */
package org.geotools.referencing.factory;

import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import javax.measure.unit.Unit;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Length;

import org.opengis.referencing.*;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.opengis.parameter.ParameterValueGroup;

import org.geotools.factory.Hints;
import org.geotools.factory.Factory;
import org.geotools.factory.BufferedFactory;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.wkt.Parser;
import org.geotools.referencing.wkt.Symbols;
import org.geotools.referencing.cs.*;
import org.geotools.referencing.crs.*;
import org.geotools.referencing.datum.*;
import org.geotools.referencing.operation.MathTransformProvider;
import org.geotools.referencing.operation.DefaultMathTransformFactory;
import org.geotools.util.CanonicalSet;


/**
 * Builds Geotools implementations of {@linkplain CoordinateReferenceSystem CRS},
 * {@linkplain CoordinateSystem CS} and {@linkplain Datum datum} objects. Most factory methods
 * expect properties given through a {@link Map} argument. The content of this map is described
 * in the {@link ObjectFactory} interface.
 *
 * @since 2.4
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/referencing/src/main/java/org/geotools/referencing/factory/ReferencingObjectFactory.java $
 * @version $Id: ReferencingObjectFactory.java 37299 2011-05-25 05:21:24Z mbedward $
 * @author Martin Desruisseaux (IRD)
 */
public class ReferencingObjectFactory extends ReferencingFactory
        implements CSFactory, DatumFactory, CRSFactory, BufferedFactory
{
    /**
     * The math transform factory. Will be created only when first needed.
     */
    private MathTransformFactory mtFactory;

    /**
     * The object to use for parsing <cite>Well-Known Text</cite> (WKT) strings.
     * Will be created only when first needed.
     */
    private Parser parser;

    /**
     * Set of weak references to existing objects (identifiers, CRS, Datum, whatever).
     * This set is used in order to return a pre-existing object instead of creating a
     * new one.
     */
    private final CanonicalSet<IdentifiedObject> pool;

    /**
     * Constructs a default factory. This method is public in order to allows instantiations
     * from a {@linkplain javax.imageio.spi.ServiceRegistry service registry}. Users should
     * not instantiate this factory directly, but use one of the following lines instead:
     *
     * <blockquote><pre>
     * {@linkplain DatumFactory} factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getDatumFactory getDatumFactory}null);
     * {@linkplain CSFactory}    factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getCSFactory    getCSFactory}(null);
     * {@linkplain CRSFactory}   factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getCRSFactory   getCRSFactory}(null);
     * </pre></blockquote>
     */
    public ReferencingObjectFactory() {
        this(null);
    }

    /**
     * Constructs a factory with the specified hints. Users should not instantiate this
     * factory directly, but use one of the following lines instead:
     *
     * <blockquote><pre>
     * {@linkplain DatumFactory} factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getDatumFactory getDatumFactory}(hints);
     * {@linkplain CSFactory}    factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getCSFactory    getCSFactory}(hints);
     * {@linkplain CRSFactory}   factory = FactoryFinder.{@linkplain ReferencingFactoryFinder#getCRSFactory   getCRSFactory}(hints);
     * </pre></blockquote>
     *
     * @param hints An optional set of hints, or {@code null} if none.
     *
     * @since 2.5
     */
    public ReferencingObjectFactory(final Hints hints) {
        pool = CanonicalSet.newInstance(IdentifiedObject.class);
        if (hints != null && !hints.isEmpty()) {
            /*
             * Creates the dependencies (MathTransform factory, WKT parser...) now because
             * we need to process user's hints. Then, we will keep only the relevant hints.
             */
            mtFactory = ReferencingFactoryFinder.getMathTransformFactory(hints);
            final DatumFactory datumFactory = ReferencingFactoryFinder.getDatumFactory(hints);
            createParser(datumFactory, mtFactory);
            addHints(datumFactory);
            addHints(mtFactory);
        }
    }

    /**
     * Copies the hints from the supplied factory. Note that we do not expose the factories
     * themself (at the contrary of what we usually do) because it is a little bit strange
     * to declare that this factory depends on an other {@link DatumFactory}. It is only a
     * trick for getting the WKT paser to work with aliases.
     *
     * @todo We should remove this trick if we can. Possible alternatives may be: make
     *       DatumAliases to implements CRSFactory with appropriate createWKT(String)
     *       method; move the createWKT(String) method out of CRSFactory interface.
     */
    private void addHints(final Object factory) {
        if (factory instanceof Factory) {
            hints.putAll(((Factory) factory).getImplementationHints());
        }
    }

    /**
     * Returns the math transform factory for internal usage only. The hints given to
     * {@link ReferencingFactoryFinder} must be null, since the non-null case should
     * have been handled by the constructor.
     *
     * @see #createParser
     */
    private synchronized MathTransformFactory getMathTransformFactory() {
        if (mtFactory == null) {
            mtFactory = ReferencingFactoryFinder.getMathTransformFactory(null);
        }
        return mtFactory;
    }



    /////////////////////////////////////////////////////////////////////////////////////////
    ////////                                                                         ////////
    ////////                        D A T U M   F A C T O R Y                        ////////
    ////////                                                                         ////////
    /////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Creates an ellipsoid from radius values.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  semiMajorAxis Equatorial radius in supplied linear units.
     * @param  semiMinorAxis Polar radius in supplied linear units.
     * @param  unit Linear units of ellipsoid axes.
     * @throws FactoryException if the object creation failed.
     */
    public Ellipsoid createEllipsoid(Map<String,?> properties,
            double semiMajorAxis, double semiMinorAxis, Unit<Length> unit) throws FactoryException
    {
        Ellipsoid ellipsoid;
        try {
            ellipsoid = DefaultEllipsoid.createEllipsoid(
                        properties, semiMajorAxis, semiMinorAxis, unit);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        ellipsoid = pool.unique(ellipsoid);
        return ellipsoid;
    }

    /**
     * Creates an ellipsoid from an major radius, and inverse flattening.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  semiMajorAxis Equatorial radius in supplied linear units.
     * @param  inverseFlattening Eccentricity of ellipsoid.
     * @param  unit Linear units of major axis.
     * @throws FactoryException if the object creation failed.
     */
    public Ellipsoid createFlattenedSphere(Map<String,?> properties,
            double semiMajorAxis, double inverseFlattening, Unit<Length> unit) throws FactoryException
    {
        Ellipsoid ellipsoid;
        try {
            ellipsoid = DefaultEllipsoid.createFlattenedSphere(
                        properties, semiMajorAxis, inverseFlattening, unit);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        ellipsoid = pool.unique(ellipsoid);
        return ellipsoid;
    }

    /**
     * Creates a prime meridian, relative to Greenwich.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  longitude Longitude of prime meridian in supplied angular units East of Greenwich.
     * @param  angularUnit Angular units of longitude.
     * @throws FactoryException if the object creation failed.
     */
    public PrimeMeridian createPrimeMeridian(Map<String,?> properties,
            double longitude, Unit<Angle> angularUnit) throws FactoryException
    {
        PrimeMeridian meridian;
        try {
            meridian = new DefaultPrimeMeridian(properties, longitude, angularUnit);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        meridian = pool.unique(meridian);
        return meridian;
    }

    /**
     * Creates geodetic datum from ellipsoid and (optionaly) Bursa-Wolf parameters.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  ellipsoid Ellipsoid to use in new geodetic datum.
     * @param  primeMeridian Prime meridian to use in new geodetic datum.
     * @throws FactoryException if the object creation failed.
     */
    public GeodeticDatum createGeodeticDatum(Map<String,?> properties,
            Ellipsoid ellipsoid, PrimeMeridian primeMeridian) throws FactoryException
    {
        GeodeticDatum datum;
        try {
            datum = new DefaultGeodeticDatum(properties, ellipsoid, primeMeridian);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        datum = pool.unique(datum);
        return datum;
    }

    /**
     * Creates a vertical datum from an enumerated type value.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  type The type of this vertical datum (often geoidal).
     * @throws FactoryException if the object creation failed.
     */
    public VerticalDatum createVerticalDatum(Map<String,?> properties, VerticalDatumType type)
            throws FactoryException
    {
        VerticalDatum datum;
        try {
            datum = new DefaultVerticalDatum(properties, type);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        datum = pool.unique(datum);
        return datum;
    }

    /**
     * Creates a temporal datum from an enumerated type value.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  origin The date and time origin of this temporal datum.
     * @throws FactoryException if the object creation failed.
     */
    public TemporalDatum createTemporalDatum(Map<String,?> properties, Date origin)
            throws FactoryException
    {
        TemporalDatum datum;
        try {
            datum = new DefaultTemporalDatum(properties, origin);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        datum = pool.unique(datum);
        return datum;
    }

    /**
     * Creates an engineering datum.
     *
     * @param  properties Name and other properties to give to the new object.
     * @throws FactoryException if the object creation failed.
     */
    public EngineeringDatum createEngineeringDatum(Map<String,?> properties)
            throws FactoryException
    {
        EngineeringDatum datum;
        try {
            datum = new DefaultEngineeringDatum(properties);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        datum = pool.unique(datum);
        return datum;
    }

    /**
     * Creates an image datum.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  pixelInCell Specification of the way the image grid is associated
     *         with the image data attributes.
     * @throws FactoryException if the object creation failed.
     */
    public ImageDatum createImageDatum(Map<String,?> properties, PixelInCell pixelInCell)
            throws FactoryException
    {
        ImageDatum datum;
        try {
            datum = new DefaultImageDatum(properties, pixelInCell);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        datum = pool.unique(datum);
        return datum;
    }



    /////////////////////////////////////////////////////////////////////////////////////////
    ////////                                                                         ////////
    ////////            C O O R D I N A T E   S Y S T E M   F A C T O R Y            ////////
    ////////                                                                         ////////
    /////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Creates a coordinate system axis from an abbreviation and a unit.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  abbreviation The coordinate axis abbreviation.
     * @param  direction The axis direction.
     * @param  unit The coordinate axis unit.
     * @throws FactoryException if the object creation failed.
     */
    public CoordinateSystemAxis createCoordinateSystemAxis(Map<String,?> properties,
            String abbreviation, AxisDirection direction, Unit<?> unit) throws FactoryException
    {
        CoordinateSystemAxis axis;
        try {
            axis = new DefaultCoordinateSystemAxis(properties, abbreviation, direction, unit);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        axis = pool.unique(axis);
        return axis;
    }

    /**
     * Creates a two dimensional cartesian coordinate system from the given pair of axis.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  axis0 The first  axis.
     * @param  axis1 The second axis.
     * @throws FactoryException if the object creation failed.
     */
    public CartesianCS createCartesianCS(Map<String,?>   properties,
                                         CoordinateSystemAxis axis0,
                                         CoordinateSystemAxis axis1) throws FactoryException
    {
        CartesianCS cs;
        try {
            cs = new DefaultCartesianCS(properties, axis0, axis1);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        cs = pool.unique(cs);
        return cs;
    }

    /**
     * Creates a three dimensional cartesian coordinate system from the given set of axis.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  axis0 The first  axis.
     * @param  axis1 The second axis.
     * @param  axis2 The third  axis.
     * @throws FactoryException if the object creation failed.
     */
    public CartesianCS createCartesianCS(Map<String,?>   properties,
                                         CoordinateSystemAxis axis0,
                                         CoordinateSystemAxis axis1,
                                         CoordinateSystemAxis axis2) throws FactoryException
    {
        CartesianCS cs;
        try {
            cs = new DefaultCartesianCS(properties, axis0, axis1, axis2);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        cs = pool.unique(cs);
        return cs;
    }

    /**
     * Creates an ellipsoidal coordinate system without ellipsoidal height.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  axis0 The first  axis.
     * @param  axis1 The second axis.
     * @throws FactoryException if the object creation failed.
     */
    public EllipsoidalCS createEllipsoidalCS(Map<String,?>   properties,
                                             CoordinateSystemAxis axis0,
                                             CoordinateSystemAxis axis1) throws FactoryException
    {
        EllipsoidalCS cs;
        try {
            cs = new DefaultEllipsoidalCS(properties, axis0, axis1);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        cs = pool.unique(cs);
        return cs;
    }

    /**
     * Creates an ellipsoidal coordinate system with ellipsoidal height.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  axis0 The first  axis.
     * @param  axis1 The second axis.
     * @param  axis2 The third  axis.
     * @throws FactoryException if the object creation failed.
     */
    public EllipsoidalCS createEllipsoidalCS(Map<String,?>   properties,
                                             CoordinateSystemAxis axis0,
                                             CoordinateSystemAxis axis1,
                                             CoordinateSystemAxis axis2) throws FactoryException
    {
        EllipsoidalCS cs;
        try {
            cs = new DefaultEllipsoidalCS(properties, axis0, axis1, axis2);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        cs = pool.unique(cs);
        return cs;
    }

    /**
     * Creates a vertical coordinate system.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  axis The axis.
     * @throws FactoryException if the object creation failed.
     */
    public VerticalCS createVerticalCS(Map<String,?>  properties,
                                       CoordinateSystemAxis axis) throws FactoryException
    {
        VerticalCS cs;
        try {
            cs = new DefaultVerticalCS(properties, axis);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        cs = pool.unique(cs);
        return cs;
    }

    /////////////////////////////////////////////////////////////////////////////////////////
    ////////                                                                         ////////
    ////////  C O O R D I N A T E   R E F E R E N C E   S Y S T E M   F A C T O R Y  ////////
    ////////                                                                         ////////
    /////////////////////////////////////////////////////////////////////////////////////////


    /**
     * Creates a compound coordinate reference system from an ordered
     * list of {@code CoordinateReferenceSystem} objects.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  elements ordered array of {@code CoordinateReferenceSystem} objects.
     * @throws FactoryException if the object creation failed.
     */
    public CompoundCRS createCompoundCRS(Map<String,?> properties,
            CoordinateReferenceSystem[] elements) throws FactoryException
    {
        CompoundCRS crs;
        try {
            crs = new DefaultCompoundCRS(properties, elements);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a engineering coordinate reference system.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  datum Engineering datum to use in created CRS.
     * @param  cs The coordinate system for the created CRS.
     * @throws FactoryException if the object creation failed.
     */
    public EngineeringCRS createEngineeringCRS(Map<String,?> properties,
                                               EngineeringDatum datum,
                                               CoordinateSystem cs) throws FactoryException
    {
        EngineeringCRS crs;
        try {
            crs = new DefaultEngineeringCRS(properties, datum, cs);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a vertical coordinate reference system.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  datum Vertical datum to use in created CRS.
     * @param  cs The Vertical coordinate system for the created CRS.
     * @throws FactoryException if the object creation failed.
     */
    public VerticalCRS createVerticalCRS(Map<String,?> properties,
                                         VerticalDatum datum,
                                         VerticalCS    cs) throws FactoryException
    {
        VerticalCRS crs;
        try {
            crs = new DefaultVerticalCRS(properties, datum, cs);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a geocentric coordinate reference system from a {@linkplain CartesianCS
     * cartesian coordinate system}.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  datum Geodetic datum to use in created CRS.
     * @param  cs The cartesian coordinate system for the created CRS.
     * @throws FactoryException if the object creation failed.
     */
    public GeocentricCRS createGeocentricCRS(Map<String,?> properties,
                                             GeodeticDatum datum,
                                             CartesianCS   cs) throws FactoryException
    {
        GeocentricCRS crs;
        try {
            crs = new DefaultGeocentricCRS(properties, datum, cs);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a geographic coordinate reference system.
     * It could be <var>Latitude</var>/<var>Longitude</var> or
     * <var>Longitude</var>/<var>Latitude</var>.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  datum Geodetic datum to use in created CRS.
     * @param  cs The ellipsoidal coordinate system for the created CRS.
     * @throws FactoryException if the object creation failed.
     */
    public GeographicCRS createGeographicCRS(Map<String,?> properties,
                                             GeodeticDatum datum,
                                             EllipsoidalCS cs) throws FactoryException
    {
        GeographicCRS crs;
        try {
            crs = new DefaultGeographicCRS(properties, datum, cs);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a derived coordinate reference system from a conversion.
     * It is the user's responsability to ensure that the conversion performs all required steps,
     * including {@linkplain AbstractCS#swapAndScaleAxis unit conversions and change of axis order},
     * if needed.
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  baseCRS Coordinate reference system to base projection on.
     * @param  conversionFromBase The {@linkplain DefiningConversion defining conversion}.
     * @param  derivedCS The coordinate system for the derived CRS.
     * @throws FactoryException if the object creation failed.
     *
     * @since 2.5
     */
    public DerivedCRS createDerivedCRS(Map<String,?>          properties,
                                       CoordinateReferenceSystem baseCRS,
                                       Conversion     conversionFromBase,
                                       CoordinateSystem        derivedCS) throws FactoryException
    {
        MathTransform mt = conversionFromBase.getMathTransform();
        if (mt == null) {
            final ParameterValueGroup parameters = conversionFromBase.getParameterValues();
            final MathTransformFactory mtFactory = getMathTransformFactory();
            mt = mtFactory.createParameterizedTransform(parameters);
        }
        DerivedCRS crs;
        try {
            crs = new DefaultDerivedCRS(properties, conversionFromBase, baseCRS, mt, derivedCS);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a projected coordinate reference system from a conversion. The supplied
     * conversion should <strong>not</strong> includes the operation steps for performing
     * {@linkplain AbstractCS#swapAndScaleAxis unit conversions and change of axis order}
     * since those operations will be inferred by this constructor
     *
     * @param  properties Name and other properties to give to the new object.
     * @param  baseCRS Geographic coordinate reference system to base projection on.
     * @param  conversionFromBase The {@linkplain DefiningConversion defining conversion}.
     * @param  derivedCS The coordinate system for the projected CRS.
     * @throws FactoryException if the object creation failed.
     *
     * @since 2.5
     */
    public ProjectedCRS createProjectedCRS(Map<String,?> properties,
                                           GeographicCRS baseCRS,
                                           Conversion    conversionFromBase,
                                           CartesianCS   derivedCS) throws FactoryException
    {
        MathTransform mt;
        final MathTransform existing = conversionFromBase.getMathTransform();
        final MathTransformFactory mtFactory = getMathTransformFactory();
        if (existing != null && mtFactory instanceof DefaultMathTransformFactory) {
            /*
             * In the particular case of GeoTools implementation, we use a shortcut which avoid
             * the cost of creating a new parameterized transform; we use directly the existing
             * transform instance instead. It also avoid slight rounding errors as a side-effect.
             * This is because when a MapProjection is created, the angular parameters given to
             * the construtor are converted from degrees to radians. When the parameters are asked
             * back with 'conversionFromBase.getParameterValues()', they are converted back from
             * radians to degrees resulting in values slightly different than the original ones.
             * Those slight differences are enough for getting a math transform which is different
             * in the sense of MapProjection.equals(Object), with the usual consequences on cached
             * instances.
             */
            mt = ((DefaultMathTransformFactory) mtFactory).createBaseToDerived(baseCRS, existing, derivedCS);
        } else {
            /*
             * Non-GeoTools implementation, or no existing MathTransform instance.
             * Creates the transform from the parameters.
             */
            final ParameterValueGroup parameters = conversionFromBase.getParameterValues();
            mt = mtFactory.createBaseToDerived(baseCRS, parameters, derivedCS);
            OperationMethod method = conversionFromBase.getMethod();
            if (!(method instanceof MathTransformProvider)) {
                /*
                 * Our Geotools implementation of DefaultProjectedCRS may not be able to detect
                 * the conversion type (PlanarProjection, CylindricalProjection, etc.)  because
                 * we rely on the Geotools-specific MathTransformProvider for that. We will try
                 * to help it with the optional "conversionType" hint,  providing that the user
                 * do not already provides this hint.
                 */
                if (!properties.containsKey(DefaultProjectedCRS.CONVERSION_TYPE_KEY)) {
                    method = mtFactory.getLastMethodUsed();
                    if (method instanceof MathTransformProvider) {
                        final Map<String,Object> copy = new HashMap<String,Object>(properties);
                        copy.put(DefaultProjectedCRS.CONVERSION_TYPE_KEY,
                                ((MathTransformProvider) method).getOperationType());
                        properties = copy;
                    }
                }
            }
            /*
             * If the user gave an explicit conversion, checks if it is suitable.
             * It may not be suitable is unit conversion, axis switch, etc. have
             * been inserted in the operation chain by 'createBaseToDerived'.
             */
            if (existing != null && existing.equals(mt)) {
                mt = existing;
            }
        }
        ProjectedCRS crs;
        try {
            crs = new DefaultProjectedCRS(properties, conversionFromBase, baseCRS, mt, derivedCS);
        } catch (IllegalArgumentException exception) {
            throw new FactoryException(exception);
        }
        crs = pool.unique(crs);
        return crs;
    }

    /**
     * Creates a coordinate reference system object from a string.
     *
     * @param  wkt Coordinate system encoded in Well-Known Text format.
     * @throws FactoryException if the object creation failed.
     */
    public synchronized CoordinateReferenceSystem createFromWKT(final String wkt)
            throws FactoryException
    {
        /*
         * Note: while this factory is thread safe, the WKT parser is not.
         * Since we share a single instance of this parser, we must synchronize.
         */
        if (parser == null) {
            createParser(ReferencingFactoryFinder.getDatumFactory(null),
                    getMathTransformFactory());
        }
        try {
            return parser.parseCoordinateReferenceSystem(wkt);
        } catch (ParseException exception) {
            final Throwable cause = exception.getCause();
            if (cause instanceof FactoryException) {
                throw (FactoryException) cause;
            }
            throw new FactoryException(exception);
        }
    }

    /**
     * Creates inconditionnaly the WKT parser. This is factored out as a single private method
     * for making easier to spot the places in this code that need to create the parser and the
     * datum-alias patch.
     */
    private void createParser(final DatumFactory datumFactory, final MathTransformFactory mtFactory) {
        parser = new Parser(Symbols.DEFAULT, datumFactory, this, this, mtFactory);
    }
}
