com.google.gdata.model.ElementMetadataImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gdata.model.ElementMetadataImpl.java

Source

/**
 * Mule Google Api Commons
 *
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package com.google.gdata.model;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Maps;
import com.google.gdata.util.common.xml.XmlNamespace;
import com.google.gdata.model.ElementCreatorImpl.AttributeInfo;
import com.google.gdata.model.ElementCreatorImpl.ElementInfo;
import com.google.gdata.util.ParseException;
import com.google.gdata.wireformats.ContentCreationException;
import com.google.gdata.wireformats.ObjectConverter;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Immutable implementation of the element metadata.  This class delegates to
 * the schema for binding to other contexts and for retrieving children, and
 * uses an {@link AdaptationRegistry} for dealing with adaptations.
 *
 * <p>Each instance of this class is bound to a specific schema, parent key,
 * element key, and metadata context, see {@link MetadataImpl}.
 *
 * 
 */
final class ElementMetadataImpl<D, E extends Element> extends MetadataImpl<D> implements ElementMetadata<D, E> {

    private static final ElementValidator DEFAULT_VALIDATOR = new MetadataValidator();

    // Element metadata properties.
    private final ElementKey<D, E> elemKey;
    private final ElementKey<D, E> sourceKey;
    private final Cardinality cardinality;
    private final boolean isContentRequired;
    private final ElementValidator validator;
    private final Object properties;
    private final VirtualElementHolder virtualElementHolder;
    private final boolean isFlattened;

    /** Metadata for element's attributes and child elements. */
    private final ImmutableMap<QName, AttributeKey<?>> attributes;
    private final ImmutableMap<QName, AttributeKey<?>> renamedAttributes;
    private final ImmutableMap<QName, ElementKey<?, ?>> elements;
    private final ImmutableMap<QName, ElementKey<?, ?>> renamedElements;

    /** Adaptation helper for dealing with adaptors on this element. */
    private final AdaptationRegistry adaptations;

    /**
     * Constructs a new immutable element metadata instance from the given
     * declared metadata.
     */
    ElementMetadataImpl(Schema schema, ElementTransform transform, ElementKey<?, ?> parent, ElementKey<D, E> key,
            MetadataContext context) {
        super(schema, transform, parent, key, context);

        this.elemKey = key;
        TransformKey transformSource = transform.getSource();
        if (transformSource != null) {
            // Use the ID of the transform source for the source key.
            ElementKey<D, E> transformSourceKey = ElementKey.of(transformSource.getKey().getId(), key.getDatatype(),
                    key.getElementType());

            if (transformSourceKey.equals(elemKey)) {
                this.sourceKey = elemKey;
            } else {
                this.sourceKey = transformSourceKey;
            }
        } else {
            this.sourceKey = elemKey;
        }

        transform = ElementTransform.mergeSource(schema, key, transform, context);

        this.cardinality = firstNonNull(transform.getCardinality(), Cardinality.SINGLE);
        this.isContentRequired = firstNonNull(transform.getContentRequired(), true);
        this.validator = firstNonNull(transform.getValidator(), DEFAULT_VALIDATOR);
        this.properties = transform.getProperties();
        this.virtualElementHolder = transform.getVirtualElementHolder();
        this.isFlattened = transform.isFlattened();

        this.attributes = getAttributes(transform.getAttributes().values());
        this.renamedAttributes = getRenamedAttributes();
        this.elements = getElements(transform.getElements().values());
        this.renamedElements = getRenamedElements();

        if (transform.getAdaptations().isEmpty()) {
            this.adaptations = null;
        } else {
            this.adaptations = AdaptationRegistryFactory.create(schema, transform);
        }
    }

