/* 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.common.collect.Sets; import com.google.gdata.model.ElementCreatorImpl.AttributeInfo; import com.google.gdata.model.ElementCreatorImpl.ElementInfo; import java.util.Map; import java.util.Set; import java.util.logging.Logger; /** * A builder for the {@link AdaptationRegistry} class, this generates the * union maps of attributes and child elements. The {@link #create} method is * used to create an immutable adaptation registry that handles the runtime * information. * * */ class AdaptationRegistryFactory { // Logger to log warnings when calculating the attributes and elements. private static final Logger logger = Logger.getLogger( AdaptationRegistryFactory.class.getName()); /** * Constructs a new adaptation registry from the given transform. */ static AdaptationRegistry create(Schema schema, ElementTransform transform) { return new AdaptationRegistry(transform.getAdaptations(), unionAttributes(schema, transform), unionElements(schema, transform)); } /** * Create a union of the base attribute map + any adaptor attribute maps. * The result is a map from attribute names to the most appropriate * adaptation and attribute key for that name. This map will contain only * attributes that are not defined in the base transform and have the same * datatype in all adaptations in which they appear. * * <p>If we find multiple incompatible attributes of the same name, we log a * warning and leave them out of the union map. This means that during * parsing the incompatible attributes will be parsed as if they were * undeclared, and later adapted to the correct datatype during resolution. */ private static Map<QName, AttributeKey<?>> unionAttributes( Schema schema, ElementTransform transform) { Map<QName, AttributeKey<?>> union = Maps.newLinkedHashMap(); Set<QName> base = getAttributeNames(transform); Set<QName> invalid = Sets.newHashSet(); for (ElementKey<?, ?> adaptorKey : transform.getAdaptations().values()) { // We get the adaptor transform so we can access its attributes. ElementTransform adaptor = schema.getTransform(null, adaptorKey, null); if (adaptor == null) { throw new IllegalStateException("Invalid adaptor key " + adaptorKey); } for (AttributeInfo info : adaptor.getAttributes().values()) { AttributeKey<?> key = info.key; QName id = key.getId(); // Skip attributes contained in the base transform. if (base.contains(id)) { continue; } // Skip any attributes that we already know are invalid. if (invalid.contains(id)) { continue; } AttributeKey<?> existing = union.get(id); if (existing != null) { // Check that multiple attributes with the same ID are compatible. if (!checkCompatible(existing, key)) { union.remove(id); invalid.add(id); } } else { union.put(id, key); } } } // Return an immutable copy of the union of attributes. If this is empty // it will be a reference to the empty immutable map. return ImmutableMap.copyOf(union); } /** * Gets the set of qnames of attributes defined in the given transform. */ private static Set<QName> getAttributeNames(ElementTransform transform) { Set<QName> result = Sets.newHashSet(); for (AttributeInfo info : transform.getAttributes().values()) { result.add(info.key.getId()); } return result; } /** * Create a union of the base element map + any adaptor element maps. * The result is a map from element names to the most appropriate * adaptation and element key for that name. This map will contain only * elements that are not defined in the base transform and have the same * datatype and compatible element types in all adaptations in which they * appear. Two element types are compatible if one is assignable to the other. * * <p>If we find multiple incompatible elements of the same name, we log a * warning and leave them out of the union map. This means that during * parsing the incompatible elements will be parsed as if they were * undeclared, and later adapted to the correct type during resolution. */ private static Map<QName, ElementKey<?, ?>> unionElements( Schema schema, ElementTransform transform) { Map<QName, ElementKey<?, ?>> union = Maps.newLinkedHashMap(); Set<QName> invalid = Sets.newHashSet(); Set<QName> base = getElementNames(transform); for (ElementKey<?, ?> adaptorKey : transform.getAdaptations().values()) { // We get the transform for the adaptor so we can check its element keys. ElementTransform adaptor = schema.getTransform(null, adaptorKey, null); for (ElementInfo info : adaptor.getElements().values()) { ElementKey<?, ?> key = info.key; QName id = key.getId(); // Skip child elements contained in the base transform. if (base.contains(id)) { continue; } // Skip any child elements that we already know are invalid. if (invalid.contains(id)) { continue; } ElementKey<?, ?> existing = union.get(id); ElementKey<?, ?> compatible = key; if (existing != null) { // Check that multiple elements with the same ID are compatible. compatible = checkCompatibleElements(existing, key); } if (compatible == null) { union.remove(id); invalid.add(id); } else if (compatible == key) { union.put(id, key); } } } // Return an immutable copy of the union of elements. If this is empty // it will be a reference to the empty immutable map. return ImmutableMap.copyOf(union); } /** * Gets the set of qnames of child elements defined in the given transform. */ private static Set<QName> getElementNames(ElementTransform transform) { Set<QName> result = Sets.newHashSet(); for (ElementInfo info : transform.getElements().values()) { result.add(info.key.getId()); } return result; } /** * Checks that two metadata keys are compatible. The datatypes of the * two metadata keys must be identical for them to be considered compatible. */ private static boolean checkCompatible( MetadataKey<?> first, MetadataKey<?> second) { boolean compatible = true; Class<?> firstType = first.getDatatype(); Class<?> secondType = second.getDatatype(); if (firstType != secondType) { logger.warning( "Incompatible datatypes. First(" + first + "): " + firstType + " but Second(" + second + "): " + secondType); compatible = false; } return compatible; } /** * Checks that two element keys are compatible. This checks that the element * types are compatible and the datatypes are the same. If the keys are not * compatible we log a warning explaining why and return {@code null}. If the * element types of the two elements are not the same, but one is a supertype * of the other, we use the supertype, so we parse into the most general type * first and later adapt to a more specific type if necessary. */ private static ElementKey<?, ?> checkCompatibleElements( ElementKey<?, ?> first, ElementKey<?, ?> second) { ElementKey<?, ?> match = first; boolean compatible = true; if (!checkCompatible(first, second)) { compatible = false; } Class<? extends Element> firstType = first.getElementType(); Class<? extends Element> secondType = second.getElementType(); if (firstType != secondType && !firstType.isAssignableFrom(secondType)) { if (secondType.isAssignableFrom(firstType)) { match = second; } else { logger.warning("Incompatible element types." + " First(" + first + "): " + firstType + " but Second(" + second + "): " + secondType); compatible = false; } } return compatible ? match : null; } // Private constructor, static-only class. private AdaptationRegistryFactory() {} }