/*
* 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.util.Collections;
import java.util.List;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.util.NameSpace;
import org.opengis.util.ScopedName;
/**
* Identifier within a name space for a local object. This could be the target object of the
* {@link GenericName}, or a pointer to another name space (with a new {@link GenericName})
* one step closer to the target of the identifier.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see NameFactory
*/
public class LocalName extends org.geotools.util.GenericName implements org.opengis.util.LocalName {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -5627125375582385822L;
/**
* The view of this object as a scoped name.
*/
private final ScopedName asScopedName;
/**
* The name, either as a {@link String} or an {@link InternationalString}.
*/
private final CharSequence name;
/**
* The name as a string.
* If not provided, will be built only when first needed.
*/
private transient String asString;
/**
* The name as an international string.
* If not provided, will be built only when first needed.
*/
private transient InternationalString asInternationalString;
/**
* The sequence of local name for this {@linkplain GenericName generic name}.
* Since this object is itself a locale name, this list is always a singleton
* containing only {@code this}. It will be built only when first needed.
*/
private transient List<org.opengis.util.LocalName> parsedNames;
/**
* Constructs a local name from the specified string with no scope.
* If the specified name is an {@link InternationalString}, then the
* <code>{@linkplain InternationalString#toString(java.util.Locale) toString}(null)</code>
* method will be used in order to fetch an unlocalized name. Otherwise, the
* <code>{@linkplain CharSequence#toString toString}()</code> method will be used.
*
* @param name The local name (never {@code null}).
*/
public LocalName(final CharSequence name) {
this(null, name);
}
/**
* Constructs a local name from the specified international string.
*
* This constructor is not public since it can't be used from outside
* of {@link org.geotools.util.ScopedName} constructor (otherwise some
* methods in this class may have the wrong semantic).
*
* @param asScopedName The view of this object as a scoped name.
* @param name The local name (never {@code null}).
*/
LocalName(final ScopedName asScopedName, final CharSequence name) {
this.asScopedName = asScopedName;
this.name = validate(name);
AbstractInternationalString.ensureNonNull("name", name);
}
/**
* Returns the scope (name space) of this generic name.
* This method is protected from overriding by the user.
*/
private GenericName getInternalScope() {
if (asScopedName != null) {
final NameSpace scope = asScopedName.scope();
if (scope != null) {
return scope.name();
}
}
return null;
}
/**
* Returns the scope (name space) of this generic name.
*
* @deprecated Replaced by {@link #scope}.
*/
@Deprecated
public GenericName getScope() {
return getInternalScope();
}
/**
* 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"}.
*
* @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() {
return (asScopedName!=null) ? asScopedName.scope() : super.scope();
}
/**
* Returns the depth, which is always 1 for a local name.
*
* @since 2.3
*/
public int depth() {
return 1;
}
/**
* Returns the sequence of local name for this {@linkplain GenericName generic name}.
* Since this object is itself a locale name, this method always returns a singleton
* containing only {@code this}.
*/
public List<org.opengis.util.LocalName> getParsedNames() {
// No need to sychronize: it is not a big deal if this object is built twice.
if (parsedNames == null) {
parsedNames = Collections.singletonList((org.opengis.util.LocalName) this);
}
return parsedNames;
}
/**
* Since this object is already a local name, this method always returns {@code this}.
*/
@Override
public org.opengis.util.LocalName head() {
return this;
}
/**
* Since this object is already a local name, this method always returns {@code this}.
*/
@Override
public org.opengis.util.LocalName tip() {
return this;
}
/**
* Returns a view of this object as a scoped name,
* or {@code null} if this name has no scope.
*
* @deprecated Replaced by {@link #toFullyQualifiedName}.
*/
@Deprecated
public ScopedName asScopedName() {
return asScopedName;
}
/**
* Returns a view of this name as a fully-qualified name. The {@linkplain #scope scope}
* of a fully qualified name must be {@linkplain NameSpace#isGlobal global}. This method
* never returns {@code null}.
*
* @since 2.3
*/
public GenericName toFullyQualifiedName() {
if (asScopedName == null) {
return this;
}
return asScopedName;
}
/**
* Returns this name expanded with the specified scope. One may represent this operation
* as a concatenation of the specified {@code name} with {@code this}. In pseudo-code,
* the following relationships must hold:
* <p>
* <ul>
* <li><code>push(<var>name</var>).getParsedList() ==
* <var>name</var>.getParsedList().addAll({@linkplain #getParsedNames()})</code></li>
* <li><code>push(<var>name</var>).scope() == <var>name</var>.{@linkplain #scope()}</code></li>
* </ul>
* <p>
* <strong>Note:</strong> Those conditions can be understood in terms of the Java
* {@link Object#equals equals} method instead of the Java identity comparator {@code ==}.
*
* @since 2.3
*
* @todo Not yet implemented.
*/
public ScopedName push(GenericName scope) {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns a locale-independant string representation of this local name.
* This string do not includes the scope, which is consistent with the
* {@linkplain #getParsedNames parsed names} definition.
*/
@Override
public String toString() {
if (asString == null) {
if (name instanceof InternationalString) {
// We really want the 'null' locale, not the system default one.
asString = ((InternationalString) name).toString(null);
} else {
asString = name.toString();
}
}
return asString;
}
/**
* Returns a local-dependent string representation of this locale name.
*/
@Override
public InternationalString toInternationalString() {
if (asInternationalString == null) {
if (name instanceof InternationalString) {
asInternationalString = (InternationalString) name;
} else {
asInternationalString = new SimpleInternationalString(name.toString());
}
}
return asInternationalString;
}
/**
* 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 case-insensitive.
*/
@Override
public int compareTo(final GenericName object) {
return toString().compareToIgnoreCase(object.toString());
}
/**
* Compares this local name with the specified object for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object!=null && object.getClass().equals(getClass())) {
final LocalName that = (LocalName) object;
// Do not use 'asScopedName' in order to avoid never-ending loop.
return Utilities.equals(this.getInternalScope(), that.getInternalScope()) &&
Utilities.equals(this.name, that.name);
}
return false;
}
/**
* Returns a hash code value for this local name.
*/
@Override
public int hashCode() {
int code = (int)serialVersionUID;
// Do not use 'asScopedName' in order to avoid never-ending loop.
if (name != null) code ^= name.hashCode();
return code;
}
}