/google-http-client/src/main/java/com/google/api/client/xml/XmlNamespaceDictionary.java
https://code.google.com/p/google-http-java-client/ · Java · 437 lines · 270 code · 26 blank · 141 comment · 87 complexity · 86967e56d9ee6fa41e8ec256a6d225f9 MD5 · raw file
- /*
- * Copyright (c) 2010 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.api.client.xml;
- import com.google.api.client.util.Data;
- import com.google.api.client.util.DateTime;
- import com.google.api.client.util.FieldInfo;
- import com.google.api.client.util.Types;
- import com.google.common.base.Preconditions;
- import org.xmlpull.v1.XmlSerializer;
- import java.io.IOException;
- import java.io.StringWriter;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.SortedSet;
- import java.util.TreeSet;
- /**
- * Thread-safe XML namespace dictionary that provides a one-to-one map of namespace alias to URI.
- *
- * <p>
- * Implementation is thread-safe. For maximum efficiency, applications should use a single
- * globally-shared instance of the XML namespace dictionary.
- * </p>
- *
- * <p>
- * A namespace alias is uniquely mapped to a single namespace URI, and a namespace URI is uniquely
- * mapped to a single namespace alias. In other words, it is not possible to have duplicates.
- * </p>
- *
- * <p>
- * Sample usage:
- * </p>
- *
- * <pre>{@code
- static final XmlNamespaceDictionary DICTIONARY = new XmlNamespaceDictionary()
- .set("", "http://www.w3.org/2005/Atom")
- .set("activity", "http://activitystrea.ms/spec/1.0/")
- .set("georss", "http://www.georss.org/georss")
- .set("media", "http://search.yahoo.com/mrss/")
- .set("thr", "http://purl.org/syndication/thread/1.0");
- *}</pre>
- *
- * @since 1.0
- * @author Yaniv Inbar
- */
- public final class XmlNamespaceDictionary {
- /**
- * Map from XML namespace alias (or {@code ""} for the default namespace) to XML namespace URI.
- */
- private final HashMap<String, String> namespaceAliasToUriMap = new HashMap<String, String>();
- /**
- * Map from XML namespace URI to XML namespace alias (or {@code ""} for the default namespace).
- */
- private final HashMap<String, String> namespaceUriToAliasMap = new HashMap<String, String>();
- /**
- * Returns the namespace alias (or {@code ""} for the default namespace) for the given namespace
- * URI.
- *
- * @param uri namespace URI
- * @since 1.3
- */
- public synchronized String getAliasForUri(String uri) {
- return namespaceUriToAliasMap.get(Preconditions.checkNotNull(uri));
- }
- /**
- * Returns the namespace URI for the given namespace alias (or {@code ""} for the default
- * namespace).
- *
- * @param alias namespace alias (or {@code ""} for the default namespace)
- * @since 1.3
- */
- public synchronized String getUriForAlias(String alias) {
- return namespaceAliasToUriMap.get(Preconditions.checkNotNull(alias));
- }
- /**
- * Returns an unmodified set of map entries for the map from namespace alias (or {@code ""} for
- * the default namespace) to namespace URI.
- *
- * @since 1.3
- */
- public synchronized Map<String, String> getAliasToUriMap() {
- return Collections.unmodifiableMap(namespaceAliasToUriMap);
- }
- /**
- * Returns an unmodified set of map entries for the map from namespace URI to namespace alias (or
- * {@code ""} for the default namespace).
- *
- * @since 1.3
- */
- public synchronized Map<String, String> getUriToAliasMap() {
- return Collections.unmodifiableMap(namespaceUriToAliasMap);
- }
- /**
- * Adds a namespace of the given alias and URI.
- *
- * <p>
- * If the uri is {@code null}, the namespace alias will be removed. Similarly, if the alias is
- * {@code null}, the namespace URI will be removed. Otherwise, if the alias is already mapped to a
- * different URI, it will be remapped to the new URI. Similarly, if a URI is already mapped to a
- * different alias, it will be remapped to the new alias.
- * </p>
- *
- * @param alias alias or {@code null} to remove the namespace URI
- * @param uri namespace URI or {@code null} to remove the namespace alias
- * @return this namespace dictionary
- * @since 1.3
- */
- public synchronized XmlNamespaceDictionary set(String alias, String uri) {
- String previousUri = null;
- String previousAlias = null;
- if (uri == null) {
- if (alias != null) {
- previousUri = namespaceAliasToUriMap.remove(alias);
- }
- } else if (alias == null) {
- previousAlias = namespaceUriToAliasMap.remove(uri);
- } else {
- previousUri = namespaceAliasToUriMap.put(
- Preconditions.checkNotNull(alias), Preconditions.checkNotNull(uri));
- if (!uri.equals(previousUri)) {
- previousAlias = namespaceUriToAliasMap.put(uri, alias);
- } else {
- previousUri = null;
- }
- }
- if (previousUri != null) {
- namespaceUriToAliasMap.remove(previousUri);
- }
- if (previousAlias != null) {
- namespaceAliasToUriMap.remove(previousAlias);
- }
- return this;
- }
- /**
- * Shows a debug string representation of an element data object of key/value pairs.
- *
- * @param element element data object ({@link GenericXml}, {@link Map}, or any object with public
- * fields)
- * @param elementName optional XML element local name prefixed by its namespace alias -- for
- * example {@code "atom:entry"} -- or {@code null} to make up something
- */
- public String toStringOf(String elementName, Object element) {
- try {
- StringWriter writer = new StringWriter();
- XmlSerializer serializer = Xml.createSerializer();
- serializer.setOutput(writer);
- serialize(serializer, elementName, element, false);
- return writer.toString();
- } catch (IOException e) {
- throw new IllegalArgumentException(e);
- }
- }
- /**
- * Shows a debug string representation of an element data object of key/value pairs.
- *
- * @param element element data object ({@link GenericXml}, {@link Map}, or any object with public
- * fields)
- * @param elementNamespaceUri XML namespace URI or {@code null} for no namespace
- * @param elementLocalName XML local name
- * @throws IOException I/O exception
- */
- public void serialize(
- XmlSerializer serializer, String elementNamespaceUri, String elementLocalName, Object element)
- throws IOException {
- serialize(serializer, elementNamespaceUri, elementLocalName, element, true);
- }
- /**
- * Shows a debug string representation of an element data object of key/value pairs.
- *
- * @param element element data object ({@link GenericXml}, {@link Map}, or any object with public
- * fields)
- * @param elementName XML element local name prefixed by its namespace alias
- * @throws IOException I/O exception
- */
- public void serialize(XmlSerializer serializer, String elementName, Object element)
- throws IOException {
- serialize(serializer, elementName, element, true);
- }
- private void serialize(XmlSerializer serializer, String elementNamespaceUri,
- String elementLocalName, Object element, boolean errorOnUnknown) throws IOException {
- String elementAlias = elementNamespaceUri == null ? null : getAliasForUri(elementNamespaceUri);
- startDoc(serializer, element, errorOnUnknown, elementAlias).serialize(
- serializer, elementNamespaceUri, elementLocalName);
- serializer.endDocument();
- }
- private void serialize(
- XmlSerializer serializer, String elementName, Object element, boolean errorOnUnknown)
- throws IOException {
- String elementAlias = "";
- if (elementName != null) {
- int colon = elementName.indexOf(':');
- if (colon != -1) {
- elementAlias = elementName.substring(0, colon);
- }
- }
- startDoc(serializer, element, errorOnUnknown, elementAlias).serialize(serializer, elementName);
- serializer.endDocument();
- }
- private ElementSerializer startDoc(
- XmlSerializer serializer, Object element, boolean errorOnUnknown, String elementAlias)
- throws IOException {
- serializer.startDocument(null, null);
- SortedSet<String> aliases = new TreeSet<String>();
- computeAliases(element, aliases);
- if (elementAlias != null) {
- aliases.add(elementAlias);
- }
- for (String alias : aliases) {
- String uri = getNamespaceUriForAliasHandlingUnknown(errorOnUnknown, alias);
- serializer.setPrefix(alias, uri);
- }
- return new ElementSerializer(element, errorOnUnknown);
- }
- private void computeAliases(Object element, SortedSet<String> aliases) {
- for (Map.Entry<String, Object> entry : Data.mapOf(element).entrySet()) {
- Object value = entry.getValue();
- if (value != null) {
- String name = entry.getKey();
- if (!Xml.TEXT_CONTENT.equals(name)) {
- int colon = name.indexOf(':');
- boolean isAttribute = name.charAt(0) == '@';
- if (colon != -1 || !isAttribute) {
- String alias = colon == -1 ? "" : name.substring(name.charAt(0) == '@' ? 1 : 0, colon);
- aliases.add(alias);
- }
- Class<?> valueClass = value.getClass();
- if (!isAttribute && !Data.isPrimitive(valueClass)) {
- if (value instanceof Iterable<?> || valueClass.isArray()) {
- for (Object subValue : Types.iterableOf(value)) {
- computeAliases(subValue, aliases);
- }
- } else {
- computeAliases(value, aliases);
- }
- }
- }
- }
- }
- }
- /**
- * Returns the namespace URI to use for serialization for a given namespace alias, possibly using
- * a predictable made-up namespace URI if the alias is not recognized.
- *
- * <p>
- * Specifically, if the namespace alias is not recognized, the namespace URI returned will be
- * {@code "http://unknown/"} plus the alias, unless {@code errorOnUnknown} is {@code true} in
- * which case it will throw an {@link IllegalArgumentException}.
- * </p>
- *
- * @param errorOnUnknown whether to thrown an exception if the namespace alias is not recognized
- * @param alias namespace alias
- * @return namespace URI, using a predictable made-up namespace URI if the namespace alias is not
- * recognized
- * @throws IllegalArgumentException if the namespace alias is not recognized and {@code
- * errorOnUnkown} is {@code true}
- */
- String getNamespaceUriForAliasHandlingUnknown(boolean errorOnUnknown, String alias) {
- String result = getUriForAlias(alias);
- if (result == null) {
- Preconditions.checkArgument(
- !errorOnUnknown, "unrecognized alias: %s", alias.length() == 0 ? "(default)" : alias);
- return "http://unknown/" + alias;
- }
- return result;
- }
- /**
- * Returns the namespace alias to use for a given namespace URI, throwing an exception if the
- * namespace URI can be found in this dictionary.
- *
- * @param namespaceUri namespace URI
- * @throws IllegalArgumentException if the namespace URI is not found in this dictionary
- */
- String getNamespaceAliasForUriErrorOnUnknown(String namespaceUri) {
- String result = getAliasForUri(namespaceUri);
- Preconditions.checkArgument(result != null,
- "invalid XML: no alias declared for namesapce <%s>; "
- + "work-around by setting XML namepace directly by calling the set method of %s",
- namespaceUri, XmlNamespaceDictionary.class.getName());
- return result;
- }
- class ElementSerializer {
- private final boolean errorOnUnknown;
- Object textValue = null;
- final List<String> attributeNames = new ArrayList<String>();
- final List<Object> attributeValues = new ArrayList<Object>();
- final List<String> subElementNames = new ArrayList<String>();
- final List<Object> subElementValues = new ArrayList<Object>();
- ElementSerializer(Object elementValue, boolean errorOnUnknown) {
- this.errorOnUnknown = errorOnUnknown;
- Class<?> valueClass = elementValue.getClass();
- if (Data.isPrimitive(valueClass) && !Data.isNull(elementValue)) {
- textValue = elementValue;
- } else {
- for (Map.Entry<String, Object> entry : Data.mapOf(elementValue).entrySet()) {
- Object fieldValue = entry.getValue();
- if (fieldValue != null && !Data.isNull(fieldValue)) {
- String fieldName = entry.getKey();
- if (Xml.TEXT_CONTENT.equals(fieldName)) {
- textValue = fieldValue;
- } else if (fieldName.charAt(0) == '@') {
- attributeNames.add(fieldName.substring(1));
- attributeValues.add(fieldValue);
- } else {
- subElementNames.add(fieldName);
- subElementValues.add(fieldValue);
- }
- }
- }
- }
- }
- void serialize(XmlSerializer serializer, String elementName) throws IOException {
- String elementLocalName = null;
- String elementNamespaceUri = null;
- if (elementName != null) {
- int colon = elementName.indexOf(':');
- elementLocalName = elementName.substring(colon + 1);
- String alias = colon == -1 ? "" : elementName.substring(0, colon);
- elementNamespaceUri = getNamespaceUriForAliasHandlingUnknown(errorOnUnknown, alias);
- }
- serialize(serializer, elementNamespaceUri, elementLocalName);
- }
- void serialize(XmlSerializer serializer, String elementNamespaceUri, String elementLocalName)
- throws IOException {
- boolean errorOnUnknown = this.errorOnUnknown;
- if (elementLocalName == null) {
- if (errorOnUnknown) {
- throw new IllegalArgumentException("XML name not specified");
- }
- elementLocalName = "unknownName";
- }
- serializer.startTag(elementNamespaceUri, elementLocalName);
- // attributes
- int num = attributeNames.size();
- for (int i = 0; i < num; i++) {
- String attributeName = attributeNames.get(i);
- int colon = attributeName.indexOf(':');
- String attributeLocalName = attributeName.substring(colon + 1);
- String attributeNamespaceUri = colon == -1 ? null : getNamespaceUriForAliasHandlingUnknown(
- errorOnUnknown, attributeName.substring(0, colon));
- serializer.attribute(
- attributeNamespaceUri, attributeLocalName, toSerializedValue(attributeValues.get(i)));
- }
- // text
- if (textValue != null) {
- serializer.text(toSerializedValue(textValue));
- }
- // elements
- num = subElementNames.size();
- for (int i = 0; i < num; i++) {
- Object subElementValue = subElementValues.get(i);
- String subElementName = subElementNames.get(i);
- Class<? extends Object> valueClass = subElementValue.getClass();
- if (subElementValue instanceof Iterable<?> || valueClass.isArray()) {
- for (Object subElement : Types.iterableOf(subElementValue)) {
- if (subElement != null && !Data.isNull(subElement)) {
- new ElementSerializer(subElement, errorOnUnknown).serialize(
- serializer, subElementName);
- }
- }
- } else {
- new ElementSerializer(subElementValue, errorOnUnknown).serialize(
- serializer, subElementName);
- }
- }
- serializer.endTag(elementNamespaceUri, elementLocalName);
- }
- }
- static String toSerializedValue(Object value) {
- if (value instanceof Float) {
- Float f = (Float) value;
- if (f.floatValue() == Float.POSITIVE_INFINITY) {
- return "INF";
- }
- if (f.floatValue() == Float.NEGATIVE_INFINITY) {
- return "-INF";
- }
- }
- if (value instanceof Double) {
- Double d = (Double) value;
- if (d.doubleValue() == Double.POSITIVE_INFINITY) {
- return "INF";
- }
- if (d.doubleValue() == Double.NEGATIVE_INFINITY) {
- return "-INF";
- }
- }
- if (value instanceof String || value instanceof Number || value instanceof Boolean) {
- return value.toString();
- }
- if (value instanceof DateTime) {
- return ((DateTime) value).toStringRfc3339();
- }
- if (value instanceof Enum<?>) {
- return FieldInfo.of((Enum<?>) value).getName();
- }
- throw new IllegalArgumentException("unrecognized value type: " + value.getClass());
- }
- }