    /**
     * Creates an immutable map of attributes from the given collection of
     * attribute info objects.  The info objects are used in transforms to allow
     * bumping attributes to the end of the list, to change their order, but once
     * we are creating the element metadata the order is set and we just need the
     * keys.
     */
    private ImmutableMap<QName, AttributeKey<?>> getAttributes(Collection<AttributeInfo> infos) {
        Builder<QName, AttributeKey<?>> builder = ImmutableMap.builder();
        for (AttributeInfo info : infos) {
            builder.put(info.key.getId(), info.key);
        }
        return builder.build();
    }

    /**
     * Creates an immutable map of renamed attributes from the collection of
     * attribute keys. This binds the attributes and checks if they have a
     * different name under the context of this metadata, and returns any
     * attributes with an alternate name in the map.
     */
    private ImmutableMap<QName, AttributeKey<?>> getRenamedAttributes() {
        Builder<QName, AttributeKey<?>> builder = ImmutableMap.builder();
        for (AttributeKey<?> key : attributes.values()) {
            AttributeMetadata<?> bound = bindAttribute(key);
            QName boundName = bound.getName();
            if (!boundName.equals(key.getId())) {
                builder.put(boundName, key);
            }
        }
        return builder.build();
    }

    /**
     * Creates an immutable map of child elements from the given collection of
     * element info objects. The info objects are used in transforms to allow
     * bumping elements to the end of the list, to change their order, but once we
     * are creating the element metadata the order is set and we just need the
     * keys.
     */
    private ImmutableMap<QName, ElementKey<?, ?>> getElements(Collection<ElementInfo> infos) {
        Builder<QName, ElementKey<?, ?>> builder = ImmutableMap.builder();
        for (ElementInfo info : infos) {
            builder.put(info.key.getId(), info.key);
        }
        return builder.build();
    }

    /**
     * Creates an immutable map of renamed child elements from the collection of
     * element keys. This gets the transform for each child and checks if there
     * is a new, different name, and if so creates a map of those new names back
     * to the appropriate key, for use during parsing.
     */
    private ImmutableMap<QName, ElementKey<?, ?>> getRenamedElements() {

        Map<QName, ElementKey<?, ?>> renamed = Maps.newLinkedHashMap();

        for (ElementKey<?, ?> key : elements.values()) {
            ElementTransform transform = schema.getTransform(sourceKey, key, context);
            QName childName = transform.getName();
            if (childName != null && !childName.equals(key.getId())) {

                // We only use the first renamed element if multiple elements are named
                // the same.
                if (!renamed.containsKey(childName)) {
                    renamed.put(childName, key);
                }
            }
        }
        return ImmutableMap.copyOf(renamed);
    }

    /**
     * Adapts this element metadata to a different kind, using the provided key.
     * Will return {@code null} if no adaptation on the given kind exists. If an
     * adaptation does exist it will bind the adaptation under the same parent and
     * context as this element.
     */
    public ElementKey<?, ?> adapt(String kind) {
        return (adaptations != null) ? adaptations.getAdaptation(kind) : null;
    }

    /**
     * Returns true if the attributes contain the given attribute key, based on
     * its ID.
     */
    public boolean isDeclared(AttributeKey<?> key) {
        return attributes.containsKey(key.getId());
    }

