com.android.manifmerger.XmlNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of manifest-merger Show documentation
Show all versions of manifest-merger Show documentation
A Library to merge Android manifests.
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.android.manifmerger;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.concurrency.Immutable;
import com.android.ide.common.blame.SourceFile;
import com.android.ide.common.blame.SourceFilePosition;
import com.android.ide.common.blame.SourcePosition;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Common behavior of any xml declaration.
*/
public abstract class XmlNode {
protected static final Function NODE_TO_NAME =
new Function() {
@Override
public String apply(Node input) {
return input.getNodeName();
}
};
@Nullable
private NodeKey mOriginalId = null;
/**
* Returns a constant Nodekey that can be used throughout the lifecycle of the xml element.
* The {@link #getId} can return different values over time as the key of the element can be
* for instance, changed through placeholder replacement.
*/
@NonNull
public synchronized NodeKey getOriginalId() {
if (mOriginalId == null) {
mOriginalId = getId();
}
return mOriginalId;
}
/**
* Returns an unique id within the manifest file for the element.
*/
@NonNull
public abstract NodeKey getId();
/**
* Returns the element's position
*/
@NonNull
public abstract SourcePosition getPosition();
/**
* Returns the element's document xml source file location.
*/
@NonNull
public abstract SourceFile getSourceFile();
/**
* Returns the element's document xml source file location.
*/
@NonNull
public SourceFilePosition getSourceFilePosition() {
return new SourceFilePosition(getSourceFile(), getPosition());
}
/**
* Returns the element's xml
*/
@NonNull
public abstract Node getXml();
/**
* Returns the name of this xml element or attribute.
*/
@NonNull
public abstract NodeName getName();
/**
* Abstraction to an xml name to isolate whether the name has a namespace or not.
*/
public interface NodeName {
/**
* Returns true if this attribute name has a namespace declaration and that namespapce is
* the same as provided, false otherwise.
*/
boolean isInNamespace(@NonNull String namespaceURI);
/**
* Adds a new attribute of this name to a xml element with a value.
* @param to the xml element to add the attribute to.
* @param withValue the new attribute's value.
*/
void addToNode(@NonNull Element to, String withValue);
/**
* The local name.
*/
String getLocalName();
}
/**
* Factory method to create an instance of {@link com.android.manifmerger.XmlNode.NodeName}
* for an existing xml node.
* @param node the xml definition.
* @return an instance of {@link com.android.manifmerger.XmlNode.NodeName} providing
* namespace handling.
*/
@NonNull
public static NodeName unwrapName(@NonNull Node node) {
return node.getNamespaceURI() == null
? new Name(node.getNodeName())
: new NamespaceAwareName(node);
}
@NonNull
public static NodeName fromXmlName(@NonNull String name) {
return (name.contains(":"))
? new NamespaceAwareName(SdkConstants.ANDROID_URI,
name.substring(0, name.indexOf(':')),
name.substring(name.indexOf(':') + 1))
: new Name(name);
}
@NonNull
public static NodeName fromNSName(
@NonNull String namespaceUri, @NonNull String prefix, @NonNull String localName) {
return new NamespaceAwareName(namespaceUri, prefix, localName);
}
/**
* Returns the position of this attribute in the original xml file. This may return an invalid
* location as this xml fragment does not exist in any xml file but is the temporary result
* of the merging process.
* @return a human readable position.
*/
@NonNull
public String printPosition() {
return getSourceFilePosition().print(true /*shortFormat*/);
}
/**
* Implementation of {@link com.android.manifmerger.XmlNode.NodeName} for an
* node's declaration not using a namespace.
*/
public static final class Name implements NodeName {
private final String mName;
private Name(@NonNull String name) {
this.mName = Preconditions.checkNotNull(name);
}
@Override
public boolean isInNamespace(@NonNull String namespaceURI) {
return false;
}
@Override
public void addToNode(@NonNull Element to, String withValue) {
to.setAttribute(mName, withValue);
}
@Override
public boolean equals(@Nullable Object o) {
return (o != null && o instanceof Name && ((Name) o).mName.equals(this.mName));
}
@Override
public int hashCode() {
return Objects.hashCode(mName);
}
@Override
public String toString() {
return mName;
}
@Override
public String getLocalName() {
return mName;
}
}
/**
* Implementation of the {@link com.android.manifmerger.XmlNode.NodeName} for a namespace aware attribute.
*/
public static final class NamespaceAwareName implements NodeName {
@NonNull
private final String mNamespaceURI;
// ignore for comparison and hashcoding since different documents can use different
// prefixes for the same namespace URI.
@NonNull
private final String mPrefix;
@NonNull
private final String mLocalName;
private NamespaceAwareName(@NonNull Node node) {
this.mNamespaceURI = Preconditions.checkNotNull(node.getNamespaceURI());
this.mPrefix = Preconditions.checkNotNull(node.getPrefix());
this.mLocalName = Preconditions.checkNotNull(node.getLocalName());
}
private NamespaceAwareName(@NonNull String namespaceURI,
@NonNull String prefix,
@NonNull String localName) {
mNamespaceURI = Preconditions.checkNotNull(namespaceURI);
mPrefix = Preconditions.checkNotNull(prefix);
mLocalName = Preconditions.checkNotNull(localName);
}
@Override
public boolean isInNamespace(@NonNull String namespaceURI) {
return mNamespaceURI.equals(namespaceURI);
}
@Override
public void addToNode(@NonNull Element to, String withValue) {
// TODO: consider standardizing everything on "android:"
to.setAttributeNS(mNamespaceURI, mPrefix + ":" + mLocalName, withValue);
}
@Override
public int hashCode() {
return Objects.hashCode(mNamespaceURI, mLocalName);
}
@Override
public boolean equals(@Nullable Object o) {
return (o != null && o instanceof NamespaceAwareName
&& ((NamespaceAwareName) o).mLocalName.equals(this.mLocalName)
&& ((NamespaceAwareName) o).mNamespaceURI.equals(this.mNamespaceURI));
}
@NonNull
@Override
public String toString() {
return mPrefix + ":" + mLocalName;
}
@NonNull
@Override
public String getLocalName() {
return mLocalName;
}
}
/**
* A xml element or attribute key.
*/
@Immutable
public static class NodeKey {
@NonNull
private final String mKey;
NodeKey(@NonNull String key) {
mKey = key;
}
public static NodeKey fromXml(@NonNull Element element) {
return new OrphanXmlElement(element).getId();
}
@NonNull
@Override
public String toString() {
return mKey;
}
@Override
public boolean equals(@Nullable Object o) {
return (o != null && o instanceof NodeKey && ((NodeKey) o).mKey.equals(this.mKey));
}
@Override
public int hashCode() {
return Objects.hashCode(mKey);
}
}
}