/* 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 representing a particular metadata instance. All metadata keys have at
* least an ID and a datatype. Metadata keys have a natural ordering based first
* on key type, then on the natural ordering within that key type. Metadata keys
* also support equivalence and matching, both of which are defined by subtypes.
* See {@link AttributeKey} and {@link ElementKey} for the details.
*
* @param <D> the datatype of this key
*
*/
public abstract class MetadataKey<D> implements Comparable<MetadataKey<?>> {
// The id and datatype for this key.
final QName id;
final Class<? extends D> datatype;
/**
* Construct a new attribute key. The datatype must not be {@code null}. A
* null {@code id} indicates that this value is a construct and should not be
* used directly.
*/
MetadataKey(QName id, Class<? extends D> datatype) {
Preconditions.checkNotNull(datatype, "datatype");
this.id = id;
this.datatype = datatype;
}
/**
* Returns the id of the property. This is the canonical name of the
* property, and will stay the same across transforms and contexts. It is
* based on the XML schema for the atom representation.
*
* @return the unique identifier of the property.
*/
public QName getId() {
return id;
}
/**
* Returns the datatype of the property. This the type of the attribute value
* or the text content of an element.
*
* @return the datatype of the property
*/
public Class<? extends D> getDatatype() {
return datatype;
}
/**
* Returns {@code true} if this key matches the given key.
*/
public abstract boolean matches(MetadataKey<?> other);
/**
* Match on id and datatype, used by subclasses to implement matching.
*/
boolean matchIdAndDatatype(MetadataKey<?> other) {
if (id != null && !id.matches(other.id)) {
return false;
}
// Datatypes much match if they are non-default types.
return datatype.isAssignableFrom(other.datatype)
|| other.datatype == String.class;
}
/**
* Order two QNames, where either may be null. A null value is considered
* lower than a non-null value, and will come before it in the list. This
* forces more-specific qnames to appear later.
*/
static int compareQName(QName a, QName b) {
if (a == b) {
return 0;
}
// If a is null (and b non-null), a < b.
if (a == null) {
return -1;
}
// If b is null (and a non-null), a > b.
if (b == null) {
return 1;
}
// Else compare using normal QName compare.
return a.compareTo(b);
}
/**
* Order two class objects. If a class is a supertype of another class, it
* comes before that class, so more specific classes are later in the list.
* If neither class is a supertype of the other, the classes are ordered
* based on the lexical ordering of the point in the class hierarchy where the
* two classes diverge.
*/
static int compareClass(Class<?> a, Class<?> b) {
if (a == b) {
return 0;
}
// If a is a supertype of b, return -1
if (a.isAssignableFrom(b)) {
return -1;
}
// If b is a supertype of a, return 1.
if (b.isAssignableFrom(a)) {
return 1;
}
// Compare based on the lexical ordering of the most general superclass
// of the given classes that don't match.
a = getFirstNonAssignable(a, b);
b = getFirstNonAssignable(b, a);
// Compare them based on lexical ordering of the non-assignable classes.
return a.getName().compareTo(b.getName());
}
/**
* Find the first class in a's type hierarchy that is not assignable
* from b. This is the first class after a and b's common ancestor (which
* may be Object).
*/
static Class<?> getFirstNonAssignable(Class<?> a, Class<?> b) {
Class<?> superA = a.getSuperclass();
while (!superA.isAssignableFrom(b)) {
a = superA;
superA = a.getSuperclass();
}
return a;
}
@Override
public String toString() {
return "{MetadataKey " + id + ", D:" + datatype + "}";
}
}