com.google.api.client.xml.XmlNamespaceDictionary Maven / Gradle / Ivy
/*
* 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.Beta;
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.Preconditions;
import com.google.api.client.util.Types;
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;
import org.xmlpull.v1.XmlSerializer;
/**
* {@link Beta}
* Thread-safe XML namespace dictionary that provides a one-to-one map of namespace alias to URI.
*
* Implementation is thread-safe. For maximum efficiency, applications should use a single
* globally-shared instance of the XML namespace dictionary.
*
*
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.
*
*
Sample usage:
*
*
{@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");
* }
*
* @since 1.0
* @author Yaniv Inbar
*/
@Beta
public final class XmlNamespaceDictionary {
/**
* Map from XML namespace alias (or {@code ""} for the default namespace) to XML namespace URI.
*/
private final HashMap namespaceAliasToUriMap = new HashMap();
/**
* Map from XML namespace URI to XML namespace alias (or {@code ""} for the default namespace).
*/
private final HashMap namespaceUriToAliasMap = new HashMap();
/**
* 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 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 getUriToAliasMap() {
return Collections.unmodifiableMap(namespaceUriToAliasMap);
}
/**
* Adds a namespace of the given alias and URI.
*
* 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.
*
* @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 aliases = new TreeSet();
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 aliases) {
for (Map.Entry 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) && !valueClass.isEnum()) {
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.
*
* 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}.
*
* @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
* errorOnUnknown} 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;
}
@Beta
class ElementSerializer {
private final boolean errorOnUnknown;
Object textValue = null;
final List attributeNames = new ArrayList();
final List