/* 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.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gdata.model.ElementCreatorImpl.Action;
import com.google.gdata.model.ElementCreatorImpl.AttributeInfo;
import com.google.gdata.model.ElementCreatorImpl.ElementInfo;
import com.google.gdata.model.ElementMetadata.Cardinality;
import java.util.Map;
/**
* An immutable transform of an element. Simply holds the state of the transform
* in nullable fields. Used to create the actual metadata, see
* {@link ElementMetadataImpl}.
*
*
*/
final class ElementTransform extends Transform {
/**
* The empty element transform, used to save space by not creating many empty
* transforms.
*/
static final ElementTransform EMPTY = new ElementTransform();
/**
* Creates an element transform for the given creator. This is used to turn
* our mutable {@link ElementCreator} instances into immutable transforms that
* we can store in the metadata registry.
*/
static ElementTransform create(ElementCreatorImpl creator) {
ElementTransform transform = new ElementTransform(creator);
if (transform.isEmpty()) {
return EMPTY;
}
return transform;
}
/**
* Creates a composite element transform from the given parts. This is used to
* turn a collection of matching element transforms into a single composite
* transform that can then be turned into metadata. See
* {@link #ElementTransform(ElementKey, Iterable)} for details.
*/
static ElementTransform create(
ElementKey<?, ?> key, Iterable<ElementTransform> parts) {
ElementTransform composite = new ElementTransform(key, parts);
if (composite.isEmpty()) {
return EMPTY;
}
return composite;
}
/**
* Creates an element transform that includes source information, if the
* transform has been moved from a different location. This allows
* modifications to the source to also affect any transforms that reference
* that source. See
* {@link #ElementTransform(ElementKey, ElementTransform, ElementTransform)}
* for details.
*/
static ElementTransform mergeSource(Schema schema, ElementKey<?, ?> key,
ElementTransform transform, MetadataContext context) {
TransformKey sourceKey = transform.getSource();
if (sourceKey != null) {
ElementTransform source = schema.getTransform(sourceKey.getParent(),
(ElementKey<?, ?>) sourceKey.getKey(), context);
if (source != null) {
return new ElementTransform(key, transform, source);
}
}
return transform;
}
// Immutable nullable fields that contain the transform information.
private final Cardinality cardinality;
private final Boolean contentRequired;
private final ElementValidator validator;
private final Object properties;
private final VirtualElementHolder virtualElementHolder;
private final boolean flattened;
// The maps will be immutable empty maps, and not null, if they contain
// no values. All immutable maps use the same instance so this doesn't waste
// any space, and makes merging simpler (no null checks).
private final Map<QName, AttributeInfo> attributes;
private final Map<QName, ElementInfo> elements;
private final Map<String, ElementKey<?, ?>> adaptations;
/**
* Constructs an empty element transform. Because transforms are immutable
* this method is only called to construct the {@link #EMPTY} instance.
*/
private ElementTransform() {
super();
this.cardinality = null;
this.contentRequired = null;
this.validator = null;
this.properties = null;
this.virtualElementHolder = null;
this.flattened = false;
this.attributes = ImmutableMap.of();
this.elements = ImmutableMap.of();
this.adaptations = ImmutableMap.of();
}
/**
* Constructs an element transform for the declared element metadata described
* by the given element creator.
*/
private ElementTransform(ElementCreatorImpl builder) {
super(builder);
this.cardinality = builder.getCardinality();
this.contentRequired = builder.getContentRequired();
this.validator = builder.getValidator();
this.properties = builder.getProperties();
this.virtualElementHolder = builder.getVirtualElementHolder();
this.flattened = builder.isFlattened();
this.attributes = ImmutableMap.copyOf(builder.getAttributes());
this.elements = ImmutableMap.copyOf(builder.getElements());
this.adaptations = ImmutableMap.copyOf(builder.getAdaptations());
}
/**
* Constructs a composite element transform from the given parts. Transforms
* are combined by allowing values that appear later in the iterable to
* override values that appear earlier.
*/
private ElementTransform(ElementKey<?, ?> key,
Iterable<ElementTransform> parts) {
super(parts);
Cardinality compositeCardinality = null;
Boolean compositeContentRequired = null;
ElementValidator compositeValidator = null;
Object compositeProperties = null;
VirtualElementHolder compositeVirtualElementHolder = null;
boolean compositeFlattened = false;
Map<QName, AttributeInfo> compositeAttributes = Maps.newLinkedHashMap();
Map<QName, ElementInfo> compositeElements = Maps.newLinkedHashMap();
Map<String, ElementKey<?, ?>> compositeAdaptors = Maps.newLinkedHashMap();
for (ElementTransform part : parts) {
if (part.cardinality != null) {
compositeCardinality = part.cardinality;
}
if (part.contentRequired != null) {
compositeContentRequired = part.contentRequired;
}
if (part.validator != null) {
compositeValidator = part.validator;
}
if (part.properties != null) {
compositeProperties = part.properties;
}
if (part.virtualElementHolder != null) {
compositeVirtualElementHolder = part.virtualElementHolder;
}
if (part.flattened) {
compositeFlattened = true;
}
for (Map.Entry<QName, AttributeInfo> entry
: part.attributes.entrySet()) {
QName attId = entry.getKey();
AttributeInfo attInfo = entry.getValue();
if (attInfo.action == Action.ADD) {
compositeAttributes.remove(attId);
}
compositeAttributes.put(attId, attInfo);
}
for (Map.Entry<QName, ElementInfo> entry
: part.elements.entrySet()) {
QName childId = entry.getKey();
ElementInfo childInfo = entry.getValue();
if (childInfo.action == Action.ADD) {
compositeElements.remove(childId);
}
compositeElements.put(childId, childInfo);
}
for (Map.Entry<String, ElementKey<?, ?>> entry
: part.adaptations.entrySet()) {
ElementKey<?, ?> adaptor = entry.getValue();
if (isValidAdaptation(key, adaptor)) {
compositeAdaptors.put(entry.getKey(), adaptor);
}
}
}
// Assign the final fields.
this.cardinality = compositeCardinality;
this.contentRequired = compositeContentRequired;
this.validator = compositeValidator;
this.properties = compositeProperties;
this.virtualElementHolder = compositeVirtualElementHolder;
this.flattened = compositeFlattened;
this.attributes = ImmutableMap.copyOf(compositeAttributes);
this.elements = ImmutableMap.copyOf(compositeElements);
this.adaptations = ImmutableMap.copyOf(compositeAdaptors);
}
/**
* Constructs a composite element transform from the transform and source.
* This differs from the {@link #ElementTransform(ElementKey, Iterable)}
* constructor by only using certain values from the source. The differences
* are:
* <ul>
* <li>Whether the content is required is not pulled from the source.</li>
* <li>Attributes are first added from the source, and then any attributes
* that were in the transform but not in the source are added to the end. In
* particular this means that any attributes that were marked as "add" in the
* transform are not moved to the end of the order, as the would be in the
* composite constructor.</li>
* <li>Elements are added in the same way as attributes, first from the source
* and then any new attributes added at the end.</li>
* <li>Adaptors are handled similarly, first from the source and then from the
* transform.</li>
* </ul>
*
* <p>See {@link Transform#Transform(Transform, Transform)} for the changes to
* fields shared between attribute and element transforms.
*/
private ElementTransform(ElementKey<?, ?> key,
ElementTransform transform, ElementTransform source) {
super(transform, source);
this.cardinality = first(transform.cardinality, source.cardinality);
this.contentRequired = transform.contentRequired;
this.validator = first(transform.validator, source.validator);
this.properties = first(transform.properties, source.properties);
this.virtualElementHolder = first(
transform.virtualElementHolder, source.virtualElementHolder);
this.flattened = transform.isFlattened() || source.isFlattened();
// For now we only support appending properties onto the source, so we use
// the source first and then add any previously unseen properties onto the
// end.
Map<QName, AttributeInfo> compositeAttributes = Maps.newLinkedHashMap();
compositeAttributes.putAll(source.getAttributes());
for (Map.Entry<QName, AttributeInfo> entry
: transform.attributes.entrySet()) {
QName attId = entry.getKey();
if (!compositeAttributes.containsKey(attId)) {
compositeAttributes.put(entry.getKey(), entry.getValue());
}
}
this.attributes = ImmutableMap.copyOf(compositeAttributes);
Map<QName, ElementInfo> compositeElements = Maps.newLinkedHashMap();
compositeElements.putAll(source.getElements());
for (Map.Entry<QName, ElementInfo> entry
: transform.elements.entrySet()) {
QName childId = entry.getKey();
if (!compositeElements.containsKey(childId)) {
compositeElements.put(childId, entry.getValue());
}
}
this.elements = ImmutableMap.copyOf(compositeElements);
Map<String, ElementKey<?, ?>> compositeAdaptors = Maps.newLinkedHashMap();
compositeAdaptors.putAll(source.getAdaptations());
for (Map.Entry<String, ElementKey<?, ?>> entry
: transform.adaptations.entrySet()) {
String kind = entry.getKey();
ElementKey<?, ?> adaptor = entry.getValue();
if (!compositeAdaptors.containsKey(kind)
&& isValidAdaptation(key, adaptor)) {
compositeAdaptors.put(kind, adaptor);
}
}
this.adaptations = ImmutableMap.copyOf(compositeAdaptors);
}
/**
* Checks if the given adaptation is valid. An adaptation is only valid as
* part of a composite if the adaptor type is a subtype of the source type.
*/
private static boolean isValidAdaptation(ElementKey<?, ?> source,
ElementKey<?, ?> adaptor) {
Class<?> sourceType = source.getElementType();
Class<?> adaptorType = adaptor.getElementType();
if (sourceType == adaptorType) {
return false;
}
return sourceType.isAssignableFrom(adaptorType);
}
/**
* Creates a new element metadata instance based on this transform.
*/
<D, E extends Element> ElementMetadata<D, E> toMetadata(
Schema schema, ElementKey<?, ?> parent, ElementKey<D, E> key,
MetadataContext context) {
return new ElementMetadataImpl<D, E>(schema, this, parent, key, context);
}
Cardinality getCardinality() {
return cardinality;
}
Boolean getContentRequired() {
return contentRequired;
}
ElementValidator getValidator() {
return validator;
}
Object getProperties() {
return properties;
}
VirtualElementHolder getVirtualElementHolder() {
return virtualElementHolder;
}
boolean isFlattened() {
return flattened;
}
Map<QName, AttributeInfo> getAttributes() {
return attributes;
}
Map<QName, ElementInfo> getElements() {
return elements;
}
Map<String, ElementKey<?, ?>> getAdaptations() {
return adaptations;
}
@Override
boolean isEmpty() {
return super.isEmpty()
&& cardinality == null
&& contentRequired == null
&& validator == null
&& properties == null
&& virtualElementHolder == null
&& !flattened
&& attributes.isEmpty()
&& elements.isEmpty()
&& adaptations.isEmpty();
}
}