diff options
author | Googler <noreply@google.com> | 2016-07-14 22:31:10 +0000 |
---|---|---|
committer | Dmitry Lomov <dslomov@google.com> | 2016-07-15 13:31:04 +0000 |
commit | 3b25028750dd7a6df6777f6c70c1feae9063a630 (patch) | |
tree | ed88d83b83b6fad418199d48c146a27e35fa0782 /src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java | |
parent | e629537510a297b587d7204549dd2aae9222728e (diff) |
Record and propagate namespaces from the <resources> element correctly.
* Reduces the size of merged values.xml
* Improves correctness of merged xml
Sadly, this is also backwards compatible by allowing multiple definitions of a prefix with different namespaces.
Will be cleaned up after transition.
--
MOS_MIGRATED_REVID=127481147
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java new file mode 100644 index 0000000000..cbaabea740 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java @@ -0,0 +1,159 @@ +// Copyright 2016 The Bazel Authors. All rights reserved. +// +// 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.devtools.build.android.xml; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.android.DataResourceXml; +import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Logger; +import javax.xml.namespace.QName; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.StartElement; + +/** + * Represents a collection of xml namespaces. + * + * <p>Each <resources> can have xmlns declarations. Since the merging process generates the + * resources tag to combining multiple {@link DataResourceXml}s, the Namespaces must be tracked and + * kept with each value. + */ +public class Namespaces implements Iterable<Entry<String, String>> { + private static final Logger logger = Logger.getLogger(Namespaces.class.getCanonicalName()); + private static final Namespaces EMPTY_INSTANCE = + new Namespaces(ImmutableMap.<String, String>of()); + + /** + * Collects prefix and uri pairs from elements. + */ + public static class Collector { + private Map<String, String> prefixToUri = new HashMap<>(); + + public Namespaces toNamespaces() { + return Namespaces.from(prefixToUri); + } + + /** + * Collects all the prefix and uri pairs from a start element. + * + * <p>Since {@link Namespaces} represents top level declarations, collectFrom ignores any prefix + * that is declared on the element. Those will be handled by the {@link XmlResourceValue} + * individually. + * + * @param start The element to collect prefix and uris from. + * @return The current namespace builder. + */ + public Collector collectFrom(StartElement start) { + Iterator<Attribute> attributes = XmlResourceValues.iterateAttributesFrom(start); + Iterator<Namespace> localNamespaces = XmlResourceValues.iterateNamespacesFrom(start); + // Collect the local prefixes to make sure a prefix isn't declared locally. + Set<String> prefixes = new HashSet<>(); + while (localNamespaces.hasNext()) { + prefixes.add(localNamespaces.next().getPrefix()); + } + collectFrom(start.getName(), prefixes); + while (attributes.hasNext()) { + collectFrom(attributes.next().getName(), prefixes); + } + return this; + } + + void collectFrom(QName name, Set<String> localPrefixes) { + String prefix = name.getPrefix(); + if (!prefix.isEmpty() && !localPrefixes.contains(prefix)) { + // If the prefix exists and is not a locally declared prefix, add the prefix and uri. + prefixToUri.put(prefix, name.getNamespaceURI()); + } + } + } + + public static Collector collector() { + return new Collector(); + } + + public static Namespaces from(Map<String, String> prefixToUri) { + if (prefixToUri.isEmpty()) { + return empty(); + } + return new Namespaces(ImmutableMap.copyOf(prefixToUri)); + } + + public static Namespaces empty() { + return EMPTY_INSTANCE; + } + + private ImmutableMap<String, String> prefixToUri; + + private Namespaces(ImmutableMap<String, String> prefixToUri) { + this.prefixToUri = prefixToUri; + } + + /** Combines two {@link Namespaces} into a new instance and returns it. */ + public Namespaces union(Namespaces other) { + // No prefixes to add, return the other. + if (prefixToUri.isEmpty()) { + return other; + } + // TODO(corysmith): Issue error when prefixes are mapped to different uris. + // Keeping behavior for backwards compatibility. + Map<String, String> combinedNamespaces = new LinkedHashMap<>(); + combinedNamespaces.putAll(other.prefixToUri); + for (Entry<String, String> namespace : prefixToUri.entrySet()) { + String prefix = namespace.getKey(); + String namespaceUri = namespace.getValue(); + if (combinedNamespaces.containsKey(prefix) + && !combinedNamespaces.get(prefix).equals(namespaceUri)) { + logger.warning( + String.format( + "%s has multiple namespaces: %s and %s. Using %s." + + " This will be an error in the future.", + prefix, namespaceUri, combinedNamespaces.get(prefix), namespaceUri)); + } + combinedNamespaces.put(prefix, namespaceUri); + } + return Namespaces.from(combinedNamespaces); + } + + @Override + public Iterator<Entry<String, String>> iterator() { + return prefixToUri.entrySet().iterator(); + } + + public Map<String, String> asMap() { + return prefixToUri; + } + + @Override + public int hashCode() { + return prefixToUri.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Namespaces) { + Namespaces other = (Namespaces) obj; + return Objects.equals(prefixToUri, other.prefixToUri); + } + return false; + } +} |