0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation;
0010: * version 2.1 of the License.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * This package contains documentation from OpenGIS specifications.
0018: * OpenGIS consortium's work is fully acknowledged here.
0019: */
0020: package org.geotools.referencing;
0021:
0022: // J2SE dependencies
0023: import java.io.ObjectStreamException;
0024: import java.io.Serializable;
0025: import java.util.Arrays;
0026: import java.util.Collection;
0027: import java.util.Collections;
0028: import java.util.Comparator;
0029: import java.util.Iterator;
0030: import java.util.LinkedHashSet;
0031: import java.util.List;
0032: import java.util.Map;
0033: import java.util.Set;
0034: import java.util.HashMap;
0035: import java.util.logging.Level;
0036: import java.util.logging.Logger;
0037: import javax.units.SI;
0038: import javax.units.Unit;
0039:
0040: // OpenGIS dependencies
0041: import org.opengis.metadata.Identifier;
0042: import org.opengis.metadata.citation.Citation;
0043: import org.opengis.parameter.InvalidParameterValueException;
0044: import org.opengis.referencing.IdentifiedObject;
0045: import org.opengis.referencing.AuthorityFactory;
0046: import org.opengis.referencing.ObjectFactory;
0047: import org.opengis.referencing.ReferenceIdentifier;
0048: import org.opengis.util.GenericName;
0049: import org.opengis.util.InternationalString;
0050: import org.opengis.util.LocalName;
0051: import org.opengis.util.ScopedName;
0052:
0053: // Geotools dependencies
0054: import org.geotools.metadata.iso.citation.Citations;
0055: import org.geotools.referencing.wkt.Formattable;
0056: import org.geotools.resources.Utilities;
0057: import org.geotools.resources.i18n.Errors;
0058: import org.geotools.resources.i18n.ErrorKeys;
0059: import org.geotools.resources.i18n.Logging;
0060: import org.geotools.resources.i18n.LoggingKeys;
0061: import org.geotools.util.GrowableInternationalString;
0062: import org.geotools.util.NameFactory;
0063:
0064: /**
0065: * A base class for metadata applicable to reference system objects.
0066: * When {@link AuthorityFactory} is used to create an object, the
0067: * {@linkplain ReferenceIdentifier#getAuthority authority} and
0068: * {@linkplain ReferenceIdentifier#getCode authority code} values are set to the authority
0069: * name of the factory object, and the authority code supplied by the client, respectively.
0070: * When {@link ObjectFactory} creates an object, the {@linkplain #getName() name} is set to
0071: * the value supplied by the client and all of the other metadata items are left empty.
0072: * <p>
0073: * This class is conceptually <cite>abstract</cite>, even if it is technically possible to
0074: * instantiate it. Typical applications should create instances of the most specific subclass with
0075: * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to
0076: * identify the exact type. For example it is not possible to infer the exact coordinate system from
0077: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
0078: * Known Text</cite></A> is some cases (e.g. in a {@code LOCAL_CS} element). In such exceptional
0079: * situation, a plain {@link org.geotools.referencing.cs.AbstractCS} object may be instantiated.
0080: *
0081: * @since 2.1
0082: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/AbstractIdentifiedObject.java $
0083: * @version $Id: AbstractIdentifiedObject.java 28264 2007-12-05 21:53:08Z desruisseaux $
0084: * @author Martin Desruisseaux
0085: */
0086: public class AbstractIdentifiedObject extends Formattable implements
0087: IdentifiedObject, Serializable {
0088: /**
0089: * Serial number for interoperability with different versions.
0090: */
0091: private static final long serialVersionUID = -5173281694258483264L;
0092:
0093: /**
0094: * An empty array of identifiers. This is usefull for fetching identifiers as an array,
0095: * using the following idiom:
0096: * <blockquote><pre>
0097: * {@linkplain #getIdentifiers()}.toArray(EMPTY_IDENTIFIER_ARRAY);
0098: * </pre></blockquote>
0099: */
0100: public static final ReferenceIdentifier[] EMPTY_IDENTIFIER_ARRAY = new ReferenceIdentifier[0];
0101:
0102: /**
0103: * An empty array of alias. This is usefull for fetching alias as an array,
0104: * using the following idiom:
0105: * <blockquote><pre>
0106: * {@linkplain #getAlias()}.toArray(EMPTY_ALIAS_ARRAY);
0107: * </pre></blockquote>
0108: */
0109: public static final GenericName[] EMPTY_ALIAS_ARRAY = new GenericName[0];
0110:
0111: /**
0112: * A comparator for sorting identified objects by {@linkplain #getName() name}.
0113: */
0114: public static final Comparator NAME_COMPARATOR = new NameComparator();
0115:
0116: private static final class NameComparator implements Comparator,
0117: Serializable {
0118: public int compare(final Object o1, final Object o2) {
0119: return doCompare(((IdentifiedObject) o1).getName()
0120: .getCode(), ((IdentifiedObject) o2).getName()
0121: .getCode());
0122: }
0123:
0124: protected Object readResolve() throws ObjectStreamException {
0125: return NAME_COMPARATOR;
0126: }
0127: }
0128:
0129: /**
0130: * A comparator for sorting identified objects by {@linkplain #getIdentifiers identifiers}.
0131: */
0132: public static final Comparator IDENTIFIER_COMPARATOR = new IdentifierComparator();
0133:
0134: private static final class IdentifierComparator implements
0135: Comparator, Serializable {
0136: public int compare(final Object o1, final Object o2) {
0137: Collection/*<ReferenceIdentifier>*/a1 = ((IdentifiedObject) o1)
0138: .getIdentifiers();
0139: Collection/*<ReferenceIdentifier>*/a2 = ((IdentifiedObject) o2)
0140: .getIdentifiers();
0141: if (a1 == null)
0142: a1 = Collections.EMPTY_SET;
0143: if (a2 == null)
0144: a2 = Collections.EMPTY_SET;
0145: final Iterator i1 = a1.iterator();
0146: final Iterator i2 = a2.iterator();
0147: boolean n1, n2;
0148: while ((n1 = i1.hasNext()) & (n2 = i2.hasNext())) { // Really '&', not '&&'
0149: final int c = doCompare(((ReferenceIdentifier) i1
0150: .next()).getCode(), ((ReferenceIdentifier) i2
0151: .next()).getCode());
0152: // TODO: remove (ReferenceIdentifier) cast once we will be allowed to compile for J2SE 1.5.
0153: if (c != 0) {
0154: return c;
0155: }
0156: }
0157: if (n1)
0158: return +1;
0159: if (n2)
0160: return -1;
0161: return 0;
0162: }
0163:
0164: protected Object readResolve() throws ObjectStreamException {
0165: return IDENTIFIER_COMPARATOR;
0166: }
0167: }
0168:
0169: /**
0170: * A comparator for sorting identified objects by {@linkplain #getRemarks remarks}.
0171: */
0172: public static final Comparator REMARKS_COMPARATOR = new RemarksComparator();
0173:
0174: private static final class RemarksComparator implements Comparator,
0175: Serializable {
0176: public int compare(final Object o1, final Object o2) {
0177: return doCompare(((IdentifiedObject) o1).getRemarks(),
0178: ((IdentifiedObject) o2).getRemarks());
0179: }
0180:
0181: protected Object readResolve() throws ObjectStreamException {
0182: return REMARKS_COMPARATOR;
0183: }
0184: }
0185:
0186: /**
0187: * The name for this object or code. Should never be {@code null}.
0188: */
0189: private final ReferenceIdentifier name;
0190:
0191: /**
0192: * An alternative name by which this object is identified.
0193: */
0194: private final Collection/*<GenericName>*/alias;
0195:
0196: /**
0197: * An identifier which references elsewhere the object's defining information.
0198: * Alternatively an identifier by which this object can be referenced.
0199: */
0200: private final Set/*<ReferenceIdentifier>*/identifiers;
0201:
0202: /**
0203: * Comments on or information about this object, or {@code null} if none.
0204: */
0205: private final InternationalString remarks;
0206:
0207: /**
0208: * Constructs a new identified object with the same values than the specified one.
0209: * This copy constructor provides a way to wrap an arbitrary implementation into a
0210: * Geotools one or a user-defined one (as a subclass), usually in order to leverage
0211: * some implementation-specific API. This constructor performs a shallow copy,
0212: * i.e. the properties are not cloned.
0213: */
0214: public AbstractIdentifiedObject(final IdentifiedObject object) {
0215: name = object.getName();
0216: alias = object.getAlias();
0217: identifiers = object.getIdentifiers();
0218: remarks = object.getRemarks();
0219: }
0220:
0221: /**
0222: * Constructs an object from a set of properties. Keys are strings from the table below.
0223: * Key are case-insensitive, and leading and trailing spaces are ignored. The map given in
0224: * argument shall contains at least a {@code "name"} property. Other properties listed
0225: * in the table below are optional.
0226: * <p>
0227: * <table border='1'>
0228: * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
0229: * <th nowrap>Property name</th>
0230: * <th nowrap>Value type</th>
0231: * <th nowrap>Value given to</th>
0232: * </tr>
0233: * <tr>
0234: * <td nowrap> {@link #NAME_KEY "name"} </td>
0235: * <td nowrap> {@link String} or {@link ReferenceIdentifier} </td>
0236: * <td nowrap> {@link #getName()}</td>
0237: * </tr>
0238: * <tr>
0239: * <td nowrap> {@link #ALIAS_KEY "alias"} </td>
0240: * <td nowrap> {@link String}, <code>{@linkplain String}[]</code>,
0241: * {@link GenericName} or <code>{@linkplain GenericName}[]</code> </td>
0242: * <td nowrap> {@link #getAlias}</td>
0243: * </tr>
0244: * <tr>
0245: * <td nowrap> {@link ReferenceIdentifier#AUTHORITY_KEY "authority"} </td>
0246: * <td nowrap> {@link String} or {@link Citation} </td>
0247: * <td nowrap> {@link ReferenceIdentifier#getAuthority} on the {@linkplain #getName() name}</td>
0248: * </tr>
0249: * <tr>
0250: * <td nowrap> {@link ReferenceIdentifier#CODESPACE_KEY "version"} </td>
0251: * <td nowrap> {@link String} </td>
0252: * <td nowrap> {@link ReferenceIdentifier#getCodeSpace} on the {@linkplain #getName() name}</td>
0253: * </tr>
0254: * <tr>
0255: * <td nowrap> {@link ReferenceIdentifier#VERSION_KEY "version"} </td>
0256: * <td nowrap> {@link String} </td>
0257: * <td nowrap> {@link ReferenceIdentifier#getVersion} on the {@linkplain #getName() name}</td>
0258: * </tr>
0259: * <tr>
0260: * <td nowrap> {@link #IDENTIFIERS_KEY "identifiers"} </td>
0261: * <td nowrap> {@link ReferenceIdentifier} or <code>{@linkplain ReferenceIdentifier}[]</code> </td>
0262: * <td nowrap> {@link #getIdentifiers}</td>
0263: * </tr>
0264: * <tr>
0265: * <td nowrap> {@link #REMARKS_KEY "remarks"} </td>
0266: * <td nowrap> {@link String} or {@link InternationalString} </td>
0267: * <td nowrap> {@link #getRemarks}</td>
0268: * </tr>
0269: * </table>
0270: * <P>
0271: * Additionally, all localizable attributes like {@code "remarks"} may have a language and
0272: * country code suffix. For example the {@code "remarks_fr"} property stands for remarks in
0273: * {@linkplain java.util.Locale#FRENCH French} and the {@code "remarks_fr_CA"} property stands
0274: * for remarks in {@linkplain java.util.Locale#CANADA_FRENCH French Canadian}.
0275: * <P>
0276: * Note that the {@code "authority"} and {@code "version"} properties are ignored if the
0277: * {@code "name"} property is already a {@link Citation} object instead of a {@link String}.
0278: *
0279: * @throws InvalidParameterValueException if a property has an invalid value.
0280: * @throws IllegalArgumentException if a property is invalid for some other reason.
0281: */
0282: public AbstractIdentifiedObject(final Map properties)
0283: throws IllegalArgumentException {
0284: this (properties, null, null);
0285: }
0286:
0287: /**
0288: * Constructs an object from a set of properties and copy unrecognized properties in the
0289: * specified map. The {@code properties} argument is treated as in the {@linkplain
0290: * AbstractIdentifiedObject#AbstractIdentifiedObject(Map) one argument constructor}. All
0291: * properties unknow to this {@code AbstractIdentifiedObject} constructor are copied
0292: * in the {@code subProperties} map, after their key has been normalized (usually
0293: * lower case, leading and trailing space removed).
0294: *
0295: * <P>If {@code localizables} is non-null, then all keys listed in this argument are
0296: * treated as localizable one (i.e. may have a suffix like "_fr", "_de", etc.). Localizable
0297: * properties are stored in the {@code subProperties} map as {@link InternationalString}
0298: * objects.</P>
0299: *
0300: * @param properties Set of properties. Should contains at least {@code "name"}.
0301: * @param subProperties The map in which to copy unrecognized properties.
0302: * @param localizables Optional list of localized properties.
0303: *
0304: * @throws InvalidParameterValueException if a property has an invalid value.
0305: * @throws IllegalArgumentException if a property is invalid for some other reason.
0306: */
0307: protected AbstractIdentifiedObject(final Map properties,
0308: final Map subProperties, final String[] localizables)
0309: throws IllegalArgumentException {
0310: ensureNonNull("properties", properties);
0311: Object name = null;
0312: Object alias = null;
0313: Object identifiers = null;
0314: Object remarks = null;
0315: GrowableInternationalString growable = null;
0316: GrowableInternationalString[] subGrowables = null;
0317: /*
0318: * Iterate through each map entry. This have two purposes:
0319: *
0320: * 1) Ignore case (a call to properties.get("foo") can't do that)
0321: * 2) Find localized remarks.
0322: *
0323: * This algorithm is sub-optimal if the map contains a lot of entries of no interest to
0324: * this object. Hopefully, most users will fill a map only with usefull entries.
0325: */
0326: NEXT_KEY: for (final Iterator it = properties.entrySet()
0327: .iterator(); it.hasNext();) {
0328: Map.Entry entry = (Map.Entry) it.next();
0329: String key = ((String) entry.getKey()).trim().toLowerCase();
0330: Object value = entry.getValue();
0331: /*
0332: * Note: String.hashCode() is part of J2SE specification,
0333: * so it should not change across implementations.
0334: */
0335: switch (key.hashCode()) {
0336: // Fix case for common keywords. They are not used
0337: // by this class, but are used by some subclasses.
0338: case -1528693765:
0339: if (key.equalsIgnoreCase("anchorPoint"))
0340: key = "anchorPoint";
0341: break;
0342: case -1805658881:
0343: if (key.equalsIgnoreCase("bursaWolf"))
0344: key = "bursaWolf";
0345: break;
0346: case 109688209:
0347: if (key.equalsIgnoreCase("operationVersion"))
0348: key = "operationVersion";
0349: break;
0350: case 1479434472:
0351: if (key.equalsIgnoreCase("coordinateOperationAccuracy"))
0352: key = "coordinateOperationAccuracy";
0353: break;
0354: case 1126917133:
0355: if (key.equalsIgnoreCase("positionalAccuracy"))
0356: key = "positionalAccuracy";
0357: break;
0358: case 1127093059:
0359: if (key.equalsIgnoreCase("realizationEpoch"))
0360: key = "realizationEpoch";
0361: break;
0362: case 1790520781:
0363: if (key.equalsIgnoreCase("domainOfValidity"))
0364: key = "domainOfValidity";
0365: break;
0366: case -1109785975:
0367: if (key.equalsIgnoreCase("validArea"))
0368: key = "validArea";
0369: break;
0370:
0371: // -------------------------------------
0372: // "name": String or ReferenceIdentifier
0373: // -------------------------------------
0374: case 3373707: {
0375: if (key.equals(NAME_KEY)) {
0376: if (value instanceof String) {
0377: name = new NamedIdentifier(properties, false);
0378: assert value.equals(((Identifier) name)
0379: .getCode()) : name;
0380: } else {
0381: // Should be an instance of ReferenceIdentifier, but we don't check
0382: // here. The type will be checked at the end of this method, which
0383: // will thrown an exception with detailed message in case of mismatch.
0384: name = value;
0385: }
0386: continue NEXT_KEY;
0387: }
0388: break;
0389: }
0390: // -------------------------------------------------------
0391: // "alias": String, String[], GenericName or GenericName[]
0392: // -------------------------------------------------------
0393: case 92902992: {
0394: if (key.equals(ALIAS_KEY)) {
0395: alias = NameFactory.toArray(value);
0396: continue NEXT_KEY;
0397: }
0398: break;
0399: }
0400: // -----------------------------------------------------------
0401: // "identifiers": ReferenceIdentifier or ReferenceIdentifier[]
0402: // -----------------------------------------------------------
0403: case 1368189162: {
0404: if (key.equals(IDENTIFIERS_KEY)) {
0405: if (value != null) {
0406: if (value instanceof ReferenceIdentifier) {
0407: identifiers = new ReferenceIdentifier[] { (ReferenceIdentifier) value };
0408: } else {
0409: identifiers = value;
0410: }
0411: }
0412: continue NEXT_KEY;
0413: }
0414: break;
0415: }
0416: // ----------------------------------------
0417: // "remarks": String or InternationalString
0418: // ----------------------------------------
0419: case 1091415283: {
0420: if (key.equals(REMARKS_KEY)) {
0421: if (value instanceof InternationalString) {
0422: remarks = value;
0423: continue NEXT_KEY;
0424: }
0425: }
0426: break;
0427: }
0428: }
0429: /*
0430: * Search for additional locales for remarks (e.g. "remarks_fr").
0431: * 'growable.add(...)' will add the value only if the key starts
0432: * with the "remarks" prefix.
0433: */
0434: if (value instanceof String) {
0435: if (growable == null) {
0436: if (remarks instanceof GrowableInternationalString) {
0437: growable = (GrowableInternationalString) remarks;
0438: } else {
0439: growable = new GrowableInternationalString();
0440: }
0441: }
0442: if (growable.add(REMARKS_KEY, key, value.toString())) {
0443: continue NEXT_KEY;
0444: }
0445: }
0446: /*
0447: * Search for user-specified localizable properties.
0448: */
0449: if (subProperties == null) {
0450: continue NEXT_KEY;
0451: }
0452: if (localizables != null) {
0453: for (int i = 0; i < localizables.length; i++) {
0454: final String prefix = localizables[i];
0455: if (key.equals(prefix)) {
0456: if (value instanceof InternationalString) {
0457: // Stores the value in 'subProperties' after the loop.
0458: break;
0459: }
0460: }
0461: if (value instanceof String) {
0462: if (subGrowables == null) {
0463: subGrowables = new GrowableInternationalString[localizables.length];
0464: }
0465: if (subGrowables[i] == null) {
0466: final Object previous = subProperties
0467: .get(prefix);
0468: if (previous instanceof GrowableInternationalString) {
0469: subGrowables[i] = (GrowableInternationalString) previous;
0470: } else {
0471: subGrowables[i] = new GrowableInternationalString();
0472: }
0473: }
0474: if (subGrowables[i].add(prefix, key, value
0475: .toString())) {
0476: continue NEXT_KEY;
0477: }
0478: }
0479: }
0480: }
0481: subProperties.put(key, value);
0482: }
0483: /*
0484: * Get the localized remarks, if it was not yet set. If a user specified remarks
0485: * both as InternationalString and as String for some locales (which is a weird
0486: * usage...), then current implementation discart the later with a warning.
0487: */
0488: if (growable != null && !growable.getLocales().isEmpty()) {
0489: if (remarks == null) {
0490: remarks = growable;
0491: } else if (!growable.isSubsetOf(remarks)) {
0492: org.geotools.util.logging.Logging.getLogger(
0493: "org.geotools.referencing").log(
0494: Logging.format(Level.WARNING,
0495: LoggingKeys.LOCALES_DISCARTED));
0496: }
0497: }
0498: /*
0499: * Get the localized user-defined properties.
0500: */
0501: if (subProperties != null && subGrowables != null) {
0502: for (int i = 0; i < subGrowables.length; i++) {
0503: growable = subGrowables[i];
0504: if (growable != null
0505: && !growable.getLocales().isEmpty()) {
0506: final String prefix = localizables[i];
0507: final Object current = subProperties.get(prefix);
0508: if (current == null) {
0509: subProperties.put(prefix, growable);
0510: } else if (!growable.isSubsetOf(current)) {
0511: org.geotools.util.logging.Logging.getLogger(
0512: "org.geotools.referencing").log(
0513: Logging.format(Level.WARNING,
0514: LoggingKeys.LOCALES_DISCARTED));
0515: }
0516: }
0517: }
0518: }
0519: /*
0520: * Stores the definitive reference to the attributes. Note that casts are performed only
0521: * there (not before). This is a wanted feature, since we want to catch ClassCastExceptions
0522: * are rethrown them as more informative exceptions.
0523: */
0524: String key = null;
0525: Object value = null;
0526: try {
0527: key = NAME_KEY;
0528: this .name = (ReferenceIdentifier) (value = name);
0529: key = ALIAS_KEY;
0530: this .alias = asSet((GenericName[]) (value = alias));
0531: key = IDENTIFIERS_KEY;
0532: this .identifiers = asSet((ReferenceIdentifier[]) (value = identifiers));
0533: key = REMARKS_KEY;
0534: this .remarks = (InternationalString) (value = remarks);
0535: } catch (ClassCastException exception) {
0536: InvalidParameterValueException e = new InvalidParameterValueException(
0537: Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, key,
0538: value), key, value);
0539: e.initCause(exception);
0540: throw e;
0541: }
0542: ensureNonNull(NAME_KEY, name);
0543: ensureNonNull(NAME_KEY, name.toString());
0544: }
0545:
0546: /**
0547: * The primary name by which this object is identified.
0548: *
0549: * @see #getName(Citation)
0550: */
0551: public ReferenceIdentifier getName() {
0552: return name;
0553: }
0554:
0555: /**
0556: * An alternative name by which this object is identified.
0557: *
0558: * @return The aliases, or an empty array if there is none.
0559: *
0560: * @see #getName(Citation)
0561: */
0562: public Collection/*<GenericName>*/getAlias() {
0563: return (alias != null) ? alias : Collections.EMPTY_SET;
0564: }
0565:
0566: /**
0567: * An identifier which references elsewhere the object's defining information.
0568: * Alternatively an identifier by which this object can be referenced.
0569: *
0570: * @return This object identifiers, or an empty array if there is none.
0571: *
0572: * @see #getIdentifier(Citation)
0573: */
0574: public Set/*<ReferenceIdentifier>*/getIdentifiers() {
0575: return (identifiers != null) ? identifiers
0576: : Collections.EMPTY_SET;
0577: }
0578:
0579: /**
0580: * Comments on or information about this object, including data source information.
0581: */
0582: public InternationalString getRemarks() {
0583: return remarks;
0584: }
0585:
0586: /**
0587: * Returns the informations provided in the specified indentified object as a map of
0588: * properties. The returned map contains key such as {@link #NAME_KEY NAME_KEY}, and
0589: * values from methods such as {@link #getName}.
0590: *
0591: * @param info The identified object to view as a properties map.
0592: * @return An view of the identified object as an immutable map.
0593: */
0594: public static Map getProperties(final IdentifiedObject info) {
0595: return new Properties(info);
0596: }
0597:
0598: /**
0599: * Returns the properties to be given to an identified object derived from the specified one.
0600: * This method is typically used for creating a new CRS identical to an existing one except
0601: * for axis units. This method returns the same properties than the supplied argument (as of
0602: * <code>{@linkplain #getProperties(IdentifiedObject) getProperties}(info)</code>), except for
0603: * the following:
0604: * <p>
0605: * <ul>
0606: * <li>The {@linkplain #getName() name}'s authority is replaced by the specified one.</li>
0607: * <li>All {@linkplain #getIdentifiers identifiers} are removed, because the new object
0608: * to be created is probably not endorsed by the original authority.</li>
0609: * </ul>
0610: * <p>
0611: * This method returns a mutable map. Consequently, callers can add their own identifiers
0612: * directly to this map if they wish.
0613: *
0614: * @param info The identified object to view as a properties map.
0615: * @param authority The new authority for the object to be created, or {@code null} if it
0616: * is not going to have any declared authority.
0617: * @return An view of the identified object as a mutable map.
0618: */
0619: public static Map getProperties(final IdentifiedObject info,
0620: final Citation authority) {
0621: final Map properties = new HashMap(getProperties(info));
0622: properties.put(NAME_KEY, new NamedIdentifier(authority, info
0623: .getName().getCode()));
0624: properties.remove(IDENTIFIERS_KEY);
0625: return properties;
0626: }
0627:
0628: /**
0629: * Returns an identifier according the given authority. This method first checks all
0630: * {@linkplain #getIdentifiers identifiers} in their iteration order. It returns the first
0631: * identifier with an {@linkplain ReferenceIdentifier#getAuthority authority} citation
0632: * {@linkplain Citations#identifierMatches(Citation,Citation) matching} the specified
0633: * authority.
0634: *
0635: * @param authority The authority for the identifier to return, or {@code null} for
0636: * the first identifier regarless its authority.
0637: * @return The object's identifier, or {@code null} if no identifier matching the specified
0638: * authority was found.
0639: *
0640: * @since 2.2
0641: */
0642: public ReferenceIdentifier getIdentifier(final Citation authority) {
0643: return getIdentifier0(this , authority);
0644: }
0645:
0646: /**
0647: * Returns an identifier according the given authority. This method performs the same search
0648: * than {@link #getIdentifier(Citation)} on arbitrary implementations of GeoAPI interface.
0649: *
0650: * @param info The object to get the identifier from.
0651: * @param authority The authority for the identifier to return, or {@code null} for
0652: * the first identifier regarless its authority.
0653: * @return The object's identifier, or {@code null} if no identifier matching the specified
0654: * authority was found.
0655: *
0656: * @since 2.2
0657: */
0658: public static ReferenceIdentifier getIdentifier(
0659: final IdentifiedObject info, final Citation authority) {
0660: if (info instanceof AbstractIdentifiedObject) {
0661: // Gives a chances to subclasses to get their overridden method invoked.
0662: return ((AbstractIdentifiedObject) info)
0663: .getIdentifier(authority);
0664: }
0665: return getIdentifier0(info, authority);
0666: }
0667:
0668: /**
0669: * Implementation of {@link #getIdentifier(Citation)}.
0670: */
0671: private static ReferenceIdentifier getIdentifier0(
0672: final IdentifiedObject info, final Citation authority) {
0673: if (info == null) {
0674: return null;
0675: }
0676: for (final Iterator it = info.getIdentifiers().iterator(); it
0677: .hasNext();) {
0678: final Identifier candidate = (Identifier) it.next();
0679: if (candidate instanceof ReferenceIdentifier) {
0680: final ReferenceIdentifier identifier = (ReferenceIdentifier) candidate;
0681: if (authority == null) {
0682: return identifier;
0683: }
0684: final Citation infoAuthority = identifier
0685: .getAuthority();
0686: if (infoAuthority != null) {
0687: if (Citations.identifierMatches(authority,
0688: infoAuthority)) {
0689: return identifier;
0690: }
0691: }
0692: }
0693: }
0694: return (authority == null) ? info.getName() : null;
0695: }
0696:
0697: /**
0698: * Returns this object's name according the given authority. This method checks first the
0699: * {@linkplain #getName() primary name}, then all {@linkplain #getAlias() alias} in their
0700: * iteration order.
0701: *
0702: * <ul>
0703: * <li><p>If the name or alias implements the {@link ReferenceIdentifier} interface,
0704: * then this method compares the {@linkplain ReferenceIdentifier#getAuthority
0705: * identifier authority} against the specified citation using the
0706: * {@link Citations#identifierMatches(Citation,Citation) identifierMatches}
0707: * method. If a matching is found, then this method returns the
0708: * {@linkplain ReferenceIdentifier#getCode identifier code} of this object.</p></li>
0709: *
0710: * <li><p>Otherwise, if the alias implements the {@link GenericName} interface, then this
0711: * method compares the {@linkplain GenericName#getScope name scope} against the specified
0712: * citation using the {@linkplain Citations#identifierMatches(Citation,String)
0713: * identifierMatches} method. If a matching is found, then this method returns the
0714: * {@linkplain GenericName#asLocalName local name} of this object.</p></li>
0715: * </ul>
0716: *
0717: * Note that alias may implement both the {@link ReferenceIdentifier} and {@link GenericName}
0718: * interfaces (for example {@link NamedIdentifier}). In such cases, the identifier view has
0719: * precedence.
0720: *
0721: * @param authority The authority for the name to return.
0722: * @return The object's name (either a {@linkplain ReferenceIdentifier#getCode code}
0723: * or a {@linkplain GenericName#asLocalName local name}), or {@code null} if
0724: * no name matching the specified authority was found.
0725: *
0726: * @see #getName()
0727: * @see #getAlias()
0728: *
0729: * @since 2.2
0730: */
0731: public String getName(final Citation authority) {
0732: return getName0(this , authority);
0733: }
0734:
0735: /**
0736: * Returns an object's name according the given authority. This method performs the same search
0737: * than {@link #getName(Citation)} on arbitrary implementations of GeoAPI interface.
0738: *
0739: * @param info The object to get the name from.
0740: * @param authority The authority for the name to return.
0741: * @return The object's name (either a {@linkplain ReferenceIdentifier#getCode code}
0742: * or a {@linkplain GenericName#asLocalName local name}), or {@code null} if
0743: * no name matching the specified authority was found.
0744: *
0745: * @since 2.2
0746: */
0747: public static String getName(final IdentifiedObject info,
0748: final Citation authority) {
0749: if (info instanceof AbstractIdentifiedObject) {
0750: // Gives a chances to subclasses to get their overridden method invoked.
0751: return ((AbstractIdentifiedObject) info).getName(authority);
0752: }
0753: return getName0(info, authority);
0754: }
0755:
0756: /**
0757: * Implementation of {@link #getName(Citation)}.
0758: */
0759: private static String getName0(final IdentifiedObject info,
0760: final Citation authority) {
0761: Identifier identifier = info.getName();
0762: if (authority == null) {
0763: return identifier.getCode();
0764: }
0765: String name = null;
0766: Citation infoAuthority = identifier.getAuthority();
0767: if (infoAuthority != null) {
0768: if (Citations.identifierMatches(authority, infoAuthority)) {
0769: name = identifier.getCode();
0770: } else {
0771: for (final Iterator it = info.getAlias().iterator(); it
0772: .hasNext();) {
0773: final GenericName alias = (GenericName) it.next();
0774: if (alias instanceof Identifier) {
0775: identifier = (Identifier) alias;
0776: infoAuthority = identifier.getAuthority();
0777: if (infoAuthority != null) {
0778: if (Citations.identifierMatches(authority,
0779: infoAuthority)) {
0780: name = identifier.getCode();
0781: break;
0782: }
0783: }
0784: } else {
0785: final GenericName scope = alias.getScope();
0786: if (scope != null) {
0787: if (Citations.identifierMatches(authority,
0788: scope.toString())) {
0789: name = alias.asLocalName().toString();
0790: break;
0791: }
0792: }
0793: }
0794: }
0795: }
0796: }
0797: return name;
0798: }
0799:
0800: /**
0801: * Returns {@code true} if either the {@linkplain #getName() primary name} or at least
0802: * one {@linkplain #getAlias alias} matches the specified string. This method performs
0803: * the search in the following order, regardless of any authority:
0804: * <ul>
0805: * <li>The {@linkplain #getName() primary name} of this object</li>
0806: * <li>The {@linkplain ScopedName fully qualified name} of an alias</li>
0807: * <li>The {@linkplain LocalName local name} of an alias</li>
0808: * </ul>
0809: *
0810: * @param name The name to compare.
0811: * @return {@code true} if the primary name of at least one alias
0812: * matches the specified {@code name}.
0813: */
0814: public boolean nameMatches(final String name) {
0815: return nameMatches(this , alias, name);
0816: }
0817:
0818: /**
0819: * Returns {@code true} if either the {@linkplain #getName() primary name} or at least
0820: * one {@linkplain #getAlias alias} matches the specified string. This method performs the
0821: * same check than the {@linkplain #nameMatches(String) non-static method} on arbitrary
0822: * object implementing the GeoAPI interface.
0823: *
0824: * @param object The object to check.
0825: * @param name The name.
0826: * @return {@code true} if the primary name of at least one alias
0827: * matches the specified {@code name}.
0828: */
0829: public static boolean nameMatches(final IdentifiedObject object,
0830: final String name) {
0831: if (object instanceof AbstractIdentifiedObject) {
0832: return ((AbstractIdentifiedObject) object)
0833: .nameMatches(name);
0834: } else {
0835: return nameMatches(object, object.getAlias(), name);
0836: }
0837: }
0838:
0839: /**
0840: * Returns {@code true} if the {@linkplain #getName() primary name} of an object matches
0841: * the primary name of one {@linkplain #getAlias alias} of the other object.
0842: *
0843: * @param o1 The first object to compare by name.
0844: * @param o2 The second object to compare by name.
0845: *
0846: * @since 2.4
0847: */
0848: public static boolean nameMatches(final IdentifiedObject o1,
0849: final IdentifiedObject o2) {
0850: return nameMatches(o1, o2.getName().getCode())
0851: || nameMatches(o2, o1.getName().getCode());
0852: }
0853:
0854: /**
0855: * Implementation of {@code nameMatches} method.
0856: *
0857: * @param object The object to check.
0858: * @param alias The list of alias in {@code object} (may be {@code null}).
0859: * This method will never modify this list. Consequently, it may be a
0860: * direct reference to an internal array.
0861: * @param name The name.
0862: * @return {@code true} if the primary name of at least one alias
0863: * matches the specified {@code name}.
0864: */
0865: private static boolean nameMatches(final IdentifiedObject object,
0866: final Collection/*<GenericName>*/alias, String name) {
0867: name = name.trim();
0868: if (name.equalsIgnoreCase(object.getName().getCode().trim())) {
0869: return true;
0870: }
0871: if (alias != null) {
0872: for (final Iterator it = alias.iterator(); it.hasNext();) {
0873: final GenericName asName = (GenericName) it.next();
0874: final ScopedName asScoped = asName.asScopedName();
0875: if (asScoped != null
0876: && name.equalsIgnoreCase(asScoped.toString()
0877: .trim())) {
0878: return true;
0879: }
0880: if (name.equalsIgnoreCase(asName.asLocalName()
0881: .toString().trim())) {
0882: return true;
0883: }
0884: }
0885: }
0886: return false;
0887: }
0888:
0889: /**
0890: * Compares the specified object with this object for equality.
0891: *
0892: * @param object The other object (may be {@code null}).
0893: * @return {@code true} if both objects are equal.
0894: */
0895: public final boolean equals(final Object object) {
0896: return (object instanceof AbstractIdentifiedObject)
0897: && equals((AbstractIdentifiedObject) object, true);
0898: }
0899:
0900: /**
0901: * Compares this object with the specified object for equality.
0902: *
0903: * If {@code compareMetadata} is {@code true}, then all available properties are
0904: * compared including {@linkplain #getName() name}, {@linkplain #getRemarks remarks},
0905: * {@linkplain #getIdentifiers identifiers code}, etc.
0906: *
0907: * If {@code compareMetadata} is {@code false}, then this method compare
0908: * only the properties needed for computing transformations. In other words,
0909: * {@code sourceCS.equals(targetCS, false)} returns {@code true} only if
0910: * the transformation from {@code sourceCS} to {@code targetCS} is
0911: * the identity transform, no matter what {@link #getName()} saids.
0912: * <P>
0913: * Some subclasses (especially {@link org.geotools.referencing.datum.AbstractDatum}
0914: * and {@link org.geotools.parameter.AbstractParameterDescriptor}) will test for the
0915: * {@linkplain #getName() name}, since objects with different name have completly
0916: * different meaning. For example nothing differentiate the {@code "semi_major"} and
0917: * {@code "semi_minor"} parameters except the name. The name comparaison may be loose
0918: * however, i.e. we may accept a name matching an alias.
0919: *
0920: * @param object The object to compare to {@code this}.
0921: * @param compareMetadata {@code true} for performing a strict comparaison, or
0922: * {@code false} for comparing only properties relevant to transformations.
0923: * @return {@code true} if both objects are equal.
0924: */
0925: public boolean equals(final AbstractIdentifiedObject object,
0926: final boolean compareMetadata) {
0927: if (object != null && object.getClass().equals(getClass())) {
0928: if (!compareMetadata) {
0929: return true;
0930: }
0931: return Utilities.equals(name, object.name)
0932: && Utilities.equals(alias, object.alias)
0933: && Utilities
0934: .equals(identifiers, object.identifiers)
0935: && Utilities.equals(remarks, object.remarks);
0936: }
0937: return false;
0938: }
0939:
0940: /**
0941: * Compares two Geotools's {@code AbstractIdentifiedObject} objects for equality. This
0942: * method is equivalent to {@code object1.<b>equals</b>(object2, <var>compareMetadata</var>)}
0943: * except that one or both arguments may be null. This convenience method is provided for
0944: * implementation of {@code equals} in subclasses.
0945: *
0946: * @param object1 The first object to compare (may be {@code null}).
0947: * @param object2 The second object to compare (may be {@code null}).
0948: * @param compareMetadata {@code true} for performing a strict comparaison, or
0949: * {@code false} for comparing only properties relevant to transformations.
0950: * @return {@code true} if both objects are equal.
0951: */
0952: static boolean equals(final AbstractIdentifiedObject object1,
0953: final AbstractIdentifiedObject object2,
0954: final boolean compareMetadata) {
0955: return (object1 == object2)
0956: || (object1 != null && object1.equals(object2,
0957: compareMetadata));
0958: }
0959:
0960: /**
0961: * Compares two OpenGIS's {@code IdentifiedObject} objects for equality. This convenience
0962: * method is provided for implementation of {@code equals} in subclasses.
0963: *
0964: * @param object1 The first object to compare (may be {@code null}).
0965: * @param object2 The second object to compare (may be {@code null}).
0966: * @param compareMetadata {@code true} for performing a strict comparaison, or
0967: * {@code false} for comparing only properties relevant to transformations.
0968: * @return {@code true} if both objects are equal.
0969: */
0970: protected static boolean equals(final IdentifiedObject object1,
0971: final IdentifiedObject object2,
0972: final boolean compareMetadata) {
0973: if (!(object1 instanceof AbstractIdentifiedObject))
0974: return Utilities.equals(object1, object2);
0975: if (!(object2 instanceof AbstractIdentifiedObject))
0976: return Utilities.equals(object2, object1);
0977: return equals((AbstractIdentifiedObject) object1,
0978: (AbstractIdentifiedObject) object2, compareMetadata);
0979: }
0980:
0981: /**
0982: * Compares two arrays of OpenGIS's {@code IdentifiedObject} objects for equality. This
0983: * convenience method is provided for implementation of {@code equals} method in subclasses.
0984: *
0985: * @param array1 The first array to compare (may be {@code null}).
0986: * @param array2 The second array to compare (may be {@code null}).
0987: * @param compareMetadata {@code true} for performing a strict comparaison, or
0988: * {@code false} for comparing only properties relevant to transformations.
0989: * @return {@code true} if both arrays are equal.
0990: */
0991: protected static boolean equals(final IdentifiedObject[] array1,
0992: final IdentifiedObject[] array2,
0993: final boolean compareMetadata) {
0994: if (array1 != array2) {
0995: if (array1 == null || array2 == null
0996: || array1.length != array2.length) {
0997: return false;
0998: }
0999: for (int i = array1.length; --i >= 0;) {
1000: if (!equals(array1[i], array2[i], compareMetadata)) {
1001: return false;
1002: }
1003: }
1004: }
1005: return true;
1006: }
1007:
1008: /**
1009: * Compares two collectionss of OpenGIS's {@code IdentifiedObject} objects for equality.
1010: * The comparaison take order in account, which make it more appropriate for {@link List}
1011: * or {@link LinkedHashSet} comparaisons. This convenience method is provided for
1012: * implementation of {@code equals} method in subclasses.
1013: *
1014: * @param collection1 The first collection to compare (may be {@code null}).
1015: * @param collection2 The second collection to compare (may be {@code null}).
1016: * @param compareMetadata {@code true} for performing a strict comparaison, or
1017: * {@code false} for comparing only properties relevant to transformations.
1018: * @return {@code true} if both collections are equal.
1019: */
1020: protected static boolean equals(
1021: final Collection/*<? extends IdentifiedObject>*/collection1,
1022: final Collection/*<? extends IdentifiedObject>*/collection2,
1023: final boolean compareMetadata) {
1024: if (collection1 == collection2) {
1025: return true;
1026: }
1027: if (collection1 == null || collection2 == null) {
1028: return false;
1029: }
1030: final Iterator it1 = collection1.iterator();
1031: final Iterator it2 = collection2.iterator();
1032: while (it1.hasNext()) {
1033: if (!it2.hasNext()
1034: || !equals((IdentifiedObject) it1.next(),
1035: (IdentifiedObject) it2.next(),
1036: compareMetadata)) {
1037: return false;
1038: }
1039: }
1040: return !it2.hasNext();
1041: }
1042:
1043: /**
1044: * Compares two objects for order. Any object may be null. This method is
1045: * used for implementation of {@link #NAME_COMPARATOR} and its friends.
1046: */
1047: private static int doCompare(final Comparable c1,
1048: final Comparable c2) {
1049: if (c1 == null) {
1050: return (c2 == null) ? 0 : -1;
1051: }
1052: if (c2 == null) {
1053: return +1;
1054: }
1055: return c1.compareTo(c2);
1056: }
1057:
1058: /**
1059: * Returns a hash value for this identified object. {@linkplain #getName() Name},
1060: * {@linkplain #getIdentifiers identifiers} and {@linkplain #getRemarks remarks}
1061: * are not taken in account. In other words, two identified objects will return
1062: * the same hash value if they are equal in the sense of <code>{@linkplain
1063: * #equals(AbstractIdentifiedObject,boolean) equals}(AbstractIdentifiedObject,
1064: * <strong>false</strong>)</code>.
1065: *
1066: * @return The hash code value. This value doesn't need to be the same
1067: * in past or future versions of this class.
1068: */
1069: public int hashCode() {
1070: // Subclasses need to overrides this!!!!
1071: return (int) serialVersionUID ^ getClass().hashCode();
1072: }
1073:
1074: /**
1075: * Returns the specified array as an immutable set, or {@code null} if the
1076: * array is empty or null. This is a convenience method for sub-classes
1077: * constructors.
1078: *
1079: * @param array The array to copy in a set. May be {@code null}.
1080: * @return A set containing the array elements, or {@code null} if none or empty.
1081: */
1082: protected static Set asSet(final Object[] array) {
1083: if (array == null) {
1084: return null;
1085: }
1086: switch (array.length) {
1087: case 0:
1088: return null;
1089: case 1:
1090: return Collections.singleton(array[0]);
1091: default:
1092: return Collections.unmodifiableSet(new LinkedHashSet(Arrays
1093: .asList(array)));
1094: }
1095:
1096: }
1097:
1098: /**
1099: * Makes sure that an argument is non-null. This is a
1100: * convenience method for subclass constructors.
1101: *
1102: * @param name Argument name.
1103: * @param object User argument.
1104: * @throws InvalidParameterValueException if {@code object} is null.
1105: */
1106: protected static void ensureNonNull(final String name,
1107: final Object object) throws IllegalArgumentException {
1108: if (object == null) {
1109: throw new InvalidParameterValueException(Errors.format(
1110: ErrorKeys.NULL_ARGUMENT_$1, name), name, object);
1111: }
1112: }
1113:
1114: /**
1115: * Makes sure an array element is non-null. This is
1116: * a convenience method for subclass constructors.
1117: *
1118: * @param name Argument name.
1119: * @param array User argument.
1120: * @param index Index of the element to check.
1121: * @throws InvalidParameterValueException if {@code array[i]} is null.
1122: */
1123: protected static void ensureNonNull(final String name,
1124: final Object[] array, final int index)
1125: throws IllegalArgumentException {
1126: if (array[index] == null) {
1127: throw new InvalidParameterValueException(Errors.format(
1128: ErrorKeys.NULL_ARGUMENT_$1, name + '[' + index
1129: + ']'), name, array);
1130: }
1131: }
1132:
1133: /**
1134: * Makes sure that the specified unit is a temporal one.
1135: * This is a convenience method for subclass constructors.
1136: *
1137: * @param unit Unit to check.
1138: * @throws IllegalArgumentException if {@code unit} is not a temporal unit.
1139: */
1140: protected static void ensureTimeUnit(final Unit unit)
1141: throws IllegalArgumentException {
1142: if (!SI.SECOND.isCompatible(unit)) {
1143: throw new IllegalArgumentException(Errors.format(
1144: ErrorKeys.NON_TEMPORAL_UNIT_$1, unit));
1145: }
1146: }
1147:
1148: /**
1149: * Makes sure that the specified unit is a linear one.
1150: * This is a convenience method for subclass constructors.
1151: *
1152: * @param unit Unit to check.
1153: * @throws IllegalArgumentException if {@code unit} is not a linear unit.
1154: */
1155: protected static void ensureLinearUnit(final Unit unit)
1156: throws IllegalArgumentException {
1157: if (!SI.METER.isCompatible(unit)) {
1158: throw new IllegalArgumentException(Errors.format(
1159: ErrorKeys.NON_LINEAR_UNIT_$1, unit));
1160: }
1161: }
1162:
1163: /**
1164: * Makes sure that the specified unit is an angular one.
1165: * This is a convenience method for subclass constructors.
1166: *
1167: * @param unit Unit to check.
1168: * @throws IllegalArgumentException if {@code unit} is not an angular unit.
1169: */
1170: protected static void ensureAngularUnit(final Unit unit)
1171: throws IllegalArgumentException {
1172: if (!SI.RADIAN.isCompatible(unit) && !Unit.ONE.equals(unit)) {
1173: throw new IllegalArgumentException(Errors.format(
1174: ErrorKeys.NON_ANGULAR_UNIT_$1, unit));
1175: }
1176: }
1177: }
|