001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2007, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.referencing.factory.epsg;
017:
018: // J2SE dependencies
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.PrintWriter;
022: import java.util.Set;
023: import java.util.Map;
024: import java.util.TreeSet;
025: import java.util.TreeMap;
026: import java.util.Iterator;
027: import java.util.Locale;
028: import java.util.Collection;
029: import java.util.logging.Level;
030: import java.net.MalformedURLException;
031: import java.net.URL;
032:
033: // OpenGIS dependencies
034: import org.opengis.metadata.citation.Citation;
035: import org.opengis.referencing.IdentifiedObject;
036: import org.opengis.referencing.FactoryException;
037: import org.opengis.referencing.crs.CRSAuthorityFactory;
038: import org.opengis.referencing.crs.CoordinateReferenceSystem;
039:
040: // Geotools dependencies
041: import org.geotools.factory.GeoTools;
042: import org.geotools.factory.Hints;
043: import org.geotools.referencing.ReferencingFactoryFinder;
044: import org.geotools.referencing.factory.AbstractAuthorityFactory;
045: import org.geotools.referencing.factory.DeferredAuthorityFactory;
046: import org.geotools.referencing.factory.FactoryNotFoundException;
047: import org.geotools.referencing.factory.PropertyAuthorityFactory;
048: import org.geotools.referencing.factory.ReferencingFactoryContainer;
049: import org.geotools.metadata.iso.citation.Citations;
050: import org.geotools.metadata.iso.citation.CitationImpl;
051: import org.geotools.io.TableWriter;
052: import org.geotools.io.IndentedLineWriter;
053: import org.geotools.resources.Arguments;
054: import org.geotools.resources.i18n.Errors;
055: import org.geotools.resources.i18n.ErrorKeys;
056: import org.geotools.resources.i18n.Logging;
057: import org.geotools.resources.i18n.LoggingKeys;
058: import org.geotools.resources.i18n.Vocabulary;
059: import org.geotools.resources.i18n.VocabularyKeys;
060:
061: /**
062: * Authority factory for {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}
063: * beyong the one defined in the EPSG database. This factory is used as a fallback when a
064: * requested code is not found in the EPSG database, or when there is no connection at all
065: * to the EPSG database. The additional CRS are defined as <cite>Well Known Text</cite> in
066: * a property file located by default in the {@code org.geotools.referencing.factory.epsg}
067: * package, and whose name should be {@value #FILENAME}. If no property file is found, the
068: * factory won't be activated. The property file can also be located in a custom directory;
069: * See {@link #getDefinitionsURL()} for more details.
070: * <p>
071: * This factory can also be used to provide custom extensions or overrides to a main EPSG factory.
072: * In order to provide a custom extension file, override the {@link #getDefinitionsURL()} method.
073: * In order to make the factory be an override, change the default priority by using the
074: * two arguments constructor (this factory defaults to {@link ThreadedEpsgFactory#PRIORITY} - 10,
075: * so it's used as an extension).
076: *
077: * @since 2.1
078: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/epsg/FactoryUsingWKT.java $
079: * @version $Id: FactoryUsingWKT.java 27848 2007-11-12 13:10:32Z desruisseaux $
080: * @author Martin Desruisseaux
081: * @author Jody Garnett
082: * @author Rueben Schulz
083: * @author Andrea Aime
084: */
085: public class FactoryUsingWKT extends DeferredAuthorityFactory implements
086: CRSAuthorityFactory {
087: /**
088: * The {@linkplain System#getProperty(String) system property} key for setting the directory
089: * where to search for the {@value #FILENAME} file.
090: *
091: * @since 2.4
092: * @deprecated Moved to {@link GeoTools#CRS_AUTHORITY_EXTRA_DIRECTORY}.
093: */
094: public static final String CRS_DIRECTORY_KEY = GeoTools.CRS_AUTHORITY_EXTRA_DIRECTORY;
095:
096: /**
097: * The authority. Will be created only when first needed.
098: *
099: * @see #getAuthority
100: */
101: private Citation authority;
102:
103: /**
104: * The default filename to read. The default {@code FactoryUsingWKT} implementation will
105: * search for the first occurence of this file in the following places:
106: * <p>
107: * <ul>
108: * <li>In the directory specified by the {@value GeoTools#CRS_DIRECTORY_KEY} system
109: * property.</li>
110: * <li>In every {@code org/geotools/referencing/factory/espg} directories found on the
111: * classpath.</li>
112: * </ul>
113: * <p>
114: * The filename part before the extension ({@code "epsg"}) denotes the authority namespace
115: * where to register the content of this file. The user-directory given by the system property
116: * may contains other property files for other authorities, like {@code "esri.properties"},
117: * but those additional authorities are not handled by the default {@code FactoryUsingWKT}
118: * class.
119: *
120: * @see #getDefinitionsURL
121: */
122: public static final String FILENAME = "epsg.properties";
123:
124: /**
125: * The factories to be given to the backing store.
126: */
127: private final ReferencingFactoryContainer factories;
128:
129: /**
130: * Default priority for this factory.
131: *
132: * @since 2.4
133: * @deprecated We will try to replace the priority mechanism by a better
134: * one in a future Geotools version.
135: */
136: protected static final int DEFAULT_PRIORITY = ThreadedEpsgFactory.PRIORITY - 10;
137:
138: /**
139: * Directory scanned for extra definitions.
140: */
141: private final File directory;
142:
143: /**
144: * Constructs an authority factory using the default set of factories.
145: */
146: public FactoryUsingWKT() {
147: this (null);
148: }
149:
150: /**
151: * Constructs an authority factory using a set of factories created from the specified hints.
152: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
153: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
154: * {@code FACTORY} hints.
155: */
156: public FactoryUsingWKT(final Hints userHints) {
157: this (userHints, DEFAULT_PRIORITY);
158: }
159:
160: /**
161: * Constructs an authority factory using the specified hints and priority.
162: */
163: protected FactoryUsingWKT(final Hints userHints, final int priority) {
164: super (userHints, priority);
165: factories = ReferencingFactoryContainer.instance(userHints);
166: Object hint = null;
167: if (userHints != null) {
168: hint = userHints.get(Hints.CRS_AUTHORITY_EXTRA_DIRECTORY);
169: }
170: if (hint instanceof File) {
171: directory = (File) hint;
172: } else if (hint instanceof String) {
173: directory = new File((String) hint);
174: } else {
175: directory = null;
176: }
177: hints.put(Hints.CRS_AUTHORITY_EXTRA_DIRECTORY, directory);
178: // Disposes the cached property file after at least 15 minutes of inactivity.
179: setTimeout(15 * 60 * 1000L);
180: }
181:
182: /**
183: * Returns the authority. The default implementation returns the first citation returned
184: * by {@link #getAuthorities()}, with the addition of identifiers from all additional
185: * authorities returned by the above method.
186: *
187: * @see #getAuthorities
188: */
189: //@Override
190: public Citation getAuthority() {
191: // No need to synchronize; this is not a big deal if we create this object twice.
192: if (authority == null) {
193: final Citation[] authorities = getAuthorities();
194: switch (authorities.length) {
195: case 0:
196: authority = Citations.EPSG;
197: break;
198: case 1:
199: authority = authorities[0];
200: break;
201: default: {
202: final CitationImpl c = new CitationImpl(authorities[0]);
203: final Collection types = c.getIdentifierTypes();
204: final Collection identifiers = c.getIdentifiers();
205: for (int i = 1; i < authorities.length; i++) {
206: types.add("Authority name");
207: identifiers.add(Citations
208: .getIdentifier(authorities[i]));
209: }
210: c.freeze();
211: authority = c;
212: break;
213: }
214: }
215: }
216: return authority;
217: }
218:
219: /**
220: * Returns the set of authorities to use as {@linkplain CoordinateReferenceSystem#getIdentifiers
221: * identifiers} for the CRS to be created. This set is given to the
222: * {@linkplain PropertyAuthorityFactory#PropertyAuthorityFactory(ReferencingFactoryContainer,
223: * Citation[], URL) properties-backed factory constructor}.
224: * <p>
225: * The default implementation returns a singleton containing only {@linkplain Citations#EPSG
226: * EPSG}. Subclasses should override this method in order to enumerate all relevant authorities,
227: * with {@linkplain Citations#EPSG EPSG} in last position. For example {@link EsriExtension}
228: * returns {{@linkplain Citations#ESRI ESRI}, {@linkplain Citations#EPSG EPSG}}.
229: *
230: * @since 2.4
231: */
232: protected Citation[] getAuthorities() {
233: return new Citation[] { Citations.EPSG };
234: }
235:
236: /**
237: * Returns the URL to the property file that contains CRS definitions.
238: * The default implementation performs the following search path:
239: * <ul>
240: * <li>If a value is set for the {@value #CRS_DIRECTORY_KEY} system property key,
241: * then the {@value #FILENAME} file will be searched in this directory.</li>
242: * <li>If no value is set for the above-cited system property, or if no {@value #FILENAME}
243: * file was found in that directory, then the first {@value #FILENAME} file found in
244: * any {@code org/geotools/referencing/factory/epsg} directory on the classpath will
245: * be used.</li>
246: * <li>If no file was found on the classpath neither, then this factory will be disabled.</li>
247: * </ul>
248: *
249: * @return The URL, or {@code null} if none.
250: */
251: protected URL getDefinitionsURL() {
252: try {
253: if (directory != null) {
254: final File file = new File(directory, FILENAME);
255: if (file.isFile()) {
256: return file.toURI().toURL();
257: }
258: }
259: } catch (SecurityException exception) {
260: org.geotools.util.logging.Logging.unexpectedException(
261: LOGGER, exception);
262: } catch (MalformedURLException exception) {
263: org.geotools.util.logging.Logging.unexpectedException(
264: LOGGER, exception);
265: }
266: return FactoryUsingWKT.class.getResource(FILENAME);
267: }
268:
269: /**
270: * Creates the backing store authority factory.
271: *
272: * @return The backing store to uses in {@code createXXX(...)} methods.
273: * @throws FactoryNotFoundException if the no {@code epsg.properties} file has been found.
274: * @throws FactoryException if the constructor failed to find or read the file.
275: * This exception usually has an {@link IOException} as its cause.
276: */
277: protected AbstractAuthorityFactory createBackingStore()
278: throws FactoryException {
279: try {
280: URL url = getDefinitionsURL();
281: if (url == null) {
282: throw new FactoryNotFoundException(Errors.format(
283: ErrorKeys.FILE_DOES_NOT_EXIST_$1, FILENAME));
284: }
285: final Iterator ids = getAuthority().getIdentifiers()
286: .iterator();
287: final String authority = ids.hasNext() ? (String) ids
288: .next() : "EPSG";
289: LOGGER.log(Logging.format(Level.CONFIG,
290: LoggingKeys.USING_FILE_AS_FACTORY_$2,
291: url.getPath(), authority));
292: return new PropertyAuthorityFactory(factories,
293: getAuthorities(), url);
294: } catch (IOException exception) {
295: throw new FactoryException(Errors.format(
296: ErrorKeys.CANT_READ_$1, FILENAME), exception);
297: }
298: }
299:
300: /**
301: * Returns a factory of the given type.
302: */
303: private static final AbstractAuthorityFactory /*T*/getFactory(
304: final Class/*<T extends AbstractAuthorityFactory>*/type) {
305: // TODO: use type.cast(...) when we will be allowed to compile for J2SE 1.5.
306: return (AbstractAuthorityFactory) ReferencingFactoryFinder
307: .getCRSAuthorityFactory("EPSG", new Hints(
308: Hints.CRS_AUTHORITY_FACTORY, type));
309: }
310:
311: /**
312: * Prints a list of codes that duplicate the ones provided by {@link ThreadedEpsgFactory}.
313: * This is used for implementation of {@linkplain #main main method} in order to check
314: * the content of the {@value #FILENAME} file (or whatever property file used as backing
315: * store for this factory) from the command line.
316: *
317: * @param out The writer where to print the report.
318: * @return The set of duplicated codes.
319: * @throws FactoryException if an error occured.
320: *
321: * @since 2.4
322: */
323: protected Set reportDuplicatedCodes(final PrintWriter out)
324: throws FactoryException {
325: final AbstractAuthorityFactory sqlFactory = getFactory(ThreadedEpsgFactory.class);
326: final Vocabulary resources = Vocabulary.getResources(null);
327: out.println(resources.getLabel(VocabularyKeys.COMPARE_WITH));
328: try {
329: final IndentedLineWriter w = new IndentedLineWriter(out);
330: w.setIndentation(4);
331: w.write(sqlFactory.getBackingStoreDescription());
332: w.flush();
333: } catch (IOException e) {
334: // Should never happen, since we are writting to a PrintWriter.
335: throw new AssertionError(e);
336: }
337: out.println();
338: final Set wktCodes = this
339: .getAuthorityCodes(IdentifiedObject.class);
340: final Set sqlCodes = sqlFactory
341: .getAuthorityCodes(IdentifiedObject.class);
342: final Set duplicated = new TreeSet();
343: for (final Iterator it = wktCodes.iterator(); it.hasNext();) {
344: final String code = ((String) it.next()).trim();
345: if (sqlCodes.contains(code)) {
346: duplicated.add(code);
347: /*
348: * Note: we don't use wktCodes.retainsAll(sqlCode) because the Set implementations
349: * are usually not the standard ones, but rather some implementations backed
350: * by a connection to the resources of the underlying factory. We also close
351: * the connection after this loop for the same reason. In addition, we take
352: * this opportunity for sorting the codes.
353: */
354: }
355: }
356: if (duplicated.isEmpty()) {
357: out.println(resources
358: .getString(VocabularyKeys.NO_DUPLICATION_FOUND));
359: } else {
360: for (final Iterator it = duplicated.iterator(); it
361: .hasNext();) {
362: final String code = (String) it.next();
363: out.print(resources
364: .getLabel(VocabularyKeys.DUPLICATED_VALUE));
365: out.println(code);
366: }
367: }
368: return duplicated;
369: }
370:
371: /**
372: * Prints a list of CRS that can't be instantiated. This is used for implementation of
373: * {@linkplain #main main method} in order to check the content of the {@value #FILENAME}
374: * file (or whatever property file used as backing store for this factory) from the command
375: * line.
376: *
377: * @param out The writer where to print the report.
378: * @return The set of codes that can't be instantiated.
379: * @throws FactoryException if an error occured while
380: * {@linkplain #getAuthorityCodes fetching authority codes}.
381: *
382: * @since 2.4
383: */
384: protected Set reportInstantiationFailures(final PrintWriter out)
385: throws FactoryException {
386: final Set codes = getAuthorityCodes(CoordinateReferenceSystem.class);
387: final Map failures = new TreeMap();
388: for (final Iterator it = codes.iterator(); it.hasNext();) {
389: final String code = (String) it.next();
390: try {
391: createCoordinateReferenceSystem(code);
392: } catch (FactoryException exception) {
393: failures.put(code, exception.getLocalizedMessage());
394: }
395: }
396: if (!failures.isEmpty()) {
397: final TableWriter writer = new TableWriter(out, " ");
398: for (final Iterator it = failures.entrySet().iterator(); it
399: .hasNext();) {
400: final Map.Entry entry = (Map.Entry) it.next();
401: writer.write((String) entry.getKey());
402: writer.write(':');
403: writer.nextColumn();
404: writer.write((String) entry.getValue());
405: writer.nextLine();
406: }
407: try {
408: writer.flush();
409: } catch (IOException e) {
410: // Should not happen, since we are writting to a PrintWriter
411: throw new AssertionError(e);
412: }
413: }
414: return failures.keySet();
415: }
416:
417: /**
418: * Prints a list of codes that duplicate the ones provided in the {@link ThreadedEpsgFactory}.
419: * The factory tested is the one registered in {@link ReferencingFactoryFinder}. By default,
420: * this is this {@code FactoryUsingWKT} class backed by the {@value #FILENAME} property file.
421: * This method can be invoked from the command line in order to check the content of the
422: * property file. Valid arguments are:
423: * <p>
424: * <table>
425: * <tr><td>{@code -test}</td><td>Try to instantiate all CRS and reports any failure
426: * to do so.</td></tr>
427: * <tr><td>{@code -duplicated}</td><td>List all codes from the WKT factory that are
428: * duplicating a code from the SQL factory.</td></tr>
429: * </table>
430: *
431: * @param args Command line arguments.
432: * @throws FactoryException if an error occured.
433: *
434: * @since 2.4
435: */
436: public static void main(final String[] args)
437: throws FactoryException {
438: main(args, FactoryUsingWKT.class);
439: }
440:
441: /**
442: * Implementation of the {@link #main} method, shared by subclasses.
443: */
444: static void main(String[] args,
445: final Class/*<? extends FactoryUsingWKT>*/type)
446: throws FactoryException {
447: final Arguments arguments = new Arguments(args);
448: Locale.setDefault(arguments.locale);
449: final boolean duplicated = arguments.getFlag("-duplicated");
450: final boolean instantiate = arguments.getFlag("-test");
451: args = arguments.getRemainingArguments(0);
452: // TODO: remove the cast when we will be allowed to compile for J2SE 1.5.
453: final FactoryUsingWKT factory = (FactoryUsingWKT) getFactory(type);
454: if (duplicated) {
455: factory.reportDuplicatedCodes(arguments.out);
456: }
457: if (instantiate) {
458: factory.reportInstantiationFailures(arguments.out);
459: }
460: factory.dispose();
461: }
462: }
|