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>" = "</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, ...)</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 = 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 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: }
|