aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-07-14 22:31:10 +0000
committerGravatar Dmitry Lomov <dslomov@google.com>2016-07-15 13:31:04 +0000
commit3b25028750dd7a6df6777f6c70c1feae9063a630 (patch)
treeed88d83b83b6fad418199d48c146a27e35fa0782 /src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java
parente629537510a297b587d7204549dd2aae9222728e (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.java159
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 &lt;resources&gt; 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;
+ }
+}