/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2016, 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.factory; import java.awt.RenderingHints; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import javax.xml.parsers.SAXParser; import org.geotools.resources.Arguments; import org.geotools.resources.Classes; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.util.Utilities; import org.geotools.util.Version; import org.geotools.util.logging.LoggerFactory; import org.geotools.util.logging.Logging; import org.geotools.xml.NullEntityResolver; import org.geotools.xml.PreventLocalEntityResolver; import org.xml.sax.EntityResolver; /** * Static methods relative to the global GeoTools configuration. GeoTools can be configured * in a system-wide basis through {@linkplain System#getProperties system properties}, some * of them are declared as {@link String} constants in this class. * <p> * There are many aspects to the configuration of GeoTools: * <ul> * <li>Default Settings: Are handled as the Hints returned by {@link #getDefaultHints()}, the * default values can be provided in application code, or specified using system properties.</li> * <li>Integration JNDI: Telling the GeoTools library about the facilities of an application, * or application container takes several forms. This class provides the * {@link #init(InitialContext)} method allowing to tell GeoTools about the JNDI * context to use.</li> * <li>Integration Plugins: If hosting GeoTools in a alternate plugin system such as Spring * or OSGi, application may needs to hunt down the {@code FactoryFinder}s and register * additional "Factory Iterators" for GeoTools to search using the * {@link #addFactoryIteratorProvider} method.</li> * </ul> * * @since 2.4 * * * @source $URL$ * @version $Id$ * @author Jody Garnett * @author Martin Desruisseaux */ public final class GeoTools { /** * Properties about this geotools build */ private static final Properties PROPS; static { PROPS = loadProperites("GeoTools.properties"); } private static Properties loadProperites(String resource) { Properties props = new Properties(); InputStream stream = GeoTools.class.getResourceAsStream(resource); if (stream != null) { try { props.load(stream); } catch (IOException ignore) { } finally { try { stream.close(); } catch (IOException ignore) { } } } return props; } /** * The current GeoTools version. The separator character must be the dot. */ private static final Version VERSION = new Version(PROPS.getProperty("version", "17-SNAPSHOT")); /** * The version control (svn) revision at which this version of geotools was built. */ private static final String BUILD_REVISION; static { BUILD_REVISION = PROPS.getProperty("build.revision", "-1"); } /** * The timestamp at which this version of geotools was built. */ private static final String BUILD_TIMESTAMP = PROPS.getProperty("build.timestamp", ""); /** * Object to inform about system-wide configuration changes. * We use the Swing utility listener list since it is lightweight and thread-safe. * Note that it doesn't involve any dependency to the remaining of Swing library. */ private static final EventListenerList LISTENERS = new EventListenerList(); /** * The bindings between {@linkplain System#getProperties system properties} and * a hint key. This field must be declared before any call to the {@link #bind} * method. */ private static final Map<String, RenderingHints.Key> BINDINGS = new HashMap<String, RenderingHints.Key>(); /** * The {@linkplain System#getProperty(String) system property} key for the default value to be * assigned to the {@link Hints#CRS_AUTHORITY_EXTRA_DIRECTORY CRS_AUTHORITY_EXTRA_DIRECTORY} * hint. * * @see Hints#CRS_AUTHORITY_EXTRA_DIRECTORY * @see #getDefaultHints */ public static final String CRS_AUTHORITY_EXTRA_DIRECTORY = "org.geotools.referencing.crs-directory"; static { bind(CRS_AUTHORITY_EXTRA_DIRECTORY, Hints.CRS_AUTHORITY_EXTRA_DIRECTORY); } /** * The {@linkplain System#getProperty(String) system property} key for the default * value to be assigned to the {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE} hint. * * @see Hints#EPSG_DATA_SOURCE * @see #getDefaultHints */ public static final String EPSG_DATA_SOURCE = "org.geotools.referencing.epsg-datasource"; static { bind(EPSG_DATA_SOURCE, Hints.EPSG_DATA_SOURCE); } /** * The {@linkplain System#getProperty(String) system property} key for the default * value to be assigned to the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint. * * This setting can provide a transition path for projects expecting a (<var>longitude</var>, * <var>latitude</var>) axis order on a system-wide level. Application developpers can set the * default value as below: * * <blockquote><pre> * System.setProperty(FORCE_LONGITUDE_FIRST_AXIS_ORDER, "true"); * </pre></blockquote> * * Note that this system property applies mostly to the default EPSG factory. Most other * factories ({@code "CRS"}, {@code "AUTO"}, <cite>etc.</cite>) don't need this property * since they use (<var>longitude</var>, <var>latitude</var>) axis order by design. * * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * @see #getDefaultHints */ public static final String FORCE_LONGITUDE_FIRST_AXIS_ORDER = "org.geotools.referencing.forceXY"; static { bind(FORCE_LONGITUDE_FIRST_AXIS_ORDER, Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER); } /** * The {@linkplain System#getProperty(String) system property} key for the default * value to be assigned to the {@link Hints# * ENTITY_RESOLVER} hint. * * This setting specifies the XML Entity resolver to be used when configuring a SAXParser * * @see Hints#ENTITY_RESOLVER * @see #getDefaultHints */ public static final String ENTITY_RESOLVER = "org.xml.sax.EntityResolver"; static { bind(ENTITY_RESOLVER, Hints.ENTITY_RESOLVER); } /** * The {@linkplain System#getProperty(String) system property} key for the default * value to be assigned to the {@link Hints# * RESAMPLE_TOLERANCE} hint. * * This setting specifies the tolerance used when linearizing warp transformation into * piecewise linear ones, by default it is 0.333 pixels * * @see Hints#RESAMPLE_TOLERANCE * @see #getDefaultHints */ public static final String RESAMPLE_TOLERANCE = "org.geotools.referencing.resampleTolerance"; static { bind(RESAMPLE_TOLERANCE, Hints.RESAMPLE_TOLERANCE); } /** * The {@linkplain System#getProperty(String) system property} key for the default * value to be assigned to the {@link Hints#LOCAL_DATE_TIME_HANDLING} hint. * * This setting specifies if dates shall be treated as local dates ignoring time zones. * * @see Hints#LOCAL_DATE_TIME_HANDLING * @see #getDefaultHints * @since 15.0 */ public static final String LOCAL_DATE_TIME_HANDLING = "org.geotools.localDateTimeHandling"; static { bind(LOCAL_DATE_TIME_HANDLING, Hints.LOCAL_DATE_TIME_HANDLING); } /** * The initial context. Will be created only when first needed. */ private static InitialContext context; /** * Class loaders to be added to the list in ${link {@link FactoryRegistry#getClassLoaders()}} * which are used to look-up plug-ins. Class loaders are added via * {@link #addClassLoader(ClassLoader)} */ private static final Set<ClassLoader> addedClassLoaders = Collections.synchronizedSet(new HashSet<ClassLoader>()); /** * Do not allow instantiation of this class. */ private GeoTools() { } /** * Binds the specified {@linkplain System#getProperty(String) system property} * to the specified key. Only one key can be binded to a given system property. * However the same key can be binded to more than one system property names, * in which case the extra system property names are aliases. * * @param property The system property. * @param key The key to bind to the system property. * @throws IllegalArgumentException if an other key is already bounds * to the given system property. */ private static void bind(final String property, final RenderingHints.Key key) { synchronized (BINDINGS) { final RenderingHints.Key old = BINDINGS.put(property, key); if (old == null) { return; } // Roll back BINDINGS.put(property, old); } throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "property", property)); } /** * Returns summary information about GeoTools and the current environment. * Calls {@linkplain #getEnvironmentInfo()} followed by {@linkplain #getGeoToolsJarInfo()} * and concatenates their results. * * @return requested information as a string */ public static String getAboutInfo() { final StringBuilder sb = new StringBuilder(); sb.append(getEnvironmentInfo()); sb.append(String.format("%n")); sb.append(getGeoToolsJarInfo()); return sb.toString(); } /** * Returns summary information about the GeoTools version and the host environment. * * @return information as a String */ public static String getEnvironmentInfo() { final String newline = String.format("%n"); final StringBuilder sb = new StringBuilder(); sb.append("GeoTools version ").append(getVersion().toString()); if (sb.toString().endsWith("SNAPSHOT")) { sb.append(" (built from r").append(getBuildRevision().toString()).append(")"); } sb.append(newline).append("Java version: "); sb.append(System.getProperty("java.version")); sb.append(newline).append("Operating system: "); sb.append(System.getProperty("os.name")).append(' ').append(System.getProperty("os.version")); return sb.toString(); } /** * Returns the names of the GeoTools jars on the classpath. * * @return list of jars as a formatted string */ public static String getGeoToolsJarInfo() { final StringBuilder sb = new StringBuilder(); final String newline = String.format("%n"); final String indent = " "; sb.append("GeoTools jars on classpath:"); for (String jarName : getGeoToolsJars()) { sb.append(newline).append(indent).append(jarName); } return sb.toString(); } /** * A helper method for {@linkplain #getGeoToolsJarInfo} which scans the * classpath looking for GeoTools jars matching the current version. * * @return a list of jar names */ private static List<String> getGeoToolsJars() { final Pattern pattern = Pattern.compile(".*\\/" + getVersion() + "\\/(gt-.*jar$)"); final List<String> jarNames = new ArrayList<String>(); String pathSep = System.getProperty("path.separator"); String classpath = System.getProperty("java.class.path"); StringTokenizer st = new StringTokenizer(classpath, pathSep); while (st.hasMoreTokens()) { String path = st.nextToken(); Matcher matcher = pattern.matcher(path); if (matcher.find()) { jarNames.add(matcher.group(1)); } } Collections.sort(jarNames); return jarNames; } /** * Reports back the vcs revision at which the version of GeoTools was built. * * @return The svn revision. */ public static String getBuildRevision() { return BUILD_REVISION; } /** * Reports back the timestamp at which the version of GeoTools of built. * * @return The build timestamp. */ public static String getBuildTimestamp() { return BUILD_TIMESTAMP; } /** * Returns the raw properties object containing all properties about this GeoTools build. * <p> * Example from the 14.3 release: * <ul> * <li>version=14.3</li> * <li>build.revision=2298d56000bef6f526b521a480316ea544c74571</li> * <li>build.branch=rel_14.3</li> * <li>build.timestamp=21-Mar-2016 21:30</li> * </ul> */ public static Properties getBuildProperties() { Properties props = new Properties(); props.putAll(PROPS); return props; } /** * Reports back the version of GeoTools being used. * * @return The current GeoTools version. */ public static Version getVersion(){ return VERSION; } /** * Lookup version for provided class. * <p> * Version number is determined by either: * <ul> * <li>Use of jar naming convention, matching jars such as jts-1.13.jar</li> * <li>Use of MANIFEST.MF (to check Implementation-Version, Project-Version)</li> * <li> * <li>To assist * @param type * @return Version (or null if unavailable) */ public static Version getVersion(Class<?> type) { final URL classLocation = classLocation(type); String path = classLocation.toString(); // try and extract from maven jar naming convention if (classLocation.getProtocol().equalsIgnoreCase("jar")) { String jarVersion = jarVersion(path); if( jarVersion != null ){ return new Version(jarVersion); } // try manifest try { URL manifestLocation = manifestLocation( path ); Manifest manifest = new Manifest(); try (InputStream content = manifestLocation.openStream()) { manifest.read(content); } for (String attribute : new String[] { "Implementation-Version", "Project-Version", "Specification-Version" }) { String value = manifest.getMainAttributes().getValue(attribute); if (value != null) { return new Version(value); } } } catch (IOException e) { // unavailable } } String name = type.getName(); if (name.startsWith("org.geotools") || name.startsWith("org.opengis")) { return GeoTools.getVersion(); } return null; } /** * Class location. * * @param type * @return class location */ static URL classLocation( Class<?> type ){ return type.getResource(type.getSimpleName() + ".class"); } /** * Determine jar version from static analysis of classLocation path. * @param classLocation * @return jar version, or null if unknown */ static String jarVersion( String classLocation){ if (classLocation.startsWith("jar:") || classLocation.contains(".jar!")){ String location = classLocation.substring(0, classLocation.lastIndexOf("!") + 1); int dash = location.lastIndexOf("-"); int dot = location.lastIndexOf(".jar"); if (dash != -1 && dot != -1) { return location.substring(dash + 1, dot); } } // handle custom protocols such as jboss "vfs:" or OSGi "resource" if( classLocation.contains(".jar/")){ String location = classLocation.substring(0, classLocation.indexOf(".jar/") + 4); int dash = location.lastIndexOf("-"); int dot = location.lastIndexOf(".jar"); if (dash != -1 && dot != -1) { return location.substring(dash + 1, dot); } } return null; } /** * Generate URL of MANIFEST.MF file for provided class location. * * @param classLocation * @return MANIFEST.MF location, or null if unknown */ static URL manifestLocation(String classLocation) { URL url; if (classLocation.startsWith("jar:")) { try { url = new URL(classLocation.substring(0, classLocation.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"); return url; } catch (MalformedURLException e) { return null; } } // handle custom protocols such as jboss "vfs:" or OSGi "resource" if (classLocation.contains(".jar/")) { String location = classLocation.substring(0, classLocation.indexOf(".jar/") + 4); try { url = new URL(location + "/META-INF/MANIFEST.MF"); return url; } catch (MalformedURLException e) { return null; } } return null; } /** * Lookup the MANIFEST.MF for the provided class. * <p> * This can be used to quickly verify packaging information. * @param type * @return MANIFEST.MF contents, please note contents may be empty when running from IDE */ public static Manifest getManifest(Class<?> type) { final URL classLocation = classLocation(type); Manifest manifest = new Manifest(); URL manifestLocation = manifestLocation( classLocation.toString() ); if( manifestLocation != null ){ try { try (InputStream content = manifestLocation.openStream()) { manifest.read(content); } } catch (IOException ignore) { } } if (manifest.getMainAttributes().isEmpty()) { // must be running in IDE String name = type.getName(); if (name.startsWith("org.geotools") || name.startsWith("org.opengis") || name.startsWith("net.opengis")) { String generated = "Manifest-Version: 1.0\n" + "Project-Version: " + getVersion() + "\n"; try { manifest.read(new ByteArrayInputStream(generated.getBytes())); } catch (IOException e) { } } } return manifest; } /** * Sets the global {@linkplain LoggerFactory logger factory}. * * This method is the same as {@code Logging.GEOTOOLS.setLoggerFactory(factory)}. * GeoTools ships with support for * <A HREF="http://jakarta.apache.org/commons/logging/">Commons-logging</A> and * <A HREF="http://logging.apache.org/log4j/">log4j</A>. This method exists to allow you * supply your own implementation (this is sometimes required when using a GeoTools * application in an exotic environment like Eclipse, OC4J or your application). * * @param factory The logger factory to use. * * @see Logging#setLoggerFactory(LoggerFactory) * * @since 2.4 */ public void setLoggerFactory(final LoggerFactory<?> factory) { Logging.GEOTOOLS.setLoggerFactory(factory); } /** * Initializes GeoTools for use. This convenience method performs various tasks (more may be added in the future), including setting up the * {@linkplain java.util.logging Java logging framework} in one of the following states: * <p> * <ul> * <li>If the <A HREF="http://jakarta.apache.org/commons/logging/">Commons-logging</A> framework is available, then every logging message in the * {@code org.geotools} namespace sent to the Java {@linkplain java.util.logging.Logger logger} are redirected to Commons-logging.</li> * * <li>Otherwise if the <A HREF="http://logging.apache.org/log4j">Log4J</A> framework is available, then every logging message in the * {@code org.geotools} namespace sent to the Java {@linkplain java.util.logging.Logger logger} are redirected to Log4J.</li> * * <li>Otherwise, the Java logging {@linkplain java.util.logging.Formatter formatter} for console output is replaced by a * {@linkplain org.geotools.util.logging.MonolineFormatter monoline formatter}.</li> * </ul> * <p> * In addition, the {@linkplain #getDefaultHints default hints} are initialized to the specified {@code hints}. * <p> * Invoking this method is <strong>not</strong> required fpr the GeoTools library to function. It is just a convenience method for overwriting * select Java and GeoTools default settings. Supplying these defaults is not desirable in all settings, such as writing test cases. * <p> * Example of typical invocation in a GeoServer environment: * * <pre><code> * Hints hints = new Hints(); * hints.put({@linkplain Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}, Boolean.TRUE); * hints.put({@linkplain Hints#FORCE_AXIS_ORDER_HONORING}, "http"); * GeoTools.init(hints); * </code></pre> * * @param hints The hints to use. * * @see Logging#setLoggerFactory(String) * @see Logging#forceMonolineConsoleOutput * @see Hints#putSystemDefault * @see #getDefaultHints */ public static void init(final Hints hints) { init(); if (hints != null) { // This will trigger fireConfigurationChanged() Hints.putSystemDefault(hints); } } /** * Initializes GeoTools for use. This convenience method performs various tasks (more may be added in the future), including setting up the * {@linkplain java.util.logging Java logging framework} in one of the following states: * <p> * <ul> * <li>If the <A HREF="http://jakarta.apache.org/commons/logging/">Commons-logging</A> framework is available, then every logging message in the * {@code org.geotools} namespace sent to the Java {@linkplain java.util.logging.Logger logger} are redirected to Commons-logging.</li> * * <li>Otherwise if the <A HREF="http://logging.apache.org/log4j">Log4J</A> framework is available, then every logging message in the * {@code org.geotools} namespace sent to the Java {@linkplain java.util.logging.Logger logger} are redirected to Log4J.</li> * * <li>Otherwise, the Java logging {@linkplain java.util.logging.Formatter formatter} for console output is replaced by a * {@linkplain org.geotools.util.logging.MonolineFormatter monoline formatter}.</li> * </ul> * <p> * Invoking this method is <strong>not</strong> required fpr the GeoTools library to function. It is just a convenience method for overwriting * select Java and GeoTools default settings. Supplying these defaults is not desirable in all settings, such as writing test cases. * <p> * * @see Logging#setLoggerFactory(String) * @see Logging#forceMonolineConsoleOutput * @see Hints#putSystemDefault * @see #getDefaultHints */ public static void init(){ final Logging log = Logging.GEOTOOLS; try { log.setLoggerFactory("org.geotools.util.logging.CommonsLoggerFactory"); } catch (ClassNotFoundException commonsException) { try { log.setLoggerFactory("org.geotools.util.logging.Log4JLoggerFactory"); } catch (ClassNotFoundException log4jException) { // Nothing to do, we already tried our best. } } // If java logging is used, force monoline console output. if (log.getLoggerFactory() == null) { log.forceMonolineConsoleOutput(); } } /** * Provides GeoTools with the JNDI context for resource lookup. * * @param applicationContext The initial context to use. * * @see #getInitialContext * * @since 2.4 */ public static void init(final InitialContext applicationContext) { synchronized (GeoTools.class) { context = applicationContext; } fireConfigurationChanged(); } /** * Scans {@linkplain System#getProperties system properties} for any property keys * defined in this class, and add their values to the specified map of hints. For * example if the {@value #FORCE_LONGITUDE_FIRST_AXIS_ORDER} system property is * defined, then the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint will be added to the set of hints. * * @return {@code true} if at least one hint changed as a result of this scan, * or {@code false} otherwise. */ static boolean scanForSystemHints(final Map<RenderingHints.Key, Object> hints) { boolean changed = false; synchronized (BINDINGS) { for (final Map.Entry<String, RenderingHints.Key> entry : BINDINGS.entrySet()) { final String propertyKey = entry.getKey(); final String property; try { property = System.getProperty(propertyKey); } catch (SecurityException e) { unexpectedException(e); continue; } if (property != null) { /* * Converts the system property value from String to Object (java.lang.Boolean * or java.lang.Number). We perform this conversion only if the key is exactly * of kind Hints.Key, not a subclass like ClassKey, in order to avoid useless * class loading on 'getValueClass()' method invocation (ClassKey don't make * sense for Boolean and Number, which are the only types that we convert here). */ Object value = property; final RenderingHints.Key hintKey = entry.getValue(); if (hintKey.getClass().equals(Hints.Key.class)) { final Class<?> type = ((Hints.Key) hintKey).getValueClass(); if (type.equals(Boolean.class)) { value = Boolean.valueOf(property); } else if (Number.class.isAssignableFrom(type)) try { value = Classes.valueOf(type, property); } catch (NumberFormatException e) { unexpectedException(e); continue; } } final Object old; try { old = hints.put(hintKey, value); } catch (IllegalArgumentException e) { // The property value is illegal for this hint. unexpectedException(e); continue; } if (!changed && !Utilities.equals(old, value)) { changed = true; } } } } return changed; } /** * Logs an exception as if it originated from {@link Hints#scanSystemProperties}, * since it is the public API that may invokes this method. */ private static void unexpectedException(final Exception exception) { Logging.unexpectedException(Hints.class, "scanSystemProperties", exception); } /** * Returns the default set of hints used for the various utility classes. * This default set is determined by: * <p> * <ul> * <li>The {@linplain System#getProperties system properties} available. Some property * keys are enumerated in the {@link GeoTools} class.</li> * <li>Any hints added by call to the {@link Hints#putSystemDefault} * or {@link #init} method.</li> * </ul> * <p> * <b>Long term plan:</b> * We would like to transition the utility classes to being injected with their * required factories, either by taking Hints as part of their constructor, or * otherwise. Making this change would be a three step process 1) create instance * methods for each static final class method 2) create an singleton instance of the * class 3) change each static final class method into a call to the singleton. With * this in place we could then encourage client code to make use of utility class * instances before eventually retiring the static final methods. * * @return A copy of the default hints. It is safe to add to it. */ public static Hints getDefaultHints() { return Hints.getDefaults(false); } /** * Used to combine provided hints with global GeoTools defaults. * * @param hints * @return */ public static Hints addDefaultHints(final Hints hints) { final Hints completed = getDefaultHints(); if (hints != null) { completed.add(hints); } return completed; } /** * Returns the default entity resolver, used to configure {@link SAXParser}. * * @param hints An optional set of hints, or {@code null} if none, see {@link Hints#ENTITY_RESOLVER}. * @return An entity resolver (never {@code null}) */ public static EntityResolver getEntityResolver(Hints hints) { if (hints == null) { hints = getDefaultHints(); } if (hints.containsKey(Hints.ENTITY_RESOLVER)) { Object hint = hints.get(Hints.ENTITY_RESOLVER); if (hint == null) { return NullEntityResolver.INSTANCE; } else if (hint instanceof EntityResolver) { return (EntityResolver) hint; } else if (hint instanceof String) { String className = (String) hint; return instantiate(className,EntityResolver.class, PreventLocalEntityResolver.INSTANCE); } } return PreventLocalEntityResolver.INSTANCE; } /** * Create instance of className (or access singleton INSTANCE field). * * @param className Class name to instantiate * @param type Class of object created * @param defaultValue Default to be provided, may be null * @return EntityResolver, defaults to {@link PreventLocalEntityResolver#INSTANCE} if unavailable. */ static <T,D extends T> T instantiate(String className, Class<T> type, D defaultValue){ if( className == null){ return defaultValue; } final Logger LOGGER = Logging.getLogger("org.geotools.xml"); try { Class<?> kind = Class.forName(className); // step 1 look for instance field for (Field field : kind.getDeclaredFields()) { int modifier = field.getModifiers(); if ("INSTANCE".equals(field.getName()) && Modifier.isStatic(modifier) && Modifier.isPublic(modifier)) { try { Object value = field.get(null); if (value != null && value instanceof EntityResolver) { return type.cast(value); } else { LOGGER.log(Level.FINER, "Unable to use ENTITY_RESOLVER: " + className + ".INSTANCE"); } } catch (Throwable t) { LOGGER.log(Level.FINER, "Unable to instantiate ENTITY_RESOLVER: " + className + ".INSTANCE", t); } return defaultValue; } } // step 2 no argument constructor try { Object value = kind.newInstance(); if (type.isInstance(value)) { return type.cast(value); } } catch (InstantiationException | IllegalAccessException e) { LOGGER.log(Level.FINER, "Unable to instantiate ENTITY_RESOLVER: " + e.getMessage(), e); } } catch (ClassNotFoundException notFound) { LOGGER.log(Level.FINER, "Unable to instantiate ENTITY_RESOLVER: " + notFound.getMessage(), notFound); } return defaultValue; } /** * Returns the default initial context. * * @param hints An optional set of hints, or {@code null} if none. * @return The initial context (never {@code null}). * @throws NamingException if the initial context can't be created. * * @see #init(InitialContext) * * @since 2.4 */ public static synchronized InitialContext getInitialContext(final Hints hints) throws NamingException { if (context == null) { context = new InitialContext(); } return context; } /** * Clears the initial context (closes it if not null) * @throws NamingException * * @since 15.0 */ public static synchronized void clearInitialContext() throws NamingException { if(context != null) { context.close(); } context = null; } /** * Converts a GeoTools name to the syntax used by the {@linkplain #getInitialContext * GeoTools JNDI context}. Names may be constructed in a variety of ways depending on * the implementation of {@link InitialContext}. GeoTools uses {@code "jdbc:EPSG"} * internally, but many implementaitons use the form {@code "jdbc/EPSG"}. Calling * this method before use will set the name right. * * @param name Name of the form {@code "jdbc:EPSG"}, or {@code null}. * @return Name fixed up with {@link Context#composeName(String,String)}, * or {@code null} if the given name was null. * * @since 2.4 */ public static String fixName(final String name) { return fixName(null, name, null); } /** * Converts a GeoTools name to the syntax used by the specified JNDI context. * This method is similar to {@link #fixName(String)}, but uses the specified * context instead of the GeoTools one. * * @param context The context to use, or {@code null} if none. * @param name Name of the form {@code "jdbc:EPSG"}, or {@code null}. * @return Name fixed up with {@link Context#composeName(String,String)}, * or {@code null} if the given name was null. * * @since 2.4 */ public static String fixName(final Context context, final String name) { return (context != null) ? fixName(context, name, null) : name; } /** * Implementation of {@code fixName} method. If the context is {@code null}, then * the {@linkplain #getInitialContext GeoTools initial context} will be fetch only * when first needed. */ private static String fixName(Context context, final String name, final Hints hints) { String fixed = null; if (name != null) { final StringTokenizer tokens = new StringTokenizer(name, ":/"); while (tokens.hasMoreTokens()) { final String part = tokens.nextToken(); if (fixed == null) { fixed = part; } else try { if (context == null) { context = getInitialContext(hints); } fixed = context.composeName(fixed, part); } catch (NamingException e) { Logging.unexpectedException(GeoTools.class, "fixName", e); return name; } } } return fixed; } /** * Adds an alternative way to search for factory implementations. {@link FactoryRegistry} has * a default mechanism bundled in it, which uses the content of all {@code META-INF/services} * directories found on the classpath. This {@code addFactoryIteratorProvider} method allows * to specify additional discovery algorithms. It may be useful in the context of some * frameworks that use the <cite>constructor injection</cite> pattern, like the * <a href="http://www.springframework.org/">Spring framework</a>. * * @param provider A new provider for factory iterators. */ public static void addFactoryIteratorProvider(final FactoryIteratorProvider provider) { FactoryIteratorProviders.addFactoryIteratorProvider(provider); } /** * Removes a provider that was previously {@linkplain #addFactoryIteratorProvider added}. * Note that factories already obtained from the specified provider will not be * {@linkplain FactoryRegistry#deregisterServiceProvider deregistered} by this method. * * @param provider The provider to remove. */ public static void removeFactoryIteratorProvider(final FactoryIteratorProvider provider) { FactoryIteratorProviders.removeFactoryIteratorProvider(provider); } /** * Adds the specified listener to the list of objects to inform when system-wide * configuration changed. * * @param listener The listener to add. */ public static void addChangeListener(final ChangeListener listener) { removeChangeListener(listener); // Ensure singleton. LISTENERS.add(ChangeListener.class, listener); } /** * Removes the specified listener from the list of objects to inform when system-wide * configuration changed. * * @param listener The listener to remove. */ public static void removeChangeListener(final ChangeListener listener) { LISTENERS.remove(ChangeListener.class, listener); } /** * Informs every listeners that system-wide configuration changed. */ public static void fireConfigurationChanged() { final ChangeEvent event = new ChangeEvent(GeoTools.class); final Object[] listeners = LISTENERS.getListenerList(); for (int i=0; i<listeners.length; i+=2) { if (listeners[i] == ChangeListener.class) { ((ChangeListener) listeners[i+1]).stateChanged(event); } } } /** * Adds a class loader to be included in the list of class loaders that are used to locate * GeoTools plug-ins. * <p> * Client code that calls this method may also need to call {@link FactoryRegistry#scanForPlugins()} * on any existing registry to force it to clear its cache and use the added class loader to * locate plugins. * </p> * * @param classLoader The class loader. */ public static void addClassLoader(ClassLoader classLoader) { addedClassLoaders.add(classLoader); fireConfigurationChanged(); } /** * Returns the class loaders added via {@link #addClassLoader(ClassLoader)}. */ static Set<ClassLoader> getClassLoaders() { return addedClassLoaders; } /** * Reports the GeoTools {@linkplain #getVersion version} number to the * {@linkplain System#out standard output stream}. * * @param args Command line arguments. */ public static void main(String[] args) { final Arguments arguments = new Arguments(args); args = arguments.getRemainingArguments(0); arguments.out.print("GeoTools version "); arguments.out.println(getVersion()); final Hints hints = getDefaultHints(); if (hints!=null && !hints.isEmpty()) { arguments.out.println(hints); } } }