    /**
     * Finds the most appropriate attribute metadata for parsing the given ID.
     * First looks locally, if that fails it then looks locally for a attribute
     * with the "*" local name (used to allow foo:* attribute metadata), and then
     * if that fails it looks in the adaptations.  This allows us to parse an
     * attribute that is declared in adaptations as declared metadata instead
     * of undeclared metadata.  If it still hasn't found an attribute this method
     * will return null.
     */
    public AttributeKey<?> findAttribute(QName id) {
        // First check any renamed attributes, as those take precedence.
        if (!renamedAttributes.isEmpty()) {
            AttributeKey<?> attKey = renamedAttributes.get(id);
            if (attKey != null) {
                return attKey;
            }
        }
        if (!attributes.isEmpty()) {
            AttributeKey<?> attKey = attributes.get(id);
            if (attKey != null) {
                return attKey;
            }

            // For wildcarded ids, iterate and return the first matching attribute
            if (id.matchesAnyNamespace()) {
                for (Map.Entry<QName, AttributeKey<?>> attrEntry : attributes.entrySet()) {
                    if (id.matches(attrEntry.getKey())) {
                        return attrEntry.getValue();
                    }
                }
            } else if (!id.matchesAnyLocalName()) {
                // See if there is a foo:* match for the given id.
                attKey = attributes.get(toWildcardLocalName(id));
                if (attKey != null) {
                    return AttributeKey.of(id, attKey.getDatatype());
                }
            }
        }

        if (adaptations != null) {
            AttributeKey<?> attKey = adaptations.findAttribute(id);
            if (attKey != null) {
                return attKey;
            }
        }

        return null;
    }

    /**
     * Returns {@code true} if the given element key is declared, by ID.
     */
    public boolean isDeclared(ElementKey<?, ?> element) {
        return elements.containsKey(element.getId());
    }

    /**
     * Finds the most appropriate child metadata for parsing the given ID.
     * First looks locally, if that fails it then looks locally for an element
     * with the "*" local name (used to allow foo:* element metadata), and then
     * if that fails it looks in the adaptations.  This allows us to parse an
     * element that is declared in adaptations as declared metadata instead
     * of undeclared metadata.  If after looking in the adaptations we still have
     * not found a key for the QName, we return null.
     */
    public ElementKey<?, ?> findElement(QName id) {
        // First check any renamed elements, as those take precedence.
        if (!renamedElements.isEmpty()) {
            ElementKey<?, ?> childKey = renamedElements.get(id);
            if (childKey != null) {
                return childKey;
            }
        }
        if (!elements.isEmpty()) {
            ElementKey<?, ?> childKey = elements.get(id);
            if (childKey != null) {
                return childKey;
            }

            // For wildcarded ids, iterate and return the first matching element
            if (id.matchesAnyNamespace()) {
                for (Map.Entry<QName, ElementKey<?, ?>> elemEntry : elements.entrySet()) {
                    if (id.matches(elemEntry.getKey())) {
                        return elemEntry.getValue();
                    }
                }
            } else if (!id.matchesAnyLocalName()) {
                // See if there is a foo:* match for the provided namespace.
                childKey = elements.get(toWildcardLocalName(id));
                if (childKey != null) {
                    return ElementKey.of(id, childKey.getDatatype(), childKey.getElementType());
                }
            }
        }

        if (adaptations != null) {
            ElementKey<?, ?> childKey = adaptations.findElement(id);
            if (childKey != null) {
                return childKey;
            }
        }

        return null;
    }

    /**
     * Binds this metadata to a different context.  This just asks the registry
     * to handle the binding for us.
     */
    public ElementMetadata<D, E> bind(MetadataContext context) {
        return schema.bind(parent, elemKey, context);
    }

    @Override
    public ElementKey<D, E> getKey() {
        return elemKey;
    }

    public Cardinality getCardinality() {
        return cardinality;
    }

    public boolean isContentRequired() {
        return isContentRequired;
    }

    public boolean isReferenced() {
        return isVisible();
    }

    public boolean isSelected(Element e) {
        return isVisible();
    }

    public boolean isFlattened() {
        return isFlattened;
    }

    public Object getProperties() {
        return properties;
    }

    public ElementValidator getValidator() {
        return validator;
    }

    public void validate(ValidationContext vc, Element e) {
        if (validator != null) {
            validator.validate(vc, e, this);
        }
    }

    public Iterator<Attribute> getAttributeIterator(Element element) {
        return element.getAttributeIterator(this);
    }

    public ImmutableCollection<AttributeKey<?>> getAttributes() {
        return attributes.values();
    }

    public <K> AttributeMetadata<K> bindAttribute(AttributeKey<K> key) {
        return schema.bind(sourceKey, key, context);
    }

