/* 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; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import java.util.Map; /** * An immutable set of metadata. Stores an entire tree of metadata about a * service or feed provider in a fast, cached, immutable form, so that runtime * use of metadata is very cheap. Schemas can be created by using a * {@link MetadataRegistry}, which can be created using {@link #builder()}. * * */ public final class Schema { /** * Static factory method to allow the standard code of * {@code Schema.builder()} to return something useful. */ public static MetadataRegistry builder() { return new MetadataRegistry(); } /** * Calculate the root key for a given element key. This is the key we use * when finding the appropriate element registry to store information in. */ static RootKey getRootKey(ElementKey<?, ?> key) { return RootKey.get(key); } /** * Calculate the root key for a given attribute key. This is the key we use * when finding the appropriate attribute registry to store information in, * and is just the ID with a dummy datatype. */ static RootKey getRootKey(AttributeKey<?> key) { return RootKey.get(key); } // The element registries store the actual data, this map is immutable. private final Map<RootKey, ElementMetadataRegistry> elements; // The attribute registries store the actual data, this map is immutable. private final Map<RootKey, AttributeMetadataRegistry> attributes; /** * Create a schema from the given metadata registry. */ Schema(MetadataRegistry registry) { this.elements = buildElements(registry, this); this.attributes = buildAttributes(registry, this); } /** * Creates the immutable map of attribute key -> attribute registry. Once * this is created there is no more modifying attribute metadata, its all set. */ private static ImmutableMap<RootKey, AttributeMetadataRegistry> buildAttributes(MetadataRegistry registry, Schema schema) { Builder<RootKey, AttributeMetadataRegistry> attributeBuilder = ImmutableMap.builder(); for (Map.Entry<RootKey, AttributeMetadataRegistryBuilder> entry : registry.getAttributes().entrySet()) { attributeBuilder.put(entry.getKey(), entry.getValue().create(schema)); } return attributeBuilder.build(); } /** * Creates the immutable map of element key -> element registry. Once this * is created there is no more modifying element metadata, its all set. */ private static ImmutableMap<RootKey, ElementMetadataRegistry> buildElements(MetadataRegistry registry, Schema schema) { Builder<RootKey, ElementMetadataRegistry> elementBuilder = ImmutableMap.builder(); for (Map.Entry<RootKey, ElementMetadataRegistryBuilder> entry : registry.getElements().entrySet()) { elementBuilder.put(entry.getKey(), entry.getValue().create(schema)); } return elementBuilder.build(); } /** * Returns the default metadata for the element key. */ public <D, E extends Element> ElementMetadata<D, E> bind( ElementKey<D, E> key) { return bind(null, key, null); } /** * Returns the metadata for the element key bound to the context. */ public <D, E extends Element> ElementMetadata<D, E> bind( ElementKey<D, E> key, MetadataContext context) { return bind(null, key, context); } /** * Returns the metadata for the child element in the parent. */ public <D, E extends Element> ElementMetadata<D, E> bind( ElementKey<?, ?> parent, ElementKey<D, E> child) { return bind(parent, child, null); } /** * Returns the metadata for the child element in the parent, bound to the * context. */ public <D, E extends Element> ElementMetadata<D, E> bind( ElementKey<?, ?> parent, ElementKey<D, E> child, MetadataContext context) { ElementMetadataRegistry childRegistry = getElement(child); return (childRegistry == null) ? null : childRegistry.bind(parent, child, context); } /** * Provides direct access to the transform for other classes in this package, * to avoid circular dependencies causing infinite loops. This method will * first check for the actual type of metadata key in use and delegate to * the appropriate metadata registry. */ Transform getTransform(ElementKey<?, ?> parent, MetadataKey<?> key, MetadataContext context) { if (key instanceof AttributeKey<?>) { return getTransform(parent, (AttributeKey<?>) key, context); } else { return getTransform(parent, (ElementKey<?, ?>) key, context); } } /** * Provides direct access to attribute transforms. */ AttributeTransform getTransform(ElementKey<?, ?> parent, AttributeKey<?> attribute, MetadataContext context) { AttributeMetadataRegistry attributeRegistry = getAttribute(attribute); return (attributeRegistry == null) ? null : attributeRegistry.getTransform(parent, attribute, context); } /** * Provides direct access to element transforms. */ ElementTransform getTransform(ElementKey<?, ?> parent, ElementKey<?, ?> child, MetadataContext context) { ElementMetadataRegistry childRegistry = getElement(child); return (childRegistry == null) ? null : childRegistry.getTransform(parent, child, context); } /** * Gets an element from the element map by first finding the appropriate * root key and then indexing into the elements based on that key. */ private ElementMetadataRegistry getElement(ElementKey<?, ?> key) { return elements.get(getRootKey(key)); } /** * Returns the default metadata for the given attribute. */ public <D> AttributeMetadata<D> bind(ElementKey<?, ?> parent, AttributeKey<D> attribute) { return bind(parent, attribute, null); } /** * Returns the metadata for the attribute, bound to the context. */ public <D> AttributeMetadata<D> bind(ElementKey<?, ?> parent, AttributeKey<D> attribute, MetadataContext context) { AttributeMetadataRegistry attRegistry = getAttribute(attribute); return (attRegistry == null) ? null : attRegistry.bind(parent, attribute, context); } /** * Gets an attribute from the attribute map by first finding the appropriate * root key and then indexing into the attributes based on that key. */ private AttributeMetadataRegistry getAttribute(AttributeKey<?> key) { return attributes.get(getRootKey(key)); } /** * A root key to a particular attribute or element. A root key represents * the root of an attribute or element metadata tree. For attributes, the * root is based on the ID of the attribute. For elements, if the element * type is {@link Element}, the root is based on the ID. If the element type * is some subclass of {@code Element}, the root will be based on the first * class after element in the type hierarchy, and ID will be ignored. */ static class RootKey { /** * Calculate the root key for a given element key based on the ID or type. */ private static RootKey get(ElementKey<?, ?> key) { QName id = key.getId(); Class<? extends Element> elementType = key.getElementType(); if (elementType != Element.class) { Class<? extends Element> superClass = getSuper(elementType); while (superClass != Element.class) { elementType = superClass; superClass = getSuper(elementType); } // For element keys using a subclass of Element, index by type. return new RootKey(elementType); } else { // For element keys that reference Element directly, index by root ID. return new RootKey(getRootId(id)); } } /** * Creates a root key from the given attribute key's ID. */ private static RootKey get(AttributeKey<?> key) { return new RootKey(getRootId(key.getId())); } /** * Gets the root ID for a qualified name. All names in the same namespace * will be stored together, to allow transforms to affect all properties in * a namespace through transforms on uri:*. */ private static QName getRootId(QName id) { if (id.getNs() != null && !"*".equals(id.getLocalName())) { return new QName(id.getNs(), "*"); } return id; } /** * Cast the superclass of a class we know is at least two levels away from * Element as a subclass of Element. */ private static Class<? extends Element> getSuper( Class<? extends Element> type) { return type.getSuperclass().asSubclass(Element.class); } private final QName id; private final Class<?> type; private RootKey(QName id) { Preconditions.checkNotNull(id); this.id = id; this.type = null; } private RootKey(Class<?> type) { Preconditions.checkNotNull(type); this.id = null; this.type = type; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof RootKey)) { return false; } RootKey other = (RootKey) obj; if (type != null) { return type == other.type; } return id.equals(other.id); } @Override public int hashCode() { if (type != null) { return type.hashCode(); } return id.hashCode(); } @Override public String toString() { return (type == null) ? "{Root (" + id + ")}" : "{Root (" + type + ")}"; } } }