Source Code Cross Referenced for MetadataBuilder.java in  » GIS » GeoTools-2.4.1 » org » geotools » coverage » io » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.coverage.io 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Geotools 2 - OpenSource mapping toolkit
0003:         * (C) 2005, Geotools Project Management Committee (PMC)
0004:         * (C) 2001, Institut de Recherche pour le Développement
0005:         *
0006:         *    This library is free software; you can redistribute it and/or
0007:         *    modify it under the terms of the GNU Lesser General Public
0008:         *    License as published by the Free Software Foundation; either
0009:         *    version 2.1 of the License, or (at your option) any later version.
0010:         *
0011:         *    This library is distributed in the hope that it will be useful,
0012:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0013:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014:         *    Lesser General Public License for more details.
0015:         *
0016:         *    You should have received a copy of the GNU Lesser General Public
0017:         *    License along with this library; if not, write to the Free Software
0018:         *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0019:         */
0020:        package org.geotools.coverage.io;
0021:
0022:        // J2SE dependencies
0023:        import java.awt.Image;
0024:        import java.awt.image.RenderedImage;
0025:        import java.io.*;
0026:        import java.net.URL;
0027:        import java.text.DateFormat;
0028:        import java.text.DecimalFormat;
0029:        import java.text.NumberFormat;
0030:        import java.text.ParseException;
0031:        import java.text.SimpleDateFormat;
0032:        import java.util.*;
0033:        import java.util.logging.Level;
0034:        import java.util.logging.LogRecord;
0035:
0036:        // Extensions
0037:        import javax.units.Unit;
0038:        import javax.units.SI;
0039:        import javax.units.NonSI;
0040:
0041:        // JAI dependencies
0042:        import javax.imageio.IIOException;
0043:        import javax.media.jai.DeferredData;
0044:        import javax.media.jai.DeferredProperty;
0045:        import javax.media.jai.ParameterList;
0046:        import javax.media.jai.PropertySource;
0047:
0048:        // OpenGIS dependencies
0049:        import org.opengis.coverage.grid.GridCoverage;
0050:        import org.opengis.coverage.grid.GridRange;
0051:        import org.opengis.metadata.extent.GeographicBoundingBox;
0052:        import org.opengis.parameter.GeneralParameterDescriptor;
0053:        import org.opengis.parameter.ParameterDescriptor;
0054:        import org.opengis.parameter.ParameterValueGroup;
0055:        import org.opengis.parameter.ParameterValue;
0056:        import org.opengis.parameter.ParameterNotFoundException;
0057:        import org.opengis.referencing.FactoryException;
0058:        import org.opengis.referencing.IdentifiedObject;
0059:        import org.opengis.referencing.NoSuchIdentifierException;
0060:        import org.opengis.referencing.cs.CoordinateSystem;
0061:        import org.opengis.referencing.cs.CoordinateSystemAxis;
0062:        import org.opengis.referencing.cs.CartesianCS;
0063:        import org.opengis.referencing.cs.EllipsoidalCS;
0064:        import org.opengis.referencing.crs.CRSFactory;
0065:        import org.opengis.referencing.crs.CoordinateReferenceSystem;
0066:        import org.opengis.referencing.crs.GeographicCRS;
0067:        import org.opengis.referencing.crs.ProjectedCRS;
0068:        import org.opengis.referencing.crs.TemporalCRS;
0069:        import org.opengis.referencing.datum.Datum;
0070:        import org.opengis.referencing.datum.Ellipsoid;
0071:        import org.opengis.referencing.datum.GeodeticDatum;
0072:        import org.opengis.referencing.datum.PrimeMeridian;
0073:        import org.opengis.referencing.operation.Conversion;
0074:        import org.opengis.referencing.operation.Projection;
0075:        import org.opengis.referencing.operation.OperationMethod;
0076:        import org.opengis.referencing.operation.TransformException;
0077:        import org.opengis.referencing.operation.MathTransformFactory;
0078:        import org.opengis.geometry.Envelope;
0079:        import org.opengis.util.Cloneable;
0080:
0081:        // Geotools dependencies
0082:        import org.geotools.io.TableWriter;
0083:        import org.geotools.resources.Utilities;
0084:        import org.geotools.resources.CRSUtilities;
0085:        import org.geotools.resources.i18n.Errors;
0086:        import org.geotools.resources.i18n.ErrorKeys;
0087:        import org.geotools.referencing.CRS;
0088:        import org.geotools.referencing.wkt.Formattable;
0089:        import org.geotools.referencing.wkt.UnformattableObjectException;
0090:        import org.geotools.referencing.factory.FactoryGroup;
0091:        import org.geotools.referencing.crs.DefaultTemporalCRS;
0092:        import org.geotools.referencing.cs.DefaultCartesianCS;
0093:        import org.geotools.referencing.cs.DefaultEllipsoidalCS;
0094:        import org.geotools.referencing.datum.DefaultPrimeMeridian;
0095:        import org.geotools.referencing.operation.DefiningConversion;
0096:        import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
0097:        import org.geotools.coverage.grid.GeneralGridRange;
0098:        import org.geotools.coverage.GridSampleDimension;
0099:        import org.geotools.geometry.GeneralEnvelope;
0100:
0101:        /**
0102:         * Helper class for creating OpenGIS's object from a set of metadata. Metadata are
0103:         * <cite>key-value</cite> pairs, for example {@code "Units=meters"}. There is a wide
0104:         * variety of ways to contruct OpenGIS's objects from <cite>key-value</cite> pairs, and
0105:         * supporting them is not always straightforward. The {@code MetadataBuilder} class
0106:         * tries to make the work easier. It defines a set of format-neutral keys (i.e. keys not
0107:         * related to any specific file format). Before parsing a file, the mapping between
0108:         * format-neutral keys and "real" keys used in a particuler file format <strong>must</strong>
0109:         * be specified. This mapping is constructed with calls to {@link #addAlias}. For example,
0110:         * one may want to parse the following informations:
0111:         *
0112:         * <blockquote><pre>
0113:         * XMinimum           = 217904.31
0114:         * YMaximum           = 5663495.1
0115:         * XResolution        = 1000.0000
0116:         * YResolution        = 1000.0000
0117:         * Units              = meters
0118:         * Projection         = Mercator_1SP
0119:         * Central meridian   = -15.2167
0120:         * Latitude of origin =  28.0667
0121:         * False easting      = 0.00000000
0122:         * False northing     = 0.00000000
0123:         * Ellipsoid          = Clarke 1866
0124:         * Datum              = Clarke 1866
0125:         * </pre></blockquote>
0126:         *
0127:         * Before to be used for parsing such informations, a {@code MetadataBuilder} object
0128:         * must be setup using the following code:
0129:         *
0130:         * <blockquote><pre>
0131:         * addAlias({@link #X_MINIMUM},    "XMinimum");
0132:         * addAlias({@link #Y_MAXIMUM},    "YMaximum");
0133:         * addAlias({@link #X_RESOLUTION}, "XResolution");
0134:         * addAlias({@link #Y_RESOLUTION}, "YResolution");
0135:         * // etc...
0136:         * </pre></blockquote>
0137:         *
0138:         * Once the mapping is etablished, {@code MetadataBuilder} provides a set of {@code getXXX()}
0139:         * methods for constructing various objects from those informations. For example, the
0140:         * {@link #getCoordinateReferenceSystem} method constructs a {@link CoordinateReferenceSystem}
0141:         * object using available informations.
0142:         *
0143:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/coverage/io/MetadataBuilder.java $
0144:         * @version $Id: MetadataBuilder.java 25699 2007-05-31 15:55:07Z desruisseaux $
0145:         * @author Martin Desruisseaux
0146:         *
0147:         * @since 2.2
0148:         */
0149:        public class MetadataBuilder {
0150:            /**
0151:             * Set of commonly used symbols for "metres".
0152:             *
0153:             * @todo Needs a more general way to set unit symbols once the Unit API is completed.
0154:             */
0155:            private static final String[] METRES = { "meter", "meters",
0156:                    "metre", "metres", "m" };
0157:
0158:            /**
0159:             * Set of commonly used symbols for "degrees".
0160:             *
0161:             * @todo Needs a more general way to set unit symbols once the Unit API is completed.
0162:             */
0163:            private static final String[] DEGREES = { "degree", "degrees",
0164:                    "deg", "°" };
0165:
0166:            /**
0167:             * Small tolerance factor when checking metadata for consistency.
0168:             */
0169:            private static final double EPS = 1E-6;
0170:
0171:            /**
0172:             * Key for the {@linkplain CoordinateReferenceSystem coordinate reference system}.
0173:             * The {@link #getCoordinateReferenceSystem} method looks for this metadata.
0174:             *
0175:             * @see #UNITS
0176:             * @see #DATUM
0177:             * @see #PROJECTION
0178:             */
0179:            public static final Key COORDINATE_REFERENCE_SYSTEM = new Key(
0180:                    "CoordinateReferenceSystem") {
0181:                public Object getValue(final GridCoverage coverage) {
0182:                    return coverage.getCoordinateReferenceSystem();
0183:                }
0184:            };
0185:
0186:            /**
0187:             * Key for the {@linkplain CoordinateSystemAxis coordinate system axis} units.
0188:             * The {@link #getUnit} method looks for this metadata. The following heuristic
0189:             * rule may be applied in order to infer the CRS from the units:
0190:             * <p>
0191:             * <ul>
0192:             *   <li>If the unit is compatible with {@linkplain NonSI#DEGREE_ANGLE degrees},
0193:             *       then a {@linkplain GeographicCRS geographic CRS} is assumed.</li>
0194:             *   <li>Otherwise, if this unit is compatible with {@linkplain SI#METER metres},
0195:             *       then a {@linkplain ProjectedCRS projected CRS} is assumed.</li>
0196:             * </ul>
0197:             *
0198:             * @see #ELLIPSOID
0199:             * @see #DATUM
0200:             * @see #PROJECTION
0201:             * @see #COORDINATE_REFERENCE_SYSTEM
0202:             */
0203:            public static final Key UNITS = new Key("Unit") {
0204:                public Object getValue(final GridCoverage coverage) {
0205:                    Unit unit = null;
0206:                    final CoordinateReferenceSystem crs = coverage
0207:                            .getCoordinateReferenceSystem();
0208:                    if (crs != null) {
0209:                        final CoordinateSystem cs = crs.getCoordinateSystem();
0210:                        if (cs != null) {
0211:                            for (int i = cs.getDimension(); --i >= 0;) {
0212:                                final Unit candidate = cs.getAxis(i).getUnit();
0213:                                if (candidate != null) {
0214:                                    if (unit == null) {
0215:                                        unit = candidate;
0216:                                    } else if (!unit.equals(candidate)) {
0217:                                        return null;
0218:                                    }
0219:                                }
0220:                            }
0221:                        }
0222:                    }
0223:                    return unit;
0224:                }
0225:            };
0226:
0227:            /**
0228:             * Key for the coordinate reference system's {@linkplain Datum datum}.
0229:             * The {@link #getGeodeticDatum} method looks for this metadata.
0230:             *
0231:             * @see #UNITS
0232:             * @see #ELLIPSOID
0233:             * @see #PROJECTION
0234:             * @see #COORDINATE_REFERENCE_SYSTEM
0235:             */
0236:            public static final Key DATUM = new Key("Datum") {
0237:                public Object getValue(final GridCoverage coverage) {
0238:                    return CRSUtilities.getDatum(coverage
0239:                            .getCoordinateReferenceSystem());
0240:                }
0241:            };
0242:
0243:            /**
0244:             * Key for the coordinate reference system {@linkplain Ellipsoid ellipsoid}.
0245:             * The {@link #getEllipsoid} method looks for this metadata.
0246:             *
0247:             * @see #UNITS
0248:             * @see #DATUM
0249:             * @see #PROJECTION
0250:             * @see #COORDINATE_REFERENCE_SYSTEM
0251:             */
0252:            public static final Key ELLIPSOID = new Key("Ellipsoid") {
0253:                public Object getValue(final GridCoverage coverage) {
0254:                    return CRS.getEllipsoid(coverage
0255:                            .getCoordinateReferenceSystem());
0256:                }
0257:            };
0258:
0259:            /**
0260:             * Key for the {@linkplain OperationMethod operation method}. The {@link #getProjection}
0261:             * method looks for this metadata. The operation method name determines the {@linkplain
0262:             * MathTransformFactory#getDefaultParameters math transform implementation and its list
0263:             * of parameters}. This name is the projection <cite>classification</cite>.
0264:             * <p>
0265:             * If this metadata is not defined, then the operation name is inferred from the
0266:             * {@linkplain #PROJECTION projection name}.
0267:             *
0268:             * @see #PROJECTION
0269:             * @see #COORDINATE_REFERENCE_SYSTEM
0270:             */
0271:            public static final Key OPERATION_METHOD = new Key(
0272:                    "OperationMethod") {
0273:                public Object getValue(final GridCoverage coverage) {
0274:                    final Projection projection = (Projection) PROJECTION
0275:                            .getValue(coverage);
0276:                    return (projection != null) ? projection.getName()
0277:                            .getCode() : null;
0278:                }
0279:            };
0280:
0281:            /**
0282:             * Key for the {@linkplain Projection projection}. The {@link #getProjection} method looks
0283:             * for this metadata. If the metadata is not defined, then the projection name is assumed
0284:             * the same than the {@linkplain #OPERATION_METHOD operation method} name.
0285:             *
0286:             * @see #SEMI_MAJOR
0287:             * @see #SEMI_MINOR
0288:             * @see #LATITUDE_OF_ORIGIN
0289:             * @see #CENTRAL_MERIDIAN
0290:             * @see #FALSE_EASTING
0291:             * @see #FALSE_NORTHING
0292:             */
0293:            public static final Key PROJECTION = new Key("Projection") {
0294:                public Object getValue(final GridCoverage coverage) {
0295:                    final ProjectedCRS crs;
0296:                    crs = CRS.getProjectedCRS(coverage
0297:                            .getCoordinateReferenceSystem());
0298:                    return (crs != null) ? crs.getConversionFromBase() : null;
0299:                }
0300:            };
0301:
0302:            /**
0303:             * Key for the {@code "semi_major"} projection parameter. There is no specific method
0304:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0305:             *
0306:             * @see #SEMI_MINOR
0307:             * @see #LATITUDE_OF_ORIGIN
0308:             * @see #CENTRAL_MERIDIAN
0309:             * @see #FALSE_EASTING
0310:             * @see #FALSE_NORTHING
0311:             * @see #PROJECTION
0312:             */
0313:            public static final Key SEMI_MAJOR = new ProjectionKey("semi_major");
0314:
0315:            /**
0316:             * Key for the {@code "semi_minor"} projection parameter. There is no specific method
0317:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0318:             *
0319:             * @see #SEMI_MAJOR
0320:             * @see #LATITUDE_OF_ORIGIN
0321:             * @see #CENTRAL_MERIDIAN
0322:             * @see #FALSE_EASTING
0323:             * @see #FALSE_NORTHING
0324:             * @see #PROJECTION
0325:             */
0326:            public static final Key SEMI_MINOR = new ProjectionKey("semi_minor");
0327:
0328:            /**
0329:             * Key for the {@code "latitude_of_origin"} projection parameter. There is no specific method
0330:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0331:             *
0332:             * @see #SEMI_MAJOR
0333:             * @see #SEMI_MINOR
0334:             * @see #CENTRAL_MERIDIAN
0335:             * @see #FALSE_EASTING
0336:             * @see #FALSE_NORTHING
0337:             * @see #PROJECTION
0338:             */
0339:            public static final Key LATITUDE_OF_ORIGIN = new ProjectionKey(
0340:                    "latitude_of_origin");
0341:
0342:            /**
0343:             * Key for the {@code "central_meridian"} projection parameter. There is no specific method
0344:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0345:             *
0346:             * @see #SEMI_MAJOR
0347:             * @see #SEMI_MINOR
0348:             * @see #LATITUDE_OF_ORIGIN
0349:             * @see #FALSE_EASTING
0350:             * @see #FALSE_NORTHING
0351:             * @see #PROJECTION
0352:             */
0353:            public static final Key CENTRAL_MERIDIAN = new ProjectionKey(
0354:                    "central_meridian");
0355:
0356:            /**
0357:             * Key for the {@code "false_easting"} projection parameter. There is no specific method
0358:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0359:             *
0360:             * @see #SEMI_MAJOR
0361:             * @see #SEMI_MINOR
0362:             * @see #LATITUDE_OF_ORIGIN
0363:             * @see #CENTRAL_MERIDIAN
0364:             * @see #FALSE_NORTHING
0365:             * @see #PROJECTION
0366:             */
0367:            public static final Key FALSE_EASTING = new ProjectionKey(
0368:                    "false_easting");
0369:
0370:            /**
0371:             * Key for the {@code "false_northing"} projection parameter. There is no specific method
0372:             * for this key. However, this key may be queried indirectly by {@link #getProjection}.
0373:             *
0374:             * @see #SEMI_MAJOR
0375:             * @see #SEMI_MINOR
0376:             * @see #LATITUDE_OF_ORIGIN
0377:             * @see #CENTRAL_MERIDIAN
0378:             * @see #FALSE_EASTING
0379:             * @see #PROJECTION
0380:             */
0381:            public static final Key FALSE_NORTHING = new ProjectionKey(
0382:                    "false_northing");
0383:
0384:            /**
0385:             * Key for the minimal <var>x</var> value (western limit).
0386:             * This is usually the longitude coordinate of the <em>upper left</em> corner.
0387:             * The {@link #getEnvelope} method looks for this metadata in order to set the
0388:             * {@linkplain Envelope#getMinimum minimal coordinate} for dimension <strong>0</strong>.
0389:             *
0390:             * @see #X_MAXIMUM
0391:             * @see #Y_MINIMUM
0392:             * @see #Y_MAXIMUM
0393:             * @see #X_RESOLUTION
0394:             * @see #Y_RESOLUTION
0395:             */
0396:            public static final Key X_MINIMUM = new EnvelopeKey("XMinimum",
0397:                    (byte) 0, EnvelopeKey.MINIMUM);
0398:
0399:            /**
0400:             * Key for the minimal <var>y</var> value (southern limit).
0401:             * This is usually the latitude coordinate of the <em>bottom right</em> corner.
0402:             * The {@link #getEnvelope} method looks for this metadata. in order to set the
0403:             * {@linkplain Envelope#getMinimum minimal coordinate} for dimension <strong>1</strong>.
0404:             *
0405:             * @see #X_MINIMUM
0406:             * @see #X_MAXIMUM
0407:             * @see #Y_MAXIMUM
0408:             * @see #X_RESOLUTION
0409:             * @see #Y_RESOLUTION
0410:             */
0411:            public static final Key Y_MINIMUM = new EnvelopeKey("YMinimum",
0412:                    (byte) 1, EnvelopeKey.MINIMUM);
0413:
0414:            /**
0415:             * Key for the minimal <var>z</var> value. This is usually the minimal altitude.
0416:             * The {@link #getEnvelope} method looks for this metadata in order to set the
0417:             * {@linkplain Envelope#getMinimum minimal coordinate} for dimension <strong>2</strong>.
0418:             *
0419:             * @see #Z_MAXIMUM
0420:             * @see #Z_RESOLUTION
0421:             * @see #DEPTH
0422:             */
0423:            public static final Key Z_MINIMUM = new EnvelopeKey("ZMinimum",
0424:                    (byte) 2, EnvelopeKey.MINIMUM);
0425:
0426:            /**
0427:             * Key for the maximal <var>x</var> value (eastern limit).
0428:             * This is usually the longitude coordinate of the <em>bottom right</em> corner.
0429:             * The {@link #getEnvelope} method looks for this metadata in order to set the
0430:             * {@linkplain Envelope#getMaximum maximal coordinate} for dimension <strong>0</strong>.
0431:             *
0432:             * @see #X_MINIMUM
0433:             * @see #Y_MINIMUM
0434:             * @see #Y_MAXIMUM
0435:             * @see #X_RESOLUTION
0436:             * @see #Y_RESOLUTION
0437:             */
0438:            public static final Key X_MAXIMUM = new EnvelopeKey("XMaximum",
0439:                    (byte) 0, EnvelopeKey.MAXIMUM);
0440:
0441:            /**
0442:             * Key for the maximal <var>y</var> value (northern limit).
0443:             * This is usually the latitude coordinate of the <em>upper left</em> corner.
0444:             * The {@link #getEnvelope} method looks for this metadata in order to set the
0445:             * {@linkplain Envelope#getMaximum maximal coordinate} for dimension <strong>1</strong>.
0446:             *
0447:             * @see #X_MINIMUM
0448:             * @see #X_MAXIMUM
0449:             * @see #Y_MINIMUM
0450:             * @see #X_RESOLUTION
0451:             * @see #Y_RESOLUTION
0452:             */
0453:            public static final Key Y_MAXIMUM = new EnvelopeKey("YMaximum",
0454:                    (byte) 1, EnvelopeKey.MAXIMUM);
0455:
0456:            /**
0457:             * Key for the maximal <var>z</var> value. This is usually the maximal altitude.
0458:             * The {@link #getEnvelope} method looks for this metadata in order to set the
0459:             * {@linkplain Envelope#getMaximum maximal coordinate} for dimension <strong>2</strong>.
0460:             *
0461:             * @see #Z_MINIMUM
0462:             * @see #Z_RESOLUTION
0463:             * @see #DEPTH
0464:             */
0465:            public static final Key Z_MAXIMUM = new EnvelopeKey("ZMaximum",
0466:                    (byte) 2, EnvelopeKey.MAXIMUM);
0467:
0468:            /**
0469:             * Key for the resolution among the <var>x</var> axis. The {@link #getEnvelope} method looks
0470:             * for this metadata in order to infer the coordinates for dimension <strong>0</strong>.
0471:             *
0472:             * @see #X_MINIMUM
0473:             * @see #X_MAXIMUM
0474:             * @see #Y_MINIMUM
0475:             * @see #Y_MAXIMUM
0476:             * @see #Y_RESOLUTION
0477:             */
0478:            public static final Key X_RESOLUTION = new EnvelopeKey(
0479:                    "XResolution", (byte) 0, EnvelopeKey.RESOLUTION);
0480:
0481:            /**
0482:             * Key for the resolution among the <var>y</var> axis. The {@link #getEnvelope} method looks
0483:             * for this metadata in order to infer the coordinates for dimension <strong>1</strong>.
0484:             *
0485:             * @see #X_MINIMUM
0486:             * @see #X_MAXIMUM
0487:             * @see #Y_MINIMUM
0488:             * @see #Y_MAXIMUM
0489:             * @see #X_RESOLUTION
0490:             * @see #WIDTH
0491:             * @see #HEIGHT
0492:             */
0493:            public static final Key Y_RESOLUTION = new EnvelopeKey(
0494:                    "YResolution", (byte) 1, EnvelopeKey.RESOLUTION);
0495:
0496:            /**
0497:             * Key for the resolution among the <var>z</var> axis. The {@link #getEnvelope} method looks
0498:             * for this metadata in order to infer the coordinates for dimension <strong>2</strong>.
0499:             *
0500:             * @see #Z_MINIMUM
0501:             * @see #Z_MAXIMUM
0502:             * @see #DEPTH
0503:             */
0504:            public static final Key Z_RESOLUTION = new EnvelopeKey(
0505:                    "ZResolution", (byte) 2, EnvelopeKey.RESOLUTION);
0506:
0507:            /**
0508:             * Key for the image's width in pixels. The {@link #getGridRange} method looks for this
0509:             * metadata in order to infer the {@linkplain GridRange#getLength grid size} along the
0510:             * dimension <strong>0</strong>.
0511:             *
0512:             * @see #HEIGHT
0513:             * @see #X_RESOLUTION
0514:             * @see #Y_RESOLUTION
0515:             */
0516:            public static final Key WIDTH = new EnvelopeKey("Width", (byte) 0,
0517:                    EnvelopeKey.SIZE);
0518:
0519:            /**
0520:             * Key for the image's height in pixels. The {@link #getGridRange} method looks for this
0521:             * metadata in order to infer the {@linkplain GridRange#getLength grid size} along the
0522:             * dimension <strong>1</strong>.
0523:             *
0524:             * @see #WIDTH
0525:             * @see #X_RESOLUTION
0526:             * @see #Y_RESOLUTION
0527:             */
0528:            public static final Key HEIGHT = new EnvelopeKey("Height",
0529:                    (byte) 1, EnvelopeKey.SIZE);
0530:
0531:            /**
0532:             * Key for the image's "depth" in pixels. This metadata may exists for 3D images,
0533:             * but some implementations accept at most 1 pixel depth among the third dimension.
0534:             * The {@link #getGridRange} method looks for this metadata in order to infer the
0535:             * {@linkplain GridRange#getLength grid size} along the dimension <strong>2</strong>.
0536:             *
0537:             * @see #Z_MINIMUM
0538:             * @see #Z_MAXIMUM
0539:             * @see #Z_RESOLUTION
0540:             */
0541:            public static final Key DEPTH = new EnvelopeKey("Depth", (byte) 2,
0542:                    EnvelopeKey.SIZE);
0543:
0544:            /**
0545:             * The source (the file path or the URL) specified during the last call to a {@code load(...)}
0546:             * method.
0547:             *
0548:             * @see #load(File)
0549:             * @see #load(URL)
0550:             * @see #load(BufferedReader)
0551:             */
0552:            private String source;
0553:
0554:            /**
0555:             * The symbol to use as a separator. The full version ({@code separator}) will be used for
0556:             * formatting with {@link #listMetadata}, while the trimed version ({@code trimSeparator})
0557:             * will be used for parsing with {@link #parseLine}.
0558:             *
0559:             * @see #getSeparator
0560:             * @see #setSeparator
0561:             */
0562:            private String separator = " = ", trimSeparator = "=";
0563:
0564:            /**
0565:             * The non-localized pattern for formatting numbers (as floating point or as integer)
0566:             * and dates. If {@code null}, then the default pattern is used.
0567:             */
0568:            private String numberPattern, datePattern;
0569:
0570:            /**
0571:             * The metadata, or {@code null} if none. Keys are the caseless metadata names
0572:             * as {@link Key} objects, and values are arbitrary objects (usually {@link String}s).
0573:             * This map will be constructed only when first needed.
0574:             */
0575:            private Map metadata;
0576:
0577:            /**
0578:             * The mapping between keys and alias, or {@code null} if there is no alias.
0579:             * Keys are {@link Key} objects and values are {@link Set} of {@link Key} objects.
0580:             * This mapping is used for two purpose:
0581:             * <ul>
0582:             *   <li>If the key is a {@link Key} object, then the value is the set of alias (as
0583:             *       {@code AliasKey} objects) for this key. This set is used by {@code getXXX()}
0584:             *       methods.</li>
0585:             *   <li>If the key is an {@code AliasKey} object, then the value if the set of {@link Key}
0586:             *       which have this alias. This set is used by {@code add(...)} methods in order to check
0587:             *       for ambiguity when adding a new metadata.</li>
0588:             * </ul>
0589:             */
0590:            private Map naming;
0591:
0592:            /**
0593:             * The alias used in the last {@link #getOptional} invocation. This field is for information
0594:             * purpose only. It is used when constructing an exception for an operation failure.
0595:             */
0596:            private transient String lastAlias;
0597:
0598:            /**
0599:             * Map of objects already created. Some objects may be expensive to construct and required
0600:             * many times. For example, {@link #getCoordinateReferenceSystem} is required by some other
0601:             * methods like {@link #getRange}. Caching objects after their construction allow for faster
0602:             * execution. Keys are object names (e.g. "CoordinateReferenceSystem"), and value are the
0603:             * actual objects.
0604:             */
0605:            private transient Map cache;
0606:
0607:            /**
0608:             * The factories to use for constructing ellipsoids, projections, coordinate reference systems...
0609:             */
0610:            private final FactoryGroup factories;
0611:
0612:            /**
0613:             * The locale to use for formatting messages, or {@code null} for a default locale.
0614:             * This is <strong>not</strong> the local to use for parsing the file. This later locale
0615:             * is specified by {@link #getLocale}.
0616:             */
0617:            private Locale userLocale;
0618:
0619:            /**
0620:             * Constructs a new {@code MetadataBuilder} using default factories.
0621:             */
0622:            public MetadataBuilder() {
0623:                this (FactoryGroup.createInstance(null));
0624:            }
0625:
0626:            /**
0627:             * Constructs a new {@code MetadataBuilder} using the specified factories.
0628:             */
0629:            public MetadataBuilder(final FactoryGroup factories) {
0630:                this .factories = factories;
0631:            }
0632:
0633:            /**
0634:             * Returns the characters to use as separator between keys and values. Leading and trailing
0635:             * spaces will be keept when formatting with {@link #listMetadata}, but will be ignored
0636:             * when parsing with {@link #parseLine}. The default value is <code>"&nbsp;=&nbsp;"</code>.
0637:             */
0638:            public String getSeparator() {
0639:                return separator;
0640:            }
0641:
0642:            /**
0643:             * Set the characters to use as separator between keys and values.
0644:             */
0645:            public synchronized void setSeparator(final String separator) {
0646:                this .trimSeparator = separator.trim();
0647:                this .separator = separator;
0648:            }
0649:
0650:            /**
0651:             * Returns the pattern used for parsing and formatting values of the specified type.
0652:             * The type should be either {@code Number.class} or {@code Date.class}.
0653:             * <p>
0654:             * <ul>
0655:             *   <li>if {@code type} is assignable to {@code Number.class}, then this method
0656:             *       returns the number pattern as specified by {@link DecimalFormat}.</li>
0657:             *   <li>Otherwise, if {@code type} is assignable to {@code Date.class}, then this method
0658:             *       returns the date pattern as specified by {@link SimpleDateFormat}.</li>
0659:             * </ul>
0660:             * <p>
0661:             * In any case, this method returns {@code null} if this object should use the default
0662:             * pattern for the {@linkplain #getLocale data locale}.
0663:             *
0664:             * @param  type The data type ({@code Number.class} or {@code Date.class}).
0665:             * @return The format pattern for the specified data type, or {@code null} for
0666:             *         the default locale-dependent pattern.
0667:             * @throws IllegalArgumentException if {@code type} is not valid.
0668:             */
0669:            public String getFormatPattern(final Class type) {
0670:                if (Date.class.isAssignableFrom(type)) {
0671:                    return datePattern;
0672:                }
0673:                if (Number.class.isAssignableFrom(type)) {
0674:                    return numberPattern;
0675:                }
0676:                throw new IllegalArgumentException(Utilities.getShortName(type));
0677:            }
0678:
0679:            /**
0680:             * Set the pattern to use for parsing and formatting values of the specified type.
0681:             * The type should be either {@code Number.class} or {@code Date.class}.
0682:             *
0683:             * <ul>
0684:             *   <li>If {@code type} is assignable to <code>{@linkplain java.lang.Number}.class</code>,
0685:             *       then {@code pattern} should be a {@link DecimalFormat} pattern (example:
0686:             *       {@code "#0.###"}).</li>
0687:             *   <li>If {@code type} is assignable to <code>{@linkplain Date}.class</code>,
0688:             *       then {@code pattern} should be a {@link SimpleDateFormat} pattern
0689:             *       (example: {@code "yyyy/MM/dd HH:mm"}).</li>
0690:             * </ul>
0691:             *
0692:             * @param  type The data type ({@code Number.class} or {@code Date.class}).
0693:             * @param  pattern The format pattern for the specified data type, or {@code null}
0694:             *         for the default locale-dependent pattern.
0695:             * @throws IllegalArgumentException if {@code type} is not valid.
0696:             */
0697:            public synchronized void setFormatPattern(final Class type,
0698:                    final String pattern) {
0699:                if (Date.class.isAssignableFrom(type)) {
0700:                    datePattern = pattern;
0701:                    cache = null;
0702:                    return;
0703:                }
0704:                if (Number.class.isAssignableFrom(type)) {
0705:                    numberPattern = pattern;
0706:                    cache = null;
0707:                    return;
0708:                }
0709:                throw new IllegalArgumentException(Utilities.getShortName(type));
0710:            }
0711:
0712:            /**
0713:             * Clears this metadata set. If the same {@code MetadataBuilder} object is used for parsing
0714:             * many files, then {@code clear()} should be invoked prior any {@code load(...)} method.
0715:             * Note that {@code clear()} do not remove any alias, so this {@code MetadataBuilder} can
0716:             * been immediately reused for parsing new files of the same kind.
0717:             */
0718:            public synchronized void clear() {
0719:                source = null;
0720:                metadata = null;
0721:                cache = null;
0722:            }
0723:
0724:            /**
0725:             * Reads all metadata from a text file. The default implementation invokes
0726:             * {@link #load(BufferedReader)}. Note that this method do not invokes {@link #clear}
0727:             * prior the loading. Consequently, the loaded metadata will be added to the set of
0728:             * existing metadata.
0729:             *
0730:             * @param  header The file to read until EOF.
0731:             * @throws IOException if an error occurs during loading.
0732:             *
0733:             * @see #clear()
0734:             * @see #load(URL)
0735:             * @see #parseLine
0736:             * @see #getSource
0737:             */
0738:            public synchronized void load(final File header) throws IOException {
0739:                source = header.getPath();
0740:                final BufferedReader in = new BufferedReader(new FileReader(
0741:                        header));
0742:                load(in);
0743:                in.close();
0744:            }
0745:
0746:            /**
0747:             * Reads all metadata from an URL. The default implementation invokes
0748:             * {@link #load(BufferedReader)}. Note that this method do not invokes {@link #clear}
0749:             * prior the loading. Consequently, the loaded metadata will be added to the set of
0750:             * existing metadata.
0751:             *
0752:             * @param  header The URL to read until EOF.
0753:             * @throws IOException if an error occurs during loading.
0754:             *
0755:             * @see #clear()
0756:             * @see #load(File)
0757:             * @see #parseLine
0758:             * @see #getSource
0759:             */
0760:            public synchronized void load(final URL header) throws IOException {
0761:                source = header.getPath();
0762:                final BufferedReader in = new BufferedReader(
0763:                        new InputStreamReader(header.openStream()));
0764:                load(in);
0765:                in.close();
0766:            }
0767:
0768:            /**
0769:             * Reads all metadata from a stream. The default implementation invokes
0770:             * {@link #parseLine} for each non-empty line found in the stream. Notes:
0771:             * <p>
0772:             * <ul>
0773:             *   <li>This method is not public because it has no way to know how
0774:             *       to set the {@link #getSource source} metadata.</li>
0775:             *   <li>This method is not synchronized. Synchronization, if wanted,
0776:                     must be done from the public frontend.</li>
0777:             *   <li>This method do not invokes {@link #clear} prior the loading.</li>
0778:             * </ul>
0779:             *
0780:             * @param in The stream to read until EOF. The stream will not be closed.
0781:             * @throws IOException if an error occurs during loading.
0782:             *
0783:             * @see #clear()
0784:             * @see #load(File)
0785:             * @see #load(URL)
0786:             * @see #parseLine
0787:             */
0788:            protected void load(final BufferedReader in) throws IOException {
0789:                assert Thread.holdsLock(this );
0790:                final Set previousComments = new HashSet();
0791:                final StringBuffer comments = new StringBuffer();
0792:                final String lineSeparator = System.getProperty(
0793:                        "line.separator", "\n");
0794:                String line;
0795:                while ((line = in.readLine()) != null) {
0796:                    if (line.trim().length() != 0) {
0797:                        if (!parseLine(line)) {
0798:                            if (previousComments.add(line)) {
0799:                                comments.append(line);
0800:                                comments.append(lineSeparator);
0801:                            }
0802:                        }
0803:                    }
0804:                }
0805:                if (comments.length() != 0) {
0806:                    add((String) null, comments.toString());
0807:                }
0808:            }
0809:
0810:            /**
0811:             * Parses a line and add the key-value pair to this metadata set. The default implementation
0812:             * takes the substring on the left side of the first occurence of the {@linkplain #getSeparator
0813:             * separator} (usually the '=' character) as the key, and the substring on the right side of
0814:             * the separator as the value. For example, if {@code line} has the following value:
0815:             *
0816:             * <blockquote><pre>
0817:             * Ellipsoid = WGS 1984
0818:             * </pre></blockquote>
0819:             *
0820:             * Then, the default implementation will translate this line in
0821:             * the following call:
0822:             *
0823:             * <blockquote><pre>
0824:             * {@link #add(String,Object) add}("Ellipsoid", "WGS 1984");
0825:             * </pre></blockquote>
0826:             *
0827:             * This method returns {@code true} if it has consumed the line, or {@code false} otherwise.
0828:             * A line is "consumed" if {@code parseLine(...)} has either added the key-value pair (using
0829:             * {@link #add}), or determined that the line must be ignored (for example because
0830:             * {@code parseLine(...)} detected a character announcing a comment line). A "consumed" line
0831:             * will not receive any further treatment. The line is not consumed (i.e. this method returns
0832:             * {@code false}) if {@code parseLine(...)} don't know what to do with it. Non-consumed line
0833:             * will typically go up in a chain of {@code parseLine(...)} methods (if {@code MetadataBuilder}
0834:             * has been subclassed) until someone consume it.
0835:             *
0836:             * @param  line The line to parse.
0837:             * @return {@code true} if this method has consumed the line.
0838:             * @throws IIOException if the line is badly formatted.
0839:             * @throws AmbiguousMetadataException if a different value was already defined for the same
0840:             *         metadata name.
0841:             *
0842:             * @see #load(File)
0843:             * @see #load(URL)
0844:             * @see #add(String,Object)
0845:             */
0846:            protected boolean parseLine(final String line) throws IIOException {
0847:                final int index = line.indexOf(trimSeparator);
0848:                if (index >= 0) {
0849:                    add(line.substring(0, index), line.substring(index + 1));
0850:                    return true;
0851:                }
0852:                return false;
0853:            }
0854:
0855:            /**
0856:             * Add all metadata from the specified grid coverage. This method can be used together with
0857:             * {@link #listMetadata} as a way to format the metadata for an arbitrary grid coverage.
0858:             * The default implementation performs the following step:
0859:             * <p>
0860:             * <ul>
0861:             *   <li>For each {@code key} declared with
0862:             *       <code>{@linkplain #addAlias addAlias}(<strong>key</strong>, alias)</code>, fetchs
0863:             *       a value with <code>key.{linkplain Key#getValue getValue}(coverage)</code>.</li>
0864:             *   <li>For each value found, {@linkplain #add(String, Object) add} the value under the
0865:             *       name of the first alias found for the {@code key}.</li>
0866:             *
0867:             * @param coverage The grid coverage with metadata to add to this {@code MetadataBuilder}.
0868:             * @throws AmbiguousMetadataException if a metadata is defined twice.
0869:             *
0870:             * @see #add(RenderedImage)
0871:             * @see #add(PropertySource,String)
0872:             * @see #add(String,Object)
0873:             * @see #listMetadata
0874:             */
0875:            public synchronized void add(final GridCoverage coverage)
0876:                    throws AmbiguousMetadataException {
0877:                if (naming == null) {
0878:                    return;
0879:                }
0880:                for (final Iterator it = naming.entrySet().iterator(); it
0881:                        .hasNext();) {
0882:                    final Map.Entry entry = (Map.Entry) it.next();
0883:                    final Key key = (Key) entry.getKey();
0884:                    if (key instanceof  AliasKey) {
0885:                        continue;
0886:                    }
0887:                    final Set alias = (Set) entry.getValue();
0888:                    if (alias == null || alias.isEmpty()) {
0889:                        continue;
0890:                    }
0891:                    final AliasKey keyAsAlias = (AliasKey) alias.iterator()
0892:                            .next();
0893:                    /*
0894:                     * 'key' is one of the enumerations (X_MINIMUM, WIDTH, ELLIPSOID, etc...).
0895:                     * 'keyAsAlias' is the name to use for storing the value for this key.
0896:                     */
0897:                    add(keyAsAlias, key.getValue(coverage));
0898:                }
0899:            }
0900:
0901:            /**
0902:             * Add all metadata from the specified image.
0903:             *
0904:             * @param  image The image with metadata to add to this {@code MetadataBuilder}.
0905:             * @throws AmbiguousMetadataException if a metadata is defined twice.
0906:             *
0907:             * @see #add(GridCoverage)
0908:             * @see #add(PropertySource,String)
0909:             * @see #add(String,Object)
0910:             */
0911:            public synchronized void add(final RenderedImage image)
0912:                    throws AmbiguousMetadataException {
0913:                if (image instanceof  PropertySource) {
0914:                    // This version allow the use of deferred properties.
0915:                    add((PropertySource) image, null);
0916:                } else {
0917:                    final String[] names = image.getPropertyNames();
0918:                    if (names != null) {
0919:                        for (int i = 0; i < names.length; i++) {
0920:                            final String name = names[i];
0921:                            add(name, image.getProperty(name));
0922:                        }
0923:                    }
0924:                }
0925:            }
0926:
0927:            /**
0928:             * Add metadata from the specified property source.
0929:             *
0930:             * @param  properties The properties source.
0931:             * @param  prefix The prefix for properties to add, of {@code null} to add
0932:             *         all properties. If non-null, only properties begining with this prefix
0933:             *         will be added.
0934:             * @throws AmbiguousMetadataException if a metadata is defined twice.
0935:             *
0936:             * @see #add(GridCoverage)
0937:             * @see #add(RenderedImage)
0938:             * @see #add(String,Object)
0939:             */
0940:            public synchronized void add(final PropertySource properties,
0941:                    final String prefix) throws AmbiguousMetadataException {
0942:                final String[] names = (prefix != null) ? properties
0943:                        .getPropertyNames(prefix) : properties
0944:                        .getPropertyNames();
0945:                if (names != null) {
0946:                    for (int i = 0; i < names.length; i++) {
0947:                        final String name = names[i];
0948:                        final Class classe = properties.getPropertyClass(name);
0949:                        add(name,
0950:                                new DeferredProperty(properties, name, classe));
0951:                    }
0952:                }
0953:            }
0954:
0955:            /**
0956:             * Add a metadata for the specified key. Keys are case-insensitive, ignore leading and
0957:             * trailing whitespaces and consider any other whitespace sequences as equal to a single
0958:             * {@code '_'} character.
0959:             *
0960:             * @param  alias The key for the metadata to add. This is usually the name found in the
0961:             *         file to be parsed (this is different from {@link Key} objects, which are keys
0962:             *         in a format neutral way). This key is usually, but not always, one of the alias
0963:             *         defined with {@link #addAlias}.
0964:             * @param  value The value for the metadata to add. If {@code null} or
0965:             *         {@link Image#UndefinedProperty}, then this method do nothing.
0966:             * @throws AmbiguousMetadataException if a different value already exists for the specified
0967:             *         alias, or for an other alias bound to the same {@link Key}.
0968:             *
0969:             * @see #add(GridCoverage)
0970:             * @see #add(RenderedImage)
0971:             * @see #add(PropertySource,String)
0972:             * @see #parseLine
0973:             */
0974:            public synchronized void add(String alias, final Object value)
0975:                    throws AmbiguousMetadataException {
0976:                final AliasKey aliasAsKey;
0977:                if (alias != null) {
0978:                    alias = alias.trim();
0979:                    aliasAsKey = new AliasKey(alias);
0980:                } else {
0981:                    aliasAsKey = null;
0982:                }
0983:                add(aliasAsKey, value);
0984:            }
0985:
0986:            /**
0987:             * Implementation of the {@link #add(String, Object)} method. This method is invoked by
0988:             * {@link #add(GridCoverage)}, which iterates through each {@link AliasKey} declared in
0989:             * {@link #naming}.
0990:             */
0991:            private void add(final AliasKey aliasAsKey, Object value)
0992:                    throws AmbiguousMetadataException {
0993:                assert isValid();
0994:                if (value == null || value == Image.UndefinedProperty) {
0995:                    return;
0996:                }
0997:                if (value instanceof  CharSequence) {
0998:                    final String text = trim(value.toString().trim(), " ");
0999:                    if (text.length() == 0)
1000:                        return;
1001:                    value = text;
1002:                }
1003:                if (metadata == null) {
1004:                    metadata = new LinkedHashMap();
1005:                }
1006:                /*
1007:                 * Consistency check:
1008:                 *
1009:                 *    - First, compare the value with any older values defined for the
1010:                 *      same alias. This value is fetched only once with 'getMetadata'.
1011:                 *    - Next, compare the value with any values defined with any other
1012:                 *      alias bound to the same key. Those values are fetched in a loop
1013:                 *      with 'getOptional'.
1014:                 */
1015:                Object oldValue = getMetadata(aliasAsKey);
1016:                Key checkKey = null;
1017:                Iterator iterator = null;
1018:                while (true) {
1019:                    if (oldValue != null && !oldValue.equals(value)) {
1020:                        final String alias = aliasAsKey.toString();
1021:                        throw new AmbiguousMetadataException(Errors
1022:                                .getResources(userLocale).getString(
1023:                                        ErrorKeys.INCONSISTENT_PROPERTY_$1,
1024:                                        alias), checkKey, alias);
1025:                    }
1026:                    if (iterator == null) {
1027:                        if (naming == null)
1028:                            break;
1029:                        final Set keySet = (Set) naming.get(aliasAsKey);
1030:                        if (keySet == null)
1031:                            break;
1032:                        iterator = keySet.iterator();
1033:                    }
1034:                    if (!iterator.hasNext())
1035:                        break;
1036:                    checkKey = (Key) iterator.next();
1037:                    oldValue = getOptional(checkKey);
1038:                }
1039:                /*
1040:                 * All tests are okay. Now add the metadata.
1041:                 */
1042:                cache = null;
1043:                metadata.put(aliasAsKey, value);
1044:            }
1045:
1046:            /**
1047:             * Add an alias to a key. After this method has been invoked, calls to
1048:             * <code>{@link #get get}(key)</code> will really looks for metadata named {@code alias}.
1049:             * Alias are mandatory in order to get various {@code getXXX()} methods to work for a
1050:             * particular file format.
1051:             * <p>
1052:             * For example if the file to be parsed uses the names {@code "ULX"} and {@code "ULY"} for the
1053:             * coordinate of the upper left corner, then the {@link #getEnvelope} method will not work
1054:             * unless the following alias are set:
1055:             *
1056:             * <blockquote><pre>
1057:             * addAlias({@linkplain #X_MINIMUM}, "ULX");
1058:             * addAlias({@linkplain #Y_MAXIMUM}, "ULY");
1059:             * </pre></blockquote>
1060:             *
1061:             * An arbitrary number of alias can be set for the same key. For example,
1062:             * <code>addAlias(Y_MAXIMUM,&nbsp;...)</code> could be invoked twice with {@code "ULY"} and
1063:             * {@code "Limit North"} alias. The {@code getXXX()} methods will try alias in the order they
1064:             * were added and use the first value found.
1065:             * <p>
1066:             * The same alias can also be set to more than one key. For example, the following code is
1067:             * legal. It means that pixel are square with the same horizontal and vertical resolution:
1068:             *
1069:             * <blockquote><pre>
1070:             * addAlias({@linkplain #X_RESOLUTION}, "Resolution");
1071:             * addAlias({@linkplain #Y_RESOLUTION}, "Resolution");
1072:             * </pre></blockquote>
1073:             *
1074:             * @param  key The key to add an alias. This key is format neutral.
1075:             * @param  alias The alias to add. This is the name actually used in the file to be parsed.
1076:             *         Alias are case insensitive and ignore multiple whitespace, like keys. If
1077:             *         this alias is already bound to the specified key, then this method do nothing.
1078:             * @throws AmbiguousMetadataException if the addition of the supplied alias
1079:             *         would introduce an ambiguity in the current set of metadata.
1080:             *         This occurs if the key has already an alias mapping to a different value.
1081:             *
1082:             * @see #getAlias
1083:             * @see #contains
1084:             * @see #get
1085:             */
1086:            public synchronized void addAlias(final Key key, String alias)
1087:                    throws AmbiguousMetadataException {
1088:                alias = trim(alias.trim(), " ");
1089:                final AliasKey aliasAsKey = new AliasKey(alias);
1090:                final Object metadata = getMetadata(aliasAsKey);
1091:                if (metadata != null) {
1092:                    final Object value = getOptional(key); // Checks also alias
1093:                    if (value != null && !value.equals(metadata)) {
1094:                        throw new AmbiguousMetadataException(Errors
1095:                                .getResources(userLocale).getString(
1096:                                        ErrorKeys.INCONSISTENT_PROPERTY_$1,
1097:                                        alias), key, alias);
1098:                    }
1099:                }
1100:                if (naming == null) {
1101:                    naming = new LinkedHashMap();
1102:                }
1103:                cache = null;
1104:                // Add the alias for the specified key. This is the information
1105:                // used by 'get' methods for fetching a metadata from a key.
1106:                Set set = (Set) naming.get(key);
1107:                if (set == null) {
1108:                    set = new LinkedHashSet(4);
1109:                    naming.put(key, set);
1110:                }
1111:                set.add(aliasAsKey);
1112:                // Add the key for the specified alias. This is the information used by
1113:                // 'add' to check against ambiguities. Set's order doesn't matter here,
1114:                // but we use LinkedHashSet anyway for faster iteration in key set.
1115:                set = (Set) naming.get(aliasAsKey);
1116:                if (set == null) {
1117:                    set = new LinkedHashSet(4);
1118:                    naming.put(aliasAsKey, set);
1119:                }
1120:                set.add(key);
1121:                assert isValid();
1122:            }
1123:
1124:            /**
1125:             * Checks if this object is in a valid state. {@link #naming} should
1126:             * contains a key for every values in all {@link Set} objects.
1127:             */
1128:            private boolean isValid() {
1129:                assert Thread.holdsLock(this );
1130:                if (naming != null) {
1131:                    for (final Iterator it = naming.values().iterator(); it
1132:                            .hasNext();) {
1133:                        if (!naming.keySet().containsAll((Set) it.next())) {
1134:                            return false;
1135:                        }
1136:                    }
1137:                }
1138:                return true;
1139:            }
1140:
1141:            /**
1142:             * Returns the specified value as a string.
1143:             *
1144:             * @param  value The value to cast.
1145:             * @param  key The key, for formatting error message if needed.
1146:             * @param  alias The alias, for formatting error message if needed.
1147:             * @return The value as a string.
1148:             * @throws MetadataException if the value can't be cast to a string.
1149:             */
1150:            private String toString(final Object value, final Key key,
1151:                    final String alias) throws MetadataException {
1152:                if (value == null) {
1153:                    return null;
1154:                }
1155:                if (value instanceof  CharSequence) {
1156:                    return value.toString();
1157:                }
1158:                if (value instanceof  IdentifiedObject) {
1159:                    return ((IdentifiedObject) value).getName().getCode();
1160:                }
1161:                throw new MetadataException(Errors.getResources(userLocale)
1162:                        .getString(ErrorKeys.CANT_CONVERT_FROM_TYPE_$1,
1163:                                Utilities.getShortClassName(value)), key, alias);
1164:            }
1165:
1166:            /**
1167:             * Returns the metadata value for the specified alias. No other alias than the specified
1168:             * one is examined. This method is used for the implementation of {@link #getOptional(Key)}.
1169:             * This method is also invoked by {@link #add(String,Object)} in order to check if an
1170:             * incompatible value is already set for a given alias.
1171:             *
1172:             * @param  key The key of the desired metadata. Keys are case-insensitive and
1173:             *         can be any of the alias defined with {@link #addAlias}.
1174:             * @return The metadata for the specified alias, or {@code null} if none.
1175:             */
1176:            private Object getMetadata(final AliasKey alias) {
1177:                assert Thread.holdsLock(this );
1178:                if (metadata != null) {
1179:                    Object value = metadata.get(alias);
1180:                    if (value instanceof  DeferredData) {
1181:                        value = ((DeferredData) value).getData();
1182:                    }
1183:                    if (value != null && value != Image.UndefinedProperty) {
1184:                        return value;
1185:                    }
1186:                }
1187:                return null;
1188:            }
1189:
1190:            /**
1191:             * Returns the metadata for the specified key, or {@code null} if the metadata is not
1192:             * found. This method expects a format neutral, case insensitive {@link Key} argument. In
1193:             * order to maps the key to the actual name used in the underlying metadata file, the method
1194:             * {@link #addAlias} <strong>must</strong> have been invoked prior to any {@code get} method.
1195:             *
1196:             * @param  key The key of the desired metadata. Keys are case-insensitive and
1197:             *         can be any of the alias defined with {@link #addAlias}.
1198:             * @return The metadata for the specified key, or {@code null} if none.
1199:             */
1200:            private Object getOptional(final Key key) {
1201:                assert Thread.holdsLock(this );
1202:                lastAlias = null;
1203:                if (naming != null) {
1204:                    final Set alias = (Set) naming.get(key);
1205:                    if (alias != null) {
1206:                        for (final Iterator it = alias.iterator(); it.hasNext();) {
1207:                            final AliasKey aliasAsKey = (AliasKey) it.next();
1208:                            final Object value = getMetadata(aliasAsKey);
1209:                            if (value != null) {
1210:                                lastAlias = aliasAsKey.toString();
1211:                                return value;
1212:                            }
1213:                        }
1214:                    }
1215:                }
1216:                return null;
1217:            }
1218:
1219:            /**
1220:             * Checks if this {@code MetadataBuilder} contains a value for the specified key.
1221:             * Invoking {@link #get} will thrown a {@link MissingMetadataException} if and only
1222:             * if {@link #contains} returns {@code false} for the same key.
1223:             *
1224:             * @param  key The key to test for inclusion in this {@code MetadataBuilder}.
1225:             * @return {@code true} if the given key was found.
1226:             *
1227:             * @see #get
1228:             * @see #addAlias
1229:             */
1230:            public synchronized boolean contains(final Key key) {
1231:                return getOptional(key) != null;
1232:            }
1233:
1234:            /**
1235:             * Returns the metadata for the specified key. This method expect a format neutral, case
1236:             * insensitive {@link Key} argument. In order to maps the key to the actual name used in
1237:             * the underlying metadata file, the method {@link #addAlias} <strong>must</strong> have
1238:             * been invoked prior to any {@code get} method.
1239:             *
1240:             * @param  key The key of the desired metadata. Keys are case insensitive and format neutral.
1241:             * @return Value for the specified key (never {@code null}).
1242:             * @throws MissingMetadataException if no value exists for the specified key.
1243:             *
1244:             * @see #getAsDouble
1245:             * @see #getAsInt
1246:             * @see #contains
1247:             * @see #addAlias
1248:             */
1249:            public synchronized Object get(final Key key)
1250:                    throws MissingMetadataException {
1251:                final Object value = getOptional(key);
1252:                if (value != null && value != Image.UndefinedProperty) {
1253:                    return value;
1254:                }
1255:                throw new MissingMetadataException(Errors.getResources(
1256:                        userLocale).getString(ErrorKeys.UNDEFINED_PROPERTY_$1,
1257:                        key), key, lastAlias);
1258:            }
1259:
1260:            /**
1261:             * Returns a metadata as a {@code double} value. The default implementation invokes
1262:             * {@link #getAsDouble(Key)} or {@link #getAsDate(Key)} according the metadata type:
1263:             * the metadata is assumed to be a number, except if {@code crs} is a {@link TemporalCRS}.
1264:             * In this later case, the metadata is assumed to be a {@link Date}.
1265:             *
1266:             * @param  key The key of the desired metadata. Keys are case-insensitive.
1267:             * @param  crs The coordinate reference system for the dimension of the key to be queried,
1268:             *             or {@code null} if unknow.
1269:             * @return Value for the specified key as a {@code double}.
1270:             * @throws MissingMetadataException if no value exists for the specified key.
1271:             * @throws MetadataException if the value can't be parsed as a {@code double}.
1272:             */
1273:            private double getAsDouble(final Key key,
1274:                    final CoordinateReferenceSystem crs)
1275:                    throws MetadataException {
1276:                if (crs instanceof  TemporalCRS) {
1277:                    return DefaultTemporalCRS.wrap((TemporalCRS) crs).toValue(
1278:                            getAsDate(key));
1279:                } else {
1280:                    return getAsDouble(key);
1281:                }
1282:            }
1283:
1284:            /**
1285:             * Returns a metadata as a {@code double} value. The default implementation
1286:             * invokes <code>{@link #get get}(key)</code> and parse the resulting value with
1287:             * {@link NumberFormat#parse(String)} for the {@linkplain #getLocale current locale}.
1288:             *
1289:             * @param  key The key of the desired metadata. Keys are case-insensitive.
1290:             * @return Value for the specified key as a {@code double}.
1291:             * @throws MissingMetadataException if no value exists for the specified key.
1292:             * @throws MetadataException if the value can't be parsed as a {@code double}.
1293:             *
1294:             * @see #getAsInt
1295:             * @see #get
1296:             * @see #contains
1297:             * @see #addAlias
1298:             */
1299:            public synchronized double getAsDouble(final Key key)
1300:                    throws MetadataException {
1301:                final Object value = get(key);
1302:                if (value instanceof  Number) {
1303:                    return ((Number) value).doubleValue();
1304:                }
1305:                try {
1306:                    return getNumberFormat().parse(
1307:                            toString(value, key, lastAlias)).doubleValue();
1308:                } catch (ParseException exception) {
1309:                    throw new MetadataException(exception, key, lastAlias);
1310:                }
1311:            }
1312:
1313:            /**
1314:             * Returns a metadata as a {@code int} value. The default implementation
1315:             * invokes <code>{@link #getAsDouble getAsDouble}(key)</code> and make sure
1316:             * that the resulting value is an integer.
1317:             *
1318:             * @param  key The key of the desired metadata. Keys are case-insensitive.
1319:             * @return Value for the specified key as an {@code int}.
1320:             * @throws MissingMetadataException if no value exists for the specified key.
1321:             * @throws MetadataException if the value can't be parsed as an {@code int}.
1322:             *
1323:             * @see #getAsDouble
1324:             * @see #get
1325:             * @see #contains
1326:             * @see #addAlias
1327:             */
1328:            public synchronized int getAsInt(final Key key)
1329:                    throws MetadataException {
1330:                final double value = getAsDouble(key);
1331:                final int integer = (int) value;
1332:                if (value != integer) {
1333:                    throw new MetadataException(Errors.getResources(userLocale)
1334:                            .getString(ErrorKeys.BAD_PARAMETER_$2, lastAlias,
1335:                                    new Double(value)), key, lastAlias);
1336:                }
1337:                return integer;
1338:            }
1339:
1340:            /**
1341:             * Returns a metadata as a {@link Date} value. The default implementation
1342:             * invokes <code>{@link #get get}(key)</code> and parse the resulting value with
1343:             * {@link DateFormat#parse(String)} for the {@linkplain #getLocale current locale}.
1344:             *
1345:             * @param  key The key of the desired metadata. Keys are case-insensitive.
1346:             * @return Value for the specified key as a {@link Date}.
1347:             * @throws MissingMetadataException if no value exists for the specified key.
1348:             * @throws MetadataException if the value can't be parsed as a date.
1349:             */
1350:            public synchronized Date getAsDate(final Key key)
1351:                    throws MetadataException {
1352:                final Object value = get(key);
1353:                if (value instanceof  Date) {
1354:                    return (Date) (((Date) value).clone());
1355:                }
1356:                try {
1357:                    return getDateFormat().parse(
1358:                            toString(value, key, lastAlias));
1359:                } catch (ParseException exception) {
1360:                    throw new MetadataException(exception, key, lastAlias);
1361:                }
1362:            }
1363:
1364:            /**
1365:             * Gets the object to use for parsing numbers.
1366:             */
1367:            private NumberFormat getNumberFormat() throws MetadataException {
1368:                assert Thread.holdsLock(this );
1369:                final String CACHE_KEY = "NumberFormat";
1370:                if (cache != null) {
1371:                    final Object candidate = cache.get(CACHE_KEY);
1372:                    if (candidate instanceof  NumberFormat) {
1373:                        return (NumberFormat) candidate;
1374:                    }
1375:                }
1376:                final NumberFormat format = NumberFormat
1377:                        .getNumberInstance(getLocale());
1378:                if (numberPattern != null && format instanceof  DecimalFormat) {
1379:                    ((DecimalFormat) format).applyPattern(numberPattern);
1380:                }
1381:                cache(CACHE_KEY, format);
1382:                return format; // Do not clone, since this method is private.
1383:            }
1384:
1385:            /**
1386:             * Gets the object to use for parsing dates.
1387:             */
1388:            private DateFormat getDateFormat() throws MetadataException {
1389:                assert Thread.holdsLock(this );
1390:                final String CACHE_KEY = "DateFormat";
1391:                if (cache != null) {
1392:                    final Object candidate = cache.get(CACHE_KEY);
1393:                    if (candidate instanceof  DateFormat) {
1394:                        return (DateFormat) candidate;
1395:                    }
1396:                }
1397:                final DateFormat format = DateFormat.getDateTimeInstance(
1398:                        DateFormat.SHORT, DateFormat.SHORT, getLocale());
1399:                if (datePattern != null && format instanceof  SimpleDateFormat) {
1400:                    ((SimpleDateFormat) format).applyPattern(datePattern);
1401:                }
1402:                cache(CACHE_KEY, format);
1403:                return format; // Do not clone, since this method is private.
1404:            }
1405:
1406:            /**
1407:             * Add an object in the cache.
1408:             */
1409:            private void cache(final String key, final Object object) {
1410:                assert Thread.holdsLock(this );
1411:                if (cache == null) {
1412:                    cache = new HashMap();
1413:                }
1414:                cache.put(key, object);
1415:            }
1416:
1417:            /**
1418:             * Returns the list of alias for the specified key, or {@code null}
1419:             * if the key has no alias. Alias are the names used in the underlying
1420:             * metadata file, and are format dependent.
1421:             *
1422:             * @param  key The format neutral key.
1423:             * @return The alias for the specified key, or {@code null} if none.
1424:             *
1425:             * @see #addAlias
1426:             */
1427:            public synchronized String[] getAlias(final Key key) {
1428:                assert isValid();
1429:                if (naming != null) {
1430:                    final Set alias = (Set) naming.get(key);
1431:                    if (alias != null) {
1432:                        int index = 0;
1433:                        final String[] list = new String[alias.size()];
1434:                        for (final Iterator it = alias.iterator(); it.hasNext();) {
1435:                            list[index++] = it.next().toString();
1436:                        }
1437:                        assert index == list.length;
1438:                        return list;
1439:                    }
1440:                }
1441:                return null;
1442:            }
1443:
1444:            /**
1445:             * Returns the source file name or URL. This is the path specified
1446:             * during the last call to a {@code load(...)} method.
1447:             *
1448:             * @return The source file name or URL.
1449:             * @throws MetadataException if this information can't be fetched.
1450:             *
1451:             * @link #load(File)
1452:             * @link #load(URL)
1453:             */
1454:            public String getSource() throws MetadataException {
1455:                return source;
1456:            }
1457:
1458:            /**
1459:             * Returns the locale to use when parsing metadata values as numbers, angles or dates.
1460:             * This is <strong>not</strong> the locale used for formatting error messages, if any.
1461:             * The default implementation returns {@link Locale#US}, since it is the format used
1462:             * in most data file.
1463:             *
1464:             * @return The locale to use for parsing metadata values.
1465:             * @throws MetadataException if this information can't be fetched.
1466:             *
1467:             * @see #getAsDouble
1468:             * @see #getAsInt
1469:             * @see #getAsDate
1470:             */
1471:            public Locale getLocale() throws MetadataException {
1472:                return Locale.US;
1473:            }
1474:
1475:            /**
1476:             * Returns the units, or the specified value if no units is found.
1477:             * The default value may be {@code null}.
1478:             */
1479:            private Unit getUnit(final Unit defaultValue) {
1480:                try {
1481:                    return getUnit();
1482:                } catch (MetadataException exception) {
1483:                    return defaultValue;
1484:                }
1485:            }
1486:
1487:            /**
1488:             * Returns the units. The default implementation invokes
1489:             * <code>{@linkplain #get get}({@linkplain #UNITS})</code>
1490:             * and transform the resulting string into an {@link Unit} object.
1491:             *
1492:             * @throws MissingMetadataException if no value exists for the {@link #UNITS} key.
1493:             * @throws MetadataException if the operation failed for some other reason.
1494:             *
1495:             * @see #getCoordinateReferenceSystem
1496:             */
1497:            public synchronized Unit getUnit() throws MetadataException {
1498:                final Object value = get(UNITS);
1499:                if (value instanceof  Unit) {
1500:                    return (Unit) value;
1501:                }
1502:                final String text = toString(value, UNITS, lastAlias);
1503:                if (contains(text, METRES)) {
1504:                    return SI.METER;
1505:                } else if (contains(text, DEGREES)) {
1506:                    return NonSI.DEGREE_ANGLE;
1507:                } else {
1508:                    throw new MetadataException("Unknow unit: " + text, UNITS,
1509:                            lastAlias);
1510:                }
1511:            }
1512:
1513:            /**
1514:             * Check if {@code toSearch} appears in the {@code list} array.
1515:             * Search is case-insensitive. This is a temporary patch (will be removed
1516:             * when the final API for JSR-108: Units specification will be available).
1517:             */
1518:            private static boolean contains(final String toSearch,
1519:                    final String[] list) {
1520:                for (int i = list.length; --i >= 0;) {
1521:                    if (toSearch.equalsIgnoreCase(list[i])) {
1522:                        return true;
1523:                    }
1524:                }
1525:                return false;
1526:            }
1527:
1528:            /**
1529:             * Returns the geodetic datum. The default implementation invokes
1530:             * <code>{@linkplain #get get}({@linkplain #DATUM})</code>
1531:             * and transform the resulting string into a {@link GeodeticDatum} object.
1532:             *
1533:             * @throws MissingMetadataException if no value exists for the {@link #DATUM} key.
1534:             * @throws MetadataException if the operation failed for some other reason.
1535:             *
1536:             * @see #getCoordinateReferenceSystem
1537:             * @see #getEllipsoid
1538:             */
1539:            public synchronized GeodeticDatum getGeodeticDatum()
1540:                    throws MetadataException {
1541:                final Object value = get(DATUM);
1542:                if (value instanceof  GeodeticDatum) {
1543:                    return (GeodeticDatum) value;
1544:                }
1545:                final String text = toString(value, DATUM, lastAlias);
1546:                /*
1547:                 * TODO: parse 'text' when DatumAuthorityFactory will be fully implemented.
1548:                 */
1549:                checkEllipsoid(text, "getGeodeticDatum");
1550:                return org.geotools.referencing.datum.DefaultGeodeticDatum.WGS84;
1551:            }
1552:
1553:            /**
1554:             * Returns the ellipsoid. The default implementation invokes
1555:             * <code>{@linkplain #get get}({@linkplain #ELLIPSOID})</code>
1556:             * and transform the resulting string into an {@link Ellipsoid} object.
1557:             *
1558:             * @throws MissingMetadataException if no value exists for the {@link #ELLIPSOID} key.
1559:             * @throws MetadataException if the operation failed for some other reason.
1560:             *
1561:             * @see #getCoordinateReferenceSystem
1562:             * @see #getGeodeticDatum
1563:             */
1564:            public synchronized Ellipsoid getEllipsoid()
1565:                    throws MetadataException {
1566:                final Object value = get(ELLIPSOID);
1567:                if (value instanceof  Ellipsoid) {
1568:                    return (Ellipsoid) value;
1569:                }
1570:                final String text = toString(value, ELLIPSOID, lastAlias);
1571:                /*
1572:                 * TODO: parse 'text' when DatumAuthorityFactory will be fully implemented.
1573:                 */
1574:                checkEllipsoid(text, "getEllipsoid");
1575:                return org.geotools.referencing.datum.DefaultEllipsoid.WGS84;
1576:            }
1577:
1578:            /**
1579:             * Check if the supplied ellipsoid is WGS 1984.
1580:             * This is a temporary patch.
1581:             *
1582:             * @todo parse the datum and ellipsoid names when DatumAuthorityFactory
1583:             *       will be implemented. The current EPSG factory implementation may not be enough.
1584:             */
1585:            private static synchronized void checkEllipsoid(String text,
1586:                    final String source) {
1587:                text = trim(text, " ").replace('_', ' ');
1588:                if (!text.equalsIgnoreCase("WGS 1984")
1589:                        && !text.equalsIgnoreCase("WGS1984")
1590:                        && !text.equalsIgnoreCase("WGS84")) {
1591:                    if (!emittedWarning) {
1592:                        emittedWarning = true;
1593:                        final String message = '"'
1594:                                + text
1595:                                + "\" ellipsoid not yet implemented. Default to WGS 1984.";
1596:                        final LogRecord record = new LogRecord(Level.WARNING,
1597:                                message);
1598:                        record.setSourceMethodName(source);
1599:                        record.setSourceClassName(MetadataBuilder.class
1600:                                .getName());
1601:                        AbstractGridCoverageReader.LOGGER.log(record);
1602:                    }
1603:                }
1604:            }
1605:
1606:            /** Temporary flag for {@link #checkEllipsoid}. */
1607:            private static boolean emittedWarning;
1608:
1609:            /**
1610:             * Set the specified value for the specified parameter. If the specified unit is non-null and
1611:             * compatible with the parameter value, then it will be given to the parameter. Otherwise, the
1612:             * the parameter unit is left unchanged. This heuristic rule may be acceptable only when we
1613:             * don't know for sure on which parameter the unit applies, which explain why it is not part
1614:             * of any public API. This method is for internal usage by {@link #getProjection}.
1615:             */
1616:            private static void setValue(final ParameterValue parameter,
1617:                    final double value, final Unit unit) {
1618:                if (unit != null) {
1619:                    // TODO: Remove cast when we will be allowed to compile for J2SE 1.5.
1620:                    final Unit expected = ((ParameterDescriptor) parameter
1621:                            .getDescriptor()).getUnit();
1622:                    if (expected != null && unit.isCompatible(expected)) {
1623:                        parameter.setValue(value, unit);
1624:                        return;
1625:                    }
1626:                }
1627:                parameter.setValue(value);
1628:            }
1629:
1630:            /**
1631:             * Returns the projection. The default implementation performs the following steps:
1632:             * <p>
1633:             * <ul>
1634:             *   <li>Gets the projection classification with
1635:             *       <code>{@linkplain #get get}({@linkplain #OPERATION_METHOD})</code>, or with
1636:             *       <code>{@linkplain #get get}({@linkplain #PROJECTION})</code> if no value were
1637:             *       defined for the former.</li>
1638:             *
1639:             *   <li>Gets the list of projection parameters for the above classification.</li>
1640:             *
1641:             *   <li>Gets the metadata values for each parameters in the above step. If a parameter is not
1642:             *       defined in this {@code MetadataBuilder}, then it will be left to its (projection
1643:             *       dependent) default value. Parameters are projection dependent, but will typically
1644:             *       include
1645:             *
1646:             *           {@code "semi_major"},
1647:             *           {@code "semi_minor"},
1648:             *           {@code "central_meridian"},
1649:             *           {@code "latitude_of_origin"},
1650:             *           {@code "false_easting"} and
1651:             *           {@code "false_northing"}.
1652:             *
1653:             *       The names actually used in the metadata file to be parsed must be declared as usual,
1654:             *       e.g. <code>{@linkplain #addAlias addAlias}({@linkplain #SEMI_MAJOR}, ...)</code></li>
1655:             *
1656:             *   <li>If no value was defined for {@code "semi-major"} and/or {@code "semi-minor"}
1657:             *       parameters, then invokes {@link #getEllipsoid} and uses its semi-axis length.</li>
1658:             *
1659:             *   <li>If a value exists for the optional key {@link #PROJECTION}, then takes it as
1660:             *       the projection name. The projection name is for documentation purpose only and do
1661:             *       not affect any computation. If there is no value for {@link #PROJECTION}, then
1662:             *       the projection name will be the same than the operation method name (the first step
1663:             *       above).</li>
1664:             * </ul>
1665:             *
1666:             * @return The projection.
1667:             * @throws MissingMetadataException if no value exists for the {@link #PROJECTION} or the
1668:             *         {@link #OPERATION_METHOD} keys.
1669:             * @throws MetadataException if the operation failed for some other reason
1670:             *         (for example if a parameter value can't be parsed as a {@code double}).
1671:             *
1672:             * @see #getCoordinateReferenceSystem
1673:             * @see #SEMI_MAJOR
1674:             * @see #SEMI_MINOR
1675:             * @see #LATITUDE_OF_ORIGIN
1676:             * @see #CENTRAL_MERIDIAN
1677:             * @see #FALSE_EASTING
1678:             * @see #FALSE_NORTHING
1679:             */
1680:            public synchronized Conversion getProjection()
1681:                    throws MetadataException {
1682:                /*
1683:                 * First, checks if a Projection object has already been constructed. Since
1684:                 * Projection is immutable, it is safe to returns a single instance for all.
1685:                 */
1686:                final String CACHE_KEY = "Projection";
1687:                if (cache != null) {
1688:                    final Object candidate = cache.get(CACHE_KEY);
1689:                    if (candidate instanceof  Conversion) {
1690:                        return (Conversion) candidate;
1691:                    }
1692:                }
1693:                /*
1694:                 * No projection is available in the cache. Computes it now and cache it for future use.
1695:                 * If the projection is provided, then the operation method is optional. Otherwise, the
1696:                 * operation method is mandatory.
1697:                 */
1698:                Object projection = getOptional(PROJECTION);
1699:                if (projection instanceof  Conversion) {
1700:                    return (Conversion) projection;
1701:                }
1702:                String projectionAlias = lastAlias; // Protect from change, except if projection is null.
1703:                Object operationMethod;
1704:                if (projection == null) {
1705:                    operationMethod = get(OPERATION_METHOD);
1706:                    projection = operationMethod;
1707:                    projectionAlias = lastAlias;
1708:                } else {
1709:                    operationMethod = getOptional(OPERATION_METHOD);
1710:                    if (operationMethod == null) {
1711:                        operationMethod = projection;
1712:                    }
1713:                }
1714:                /*
1715:                 * We now have the projection name and the projection classification (as operation method
1716:                 * name). Now iterates through all expected arguments for this projection, and ask a
1717:                 * matching metadata for each of them. If none is found, the projection parameter is left
1718:                 * to its default value.
1719:                 */
1720:                boolean semiMajorAxisDefined = false;
1721:                boolean semiMinorAxisDefined = false;
1722:                final ParameterValueGroup parameters;
1723:                final MathTransformFactory factory = factories
1724:                        .getMathTransformFactory();
1725:                try {
1726:                    parameters = factory.getDefaultParameters(toString(
1727:                            operationMethod, OPERATION_METHOD, lastAlias));
1728:                } catch (NoSuchIdentifierException exception) {
1729:                    throw new MetadataException(exception, OPERATION_METHOD,
1730:                            lastAlias);
1731:                }
1732:                final Unit unit = getUnit(null);
1733:                // TODO: Remove the cast when we will be allowed to compile for J2SE 1.5.
1734:                for (final Iterator it = ((org.opengis.parameter.ParameterDescriptorGroup) parameters
1735:                        .getDescriptor()).descriptors().iterator(); it
1736:                        .hasNext();) {
1737:                    final GeneralParameterDescriptor descriptor = (GeneralParameterDescriptor) it
1738:                            .next();
1739:                    if (descriptor instanceof  ParameterDescriptor) {
1740:                        final String name = descriptor.getName().getCode();
1741:                        final ParameterValue param = parameters.parameter(name);
1742:                        final double paramValue;
1743:                        try {
1744:                            paramValue = getAsDouble(new Key(name));
1745:                        } catch (MissingMetadataException exception) {
1746:                            // Parameter is not defined. Lets it to
1747:                            // its default value and continue...
1748:                            continue;
1749:                        }
1750:                        setValue(param, paramValue, unit);
1751:                        if (name.equalsIgnoreCase("semi_major"))
1752:                            semiMajorAxisDefined = true;
1753:                        if (name.equalsIgnoreCase("semi_minor"))
1754:                            semiMinorAxisDefined = true;
1755:                    }
1756:                }
1757:                /*
1758:                 * After all parameters have been set, ensures that semi major and minor axis are
1759:                 * presents. If they were already specified, ensures that their values is consistent
1760:                 * with the ellipsoid.
1761:                 */
1762:                if (!semiMajorAxisDefined || !semiMinorAxisDefined) {
1763:                    final Ellipsoid ellipsoid = getEllipsoid();
1764:                    final double semiMajor = ellipsoid.getSemiMajorAxis();
1765:                    final double semiMinor = ellipsoid.getSemiMinorAxis();
1766:                    final Unit axisUnit = ellipsoid.getAxisUnit();
1767:                    if ((semiMajorAxisDefined && parameters.parameter(
1768:                            "semi_major").doubleValue(axisUnit) != semiMajor)
1769:                            || (semiMinorAxisDefined && parameters.parameter(
1770:                                    "semi_minor").doubleValue(axisUnit) != semiMinor)) {
1771:                        throw new AmbiguousMetadataException(Errors
1772:                                .getResources(userLocale).getString(
1773:                                        ErrorKeys.AMBIGIOUS_AXIS_LENGTH),
1774:                                PROJECTION, projectionAlias);
1775:                    }
1776:                    parameters.parameter("semi_major").setValue(semiMajor,
1777:                            axisUnit);
1778:                    parameters.parameter("semi_minor").setValue(semiMinor,
1779:                            axisUnit);
1780:                }
1781:                final Conversion defining = new DefiningConversion(toString(
1782:                        projection, PROJECTION, projectionAlias), parameters);
1783:                cache(CACHE_KEY, defining);
1784:                return defining;
1785:            }
1786:
1787:            /**
1788:             * Returns the coordinate reference system. The default implementation constructs a CRS
1789:             * from the information provided by {@link #getUnit}, {@link #getGeodeticDatum} and
1790:             * {@link #getProjection}. The coordinate system name (optional) will be fetch from
1791:             * metadata {@link #COORDINATE_REFERENCE_SYSTEM}, if presents as a string.
1792:             *
1793:             * @throws MissingMetadataException if a required value is missing
1794:             *        (e.g. {@link #PROJECTION}, {@link #DATUM}, {@link #UNITS}, etc.).
1795:             * @throws MetadataException if the operation failed for some other reason.
1796:             *
1797:             * @see #getUnit
1798:             * @see #getGeodeticDatum
1799:             * @see #getProjection
1800:             */
1801:            public synchronized CoordinateReferenceSystem getCoordinateReferenceSystem()
1802:                    throws MetadataException {
1803:                /*
1804:                 * First, checks if a CRS object has already been constructed. Since CRS
1805:                 * are immutables, it is safe to returns a single instance for all.
1806:                 */
1807:                final String CACHE_KEY = "CoordinateReferenceSystem";
1808:                if (cache != null) {
1809:                    final Object candidate = cache.get(CACHE_KEY);
1810:                    if (candidate instanceof  CoordinateReferenceSystem) {
1811:                        return (CoordinateReferenceSystem) candidate;
1812:                    }
1813:                }
1814:                /*
1815:                 * No CoordinateReferenceSystem is available in the cache.
1816:                 * Computes it now and cache it for future use.
1817:                 */
1818:                Object value = getOptional(COORDINATE_REFERENCE_SYSTEM);
1819:                if (value instanceof  CoordinateReferenceSystem) {
1820:                    return (CoordinateReferenceSystem) value;
1821:                }
1822:                if (value == null) {
1823:                    value = "Generated";
1824:                }
1825:                final String crsAlias = lastAlias; // Protect from change
1826:                final String crsName = toString(value,
1827:                        COORDINATE_REFERENCE_SYSTEM, crsAlias);
1828:                final Unit unit = getUnit();
1829:                final GeodeticDatum datum = getGeodeticDatum();
1830:                final boolean isGeographic = NonSI.DEGREE_ANGLE
1831:                        .isCompatible(unit);
1832:                final Unit angularUnit = isGeographic ? unit
1833:                        : NonSI.DEGREE_ANGLE;
1834:                final Unit linearUnit = SI.METER.isCompatible(unit) ? unit
1835:                        : SI.METER;
1836:                final EllipsoidalCS geoCS = DefaultEllipsoidalCS.GEODETIC_2D
1837:                        .usingUnit(angularUnit);
1838:                final Map properties = Collections.singletonMap(
1839:                        IdentifiedObject.NAME_KEY, crsName);
1840:                final GeographicCRS geographicCRS;
1841:                final CoordinateReferenceSystem crs;
1842:                try {
1843:                    geographicCRS = factories.getCRSFactory()
1844:                            .createGeographicCRS(properties, datum, geoCS);
1845:                    if (isGeographic) {
1846:                        crs = geographicCRS;
1847:                    } else {
1848:                        final CartesianCS cs = DefaultCartesianCS.PROJECTED
1849:                                .usingUnit(linearUnit);
1850:                        final Conversion projection = getProjection();
1851:                        crs = factories.createProjectedCRS(properties,
1852:                                geographicCRS, projection, cs);
1853:                    }
1854:                    cache(CACHE_KEY, crs);
1855:                    return crs;
1856:                } catch (FactoryException exception) {
1857:                    throw new MetadataException(exception,
1858:                            COORDINATE_REFERENCE_SYSTEM, crsAlias);
1859:                }
1860:            }
1861:
1862:            /**
1863:             * Convenience method returning the envelope in geographic coordinate system using WGS
1864:             * 1984 datum.
1865:             *
1866:             * @throws MetadataException if the operation failed. This exception
1867:             *         may contains a {@link TransformException} as its cause.
1868:             *
1869:             * @see #getEnvelope
1870:             * @see #getGridRange
1871:             */
1872:            public synchronized GeographicBoundingBox getGeographicBoundingBox()
1873:                    throws MetadataException {
1874:                /*
1875:                 * First, checks if a bounding box object has already been constructed.
1876:                 * Since bounding box can be immutable after their construction, there
1877:                 * is no need to clone it before to return it.
1878:                 */
1879:                final String CACHE_KEY = "GeographicBoundingBox";
1880:                if (cache != null) {
1881:                    final Object candidate = cache.get(CACHE_KEY);
1882:                    if (candidate instanceof  GeographicBoundingBox) {
1883:                        return (GeographicBoundingBox) candidate;
1884:                    }
1885:                }
1886:                /*
1887:                 * No bounding box is available in the cache.
1888:                 * Computes it now and cache it for future use.
1889:                 */
1890:                final GeographicBoundingBox box;
1891:                try {
1892:                    box = (GeographicBoundingBox) // TODO: remove cast with J2SE 1.5
1893:                    new GeographicBoundingBoxImpl(getEnvelope()).unmodifiable();
1894:                } catch (TransformException exception) {
1895:                    throw new MetadataException(exception, null, null);
1896:                }
1897:                cache(CACHE_KEY, box);
1898:                return box;
1899:            }
1900:
1901:            /**
1902:             * Returns the envelope. The default implementation constructs an envelope
1903:             * using the values from the following keys:
1904:             * <ul>
1905:             *   <li>The horizontal limits with at least one of the following keys:
1906:             *       {@link #X_MINIMUM} and/or {@link #X_MAXIMUM}. If one of those
1907:             *       keys is missing, then {@link #X_RESOLUTION} is required.</li>
1908:             *   <li>The vertical limits with at least one of the following keys:
1909:             *       {@link #Y_MINIMUM} and/or {@link #Y_MAXIMUM}. If one of those
1910:             *       keys is missing, then {@link #Y_RESOLUTION} is required.</li>
1911:             * </ul>
1912:             *
1913:             * @throws MissingMetadataException if a required value is missing.
1914:             * @throws MetadataException if the operation failed for some other reason.
1915:             *
1916:             * @see #getGridRange
1917:             * @see #getGeographicBoundingBox
1918:             */
1919:            public synchronized Envelope getEnvelope() throws MetadataException {
1920:                /*
1921:                 * First, checks if an Envelope object has already been constructed.
1922:                 * Since Envelope is mutable, we need to clone it before to return it.
1923:                 */
1924:                final String CACHE_KEY = "Envelope";
1925:                if (cache != null) {
1926:                    Object candidate = cache.get(CACHE_KEY);
1927:                    if (candidate instanceof  Envelope) {
1928:                        if (candidate instanceof  Cloneable) {
1929:                            candidate = ((Cloneable) candidate).clone();
1930:                        }
1931:                        return (Envelope) candidate;
1932:                    }
1933:                }
1934:                /*
1935:                 * No Envelope is available in the cache.
1936:                 * Computes it now and cache it for future use.
1937:                 */
1938:                final GridRange range = getGridRange();
1939:                final CoordinateReferenceSystem crs = getCoordinateReferenceSystem();
1940:                final GeneralEnvelope envelope = new GeneralEnvelope(crs);
1941:                switch (envelope.getDimension()) {
1942:                default: // TODO: What should we do with other dimensions? Open question...
1943:                case 3:
1944:                    setRange(Z_MINIMUM, Z_MAXIMUM, Z_RESOLUTION, envelope, 2,
1945:                            range, crs); // fall through
1946:                case 2:
1947:                    setRange(Y_MINIMUM, Y_MAXIMUM, Y_RESOLUTION, envelope, 1,
1948:                            range, crs); // fall through
1949:                case 1:
1950:                    setRange(X_MINIMUM, X_MAXIMUM, X_RESOLUTION, envelope, 0,
1951:                            range, crs); // fall through
1952:                case 0:
1953:                    break;
1954:                }
1955:                cache(CACHE_KEY, envelope);
1956:                return (Envelope) envelope.clone();
1957:            }
1958:
1959:            /**
1960:             * Set the range for the specified dimension of an example. The range will be computed
1961:             * from the "?Minimum" and "?Maximum" metadata, if presents. If only one of those
1962:             * metadata is present, the "?Resolution" metadata will be used.
1963:             *
1964:             * @param minKey    Property name for the minimal value.
1965:             * @param maxKey    Property name for the maximal value.
1966:             * @param resKey    Property name for the resolution.
1967:             * @param envelope  The envelope to set.
1968:             * @param dimension The dimension in the envelope to set.
1969:             * @param gridRange The grid range.
1970:             * @param crs       The coordinate reference system
1971:             * @throws MetadataException if a metadata can't be set, or if an ambiguity has been found.
1972:             */
1973:            private void setRange(final Key minKey, final Key maxKey,
1974:                    final Key resKey, final GeneralEnvelope envelope,
1975:                    final int dimension, final GridRange gridRange,
1976:                    CoordinateReferenceSystem crs) throws MetadataException {
1977:                assert Thread.holdsLock(this );
1978:                crs = CRSUtilities.getSubCRS(crs, dimension, dimension + 1);
1979:                if (!contains(resKey)) {
1980:                    envelope.setRange(dimension, getAsDouble(minKey, crs),
1981:                            getAsDouble(maxKey, crs));
1982:                    return;
1983:                }
1984:                final double resolution = getAsDouble(resKey, crs);
1985:                final String lastAlias = this .lastAlias; // Protect from change
1986:                final int range = gridRange.getLength(dimension);
1987:                if (!contains(maxKey)) {
1988:                    final double min = getAsDouble(minKey, crs);
1989:                    envelope.setRange(dimension, min, min + resolution * range);
1990:                    return;
1991:                }
1992:                if (!contains(minKey)) {
1993:                    final double max = getAsDouble(maxKey, crs);
1994:                    envelope.setRange(dimension, max - resolution * range, max);
1995:                    return;
1996:                }
1997:                final double min = getAsDouble(minKey, crs);
1998:                final double max = getAsDouble(maxKey, crs);
1999:                envelope.setRange(dimension, min, max);
2000:                if (Math.abs((min - max) / resolution - range) > EPS) {
2001:                    throw new AmbiguousMetadataException(Errors.getResources(
2002:                            userLocale).getString(
2003:                            ErrorKeys.INCONSISTENT_PROPERTY_$1, resKey),
2004:                            resKey, lastAlias);
2005:                }
2006:            }
2007:
2008:            /**
2009:             * Returns the grid range. Default implementation fetchs the metadata values
2010:             * for keys {@link #WIDTH} and {@link #HEIGHT}, and transform the resulting
2011:             * strings into a {@link GridRange} object.
2012:             *
2013:             * @throws MissingMetadataException if a required value is missing.
2014:             * @throws MetadataException if the operation failed for some other reason.
2015:             *
2016:             * @see #getEnvelope
2017:             * @see #getGeographicBoundingBox
2018:             */
2019:            public synchronized GridRange getGridRange()
2020:                    throws MetadataException {
2021:                /*
2022:                 * First, checks if a GridRange object has already been constructed. Since
2023:                 * GridRange is immutable, it is safe to returns a single instance for all.
2024:                 */
2025:                final String CACHE_KEY = "GridRange";
2026:                if (cache != null) {
2027:                    final Object candidate = cache.get(CACHE_KEY);
2028:                    if (candidate instanceof  GridRange) {
2029:                        return (GridRange) candidate;
2030:                    }
2031:                }
2032:                /*
2033:                 * No GridRange is available in the cache.
2034:                 * Compute it now and cache it for future use.
2035:                 */
2036:                final int dimension = getCoordinateReferenceSystem()
2037:                        .getCoordinateSystem().getDimension();
2038:                final int[] lower = new int[dimension];
2039:                final int[] upper = new int[dimension];
2040:                Arrays.fill(upper, 1);
2041:                switch (dimension) {
2042:                default: // fall through
2043:                case 3:
2044:                    upper[2] = getAsInt(DEPTH); // fall through
2045:                case 2:
2046:                    upper[1] = getAsInt(HEIGHT); // fall through
2047:                case 1:
2048:                    upper[0] = getAsInt(WIDTH); // fall through
2049:                case 0:
2050:                    break;
2051:                }
2052:                final GridRange range = new GeneralGridRange(lower, upper);
2053:                cache(CACHE_KEY, range);
2054:                return range;
2055:            }
2056:
2057:            /**
2058:             * Returns the sample dimensions for each band of the {@link GridCoverage}
2059:             * to be read. If sample dimensions are not know, then this method returns
2060:             * {@code null}. The default implementation always returns {@code null}.
2061:             *
2062:             * @throws MetadataException if the operation failed.
2063:             */
2064:            public GridSampleDimension[] getSampleDimensions()
2065:                    throws MetadataException {
2066:                return null;
2067:            }
2068:
2069:            /**
2070:             * Sets the current {@link Locale} of this {@code MetadataBuilder}
2071:             * to the given value. A value of {@code null} removes any previous
2072:             * setting, and indicates that the parser should localize as it sees fit.
2073:             * <p>
2074:             * <strong>Note:</strong> this is the locale to use for formatting error messages,
2075:             * not the locale to use for parsing the file. The locale for parsing is specified
2076:             * by {@link #getLocale}.
2077:             */
2078:            final synchronized void setUserLocale(final Locale locale) {
2079:                userLocale = locale;
2080:            }
2081:
2082:            /**
2083:             * List all metadata to the specified stream. The default implementation list the
2084:             * metadata as <cite>key&nbsp;=&nbsp;value</cite> pairs. Each pair is formatted on
2085:             * its own line, and the caracter <code>'='</code> is inserted between keys and values.
2086:             * A question mark (<code>'?'</code>) is put in front of any unknow name (i.e. any name
2087:             * not specified with {@link #addAlias}).
2088:             *
2089:             * @param  out Stream to write metadata to.
2090:             * @throws IOException if an error occured while listing metadata.
2091:             *
2092:             * @see #add(GridCoverage)
2093:             * @see #toString()
2094:             */
2095:            public synchronized void listMetadata(final Writer out)
2096:                    throws IOException {
2097:                final String lineSeparator = System.getProperty(
2098:                        "line.separator", "\n");
2099:                final String comments = (String) getMetadata(null);
2100:                if (comments != null) {
2101:                    int stop = comments.length();
2102:                    while (--stop >= 0
2103:                            && Character.isSpaceChar(comments.charAt(stop)))
2104:                        ;
2105:                    out.write(comments.substring(0, stop + 1));
2106:                    out.write(lineSeparator);
2107:                    out.write(lineSeparator);
2108:                }
2109:                if (metadata != null) {
2110:                    int maxLength = 1;
2111:                    for (final Iterator it = metadata.keySet().iterator(); it
2112:                            .hasNext();) {
2113:                        final Object key = it.next();
2114:                        if (key != null) {
2115:                            final int length = key.toString().length();
2116:                            if (length > maxLength)
2117:                                maxLength = length;
2118:                        }
2119:                    }
2120:                    for (final Iterator it = metadata.entrySet().iterator(); it
2121:                            .hasNext();) {
2122:                        final Map.Entry entry = (Map.Entry) it.next();
2123:                        final Key key = (Key) entry.getKey();
2124:                        if (key != null) {
2125:                            Object value = entry.getValue();
2126:                            if (value instanceof  Number) {
2127:                                value = getNumberFormat().format(value);
2128:                            } else if (value instanceof  Date) {
2129:                                value = getDateFormat().format(value);
2130:                            } else if (value instanceof  Formattable)
2131:                                try {
2132:                                    // Format without indentation
2133:                                    value = ((Formattable) value).toWKT(0);
2134:                                } catch (UnformattableObjectException exception) {
2135:                                    // Ignore; we will use 'toString()' instead.
2136:                                }
2137:                            final boolean isKnow = (naming != null && naming
2138:                                    .containsKey(key));
2139:                            out.write(isKnow ? "  " : "? ");
2140:                            out.write(String.valueOf(key));
2141:                            out.write(Utilities.spaces(maxLength
2142:                                    - key.toString().length()));
2143:                            out.write(separator);
2144:                            out.write(String.valueOf(value));
2145:                            out.write(lineSeparator);
2146:                        }
2147:                    }
2148:                }
2149:            }
2150:
2151:            /**
2152:             * Returns a string representation of this metadata set. The default implementation
2153:             * write the class name and the envelope in geographic coordinates, as returned by
2154:             * {@link #getGeographicBoundingBox}. Then, it append the list of all metadata as
2155:             * formatted by {@link #listMetadata}.
2156:             */
2157:            public String toString() {
2158:                final String lineSeparator = System.getProperty(
2159:                        "line.separator", "\n");
2160:                final StringWriter buffer = new StringWriter();
2161:                if (source != null) {
2162:                    buffer.write("[\"");
2163:                    buffer.write(source);
2164:                    buffer.write("\"]");
2165:                }
2166:                buffer.write(lineSeparator);
2167:                try {
2168:                    final GeographicBoundingBox box = getGeographicBoundingBox();
2169:                    buffer.write(GeographicBoundingBoxImpl.toString(box,
2170:                            "DD°MM'SS\"", null));
2171:                    buffer.write(lineSeparator);
2172:                } catch (MetadataException exception) {
2173:                    // Ignore.
2174:                }
2175:                buffer.write('{');
2176:                buffer.write(lineSeparator);
2177:                try {
2178:                    final TableWriter table = new TableWriter(buffer, 2);
2179:                    table.setMultiLinesCells(true);
2180:                    table.nextColumn();
2181:                    listMetadata(table);
2182:                    table.flush();
2183:                } catch (IOException exception) {
2184:                    buffer.write(exception.getLocalizedMessage());
2185:                }
2186:                buffer.write('}');
2187:                buffer.write(lineSeparator);
2188:                return buffer.toString();
2189:            }
2190:
2191:            /**
2192:             * Trim a character string. Leading and trailing spaces are removed. Any succession of
2193:             * one ore more unicode whitespace characters (as of {@link Character#isSpaceChar(char)}
2194:             * are replaced by a single <code>'_'</code> character. Example:
2195:             *
2196:             *                       <pre>"This   is a   test"</pre>
2197:             * will be returned as   <pre>"This_is_a_test"</pre>
2198:             *
2199:             * @param  str The string to trim (may be {@code null}).
2200:             * @param  separator The separator to insert in place of succession of whitespaces.
2201:             *         Usually "_" for keys and " " for values.
2202:             * @return The trimed string, or {@code null} if <code>str</code> was null.
2203:             */
2204:            static String trim(String str, final String separator) {
2205:                if (str != null) {
2206:                    str = str.trim();
2207:                    StringBuffer buffer = null;
2208:                    loop: for (int i = str.length(); --i >= 0;) {
2209:                        if (Character.isSpaceChar(str.charAt(i))) {
2210:                            final int upper = i;
2211:                            do
2212:                                if (--i < 0)
2213:                                    break loop;
2214:                            while (Character.isSpaceChar(str.charAt(i)));
2215:                            if (buffer == null) {
2216:                                buffer = new StringBuffer(str);
2217:                            }
2218:                            buffer.replace(i + 1, upper + 1, separator);
2219:                        }
2220:                    }
2221:                    if (buffer != null) {
2222:                        return buffer.toString();
2223:                    }
2224:                }
2225:                return str;
2226:            }
2227:
2228:            /**
2229:             * A key for fetching metadata in a format independent way. For example, the northern
2230:             * limit of an image way be named <code>"Limit North"</code> is some metadata files,
2231:             * and <code>"ULY"</code> (as <cite>Upper Left Y</cite>) in other metadata files. The
2232:             * {@link MetadataBuilder#Y_MAXIMUM} allows to fetch this metadata without knowledge of
2233:             * the actual name used in the underlying metadata file.
2234:             * <p>
2235:             * Keys are case-insensitive. Furthermore, trailing and leading spaces are ignored.
2236:             * Any succession of one ore more unicode whitespace characters (as of
2237:             * {@link java.lang.Character#isSpaceChar(char)} is understood as equal to a single
2238:             * <code>'_'</code> character. For example, the key <code>"false&nbsp;&nbsp;easting"</code>
2239:             * is considered equals to <code>"false_easting"</code>.
2240:             *
2241:             * @version $Id: MetadataBuilder.java 25699 2007-05-31 15:55:07Z desruisseaux $
2242:             * @author Martin Desruisseaux
2243:             */
2244:            public static class Key implements  Serializable {
2245:                /**
2246:                 * Serial number for interoperability with different versions.
2247:                 */
2248:                private static final long serialVersionUID = -6197070349689520675L;
2249:
2250:                /**
2251:                 * The original name, as specified by the user.
2252:                 */
2253:                private final String name;
2254:
2255:                /**
2256:                 * The trimed name in lower case. This
2257:                 * is the key to use in comparaisons.
2258:                 */
2259:                private final String key;
2260:
2261:                /**
2262:                 * Construct a new key.
2263:                 *
2264:                 * @param name The key name.
2265:                 */
2266:                public Key(String name) {
2267:                    name = name.trim();
2268:                    this .name = name;
2269:                    this .key = trim(name, "_").toLowerCase();
2270:                }
2271:
2272:                /**
2273:                 * Returns the value for this key from the specified grid coverage.
2274:                 * For example the key {@link MetadataBuilder#X_MINIMUM} will returns
2275:                 * <code>coverage.getEnvelope().getMinimum(0)</code>.
2276:                 *
2277:                 * @param coverage The grid coverage from which to fetch the value.
2278:                 * @return The value, or {@code null} if none.
2279:                 */
2280:                public Object getValue(final GridCoverage coverage) {
2281:                    return null;
2282:                }
2283:
2284:                /**
2285:                 * Returns the name for this key. This is the name supplied to the constructor
2286:                 * (i.e. case and whitespaces are preserved).
2287:                 */
2288:                public String toString() {
2289:                    return name;
2290:                }
2291:
2292:                /**
2293:                 * Returns a hash code value.
2294:                 */
2295:                public int hashCode() {
2296:                    return key.hashCode();
2297:                }
2298:
2299:                /**
2300:                 * Compare this key with the supplied key for equality. Comparaison is case-insensitive
2301:                 * and considere any sequence of whitespaces as a single <code>'_'</code> character, as
2302:                 * specified in this class documentation.
2303:                 */
2304:                public boolean equals(final Object object) {
2305:                    return (object != null)
2306:                            && object.getClass().equals(getClass())
2307:                            && key.equals(((Key) object).key);
2308:                }
2309:            }
2310:
2311:            /**
2312:             * A key for metadata derived from {@link Envelope} and/or {@link GridRange}.
2313:             *
2314:             * @version $Id: MetadataBuilder.java 25699 2007-05-31 15:55:07Z desruisseaux $
2315:             * @author Martin Desruisseaux
2316:             */
2317:            private static final class EnvelopeKey extends Key {
2318:                /**
2319:                 * Serial number for interoperability with different versions.
2320:                 */
2321:                private static final long serialVersionUID = -7928870614384957795L;
2322:
2323:                /*
2324:                 * BitMask  1 = Minimum value
2325:                 *          2 = Maximum value
2326:                 *          4 = Apply on Envelope
2327:                 *          8 = Apply on GridRange
2328:                 */
2329:                /** Property for {@link Envelope#getLength}.  */
2330:                public static final byte LENGTH = 4 | 0;
2331:                /** Property for {@link Envelope#getMinimum}. */
2332:                public static final byte MINIMUM = 4 | 1;
2333:                /** Property for {@link Envelope#getMaximum}. */
2334:                public static final byte MAXIMUM = 4 | 2;
2335:                /** Property for {@link GridRange#getLength}. */
2336:                public static final byte SIZE = 8 | 0;
2337:                /** Property for {@link GridRange#getLower}.  */
2338:                public static final byte LOWER = 8 | 1;
2339:                /** Property for {@link GridRange#getUpper}.  */
2340:                public static final byte UPPER = 8 | 2;
2341:                /** Property for the resolution.              */
2342:                public static final byte RESOLUTION = 4 | 8;
2343:
2344:                /**
2345:                 * The dimension from which to fetch the value.
2346:                 */
2347:                private final byte dimension;
2348:
2349:                /**
2350:                 * The method to use for fetching the value. Should be one of {@link #MINIMUM},
2351:                 * {@link #MAXIMUM}, {@link #LOWER}, {@link #UPPER} or  {@link #RESOLUTION}.
2352:                 */
2353:                private final byte method;
2354:
2355:                /**
2356:                 * Construct a key with the specified name.
2357:                 */
2358:                public EnvelopeKey(final String name, final byte dimension,
2359:                        final byte method) {
2360:                    super (name);
2361:                    this .dimension = dimension;
2362:                    this .method = method;
2363:                }
2364:
2365:                /**
2366:                 * Returns the value for this key from the specified grid coverage.
2367:                 */
2368:                public Object getValue(final GridCoverage coverage) {
2369:                    Envelope envelope = null;
2370:                    GridRange range = null;
2371:                    if ((method & 4) != 0) {
2372:                        envelope = coverage.getEnvelope();
2373:                        if (envelope == null
2374:                                || envelope.getDimension() <= dimension) {
2375:                            return null;
2376:                        }
2377:                    }
2378:                    if ((method & 8) != 0) {
2379:                        range = coverage.getGridGeometry().getGridRange();
2380:                        if (range == null || range.getDimension() <= dimension) {
2381:                            return null;
2382:                        }
2383:                    }
2384:                    switch (method) {
2385:                    default:
2386:                        throw new AssertionError(method);
2387:                    case LENGTH:
2388:                        return new Double(envelope.getLength(dimension));
2389:                    case MINIMUM:
2390:                        return getValue(coverage, envelope
2391:                                .getMinimum(dimension));
2392:                    case MAXIMUM:
2393:                        return getValue(coverage, envelope
2394:                                .getMaximum(dimension));
2395:                    case SIZE:
2396:                        return new Integer(range.getLength(dimension));
2397:                    case LOWER:
2398:                        return new Integer(range.getLower(dimension));
2399:                    case UPPER:
2400:                        return new Integer(range.getUpper(dimension));
2401:                    case RESOLUTION: {
2402:                        return new Double(envelope.getLength(dimension)
2403:                                / range.getLength(dimension));
2404:                    }
2405:                    }
2406:                }
2407:
2408:                /**
2409:                 * Returns the specified value as a {@link Double} or {@link Date} object
2410:                 * according the coverage's coordinate system.
2411:                 */
2412:                private Object getValue(final GridCoverage coverage,
2413:                        final double value) {
2414:                    CoordinateReferenceSystem crs = coverage
2415:                            .getCoordinateReferenceSystem();
2416:                    if (crs != null) {
2417:                        crs = CRSUtilities.getSubCRS(crs, dimension,
2418:                                dimension + 1);
2419:                        if (crs instanceof  TemporalCRS) {
2420:                            return DefaultTemporalCRS.wrap((TemporalCRS) crs)
2421:                                    .toDate(value);
2422:                        }
2423:                    }
2424:                    return new Double(value);
2425:                }
2426:
2427:                /**
2428:                 * Compares this key with the supplied key for equality.
2429:                 */
2430:                public boolean equals(final Object object) {
2431:                    if (super .equals(object)) {
2432:                        final EnvelopeKey that = (EnvelopeKey) object;
2433:                        return this .dimension == that.dimension
2434:                                && this .method == that.method;
2435:                    }
2436:                    return false;
2437:                }
2438:            }
2439:
2440:            /**
2441:             * A key for metadata derived from {@link Projection}.
2442:             * The key name must be the projection parameter name.
2443:             *
2444:             * @version $Id: MetadataBuilder.java 25699 2007-05-31 15:55:07Z desruisseaux $
2445:             * @author Martin Desruisseaux
2446:             */
2447:            private static final class ProjectionKey extends Key {
2448:                /**
2449:                 * Serial number for interoperability with different versions.
2450:                 */
2451:                private static final long serialVersionUID = -6913177345764406058L;
2452:
2453:                /**
2454:                 * Construct a key with the specified name.
2455:                 */
2456:                public ProjectionKey(final String name) {
2457:                    super (name);
2458:                }
2459:
2460:                /**
2461:                 * Returns the value for this key from the specified grid coverage.
2462:                 */
2463:                public Object getValue(final GridCoverage coverage) {
2464:                    final ProjectedCRS crs = CRS.getProjectedCRS(coverage
2465:                            .getCoordinateReferenceSystem());
2466:                    if (crs != null) {
2467:                        final ParameterValueGroup parameters = crs
2468:                                .getConversionFromBase().getParameterValues();
2469:                        try {
2470:                            return parameters.parameter(toString()).getValue();
2471:                        } catch (ParameterNotFoundException exception) {
2472:                            // No value set for the specified parameter.
2473:                            // This is not an error. Just ignore...
2474:                        }
2475:                    }
2476:                    return null;
2477:                }
2478:            }
2479:
2480:            /**
2481:             * A case-insensitive key for alias name. We use a different class because the
2482:             * <code>equals</code> method must returns {@code false} when comparing
2483:             * <code>AliasKey</code> with ordinary <code>Key</code>s. This kind of key is
2484:             * for internal use only.
2485:             *
2486:             * @version $Id: MetadataBuilder.java 25699 2007-05-31 15:55:07Z desruisseaux $
2487:             * @author Martin Desruisseaux
2488:             */
2489:            private static final class AliasKey extends Key {
2490:                /**
2491:                 * Serial number for interoperability with different versions.
2492:                 */
2493:                private static final long serialVersionUID = 4546899841215386795L;
2494:
2495:                /**
2496:                 * Construct a new key for an alias.
2497:                 */
2498:                public AliasKey(final String name) {
2499:                    super (name);
2500:                }
2501:
2502:                /**
2503:                 * Returns a hash code value.
2504:                 */
2505:                public int hashCode() {
2506:                    return ~super.hashCode();
2507:                }
2508:            }
2509:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.