/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.util; import java.io.Serializable; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; import org.opengis.util.NameSpace; import org.opengis.util.LocalName; import org.opengis.util.ScopedName; // For javadoc import org.opengis.util.InternationalString; /** * Base class for {@linkplain ScopedName generic scoped} and * {@linkplain LocalName local name} structure for type and attribute * name in the context of name spaces. * <p> * <b>Note:</b> this class has a natural ordering that is inconsistent with {@link #equals equals}. * The natural ordering may be case-insensitive and ignores the {@linkplain #DEFAULT_SEPARATOR * character separator} between name elements. * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see NameFactory */ public abstract class GenericName implements org.opengis.util.GenericName, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 8685047583179337259L; /** * The default separator character. */ public static final char DEFAULT_SEPARATOR = ':'; /** * The name space, created on the fly when needed. This is a temporary * approach until we replace this class by a better implementation. */ private transient NameSpace namespace; /** * Creates a new instance of generic name. */ protected GenericName() { } /** * Ensures that the given name is a {@link String} or an {@link InternationalString}. * This is used for subclass constructors. */ static CharSequence validate(final CharSequence name) { return (name==null || name instanceof InternationalString) ? name : name.toString(); } /** * Returns the scope (name space) in which this name is local. The scope is set on creation * and is not modifiable. The scope of a name determines where a name "starts". For instance, * if a name has a {@linkplain #depth depth} of two ({@code "util.GenericName"}) and is * associated with a {@linkplain NameSpace name space} having the name {@code "org.opengis"}, * then the fully qualified name would be {@code "org.opengis.util.GenericName"}. * * @return The name space. * * @since 2.3 * * @todo To be strict, maybe we should returns {@code null} if there is no namespace. * Current implementation returns a namespace instance whith a null name. This * behavior is for transition from legacy API to later ISO 19103 revision and * may change in future GeoTools version. */ public NameSpace scope() { if (namespace == null) { namespace = new NameSpace() { public boolean isGlobal() { return false; } public org.opengis.util.GenericName name() { return getScope(); } @Deprecated public Set<org.opengis.util.GenericName> getNames() { throw new UnsupportedOperationException(); } }; } return namespace; } /** * Returns the scope (name space) of this generic name. If this name has no scope * (e.g. is the root), then this method returns {@code null}. * * @return The name space of this name. * @deprecated Replaced by {@link #scope}. */ @Deprecated public abstract org.opengis.util.GenericName getScope(); /** * Returns the depth of this name within the namespace hierarchy. This indicates the number * of levels specified by this name. For any {@link LocalName}, it is always one. For a * {@link ScopedName} it is some number greater than or equal to 2. * <p> * The depth is the length of the list returned by the {@link #getParsedNames} method. * As such it is a derived parameter. * * @return The depth of this name. * * @since 2.3 */ public int depth() { return getParsedNames().size(); } /** * Returns the sequence of {@linkplain LocalName local names} making this generic name. * Each element in this list is like a directory name in a file path name. * The length of this sequence is the generic name depth. * * @return The sequence of local names. */ public abstract List<LocalName> getParsedNames(); /** * Returns the first element in the sequence of {@linkplain #getParsedNames parsed names}. * For any {@link LocalName}, this is always {@code this}. * * @return The first element of this name. * * @since 2.6 */ public LocalName head() { final List<? extends LocalName> names = getParsedNames(); return names.get(0); } /** * Returns the last element in the sequence of {@linkplain #getParsedNames parsed names}. * For any {@link LocalName}, this is always {@code this}. * * @return The last element of this name. * * @since 2.6 */ public LocalName tip() { final List<? extends LocalName> names = getParsedNames(); return names.get(names.size() - 1); } /** * Returns a view of this object as a local name. * * @return The local part of this name. * @deprecated Renamed as {@link #tip()}. */ @Deprecated public LocalName asLocalName() { return tip(); } /** * @deprecated Renamed as {@link #tip()}. * * @since 2.3 */ @Deprecated public LocalName name() { return tip(); } /** * Returns a view of this name as a fully-qualified name, or {@code null} if none. * The {@linkplain #scope scope} of a fully qualified name must be * {@linkplain NameSpace#isGlobal global}. * <p> * If this name is a {@linkplain LocalName local name} and the {@linkplain #scope scope} * is already {@linkplain NameSpace#isGlobal global}, returns {@code null} since it is not * possible to derive a scoped name. * * @return The fully-qualified name. * @deprecated Replaced by {@link #toFullyQualifiedName}. */ @Deprecated public abstract ScopedName asScopedName(); /** * Returns the separator character. Default to <code>':'</code>. * This method is overridden by {@link org.geotools.util.ScopedName}. */ char getSeparator() { return DEFAULT_SEPARATOR; } /** * Returns a string representation of this generic name. This string representation * is local-independant. It contains all elements listed by {@link #getParsedNames} * separated by an arbitrary character (usually {@code :} or {@code /}). * This rule implies that the {@code toString()} method for a * {@linkplain ScopedName scoped name} will contains the scope, while the * {@code toString()} method for the {@linkplain LocalName local version} of * the same name will not contains the scope. * * @return A string representation of this name. */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(); final List<? extends LocalName> parsedNames = getParsedNames(); final char separator = getSeparator(); for (final Iterator<? extends LocalName> it=parsedNames.iterator(); it.hasNext();) { if (buffer.length() != 0) { buffer.append(separator); } buffer.append(it.next()); } return buffer.toString(); } /** * Returns a local-dependent string representation of this generic name. This string * is similar to the one returned by {@link #toString} except that each element has * been localized in the {@linkplain InternationalString#toString(Locale) * specified locale}. If no international string is available, then this method should * returns an implementation mapping to {@link #toString} for all locales. * * @return A localizable string representation of this name. */ public InternationalString toInternationalString() { return new International(getParsedNames(), getSeparator()); } /** * An international string built from a snapshot of {@link GenericName}. * * @version $Id$ * @author Martin Desruisseaux (IRD) */ private static final class International extends AbstractInternationalString implements Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -4234089612436334148L; /** * The sequence of {@linkplain LocalName local names} making this generic name. * This is the value returned by {@link GenericName#getParsedNames}. */ private final List<? extends LocalName> parsedNames; /** * The separator character. This is the value returned by {@link GenericName#getSeparator}. */ private final char separator; /** * Constructs a new international string from the specified {@link GenericName} fields. * * @param parsedNames The value returned by {@link GenericName#getParsedNames}. * @param separator The value returned by {@link GenericName#getSeparator}. */ public International(final List<? extends LocalName> parsedNames, final char separator) { this.parsedNames = parsedNames; this.separator = separator; } /** * Returns a string representation for the specified locale. */ public String toString(final Locale locale) { final StringBuilder buffer = new StringBuilder(); for (final LocalName name : parsedNames) { if (buffer.length() != 0) { buffer.append(separator); } buffer.append(name.toInternationalString().toString(locale)); } return buffer.toString(); } /** * Compares this international string with the specified object for equality. */ @Override public boolean equals(final Object object) { if (object!=null && object.getClass().equals(getClass())) { final International that = (International) object; return Utilities.equals(this.parsedNames, that.parsedNames) && this.separator == that.separator; } return false; } /** * Returns a hash code value for this international text. */ @Override public int hashCode() { return (int)serialVersionUID ^ parsedNames.hashCode(); } } /** * Compares this name with the specified object for order. Returns a negative integer, * zero, or a positive integer as this name lexicographically precedes, is equals to, * or follows the specified object. The comparaison is performed in the following * order: * <ul> * <li>Compares each element in the {@linkplain #getParsedNames list of parsed names}. If an * element of this name lexicographically precedes or follows the corresponding element * of the specified name, returns a negative or a positive integer respectively.</li> * <li>If all elements in both names are lexicographically equal, then if this name has less * or more elements than the specified name, returns a negative or a positive integer * respectively.</li> * <li>Otherwise, returns 0.</li> * </ul> * * @param that The name to compare with this name. * @return -1 if this name precedes the given one, +1 if it follows, 0 if equals. */ public int compareTo(final org.opengis.util.GenericName that) { final Iterator<? extends LocalName> thisNames = this.getParsedNames().iterator(); final Iterator<? extends LocalName> thatNames = that.getParsedNames().iterator(); while (thisNames.hasNext()) { if (!thatNames.hasNext()) { return +1; } final LocalName thisNext = thisNames.next(); final LocalName thatNext = thatNames.next(); if (thisNext==this && thatNext==that) { // Never-ending loop: usually an implementation error throw new IllegalStateException(); } final int compare = thisNext.compareTo(thatNext); if (compare != 0) { return compare; } } return thatNames.hasNext() ? -1 : 0; } /** * Compares this generic name with the specified object for equality. * * @param object The object to compare with this name. * @return {@code true} if the given object is equals to this one. */ @Override public boolean equals(final Object object) { if (object!=null && object.getClass().equals(getClass())) { final GenericName that = (GenericName) object; return Utilities.equals(this.getParsedNames(), that.getParsedNames()) && this.getSeparator() == that.getSeparator(); } return false; } /** * Returns a hash code value for this generic name. */ @Override public int hashCode() { return (int)serialVersionUID ^ getParsedNames().hashCode(); } }