blob: 6acec0c2618166ecb2580800c54e48bfff31b944 [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.xml;
import java.net.URI;
import java.util.Locale;
import java.util.Objects;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlSchemaType;
import org.opengis.util.InternationalString;
import org.apache.sis.util.Classes;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.internal.system.Loggers;
import static java.util.logging.Logger.getLogger;
/**
* The XML attributes defined by OGC in the
* <a href="http://schemas.opengis.net/xlink/1.0.0/xlinks.xsd">xlink</a> schema.
*
* The allowed combinations of any one attribute depend on the value of the special
* {@link #getType() type} attribute. Following is a summary of the element types
* (columns) on which the global attributes (rows) are allowed, with an indication
* of whether a value is required (R) or optional (O)
* (Source: <a href="http://www.w3.org/TR/xlink/">W3C</a>):
*
* <table class="sis">
* <caption>XLink attribute usage patterns</caption>
* <tr>
* <th> </th>
* <th style="width: 14%">{@link XLink.Type#SIMPLE simple}</th>
* <th style="width: 14%">{@link XLink.Type#EXTENDED extended}</th>
* <th style="width: 14%">{@link XLink.Type#LOCATOR locator}</th>
* <th style="width: 14%">{@link XLink.Type#ARC arc}</th>
* <th style="width: 14%">{@link XLink.Type#RESOURCE resource}</th>
* <th style="width: 14%">{@link XLink.Type#TITLE title}</th>
* </tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getType() type}</b></td> <td>R</td><td>R</td><td>R</td><td>R</td><td>R</td><td>R</td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getHRef() href}</b></td> <td>O</td><td> </td><td>R</td><td> </td><td> </td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getRole() role}</b></td> <td>O</td><td>O</td><td>O</td><td> </td><td>O</td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getArcRole() arcrole}</b></td> <td>O</td><td> </td><td> </td><td>O</td><td> </td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getTitle() title}</b></td> <td>O</td><td>O</td><td>O</td><td>O</td><td>O</td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getShow() show}</b></td> <td>O</td><td> </td><td> </td><td>O</td><td> </td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getActuate() actuate}</b></td> <td>O</td><td> </td><td> </td><td>O</td><td> </td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getLabel() label}</b></td> <td> </td><td> </td><td>O</td><td> </td><td>O</td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getFrom() from}</b></td> <td> </td><td> </td><td> </td><td>O</td><td> </td><td> </td></tr>
* <tr style="text-align:center"><td style="text-align:left"><b>{@link #getTo() to}</b></td> <td> </td><td> </td><td> </td><td>O</td><td> </td><td> </td></tr>
* </table>
*
* When {@code xlink} attributes are found at unmarshalling time instead of an object definition,
* those attributes are given to the {@link ReferenceResolver#resolve(MarshalContext, Class, XLink)}
* method. Users can override that method in order to fetch an instance in some catalog for the given
* {@code xlink} values.
*
* @author Guilhem Legal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
*
* @see <a href="http://www.w3.org/TR/xlink/">XML Linking Language</a>
* @see <a href="http://schemas.opengis.net/xlink/1.0.0/xlinks.xsd">OGC schema</a>
*
* @since 0.3
* @module
*/
@XmlTransient
public class XLink implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 4046720871882443681L;
/**
* The type of link. If {@code null}, then the type will be inferred by {@link #getType()}.
*
* @see #getType()
*/
private Type type;
/**
* A URN to an external resources, or to an other part of a XML document, or an identifier.
*
* @see #getHRef()
* @category locator
*/
private URI href;
/**
* A URI reference for some description of the arc role.
*
* @see #getRole()
* @category semantic
*/
private URI role;
/**
* A URI reference for some description of the arc role.
*
* @see #getArcRole()
* @category semantic
*/
private URI arcrole;
/**
* Just as with resources, this is simply a human-readable string with a short description
* for the arc.
*
* @see #getTitle()
* @category semantic
*/
private InternationalString title;
/**
* Communicates the desired presentation of the ending resource on traversal
* from the starting resource.
*
* @see #getShow()
* @category behavior
*/
private Show show;
/**
* Communicates the desired timing of traversal from the starting resource to the ending resource.
*
* @see #getActuate()
* @category behavior
*/
private Actuate actuate;
/**
* Identifies the target of a {@code from} or {@code to} attribute.
*
* @see #getLabel()
* @category traversal
*/
private String label;
/**
* The starting resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @see #getFrom()
* @category traversal
*/
private String from;
/**
* The ending resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @see #getTo()
* @category traversal
*/
private String to;
/**
* The cached hash code value, computed only if this {@code XLink} is unmodifiable. Otherwise,
* this field is left to zero. This field is computed when the {@link #freeze()} method has
* been invoked.
*/
private int hashCode;
/**
* Creates a new link. The initial value of all attributes is {@code null}.
*/
public XLink() {
}
/**
* Creates a new link as a copy of the given link.
*
* @param link The link to copy, or {@code null} if none.
*/
public XLink(final XLink link) {
if (link != null) {
type = link.type;
href = link.href;
role = link.role;
arcrole = link.arcrole;
title = link.title;
show = link.show;
actuate = link.actuate;
label = link.label;
from = link.from;
to = link.to;
}
}
/**
* The type of a {@code xlink}. This type can be determined from the set of non-null
* attribute values in a {@link XLink} instance.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
* @since 0.3
* @module
*
* @see XLink#getType()
*/
@XmlEnum
public enum Type {
/**
* A simple link. Allows the {@link XLink#getHRef() href}, {@link XLink#getRole() role},
* {@link XLink#getArcRole() arcrole}, {@link #getTitle() title}, {@link XLink#getShow()
* show} and {@link XLink#getActuate() actuate} attributes, all of them being optional.
*/
@XmlEnumValue("simple")
SIMPLE(0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40, 0x1),
/**
* An extended, possibly multi-resource, link. Allows the {@link XLink#getRole() role}
* and {@link #getTitle() title} attributes, all of them being optional.
*/
@XmlEnumValue("extended")
EXTENDED(0x1 | 0x4 | 0x10, 0x1),
/**
* A pointer to an external resource. Allows the {@link XLink#getHRef() href},
* {@link XLink#getRole() role}, {@link #getTitle() title} and {@link XLink#getLabel()
* label} attributes, where {@code href} is mandatory and all other are optional.
*/
@XmlEnumValue("locator")
LOCATOR(0x1 | 0x2 | 0x4 | 0x10 | 0x80, 0x1 | 0x2),
/**
* An internal resource. Allows the {@link XLink#getRole() role}, {@link #getTitle() title}
* and {@link #getLabel() label} attributes, all of them being optional.
*/
@XmlEnumValue("resource")
RESOURCE(0x1 | 0x4 | 0x10 | 0x80, 0x1),
/**
* A traversal rule between resources. Allows the {@link XLink#getArcRole() arcrole},
* {@link #getTitle() title}, {@link XLink#getShow() show}, {@link XLink#getActuate()
* actuate} {@link #getFrom() from} and {@link #getTo() to} attributes, all of them
* being optional.
*/
@XmlEnumValue("arc")
ARC(0x1 | 0x8 | 0x10 | 0x20 | 0x40 | 0x100 | 0x200, 0x1),
/**
* A descriptive title for another linking element.
*/
@XmlEnumValue("title")
TITLE(0x1, 0x1),
/**
* A special value for computing the type automatically from the {@link XLink} attributes.
* After a call to {@code XLink.setType(AUTO)}, any call to {@code XLink.getType()} will
* infer the type from the non-null attributes as according the table documented in the
* {@link XLink} javadoc.
*/
AUTO(-1, 0);
/**
* A bitmask which specified the non-null fields expected for a given type.
* The bit values are:
*
* <ul>
* <li>{@code type}: 0x1</li>
* <li>{@code href}: 0x2</li>
* <li>{@code role}: 0x4</li>
* <li>{@code arcrole}: 0x8</li>
* <li>{@code title}: 0x10</li>
* <li>{@code show}: 0x20</li>
* <li>{@code actuate}: 0x40</li>
* <li>{@code label}: 0x80</li>
* <li>{@code from}: 0x100</li>
* <li>{@code to}: 0x200</li>
* </ul>
*/
final int fieldMask, mandatory;
/**
* Creates a new type which allows the fields specified by the given mask.
*/
private Type(final int mask, final int mandatory) {
this.fieldMask = mask;
this.mandatory = mandatory;
}
/**
* Returns the attribute name for this type.
*/
final String identifier() {
return name().toLowerCase(Locale.ROOT);
}
}
/**
* Returns a mask of fields for which a non-null value has been defined.
* The bit values are defined in the {@link XLink.Type#fieldMask} javadoc.
*/
private int fieldMask() {
int mask = 0;
if (type != null) mask |= 0x1;
if (href != null) mask |= 0x2;
if (role != null) mask |= 0x4;
if (arcrole != null) mask |= 0x8;
if (title != null) mask |= 0x10;
if (show != null) mask |= 0x20;
if (actuate != null) mask |= 0x40;
if (label != null) mask |= 0x80;
if (from != null) mask |= 0x100;
if (to != null) mask |= 0x200;
return mask;
}
/**
* Returns the type of link. May have one of the following values:
*
* <ul>
* <li><b>simple:</b> a simple link</li>
* <li><b>extended:</b> an extended, possibly multi-resource, link</li>
* <li><b>locator:</b> a pointer to an external resource</li>
* <li><b>resource:</b> an internal resource</li>
* <li><b>arc:</b> a traversal rule between resources</li>
* <li><b>title:</b> a descriptive title for another linking element</li>
* </ul>
*
* The default value is {@code null}. If the {@link #setType(XLink.Type)} method has been
* invoked with the {@link org.apache.sis.xml.XLink.Type#AUTO AUTO} enum, then this method
* will infer a type from the attributes having a non-null value.
*
* @return the type of link, or {@code null}.
*/
@XmlAttribute(name = "type", namespace = Namespaces.XLINK, required = true)
public Type getType() {
if (type != Type.AUTO) {
return type;
}
Type best = null;
int min = Integer.SIZE;
final int defined = fieldMask();
final int undefined = ~(defined | 0x1);
for (final Type candidate : Type.values()) {
final int forbidden = ~candidate.fieldMask;
if (forbidden == 0) {
continue; // Skip the AUTO enum.
}
// Test if this XLink instance defines only values allowed by the candidate type.
if ((defined & forbidden) != 0) {
continue;
}
// Test if this XLink instance defines all mandatory fields.
if ((undefined & candidate.mandatory) != 0) {
continue;
}
// Select the type requerying the smallest amount of fields.
final int n = Integer.bitCount(undefined & candidate.fieldMask);
if (n < min) {
min = n;
best = candidate;
}
}
return best; // May still null.
}
/**
* Sets the type of link. Any value different than {@link org.apache.sis.xml.XLink.Type#AUTO
* Type.AUTO} (including {@code null}) will overwrite the value inferred automatically by
* {@link #getType()}. A {@code AUTO} value will enable automatic type detection.
*
* @param type the new type of link, or {@code null} if none.
*/
public void setType(final Type type) {
canWrite(0x1, "type", "type"); // We want a non-null value in all cases.
if (type != null && (fieldMask() & ~type.fieldMask) != 0) {
throw new IllegalStateException(Errors.format(Errors.Keys.InconsistentAttribute_2, "type", type.identifier()));
}
this.type = type;
}
/**
* Checks if the given attribute can be set.
*
* @param field the attribute code, as documented in {@link XLink.Type#fieldMask}.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the given field can not be set for this kind of {@code xlink}.
*/
private void canWrite(final int field, final String name, final Object value) throws IllegalStateException {
if (hashCode != 0) {
throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "XLink"));
}
final Type type = this.type;
if (type != null) {
if (value != null) {
if ((type.fieldMask & field) == 0) {
throw new IllegalStateException(Errors.format(
Errors.Keys.ForbiddenAttribute_2, name, type.identifier()));
}
} else {
if ((type.mandatory & field) != 0) {
throw new IllegalStateException(Errors.format(
Errors.Keys.MandatoryAttribute_2, name, type.identifier()));
}
}
}
}
/**
* Returns a URN to an external resources, or to an other part of a XML document, or an identifier.
*
* @return a URN to a resources, or {@code null} if none.
*
* @category locator
*/
@XmlSchemaType(name = "anyURI")
@XmlAttribute(name = "href", namespace = Namespaces.XLINK)
public URI getHRef() {
return href;
}
/**
* Sets the URN to a resources.
*
* @param href a URN to a resources, or {@code null} if none.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "href"} attribute.
*
* @category locator
*/
public void setHRef(final URI href) throws IllegalStateException {
canWrite(0x2, "href", href);
this.href = href;
}
/**
* Returns a URI reference for some description of the arc role.
*
* @return a URI reference for some description of the arc role, or {@code null} if none.
*
* @category semantic
*/
@XmlSchemaType(name = "anyURI")
@XmlAttribute(name = "role", namespace = Namespaces.XLINK)
public URI getRole() {
return role;
}
/**
* Sets the URI reference for some description of the arc role.
*
* @param role a URI reference for some description of the arc role, or {@code null} if none.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "role"} attribute.
*
* @category semantic
*/
public void setRole(final URI role) throws IllegalStateException {
canWrite(0x4, "role", role);
this.role = role;
}
/**
* Returns a URI reference for some description of the arc role.
*
* @return a URI reference for some description of the arc role, or {@code null} if none.
*
* @category semantic
*/
@XmlSchemaType(name = "anyURI")
@XmlAttribute(name = "arcrole", namespace = Namespaces.XLINK)
public URI getArcRole() {
return arcrole;
}
/**
* Sets a URI reference for some description of the arc role.
*
* @param arcrole a URI reference for some description of the arc role, or {@code null} if none.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "arcrole"} attribute.
*
* @category semantic
*/
public void setArcRole(final URI arcrole) throws IllegalStateException {
canWrite(0x8, "arcrole", arcrole);
this.arcrole = arcrole;
}
/**
* Returns a human-readable string with a short description for the arc.
*
* @return a human-readable string with a short description for the arc, or {@code null} if none.
*
* @category semantic
*/
@XmlAttribute(name = "title", namespace = Namespaces.XLINK)
public InternationalString getTitle() {
return title;
}
/**
* Sets a human-readable string with a short description for the arc.
*
* @param title a human-readable string with a short description for the arc, or {@code null} if none.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "title"} attribute.
*
* @category semantic
*/
public void setTitle(final InternationalString title) throws IllegalStateException {
canWrite(0x10, "title", title);
this.title = title;
}
/**
* Communicates the desired presentation of the ending resource on traversal
* from the starting resource.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
* @since 0.3
* @module
*
* @see XLink#getShow()
*/
@XmlEnum
public enum Show {
/**
* Load ending resource in a new window, frame, pane, or other presentation context.
*/
@XmlEnumValue("new") NEW,
/**
* Load the resource in the same window, frame, pane, or other presentation context.
*/
@XmlEnumValue("replace") REPLACE,
/**
* Load ending resource in place of the presentation of the starting resource.
*/
@XmlEnumValue("embed") EMBED,
/**
* Behavior is unconstrained; examine other markup in the link for hints.
*/
@XmlEnumValue("other") OTHER,
/**
* Behavior is unconstrained.
*/
@XmlEnumValue("none") NONE
}
/**
* Returns the desired presentation of the ending resource on traversal
* from the starting resource. It's value should be treated as follows:
*
* <ul>
* <li><b>new:</b> load ending resource in a new window, frame, pane, or other presentation context</li>
* <li><b>replace:</b> load the resource in the same window, frame, pane, or other presentation context</li>
* <li><b>embed:</b> load ending resource in place of the presentation of the starting resource</li>
* <li><b>other:</b> behavior is unconstrained; examine other markup in the link for hints</li>
* <li><b>none:</b> behavior is unconstrained</li>
* </ul>
*
* @return the desired presentation of the ending resource, or {@code null} if unspecified.
*
* @category behavior
*/
@XmlAttribute(name = "show", namespace = Namespaces.XLINK)
public Show getShow() {
return show;
}
/**
* Sets the desired presentation of the ending resource on traversal from the starting resource.
*
* @param show the desired presentation of the ending resource, or {@code null} if unspecified.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "show"} attribute.
*
* @category behavior
*/
public void setShow(final Show show) throws IllegalStateException {
canWrite(0x20, "show", show);
this.show = show;
}
/**
* Communicates the desired timing of traversal from the starting resource to the ending
* resource.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
* @since 0.3
* @module
*
* @see XLink#getActuate()
*/
@XmlEnum
public enum Actuate {
/**
* Traverse to the ending resource immediately on loading the starting resource.
*/
@XmlEnumValue("onLoad") ON_LOAD,
/**
* Traverse from the starting resource to the ending resource only on a post-loading event
* triggered for this purpose.
*/
@XmlEnumValue("onRequest") ON_REQUEST,
/**
* Behavior is unconstrained; examine other markup in link for hints.
*/
@XmlEnumValue("other") OTHER,
/**
* Behavior is unconstrained.
*/
@XmlEnumValue("none") NONE
}
/**
* Returns the desired timing of traversal from the starting resource to the ending
* resource. It's value should be treated as follows:
*
* <ul>
* <li><b>onLoad:</b> traverse to the ending resource immediately on loading the starting resource</li>
* <li><b>onRequest:</b> traverse from the starting resource to the ending resource only on a post-loading event triggered for this purpose</li>
* <li><b>other:</b> behavior is unconstrained; examine other markup in link for hints</li>
* <li><b>none:</b> behavior is unconstrained</li>
* </ul>
*
* @return the desired timing of traversal from the starting resource to the ending resource,
* or {@code null} if unspecified.
*
* @category behavior
*/
@XmlAttribute(name = "actuate", namespace = Namespaces.XLINK)
public Actuate getActuate() {
return actuate;
}
/**
* Sets the desired timing of traversal from the starting resource to the ending resource.
*
* @param actuate the desired timing of traversal from the starting resource to the ending resource,
* or {@code null} if unspecified.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "actuate"} attribute.
*
* @category behavior
*/
public void setActuate(final Actuate actuate) throws IllegalStateException {
canWrite(0x40, "actuate", actuate);
this.actuate = actuate;
}
/**
* Returns an identification of the target of a {@code from} or {@code to} attribute.
*
* @return an identification of the target of a {@code from} or {@code to} attribute, or {@code null}.
*
* @category traversal
*/
public String getLabel() {
return label;
}
/**
* Sets an identification of the target of a {@code from} or {@code to} attribute.
*
* @param label an identification of the target of a {@code from} or {@code to} attribute, or {@code null}.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "label"} attribute.
*
* @category traversal
*/
public void setLabel(final String label) throws IllegalStateException {
canWrite(0x80, "label", label);
this.label = label;
}
/**
* Returns the starting resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @return the starting resource, or {@code null}.
*
* @category traversal
*/
public String getFrom() {
return from;
}
/**
* Sets the starting resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @param from the starting resource, or {@code null}.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "from"} attribute.
*
* @category traversal
*/
public void setFrom(final String from) throws IllegalStateException {
canWrite(0x100, "from", from);
this.from = from;
}
/**
* Returns the ending resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @return the ending resource, or {@code null}.
*
* @category traversal
*/
public String getTo() {
return to;
}
/**
* Sets the ending resource. The value must correspond to the same value for some
* {@code label} attribute.
*
* @param to the ending resource, or {@code null}.
* @throws UnsupportedOperationException if this {@code xlink} is unmodifiable.
* @throws IllegalStateException if the link type {@linkplain #setType has been explicitly set}.
* and that type does not allow the {@code "to"} attribute.
*
* @category traversal
*/
public void setTo(final String to) throws IllegalStateException {
canWrite(0x200, "to", to);
this.to = to;
}
/**
* Marks this {@code xlink} as unmodifiable. After this method call, any call to a setter
* method will throw an {@link UnsupportedOperationException}.
*
* <p>After the first call to this method, any subsequent calls have no effect.</p>
*/
public void freeze() {
if (hashCode == 0) {
hashCode = hash();
}
}
/**
* Compares this {@code XLink} with the given object for equality.
*
* @param object the object to compare with this XLink.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object != null && object.getClass() == getClass()) {
final XLink that = (XLink) object;
final int h0 = hashCode;
if (h0 != 0) {
final int h1 = that.hashCode;
if (h1 != 0 && h0 != h1) {
return false; // Slight optimization using the pre-computed hash code values.
}
}
return Objects.equals(this.type, that.type) &&
Objects.equals(this.href, that.href) &&
Objects.equals(this.role, that.role) &&
Objects.equals(this.arcrole, that.arcrole) &&
Objects.equals(this.title, that.title) &&
Objects.equals(this.show, that.show) &&
Objects.equals(this.actuate, that.actuate) &&
Objects.equals(this.label, that.label) &&
Objects.equals(this.from, that.from) &&
Objects.equals(this.to, that.to);
}
return false;
}
/**
* Returns a hash code value for this XLink.
*/
@Override
public int hashCode() {
int hash = hashCode;
if (hash == 0) {
hash = hash();
// Do not save the hash code value, since it may change.
}
return hash;
}
/**
* Computes the hash code now. This method is guaranteed to return a value different
* than zero, in order to allow us to use 0 as a sentinel value for modifiable xlink.
*/
private int hash() {
int hash = Objects.hash(href, role, arcrole, title, show, actuate, label, from, to, type) ^ (int) serialVersionUID;
if (hash == 0) {
hash = -1;
}
return hash;
}
/**
* Returns a string representation of this object. The default implementation returns the
* simple class name followed by non-null attributes, as in the example below:
*
* {@preformat text
* XLink[type="locator", href="urn:ogc:def:method:EPSG::4326"]
* }
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(64);
buffer.append(Classes.getShortClassName(this)).append('[');
append(buffer, "type", getType());
append(buffer, "href", getHRef());
append(buffer, "role", getRole());
append(buffer, "arcrole", getArcRole());
append(buffer, "title", getTitle());
append(buffer, "show", getShow());
append(buffer, "actuate", getActuate());
append(buffer, "label", getLabel());
append(buffer, "from", getFrom());
append(buffer, "to", getTo());
return buffer.append(']').toString();
}
/**
* Appends the given attribute in the given buffer if the attribute value is not null.
* If the given value is an attribute, the XML name will be used rather than the Java
* field name.
*/
private static void append(final StringBuilder buffer, final String label, Object value) {
if (value != null) {
if (buffer.charAt(buffer.length() - 1) != '[') {
buffer.append(", ");
}
if (value instanceof Enum<?>) try {
final XmlEnumValue xml = value.getClass().getField(((Enum<?>) value).name()).getAnnotation(XmlEnumValue.class);
if (xml != null) {
value = xml.value();
}
} catch (NoSuchFieldException e) {
// Should never happen with Enums. But if it happen anyway, this is not a fatal error.
Logging.unexpectedException(getLogger(Loggers.XML), XLink.class, "toString", e);
}
buffer.append(label).append("=\"").append(value).append('"');
}
}
}