/* Copyright (c) 2008 Google Inc.
*
* Licensed 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 com.google.gdata.model;
import com.google.gdata.util.common.base.Preconditions;
/**
* A key referring to a particular element. Holds the ID of the element and the
* expected datatype and element type. Element keys support value-based
* equality, natural ordering, and matching.
* <ul>
* <li>An element key is {@link #equals(Object)} to another element key if their
* IDs are equivalent or both {@code null}, and their datatypes, and element
* types are the same.<li>
* <li>An element key's natural ordering is based first on the name, then on
* the element type, and finally on the data type.</li>
* <li>An element key {@link #matches(MetadataKey)} another element key if the
* ID is a match for the ID of the other key, and the datatype and element type
* are both assignable from the other key's datatype and element type.</li>
* </ul>
*
* @param <D> the datatype of the element
* @param <E> the element type of the element
*
*/
public final class ElementKey<D, E extends Element> extends MetadataKey<D> {
/**
* Return a default element key using a string datatype and
* {@link Element} as the element type. The id must not be {@code null}.
*/
public static ElementKey<String, Element> of(QName id) {
return of(id, String.class, Element.class);
}
/**
* Construct an element key with the given id and element type, but with
* a {@link Void} datatype. This is used for elements without text content.
*
* <p>The {@code elementType} must not be {@code null}. A null id is only
* valid for element types that are a subclass of {@link Element}, and are
* used as a key referring to all instances of that element type.
*/
public static <V extends Element> ElementKey<Void, V> of(
QName id, Class<? extends V> elementType) {
return of(id, Void.class, elementType);
}
/**
* Construct an element key with the given id, datatype, and element type.
* This is used for elements that contain text content.
*
* <p>The {@code elementType} must not be {@code null}. A null id is only
* valid for element types that are a subclass of {@link Element}, and are
* used as a key referring to all instances of that element type.
*/
public static <T, V extends Element> ElementKey<T, V> of(QName id,
Class<? extends T> datatype, Class<? extends V> elementType) {
return new ElementKey<T, V>(id, datatype, elementType);
}
// The element type for this key.
final Class<? extends E> elementType;
/**
* Construct a new element key. Both datatype and element type must not be
* {@code null}. The id may be null only if the key represents a construct;
* the element type must be a subclass of {@link Element}.
*/
private ElementKey(QName id, Class<? extends D> datatype,
Class<? extends E> elementType) {
super(id, datatype);
Preconditions.checkNotNull(elementType, "elementType");
if (Element.class == elementType) {
Preconditions.checkNotNull(id, "id");
}
this.elementType = elementType;
}
/**
* Returns the element type of the element.
*/
public Class<? extends E> getElementType() {
return elementType;
}
/**
* Returns {@code true} if this key is a match for the given key. This key is
* a match for the other key if the other key is also an element key and if
* the ID, datatype, and element types all match.
*/
@Override
public boolean matches(MetadataKey<?> other) {
if (other == null) {
return false;
}
if (!(other instanceof ElementKey<?, ?>)) {
return false;
}
if (!matchIdAndDatatype(other)) {
return false;
}
return elementType.isAssignableFrom(((ElementKey<?, ?>) other).elementType);
}
/**
* Compares first on ID, then on element type, then on datatype.
*/
public int compareTo(MetadataKey<?> other) {
if (other == this) {
return 0;
}
// If they aren't the same type, put element keys at the end.
if (!(other instanceof ElementKey<?, ?>)) {
return 1;
}
int compare = compareQName(id, other.id);
if (compare != 0) {
return compare;
}
compare = compareClass(elementType, ((ElementKey<?, ?>) other).elementType);
if (compare != 0) {
return compare;
}
return compareClass(datatype, other.datatype);
}
@Override
public int hashCode() {
int hashCode = datatype.hashCode();
hashCode *= 17;
if (id != null) {
hashCode += id.hashCode();
}
hashCode *= 17;
return hashCode + elementType.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != ElementKey.class) {
return false;
}
ElementKey<?, ?> o = (ElementKey<?, ?>) obj;
if (id == null) {
if (o.id != null) {
return false;
}
} else if (!id.equals(o.id)) {
return false;
}
return elementType == o.elementType
&& datatype == o.datatype;
}
@Override
public String toString() {
return "{ElementKey " + id
+ ", D:" + datatype
+ ", E:" + elementType + "}";
}
}