/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.factory;
import java.awt.RenderingHints;
import javax.swing.event.ChangeListener; // For javadoc
import java.io.File;
import java.io.Serializable;
import java.io.ObjectStreamException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.naming.Name;
import javax.sql.DataSource;
import org.opengis.util.InternationalString;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.geotoolkit.lang.Configuration;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.resources.Errors;
import static org.geotoolkit.util.collection.XCollections.unmodifiableOrCopy;
/**
* A set of hints providing control on factories to be used. They provides a way to control
* low-level details. When hints are used in conjunction with {@linkplain FactoryRegistry
* factory registry} (the Geotk service discovery mechanism), we have the complete Geotk
* plugin system. By using hints to allow application code to effect service discovery,
* we allow client code to retarget the Geotk library for their needs.
* <p>
* The following example fetch a {@linkplain CoordinateOperationFactory coordinate operation factory}
* which is tolerant to the lack of Bursa-Wolf parameters:
*
* {@preformat java
* Hints hints = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
* CoordinateOperationFactory factory = FactoryFinder.getCoordinateOperationFactory(hints);
* }
*
* Hints may be ignored if they do not apply to the object to be instantiated.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Jody Garnett (Refractions)
* @version 3.18
*
* @see Factory
* @see FactoryRegistry
*
* @since 2.0
* @module
*/
public class Hints extends RenderingHints {
/**
* A set of system-wide hints to use by default. Only one thread is expected to write
* (while more are allowed).
*/
private static final Map<RenderingHints.Key,Object> GLOBAL = new ConcurrentHashMap<>(8, 0.75f, 1);
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Metadata ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link org.opengis.util.NameFactory} instance to use.
*
* @see FactoryFinder#getNameFactory(Hints)
*
* @since 3.00
* @category Metadata
*/
public static final ClassKey NAME_FACTORY = new ClassKey(
"org.opengis.util.NameFactory");
/**
* The {@link org.opengis.metadata.citation.CitationFactory} instance to use.
*
* @see FactoryFinder#getCitationFactory(Hints)
*
* @since 3.00
* @category Metadata
*/
public static final ClassKey CITATION_FACTORY = new ClassKey(
"org.opengis.metadata.citation.CitationFactory");
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Coordinate Reference Systems ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link org.opengis.referencing.crs.CRSAuthorityFactory} instance to use.
*
* @see AuthorityFactoryFinder#getCRSAuthorityFactory(String, Hints)
* @category Referencing
*/
public static final ClassKey CRS_AUTHORITY_FACTORY = new ClassKey(
"org.opengis.referencing.crs.CRSAuthorityFactory");
/**
* The {@link org.opengis.referencing.cs.CSAuthorityFactory} instance to use.
*
* @see AuthorityFactoryFinder#getCSAuthorityFactory(String, Hints)
* @category Referencing
*/
public static final ClassKey CS_AUTHORITY_FACTORY = new ClassKey(
"org.opengis.referencing.cs.CSAuthorityFactory");
/**
* The {@link org.opengis.referencing.datum.DatumAuthorityFactory} instance to use.
*
* @see AuthorityFactoryFinder#getDatumAuthorityFactory(String, Hints)
* @category Referencing
*/
public static final ClassKey DATUM_AUTHORITY_FACTORY = new ClassKey(
"org.opengis.referencing.datum.DatumAuthorityFactory");
/**
* The {@link org.opengis.referencing.crs.CRSFactory} instance to use.
*
* @see FactoryFinder#getCRSFactory(Hints)
* @category Referencing
*/
public static final ClassKey CRS_FACTORY = new ClassKey(
"org.opengis.referencing.crs.CRSFactory");
/**
* The {@link org.opengis.referencing.cs.CSFactory} instance to use.
*
* @see FactoryFinder#getCSFactory(Hints)
* @category Referencing
*/
public static final ClassKey CS_FACTORY = new ClassKey(
"org.opengis.referencing.cs.CSFactory");
/**
* The {@link org.opengis.referencing.datum.DatumFactory} instance to use.
*
* @see FactoryFinder#getDatumFactory(Hints)
* @category Referencing
*/
public static final ClassKey DATUM_FACTORY = new ClassKey(
"org.opengis.referencing.datum.DatumFactory");
/**
* The {@link org.opengis.referencing.operation.CoordinateOperationFactory} instance to use.
*
* @see FactoryFinder#getCoordinateOperationFactory(Hints)
* @category Referencing
*/
public static final ClassKey COORDINATE_OPERATION_FACTORY = new ClassKey(
"org.opengis.referencing.operation.CoordinateOperationFactory");
/**
* The {@link org.opengis.referencing.operation.CoordinateOperationAuthorityFactory} instance
* to use.
*
* @see AuthorityFactoryFinder#getCoordinateOperationAuthorityFactory(String, Hints)
* @category Referencing
*/
public static final ClassKey COORDINATE_OPERATION_AUTHORITY_FACTORY = new ClassKey(
"org.opengis.referencing.operation.CoordinateOperationAuthorityFactory");
/**
* The {@link org.opengis.referencing.operation.MathTransformFactory} instance to use.
*
* @see FactoryFinder#getMathTransformFactory(Hints)
* @category Referencing
*/
public static final ClassKey MATH_TRANSFORM_FACTORY = new ClassKey(
"org.opengis.referencing.operation.MathTransformFactory");
/**
* The default {@link org.opengis.referencing.crs.CoordinateReferenceSystem}
* to use. This is used by some factories capable to provide a default CRS
* when no one were explicitly specified by the user.
*
* @since 2.2
* @category Referencing
*/
public static final Key DEFAULT_COORDINATE_REFERENCE_SYSTEM = new Key(
"org.opengis.referencing.crs.CoordinateReferenceSystem");
/**
* Used to direct WKT CRS Authority to a directory containing extra definitions.
* The value should be an instance of {@link File} or {@link String} refering to
* an existing directory.
* <p>
* Filenames in the supplied directory should be of the form
* <code><var>authority</var>.properties</code> where <var>authority</var>
* is the authority name space to use. For example the
* {@value org.geotoolkit.referencing.factory.epsg.PropertyEpsgFactory#FILENAME}
* file contains extra CRS to add as new EPSG codes.
*
* @since 2.4
* @category Referencing
*/
public static final FileKey CRS_AUTHORITY_EXTRA_DIRECTORY = new FileKey(false);
/**
* The {@linkplain javax.sql.DataSource data source} name to lookup from JNDI when
* initializing the {@linkplain org.geotoolkit.referencing.factory.epsg EPSG factory}.
* Possible values:
* <p>
* <ul>
* <li>{@link javax.sql.DataSource} - used as is.</li>
* <li>{@link javax.naming.Name} - used with JNDI to locate data source. This hint has no
* effect if there is no {@linkplain javax.naming.InitialContext JNDI initial context}
* setup.</li>
* <li>{@link String} - used with JNDI to locate data source. This hint has no effect if
* there is no {@linkplain javax.naming.InitialContext JNDI initial context} setup.</li>
* </ul>
*
* @since 2.4
* @category Referencing
*/
public static final Key EPSG_DATA_SOURCE = new DataSourceKey();
/**
* The preferred datum shift method to use for {@linkplain CoordinateOperation coordinate operations}.
* Valid values include {@code "Molodensky"}, {@code "Abridged Molodensky"} and {@code "Geocentric"}.
* Other values may be supplied if a {@linkplain MathTransform math transform} exists for that
* name, but this is not guaranteed to work.
*
* @see FactoryFinder#getCoordinateOperationFactory(Hints)
* @category Referencing
*/
public static final OptionKey DATUM_SHIFT_METHOD = new OptionKey(
"Molodensky", "Abridged Molodensky", // EPSG names
"Molodenski", "Abridged_Molodenski", // OGC names
"Geocentric", "*");
/**
* Tells if {@linkplain CoordinateOperation coordinate operations} should be allowed even when
* a datum shift is required while no method is found applicable. It may be for example that no
* {@linkplain org.apache.sis.referencing.datum.BursaWolfParameters Bursa Wolf parameters} were
* found for a datum shift. The default value is {@link Boolean#FALSE FALSE}, which means
* that {@linkplain org.geotoolkit.referencing.operation.DefaultCoordinateOperationFactory
* coordinate operation factory} throws an exception if such a case occurs.
* <p>
* If this hint is set to {@code TRUE}, then the users are encouraged to check the
* {@linkplain CoordinateOperation#getCoordinateOperationAccuracy coordinate operation accuracy}
* for every transformation created. If the set of operation accuracy contains
* {@link org.apache.sis.metadata.iso.quality.AbstractPositionalAccuracy#DATUM_SHIFT_OMITTED
* DATUM_SHIFT_OMITTED}, this means that an "ellipsoid shift" were applied without real datum
* shift method available, and the transformed coordinates may have one kilometer error. The
* application should warn the user (e.g. popup a message dialog box) in such case.
*
* @see FactoryFinder#getCoordinateOperationFactory(Hints)
* @category Referencing
*/
public static final Key LENIENT_DATUM_SHIFT = new Key(Boolean.class);
/**
* Tells if the {@linkplain CoordinateSystem coordinate systems} created by
* an {@linkplain CSAuthorityFactory authority factory} should be forced to
* (<var>longitude</var>, <var>latitude</var>) axis order. This hint is especially useful
* for creating {@linkplain CoordinateReferenceSystem coordinate reference system} objects
* from <A HREF="http://www.epsg.org">EPSG</A> codes. Most {@linkplain GeographicCRS geographic
* CRS} defined in the EPSG database use (<var>latitude</var>, <var>longitude</var>) axis order.
* Unfortunately, many data sources available in the world use the opposite axis order and still
* claim to use a CRS described by an EPSG code. This hint allows to handle such data.
* <p>
* This hint can be passed to the <code>{@linkplain AuthorityFactoryFinder#getCRSAuthorityFactory
* AuthorityFactoryFinder.getCRSAuthorityFactory}(...)</code> method. Whatever this hint is
* supported or not is authority dependent. In the default Geotk configuration, this hint
* is honored for codes in the {@code "EPSG"} namespace but ignored for codes in the
* {@code "urn:ogc"} namespace. See {@link #FORCE_AXIS_ORDER_HONORING} for changing this
* behavior.
*
* {@note
* The documentation saids "<cite>longitude first</cite>" for simplicity, because the axes
* reordering apply mostly to geographic CRS (in contrast, most projected CRS already have
* (<var>x</var>, <var>y</var>) axis order, in which case this hint has no effect). However,
* what Geotk actually does is to force a <cite>right-handed</cite> coordinate system. This
* approach works for projected CRS as well as geographic CRS ("<cite>longitude first</cite>"
* is an inappropriate expression for projected CRS). It even works in cases like stereographic
* projections, where the axes names look like (<var>South along 180°</var>, <var>South along 90°E</var>).
* In such cases, aiming for "<cite>longitude first</cite>" would not make sense.}
*
* @see AuthorityFactoryFinder#getCSAuthorityFactory(String, Hints)
* @see AuthorityFactoryFinder#getCRSAuthorityFactory(String, Hints)
* @see org.geotoolkit.referencing.factory.OrderedAxisAuthorityFactory
* @see org.geotoolkit.referencing.factory.epsg.LongitudeFirstEpsgFactory
*
* @since 2.3
* @category Referencing
*/
public static final Key FORCE_LONGITUDE_FIRST_AXIS_ORDER = new Key(Boolean.class);
/**
* Applies the {@link #FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint to some factories that usually
* ignore it. The <cite>axis order</cite> issue is of concern mostly to the {@code "EPSG"} name
* space. Codes in the {@value org.geotoolkit.referencing.factory.web.HTTP_AuthorityFactory#BASE_URL}
* or {@code "urn:ogc"} name space usually ignore the axis order hint, especially the later
* which is clearly defined by international standards and does <strong>not</strong> allow
* the {@code FORCE_LONGITUDE_FIRST_AXIS_ORDER} behavior in standard-compliant application.
* <p>
* If nevertheless a user really wants the {@code FORCE_LONGITUDE_FIRST_AXIS_ORDER} behavior
* despite the violation of standards, then he must explicitly assigns a comma separated list
* of authorities to this {@code FORCE_AXIS_ORDER_HONORING} hint. For example in order to apply
* the (<var>longitude</var>, <var>latitude</var>) axis order to {@code "http://www.opengis.net/"}
* and {@code "urn:ogc"} name spaces in addition of EPSG, use the following hints:
*
* {@preformat java
* hints.put(FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
* hints.put(FORCE_AXIS_ORDER_HONORING, "epsg, http, urn");
* }
*
* Let stress again that the application of (<var>longitude</var>, <var>latitude</var>) axis
* order to the {@code "urn:ogc"} name space is a clear violation of OGC specification, which
* is why Geotk wants you to provide this additional hint meaning "I'm really sure". Note
* also that {@code "epsg"} is implicit and doesn't need to be included in the above list,
* but this example does so as a matter of principle.
*
* @since 2.4
* @category Referencing
*/
public static final Key FORCE_AXIS_ORDER_HONORING = new Key(String.class);
/**
* Tells if the {@linkplain CoordinateSystem coordinate systems} created by an
* {@linkplain CSAuthorityFactory authority factory} should be forced to standard
* {@linkplain CoordinateSystemAxis#getDirection axis directions}. If {@code true},
* then {@linkplain AxisDirection#SOUTH South} axis directions are forced to
* {@linkplain AxisDirection#NORTH North}, {@linkplain AxisDirection#WEST West} axis
* directions are forced to {@linkplain AxisDirection#EAST East}, <i>etc.</i>
* If {@code false}, then the axis directions are left unchanged.
* <p>
* This hint shall be passed to the
* <code>{@linkplain AuthorityFactoryFinder#getCRSAuthorityFactory
* AuthorityFactoryFinder.getCRSAuthorityFactory}(...)</code>
* method. Whatever this hint is supported or not is authority dependent.
*
* @see FactoryFinder#getCSFactory(Hints)
* @see FactoryFinder#getCRSFactory(Hints)
* @see org.geotoolkit.referencing.factory.OrderedAxisAuthorityFactory
*
* @since 2.3
* @category Referencing
*/
public static final Key FORCE_STANDARD_AXIS_DIRECTIONS = new Key(Boolean.class);
/**
* Tells if the {@linkplain CoordinateSystem coordinate systems} created by an
* {@linkplain CSAuthorityFactory authority factory} should be forced to standard
* {@linkplain CoordinateSystemAxis#getUnit axis units}. If {@code true}, then all
* angular units are forced to degrees and linear units to meters. If {@code false},
* then the axis units are left unchanged.
* <p>
* This hint shall be passed to the
* <code>{@linkplain AuthorityFactoryFinder#getCRSAuthorityFactory
* AuthorityFactoryFinder.getCRSAuthorityFactory}(...)</code> method. Whatever this hint is
* supported or not is authority dependent.
*
* @see FactoryFinder#getCSFactory(Hints)
* @see FactoryFinder#getCRSFactory(Hints)
* @see org.geotoolkit.referencing.factory.OrderedAxisAuthorityFactory
*
* @since 2.3
* @category Referencing
*/
public static final Key FORCE_STANDARD_AXIS_UNITS = new Key(Boolean.class);
/**
* Version number of the requested service. This hint is used for example in order to get
* a {@linkplain org.opengis.referencing.crs.CRSAuthorityFactory CRS authority factory}
* backed by a particular version of EPSG database. The value should be an instance of
* {@link org.apache.sis.util.Version}.
*
* @since 2.4
* @category Referencing
*/
public static final Key VERSION = new Key("org.apache.sis.util.Version");
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Grid Coverages ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link javax.media.jai.JAI} instance to use.
*/
public static final Key JAI_INSTANCE = new Key("javax.media.jai.JAI");
/**
* The {@linkplain javax.media.jai.tilecodec.TileEncoder tile encoder} name
* (as a {@link String} value) to use during serialization of image data in
* a {@link org.geotoolkit.coverage.grid.GridCoverage2D} object. This encoding
* is given to the {@link javax.media.jai.remote.SerializableRenderedImage}
* constructor. Valid values include (but is not limited to) {@code "raw"},
* {@code "gzip"} and {@code "jpeg"}.
*
* {@note We recommend to avoid the <code>"jpeg"</code> codec for grid coverages.}
*
* @see org.geotoolkit.coverage.CoverageFactoryFinder#getGridCoverageFactory(Hints)
*
* @since 2.3
* @category Coverage
*/
public static final Key TILE_ENCODING = new Key(String.class);
/**
* The {@link org.opengis.coverage.processing.GridCoverageProcessor} instance to use.
*
* @see org.geotoolkit.coverage.CoverageFactoryFinder#getCoverageProcessor(Hints)
*
* @category Coverage
*/
public static final ClassKey GRID_COVERAGE_PROCESSOR = new ClassKey("org.opengis.coverage.processing.GridCoverageProcessor");
/**
* Forces the {@linkplain org.opengis.coverage.processing.GridCoverageProcessor grid coverage
* processor} to perform operations on the specified view.
* <p>
* Some operation when called on a {@linkplain org.geotoolkit.coverage.grid.GridCoverage2D grid
* coverage} tries to converts to {@linkplain org.geotoolkit.coverage.grid.ViewType#GEOPHYSICS
* geophysics} view before to execute. The rationale behind this is that the other views are
* just the rendered version of a coverage data, and operations like interpolations have a
* physical meaning only when applied on the geophysics view (e.g. interpolate <cite>Sea
* Surface Temperature</cite> (SST) values, not the RGB values that colorize the temperature).
* <p>
* However, in some cases like when doing pure rendering of images, we might want to force
* operations to work on {@linkplain org.geotoolkit.coverage.grid.ViewType#PHOTOGRAPHIC
* photographic} view directly, even performing color expansions as needed. This can be
* accomplished by setting this hint to the desired view. Be aware that interpolations
* after color expansions may produce colors that do not accuratly represent the geophysical
* value.
*
* @since 2.5
* @category Coverage
*/
public static final Key COVERAGE_PROCESSING_VIEW = new Key("org.geotoolkit.coverage.grid.ViewType");
/**
* The {@link org.opengis.coverage.SampleDimensionType} to use.
*
* @category Coverage
*/
public static final Key SAMPLE_DIMENSION_TYPE = new Key("org.opengis.coverage.SampleDimensionType");
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Temporal ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link org.opengis.temporal.TemporalFactory} instance to use.
*
* @see FactoryFinder#getTemporalFactory(Hints)
*
* @category Temporal
*
* @since 3.18
*/
public static final ClassKey TEMPORAL_FACTORY = new ClassKey("org.opengis.temporal.TemporalFactory");
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Geometry ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link org.opengis.geometry.PositionFactory} instance to use.
*
* @see FactoryFinder#getPositionFactory(Hints)
*
* @category Geometry
*
* @since 3.01
*/
public static final ClassKey POSITION_FACTORY = new ClassKey("org.opengis.geometry.PositionFactory");
/**
* The {@link org.opengis.geometry.primitive.PrimitiveFactory} instance to use.
*
* @see FactoryFinder#getPrimitiveFactory(Hints)
*
* @category Geometry
*
* @since 3.01
*/
public static final ClassKey PRIMITIVE_FACTORY = new ClassKey("org.opengis.geometry.primitive.PrimitiveFactory");
/**
* The {@link org.opengis.geometry.coordinate.GeometryFactory} instance to use.
*
* @see FactoryFinder#getGeometryFactory(Hints)
*
* @category Geometry
*
* @since 3.01
*/
public static final ClassKey GEOMETRY_FACTORY = new ClassKey("org.opengis.geometry.coordinate.GeometryFactory");
/**
* The {@link org.opengis.geometry.complex.ComplexFactory} instance to use.
*
* @see FactoryFinder#getComplexFactory(Hints)
*
* @category Geometry
*
* @since 3.01
*/
public static final ClassKey COMPLEX_FACTORY = new ClassKey("org.opengis.geometry.complex.ComplexFactory");
/**
* The {@link org.opengis.geometry.aggregate.AggregateFactory} instance to use.
*
* @see FactoryFinder#getAggregateFactory(Hints)
*
* @category Geometry
*
* @since 3.01
*/
public static final ClassKey AGGREGATE_FACTORY = new ClassKey("org.opengis.geometry.aggregate.AggregateFactory");
////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Feature, Filter and Style ////////
//////// ////////
////////////////////////////////////////////////////////////////////////
/**
* The {@link org.geotoolkit.feature.type.FeatureTypeFactory} instance to use.
*
* @see FactoryFinder#getFeatureTypeFactory(Hints)
*
* @category Feature
*
* @since 3.15
*/
public static final ClassKey FEATURE_TYPE_FACTORY = new ClassKey("org.geotoolkit.feature.type.FeatureTypeFactory");
/**
* The {@link org.geotoolkit.feature.FeatureFactory} instance to use.
*
* @see FactoryFinder#getFeatureFactory(Hints)
*
* @category Feature
*
* @since 3.01
*/
public static final ClassKey FEATURE_FACTORY = new ClassKey("org.geotoolkit.feature.FeatureFactory");
/**
* The {@link org.opengis.filter.FilterFactory} instance to use.
*
* @see FactoryFinder#getFilterFactory(Hints)
*
* @category Feature
*
* @since 3.00
*/
public static final ClassKey FILTER_FACTORY = new ClassKey("org.opengis.filter.FilterFactory");
/**
* The {@link org.opengis.style.StyleFactory} instance to use.
*
* @see FactoryFinder#getStyleFactory(Hints)
*
* @category Feature
*
* @since 3.00
*/
public static final ClassKey STYLE_FACTORY = new ClassKey("org.opengis.style.StyleFactory");
/**
* Creates an empty set of hints. This constructor is for {@link EmptyHints} usage only.
* All public constructors are expected to create hints initialized to the system-wide
* default. If an empty set of hints is really needed, use {@link EmptyHints#clone()}.
*
* @param dummy Ignored.
*/
Hints(final boolean dummy) {
super(null);
}
/**
* Constructs a map of hints initialized with the system-wide default values. The default
* values are those that were given to {@link #putSystemDefault putSystemDefault} and not
* yet removed with {@link #removeSystemDefault removeSystemDefault}.
*
* @since 2.5
*/
public Hints() {
super(GLOBAL);
}
/**
* Constructs a new map of hints with the specified key/value pair. First, an initial map
* is created as with the {@linkplain #Hints() no-argument constructor}. This map may not
* be empty. Then, the given key-value pair is added. If a default value was present for
* the given key, then the given value replaces the default one.
*
* @param key The key of the particular hint property.
* @param value The value of the hint property specified with {@code key}.
*/
public Hints(final RenderingHints.Key key, final Object value) {
// Don't use 'super(key,value)' because it doesn't check validity.
this();
put(key, value);
}
/**
* Constructs a new map of hints with two key/value pairs. First, an initial map is created
* as with the {@linkplain #Hints() no-argument constructor}. This map may not be empty.
* Then, the given key-value pairs are added. If a default value was present for a given
* key, then the given value replaces the default one.
*
* @param key1 The key for the first pair.
* @param value1 The value for the first pair.
* @param key2 The key2 for the second pair.
* @param value2 The value2 for the second pair.
*
* @since 2.4
*/
public Hints(final RenderingHints.Key key1, final Object value1,
final RenderingHints.Key key2, final Object value2)
{
this(key1, value1);
put (key2, value2);
}
/**
* Constructs a new map of hints from key/value pairs. First, an initial map is created
* as with the {@linkplain #Hints() no-argument constructor}. This map may not be empty.
* Then, the given key-value pairs are added. If a default value was present for a given
* key, then the given value replaces the default one.
*
* @param key1 The key for the first pair.
* @param value1 The value for the first pair.
* @param key2 The key2 for the second pair.
* @param value2 The value2 for the second pair.
* @param pairs Additional pairs of keys and values.
*
* @since 2.4
*/
public Hints(final RenderingHints.Key key1, final Object value1,
final RenderingHints.Key key2, final Object value2,
final Object... pairs)
{
this(key1, value1, key2, value2);
if ((pairs.length & 1) != 0) {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.OddArrayLength_1, pairs.length));
}
for (int i=0; i<pairs.length;) {
put(pairs[i++], pairs[i++]);
}
}
/**
* Constructs a new object with keys and values from the given map (which may be null).
* First, an initial map is created as with the {@linkplain #Hints() no-argument constructor}.
* This map may not be empty. Then, the given key-value pairs are added. If a default value
* was presents for a given key, then the given value replace the default one.
*
* @param hints A map of key/value pairs to initialize the hints, or {@code null} if none.
*/
public Hints(final Map<? extends RenderingHints.Key, ?> hints) {
this();
if (hints != null) {
putAll(hints);
}
}
/**
* Constructs a new object with keys and values from the given map (which may be null).
* First, an initial map is created as with the {@linkplain #Hints() no-argument constructor}.
* This map may not be empty. Then, the given key-value pairs are added. If a default value
* was presents for a given key, then the given value replace the default one.
*
* @param hints A map of key/value pairs to initialize the hints, or {@code null} if none.
*
* @since 2.5
*/
public Hints(final RenderingHints hints) {
this();
if (hints != null) {
putAll(hints);
}
}
/**
* Returns a new map of hints with the same content than this map.
*
* @since 2.5
*/
@Override
public Hints clone() {
return (Hints) super.clone();
}
/**
* Returns the system-wide default value for the given key. The Geotk library
* initially contains no system default, so {@code getSystemDefault(key)} returns
* null for all keys. Users can add default values using {@link #putSystemDefault
* putSystemDefault}.
* <p>
* To get a map of all system defaults, use {@code new Hints()}.
*
* @param key The hints key.
* @return The system-wide default value for the given key,
* or {@code null} if the key did not have a mapping.
*
* @since 2.4
*/
public static Object getSystemDefault(final RenderingHints.Key key) {
return GLOBAL.get(key);
}
/**
* Adds or modifies a system-wide default value. {@code Hints} instances created after
* this method call will be initialized to the union of all values specified with
* {@code putSystemDefault} and not yet {@linkplain #removeSystemDefault removed}.
* <p>
* If the given value is different than the previous one, then this method notifies
* every listeners registered with {@link Factories#addChangeListener(ChangeListener)}.
*
* @param key The hint key.
* @param value The hint value to be used as the system-wide default for the given key.
* @return The previous value for the given key, or {@code null} if none.
* @throws IllegalArgumentException if {@link Hints.Key#isCompatibleValue(Object)}
* returns {@code false} for the given value.
*
* @since 2.4
*/
@Configuration
public static Object putSystemDefault(final RenderingHints.Key key, final Object value) {
if (!key.isCompatibleValue(value)) {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.IllegalArgument_2, nameOf(key), value));
}
final Object old = GLOBAL.put(key, value);
if (!Objects.equals(value, old)) {
Factories.fireConfigurationChanged(Hints.class);
}
return old;
}
/**
* Removes the specified hints from the set of system default values.
* If the a value was present for the given key, then this method notifies
* every listeners registered with {@link Factories#addChangeListener(ChangeListener)}.
*
* @param key The hints key that needs to be removed.
* @return The value to which the key had previously been mapped,
* or {@code null} if the key did not have a mapping.
*
* @since 2.4
*/
@Configuration
public static Object removeSystemDefault(final RenderingHints.Key key) {
final Object old = GLOBAL.remove(key);
if (old != null) {
Factories.fireConfigurationChanged(Hints.class);
}
return old;
}
/**
* Returns a string representation of the hints. The default implementation formats
* the set of hints as a tree.
*
* @since 2.4
*/
@Override
public String toString() {
return Factory.toString(this);
}
/**
* Returns the enclosing class of the given key, or {@code null} if none. A special case
* is applied for {@code sun.awt.SunHints}, which maps to {@link RenderingHints}.
*/
private static Class<?> getEnclosingClass(final RenderingHints.Key key) {
Class<?> c = key.getClass().getEnclosingClass();
if (c != null && c.getName().startsWith("sun.")) {
c = RenderingHints.class;
}
return c;
}
/**
* Tries to find the name of the given key, using reflection.
*
* @param key The key for which a name is wanted, or {@code null}.
* @return The key name as declared in the static constants.
*/
static String nameOf(final RenderingHints.Key key) {
if (key == null) {
return null;
}
if (!(key instanceof Key)) {
final Field field = fieldOf(key);
if (field != null) {
return field.getName();
}
}
return key.toString();
}
/**
* Tries to find the field of the given key, using reflection. This method searches
* for a constant declared in the enclosing class, which is typically one of:
* <p>
* <ul>
* <li>{@code org.geotoolkit.factory.Hints} (this class)</li>
* <li>{@code javax.media.jai.JAI} (assuming JAI is on the classpath)</li>
* <li>{@code java.awt.RenderingHints} (actually sun.awt.SunHints at least on Sun JDK)</li>
* </ul>
*
* @param key The key for which a field is wanted, or {@code null}.
* @return The key field as declared in the static constants.
*/
static Field fieldOf(final RenderingHints.Key key) {
Field field = null;
if (key != null) {
Class<?> c = getEnclosingClass(key);
if (c == null || (field = fieldOf(c, key)) == null) {
if (key instanceof Key && c != (c = ((Key) key).getValueClass())) {
field = fieldOf(c, key);
}
}
}
return field;
}
/**
* If the given key is declared in the given class, returns its name.
* Otherwise returns {@code null}.
*/
private static String nameOf(final Class<?> type, final RenderingHints.Key key) {
final Field f = fieldOf(type, key);
return (f != null) ? f.getName() : null;
}
/**
* If the given key is declared in the given class, returns its field.
* Otherwise returns {@code null}.
*/
private static Field fieldOf(final Class<?> type, final RenderingHints.Key key) {
final Field[] fields = type.getFields();
for (int i=0; i<fields.length; i++) {
final Field f = fields[i];
/*
* Note: to be strict, the line below should ensure that the field is final in
* addition of static. Unfortunately the JAI static fields are not final in JAI 1.1.
*/
if (Modifier.isStatic(f.getModifiers())) {
final Object v;
try {
v = f.get(null);
} catch (IllegalAccessException e) {
continue;
}
if (v == key) {
return f;
}
}
}
return null;
}
/**
* The type for keys used to control various aspects of the factory
* creation. Factory creation impacts rendering (which is why extending
* {@linkplain java.awt.RenderingHints.Key rendering key} is not a complete
* non-sense), but may impact other aspects of an application as well.
*
* {@section Serialization}
* Keys are serializable if the instance to serialize is declared as a public static
* final constant in the {@linkplain Class#getEnclosingClass() enclosing class}.
* Otherwise, an {@link java.io.NotSerializableException} will be thrown.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.05
*
* @since 2.1
* @module
*/
@SuppressWarnings("serial") // Not relevant because of writeReplace()
public static class Key extends RenderingHints.Key implements Serializable {
/**
* The number of keys created up to date.
*/
private static int count;
/**
* The class name for {@link #valueClass}.
*/
private final String className;
/**
* Base class of all values for this key. Will be created from {@link #className} only when
* first required, in order to avoid too early class loading. This is significant for the
* {@link #JAI_INSTANCE} key for example, in order to avoid JAI dependencies in applications
* that do not need it.
*/
private transient Class<?> valueClass;
/**
* Constructs a new key for values of the given class.
*
* @param classe The base class for all valid values.
*/
public Key(final Class<?> classe) {
this(classe.getName());
valueClass = classe;
}
/**
* Constructs a new key for values of the given class. The class is specified by name
* instead of a {@link Class} object. This allows to defer class loading until needed.
*
* @param className Name of base class for all valid values.
*
* @since 3.00
*/
public Key(final String className) {
super(count());
this.className = className;
}
/**
* Workaround for RFE #4093999 ("Relax constraint on placement of this()/super()
* call in constructors"): {@code count++} need to be executed in a synchronized
* block since it is not an atomic operation.
*/
private static synchronized int count() {
return count++;
}
/**
* Returns the expected class for values stored under this key.
*
* @return The class of values stored under this key.
*/
public Class<?> getValueClass() {
if (valueClass == null) {
try {
valueClass = Class.forName(className);
} catch (ClassNotFoundException exception) {
Logging.unexpectedException(null, Key.class, "getValueClass", exception);
valueClass = Object.class;
}
}
return valueClass;
}
/**
* Returns {@code true} if the specified object is a valid value for this key. The default
* implementation checks if the specified value {@linkplain Class#isInstance is an instance}
* of the {@linkplain #getValueClass value class}.
* <p>
* Note that many hint keys defined in the {@link Hints} class relax this rule and accept
* {@link Class} object assignable to the expected {@linkplain #getValueClass value class}
* as well.
*
* @param value The object to test for validity.
* @return {@code true} if the value is valid; {@code false} otherwise.
*
* @see Hints.ClassKey#isCompatibleValue(Object)
* @see Hints.FileKey#isCompatibleValue(Object)
* @see Hints.IntegerKey#isCompatibleValue(Object)
* @see Hints.OptionKey#isCompatibleValue(Object)
*/
@Override
public boolean isCompatibleValue(final Object value) {
return getValueClass().isInstance(value);
}
/**
* Returns a string representation of this key. This is mostly for debugging purpose.
* The default implementation tries to infer the key name using reflection.
*/
@Override
public String toString() {
Class<?> c = getEnclosingClass(this);
String name = nameOf(c, this);
if (name == null) {
if (c != (c = getValueClass())) {
name = nameOf(c, this);
}
if (name == null) {
name = super.toString();
}
}
return name;
}
/**
* Invoked on serialization for writing a proxy instead than this {@code Key}
* instance. The proxy will use reflection in order to restore the key as one
* of the static constants defined in the {@linkplain Class#getEnclosingClass()
* enclosing class} on deserialization.
*
* @return The proxy to be serialized instead than this {@code Key}.
* @throws ObjectStreamException If this key can not be serialized
* because it is not a known constant.
*
* @since 3.05
*
* @level hidden
*/
protected final Object writeReplace() throws ObjectStreamException {
return new SerializedKey(this);
}
}
/**
* A key for value that may be specified either as instance of {@code T}, or as
* {@code Class<T>}.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.4
* @module
*/
@SuppressWarnings("serial") // Not relevant because of Key.writeReplace()
public static final class ClassKey extends Key {
/**
* Constructs a new key for values of the given class.
*
* @param classe The base class for all valid values.
*/
public ClassKey(final Class<?> classe) {
super(classe);
}
/**
* Constructs a new key for values of the given class. The class is specified by name
* instead of a {@link Class} object. This allows to defer class loading until needed.
*
* @param className Name of base class for all valid values.
*
* @since 3.00
*/
public ClassKey(final String className) {
super(className);
}
/**
* Returns {@code true} if the specified object is a valid value for this key. This
* method checks if the specified value is non-null and is one of the following:
* <p>
* <ul>
* <li>An instance of the {@linkplain #getValueClass() expected value class}.</li>
* <li>A {@link Class} assignable to the expected value class.</li>
* <li>An array of {@code Class} objects assignable to the expected value class.</li>
* </ul>
*/
@Override
public boolean isCompatibleValue(final Object value) {
if (value == null) {
return false;
}
/*
* If the value is an array of classes, invokes this method recursively
* in order to check the validity of each elements in the array.
*/
if (value instanceof Class<?>[]) {
final Class<?>[] types = (Class<?>[]) value;
for (int i=0; i<types.length; i++) {
if (!isCompatibleValue(types[i])) {
return false;
}
}
return types.length != 0;
}
/*
* If the value is a class, checks if it is assignable to the expected value class.
* As a special case, if the value is not assignable but is an abstract class while
* we expected an interface, we will accept this class anyway because the some sub-
* classes may implement the interface (we dont't really know). For example the
* AbstractAuthorityFactory class doesn't implements the CRSAuthorityFactory interface,
* but sub-classe of it do. We make this relaxation in order to preserve compatibility,
* but maybe we will make the check stricter in the future.
*/
if (value instanceof Class<?>) {
final Class<?> type = (Class<?>) value;
final Class<?> expected = getValueClass();
if (expected.isAssignableFrom(type)) {
return true;
}
if (expected.isInterface() && !type.isInterface()) {
final int modifiers = type.getModifiers();
if (Modifier.isAbstract(modifiers) && !Modifier.isFinal(modifiers)) {
return true;
}
}
return false;
}
return super.isCompatibleValue(value);
}
}
/**
* Key for hints to be specified as a {@link Path}.
* The file may also be specified as a {@link File} and {@link String} object.
*
* @author Martin Desruisseaux (IRD)
* @author Jody Garnett (Refractions)
* @version 3.00
*
* @since 2.4
* @module
*/
@SuppressWarnings("serial") // Not relevant because of Key.writeReplace()
public static final class FileKey extends Key {
/**
* {@code true} if write operations need to be allowed.
*/
private final boolean writable;
/**
* Creates a new key for {@link Path} value.
*
* @param writable {@code true} if write operations need to be allowed.
*/
public FileKey(final boolean writable) {
super(Path.class);
this.writable = writable;
}
/**
* Returns {@code true} if the specified object is a valid file or directory.
* The check performed depends on the value of the {@code writable} argument
* given to the constructor:
* <p>
* <ul>
* <li>If {@code false}, then the file must exists and be {@linkplain Files#isReadable(Path)} readable}.</li>
* <li>If {@code true}, then there is a choice:<ul>
* <li>If the file exists, it must be {@linkplain Files#isWritable(Path)} writeable}.</li>
* <li>Otherwise the file must have a {@linkplain Path#getParent parent} and
* that parent must be writable.</li></ul></li>
* </ul>
*/
@Override
public boolean isCompatibleValue(final Object value) {
final Path path;
if (value instanceof Path) {
path = (Path) value;
}else if (value instanceof File) {
path = ((File) value).toPath();
} else if (value instanceof String) {
path = Paths.get((String) value);
} else {
return false;
}
if (writable) {
if (Files.exists(path)) {
return Files.isWritable(path);
} else {
final Path parent = path.getParent();
return parent!=null && Files.isWritable(parent);
}
} else {
return Files.isReadable(path);
}
}
}
/**
* A hint used to capture a configuration setting as an integer.
* A default value is provided and may be checked with {@link #getDefault()}.
*
* @author Jody Garnett (Refractions)
* @version 3.00
*
* @since 2.4
* @module
*/
@SuppressWarnings("serial") // Not relevant because of Key.writeReplace()
public static final class IntegerKey extends Key {
/**
* The default value.
*/
private final int number;
/**
* Creates a new key with the specified default value.
*
* @param number The default value.
*/
public IntegerKey(final int number) {
super(Integer.class);
this.number = number;
}
/**
* Returns the default value.
*
* @return The default value.
*/
public int getDefault(){
return number;
}
/**
* Returns the value from the specified hints as an integer. If no value were found
* for this key, then this method returns the {@linkplain #getDefault default value}.
*
* @param hints The map where to fetch the hint value, or {@code null}.
* @return The hint value as an integer, or the default value if not hint
* was explicitly set.
*/
public int toValue(final Hints hints) {
if (hints != null) {
final Object value = hints.get(this);
if (value instanceof Number) {
return ((Number) value).intValue();
} else if (value instanceof CharSequence) {
return Integer.parseInt(value.toString());
}
}
return number;
}
/**
* Returns {@code true} if the specified object is a valid integer.
*/
@Override
public boolean isCompatibleValue(final Object value) {
if (value instanceof Short || value instanceof Integer) {
return true;
}
if (value instanceof String || value instanceof InternationalString) {
try {
Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
Logging.getLogger("org.geotoolkit.factory").finer(e.toString());
}
}
return false;
}
}
/**
* Key that allows the choice of several options. The special value {@code "*"} can be used
* as a wildcard to indicate that undocumented options may be supported (but there is no
* assurances - see {@link Hints#DATUM_SHIFT_METHOD} for example).
*
* @author Jody Garnett (Refractions)
* @version 3.00
*
* @since 2.4
* @module
*/
@SuppressWarnings("serial") // Not relevant because of Key.writeReplace()
public static final class OptionKey extends Key {
/**
* The set of options allowed.
*/
private final Set<String> options;
/**
* {@code true} if the {@code "*"} wildcard was given in the set of options.
*/
private final boolean wildcard;
/**
* Creates a new key for a configuration option.
*
* @param alternatives The available options.
*/
public OptionKey(final String... alternatives) {
super(String.class);
final Set<String> options = new TreeSet<>(Arrays.asList(alternatives));
this.wildcard = options.remove("*");
this.options = unmodifiableOrCopy(options);
}
/**
* Returns the set of available options.
*
* @return The available options.
*/
public Set<String> getOptions() {
return options;
}
/**
* Returns {@code true} if the specified object is one of the valid options. If the
* options specified at construction time contains the {@code "*"} wildcard, then
* this method returns {@code true} for every {@link String} object.
*/
@Override
public boolean isCompatibleValue(final Object value) {
return wildcard ? (value instanceof String) : options.contains(value);
}
}
/**
* Key for hints to be specified as a {@link javax.sql.DataSource}. The file may also be
* specified as a {@link String} or {@link Name} object. This key also allows null value,
* which explicitly means "<cite>no data source provided</cite>".
*
* {@note Different JNDI implementations build up their name differently, so we may need
* to look for <code>"jdbc:EPSG"</code> in JBoss and <code>"jdbc/EPSG"</code> in
* Websphere.}
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.4
* @module
*/
@SuppressWarnings("serial") // Not relevant because of Key.writeReplace()
static final class DataSourceKey extends Key {
/**
* Creates a new key for {@link javax.sql.DataSource} value.
*/
public DataSourceKey() {
super(DataSource.class);
}
/**
* Returns {@code true} if the specified object is a data source or data source name.
* The {@code null} value is also accepted, which explicitly means "<cite>no data source
* provided</cite>".
*/
@Override
public boolean isCompatibleValue(final Object value) {
return (value == null) || (value instanceof DataSource) ||
(value instanceof String) || (value instanceof Name);
}
}
}