blob: b5e50f0f3ef5535c7923477120914b16cf5beac7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.internal.jaxb;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import java.util.Objects;
import java.io.Serializable;
import java.util.logging.Level;
import org.opengis.metadata.citation.Citation;
import org.apache.sis.xml.XLink;
import org.apache.sis.xml.IdentifierMap;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.xml.ValueConverter;
import org.apache.sis.util.resources.Messages;
import org.apache.sis.metadata.iso.citation.Citations;
// Branch-dependent imports
import org.opengis.referencing.ReferenceIdentifier;
/**
* Wraps a {@link XLink}, {@link URI} or {@link UUID} as an identifier in the {@link IdentifierMap}.
* The {@linkplain #authority} is typically an instance of {@link NonMarshalledAuthority}. The value
* is an object of a type constrained by the authority.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.7
*
* @param <T> the value type, typically {@link XLink}, {@link UUID} or {@link String}.
*
* @since 0.3
* @module
*/
public final class SpecializedIdentifier<T> implements ReferenceIdentifier, Cloneable, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -1699757455535495848L;
/**
* The authority, typically as a {@link NonMarshalledAuthority} instance.
* Null value is not recommended, but this {@code SpecializedIdentifier}
* is tolerant to such cases.
*
* @see #getAuthority()
*/
private final IdentifierSpace<T> authority;
/**
* The identifier value. The identifier {@linkplain #getCode() code} will be the
* {@linkplain Object#toString() string representation} of this value, if non-null.
*
* <p>This value is set at construction time, but may be modified later by
* {@link IdentifierMapAdapter#putSpecialized(IdentifierSpace, Object)}.</p>
*
* @see #getValue()
* @see #getCode()
*/
T value;
/**
* Creates a new adapter for the given authority and identifier value.
*
* @param authority the identifier authority.
* @param value the identifier value, or {@code null} if not yet defined.
*/
public SpecializedIdentifier(final IdentifierSpace<T> authority, final T value) {
this.authority = authority;
this.value = value;
}
/**
* Creates an identifier from a text value. This method creates an instance of
* {@code SpecializedIdentifier} if the given authority is one of the "special"
* authorities declared in the {@link IdentifierSpace} interface. Otherwise a
* plain {@link IdentifierMapEntry} is created.
*
* @param authority the authority, typically as one of the {@link IdentifierSpace} constants.
* @param code the identifier code to parse.
*
* @see IdentifierMapAdapter#put(Citation, String)
*/
static ReferenceIdentifier parse(final Citation authority, final String code) {
if (authority instanceof NonMarshalledAuthority) {
final int ordinal = ((NonMarshalledAuthority) authority).ordinal;
switch (ordinal) {
case NonMarshalledAuthority.ID: {
return new SpecializedIdentifier<>(IdentifierSpace.ID, code);
}
case NonMarshalledAuthority.UUID: {
final Context context = Context.current();
final ValueConverter converter = Context.converter(context);
try {
return new SpecializedIdentifier<>(IdentifierSpace.UUID, converter.toUUID(context, code));
} catch (IllegalArgumentException e) {
parseFailure(context, code, UUID.class, e);
break;
}
}
case NonMarshalledAuthority.HREF:
case NonMarshalledAuthority.XLINK: {
final Context context = Context.current();
final ValueConverter converter = Context.converter(context);
final URI href;
try {
href = converter.toURI(context, code);
} catch (URISyntaxException e) {
parseFailure(context, code, URI.class, e);
break;
}
if (ordinal == NonMarshalledAuthority.HREF) {
return new SpecializedIdentifier<>(IdentifierSpace.HREF, href);
}
final XLink xlink = new XLink();
xlink.setHRef(href);
return new SpecializedIdentifier<>(IdentifierSpace.XLINK, xlink);
}
}
}
return new IdentifierMapEntry(authority, code);
}
/**
* Invoked by {@link #parse(Citation,String)} when a string can not be parsed.
* This is considered a non-fatal error, because the parse method can fallback
* on the generic {@link IdentifierMapEntry} in such cases.
*
* <p>This method assumes that {@link IdentifierMap#put(Object, Object)} is
* the public API by which this method has been invoked.</p>
*
* @param context the marshalling context, or {@code null} if none.
* @param value the value that we failed to parse.
* @param type the target type of the parsing process.
* @param cause the exception that occurred during the parsing process.
*/
static void parseFailure(final Context context, final String value, final Class<?> type, final Exception cause) {
Context.warningOccured(context, Level.WARNING, IdentifierMap.class, "put", cause,
Messages.class, Messages.Keys.UnparsableValueStoredAsText_2, type, value);
}
/**
* Returns the authority specified at construction time.
*
* @return the identifier authority.
*/
@Override
public Citation getAuthority() {
return authority;
}
/**
* Returns the identifier value. This is the {@linkplain #getCode() code} expressed as
* an object more specialized than {@link String}.
*
* @return the identifier value, or {@code null} if none.
*/
public T getValue() {
return value;
}
/**
* Returns a string representation of the {@linkplain #getValue() identifier value},
* or {@code null} if none.
*
* @return the identifier value.
*/
@Override
public String getCode() {
final T value = this.value;
return (value != null) ? value.toString() : null;
}
/**
* Infers a code space from the authority.
*
* @return the code space, or {@code null} if none.
*
* @since 0.5
*/
@Override
public String getCodeSpace() {
return Citations.toCodeSpace(authority);
}
/**
* Returns {@code null} since this class does not hold version information.
*
* @return {@code null}.
*
* @since 0.5
*/
@Override
public String getVersion() {
return null;
}
/**
* Returns a hash code value for this identifier.
*/
@Override
public int hashCode() {
return Objects.hashCode(value) + 31 * Objects.hashCode(authority);
}
/**
* Compares this identifier with the given object for equality.
*
* @param other the object to compare with this identifier for equality.
*/
@Override
public boolean equals(final Object other) {
if (other instanceof SpecializedIdentifier<?>) {
final SpecializedIdentifier<?> that = (SpecializedIdentifier<?>) other;
return Objects.equals(authority, that.authority) &&
Objects.equals(value, that.value);
}
return false;
}
/**
* Returns a clone of this identifier.
*
* @return a shallow clone of this identifier.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // Should never happen, since we are cloneable.
}
}
/**
* Returns a string representation of this identifier.
* Example: {@code Identifier[gco:uuid=“42924124-032a-4dfe-b06e-113e3cb81cf0”]}.
*
* @see IdentifierMapAdapter#toString()
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(60).append("Identifier[");
format(buffer, authority, getCode());
return buffer.append(']').toString();
}
/**
* Formats the given (authority, code) par value in the given buffer.
*/
static void format(final StringBuilder buffer, final Citation authority, final String code) {
buffer.append(Citations.toCodeSpace(authority)).append('=');
final boolean quote = (code != null) && (code.indexOf('[') < 0);
if (quote) buffer.append('“');
buffer.append(code);
if (quote) buffer.append('”');
}
}