    public Iterator<Element> getElementIterator(Element element) {
        return element.getElementIterator(this);
    }

    public ImmutableCollection<ElementKey<?, ?>> getElements() {
        return elements.values();
    }

    @SuppressWarnings("unchecked")
    public <K, L extends Element> ElementMetadata<K, L> bindElement(ElementKey<K, L> key) {
        return schema.bind(sourceKey, key, context);
    }

    @Override
    public Object generateValue(Element element, ElementMetadata<?, ?> metadata) {
        Object result = super.generateValue(element, metadata);
        if (result == null) {
            result = element.getTextValue(elemKey);
        }
        return result;
    }

    @Override
    public void parseValue(Element element, ElementMetadata<?, ?> metadata, Object value) throws ParseException {
        if (!super.parse(element, metadata, value)) {
            element.setTextValue(ObjectConverter.getValue(value, elemKey.getDatatype()));
        }
    }

    public SingleVirtualElement getSingleVirtualElement() {
        if (cardinality != Cardinality.SINGLE) {
            return null;
        }
        if (virtualElementHolder != null) {
            return virtualElementHolder.getSingleVirtualElement();
        }
        return null;
    }

    public MultipleVirtualElement getMultipleVirtualElement() {
        if (cardinality == Cardinality.SINGLE) {
            return null;
        }
        if (virtualElementHolder != null) {
            return virtualElementHolder.getMultipleVirtualElement();
        }
        return null;
    }

    public E createElement() throws ContentCreationException {
        return Element.createElement(elemKey);
    }

    public XmlNamespace getDefaultNamespace() {
        // The default implementation uses the default namespace for the in-use name
        return getName().getNs();
    }

    /**
     * Set of namespaces referenced by this element's metadata and all child
     * attributes and elements.   This field is lazily initialized by the first
     * call to getReferencedNamespaces().
     */
    private Collection<XmlNamespace> referencedNamespaces = null;

    public Collection<XmlNamespace> getReferencedNamespaces() {
        // The referencedNamespaces field is lazily initialized because it is
        // only required for top-level types.   A race condition is
        // harmless and will just result in multiple computations.
        if (referencedNamespaces == null) {
            ImmutableSet.Builder<XmlNamespace> builder = ImmutableSet.builder();
            Set<ElementKey<?, ?>> added = Sets.newHashSet();
            addReferencedNamespaces(this, builder, added);
            referencedNamespaces = builder.build();
        }
        return referencedNamespaces;
    }

    private static void addReferencedNamespaces(ElementMetadata<?, ?> metadata,
            ImmutableSet.Builder<XmlNamespace> builder, Set<ElementKey<?, ?>> added) {

        // Avoid recursive looping
        if (added.contains(metadata.getKey())) {
            return;
        }
        added.add(metadata.getKey());

        // Add namespace for this element (if any)
        XmlNamespace elemNs = metadata.getName().getNs();
        if (elemNs != null) {
            builder.add(elemNs);
        }

        // Add namespace for all attributes (if any)
        for (AttributeKey<?> attrKey : metadata.getAttributes()) {
            AttributeMetadata<?> attrMetadata = metadata.bindAttribute(attrKey);
            XmlNamespace attrNs = attrMetadata.getName().getNs();
            if (attrNs != null) {
                builder.add(attrNs);
            }
        }

        // Add namespace for all child elements (recursively)
        for (ElementKey<?, ?> elemKey : metadata.getElements()) {
            ElementMetadata<?, ?> childMetadata = metadata.bindElement(elemKey);
            addReferencedNamespaces(childMetadata, builder, added);
        }
    }

    /**
     * Returns an id of the form ns:*, if the given id does not already represent
     * the * localname.
     */
    private QName toWildcardLocalName(QName id) {
        return new QName(id.getNs(), QName.ANY_LOCALNAME);
    }
}