for (Node potentialNode : otherElementChildren) { if (potentialNode.getNodeType() == Node.ELEMENT_NODE) { @NonNull XmlElement otherChildNode = new XmlElement((Element) potentialNode, mDocument); if (childNode.getType() == otherChildNode.getType()) { if (childNode.getType().getNodeKeyResolver().getKeyAttributesNames() .isEmpty()) { message = childNode.compareTo(otherChildNode); if (!message.isPresent()) { return Optional.absent(); if (childNode.getKey() == null) { if (otherChildNode.getKey() == null) { return childNode.compareTo(otherChildNode); if (childNode.getKey().equals(otherChildNode.getKey())) { return childNode.compareTo(otherChildNode); ? message : Optional.of(String.format("Child %1$s not found in document %2$s", childNode.getId(), otherElement.printPosition()));
if (lowerPriorityChild.getType() == ManifestModel.NodeTypes.CUSTOM) { handleCustomElement(lowerPriorityChild, mergingReport); return; getNodeByTypeAndKey(lowerPriorityChild.getType(),lowerPriorityChild.getKey()); addElement(lowerPriorityChild, mergingReport); return; logger.verbose(lowerPriorityChild.getId() + " defined in both files..."); switch (thisChild.getType().getMergeType()) { case CONFLICT: addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format( "Node %1$s cannot be present in more than one input file and it's " + "present at %2$s and %3$s", thisChild.getType(), thisChild.printPosition(), lowerPriorityChild.printPosition() )); break; calculateNodeOperationType(thisChild, lowerPriorityChild); if (operationType == NodeOperationType.REMOVE || operationType == NodeOperationType.REPLACE) { if (thisChild.getType().areMultipleDeclarationAllowed()) { mergeChildrenWithMultipleDeclarations(lowerPriorityChild, mergingReport); } else {
/** * Merges two children when this children's type allow multiple elements declaration with the * same key value. In that case, we only merge the lower priority child if there is not already * an element with the same key value that is equal to the lower priority child. Two children * are equals if they have the same attributes and children declared irrespective of the * declaration order. * * @param lowerPriorityChild the lower priority element's child. * @param mergingReport the merging report to log errors and actions. */ private void mergeChildrenWithMultipleDeclarations( @NonNull XmlElement lowerPriorityChild, @NonNull MergingReport.Builder mergingReport) { Preconditions.checkArgument(lowerPriorityChild.getType().areMultipleDeclarationAllowed()); if (lowerPriorityChild.getType().areMultipleDeclarationAllowed()) { for (XmlElement sameTypeChild : getAllNodesByType(lowerPriorityChild.getType())) { if (sameTypeChild.getId().equals(lowerPriorityChild.getId()) && sameTypeChild.isEquals(lowerPriorityChild)) { return; } } } // if we end up here, we never found a child of this element with the same key and strictly // equals to the lowerPriorityChild so we should merge it in. addElement(lowerPriorityChild, mergingReport); }
@SuppressWarnings("SpellCheckingInspection") private ImmutableList<XmlElement> initMergeableChildren() { ImmutableList.Builder<XmlElement> mergeableNodes = new ImmutableList.Builder<XmlElement>(); NodeList nodeList = getXml().getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node instanceof Element) { XmlElement xmlElement = new XmlElement((Element) node, mDocument); mergeableNodes.add(xmlElement); } } return mergeableNodes.build(); }
private static void checkSelectorPresence(@NonNull MergingReport.Builder mergingReport, @NonNull XmlElement element) { Attr selectorAttribute = element.getXml().getAttributeNodeNS(SdkConstants.TOOLS_URI, Selector.SELECTOR_LOCAL_NAME); if (selectorAttribute!=null && !element.supportsSelector()) { String message = String.format( "Unsupported tools:selector=\"%1$s\" found on node %2$s at %3$s", selectorAttribute.getValue(), element.getId(), element.printPosition()); element.addMessage(mergingReport, ERROR, message); } }
private static void validateManifestAttribute( @NonNull MergingReport.Builder mergingReport, @NonNull XmlElement manifest, XmlDocument.Type fileType) { Attr attributeNode = manifest.getXml().getAttributeNode(AndroidManifest.ATTRIBUTE_PACKAGE); // it's ok for an overlay to not have a package name, it's not ok for a main manifest // and it's a warning for a library. if (attributeNode == null && fileType != XmlDocument.Type.OVERLAY) { manifest.addMessage(mergingReport, fileType == XmlDocument.Type.MAIN ? ERROR : WARNING, String.format( "Missing 'package' declaration in manifest at %1$s", manifest.printPosition())); } }
if (mSelector != null && !mSelector.isResolvable(getDocument().getSelectors())) { mergingReport.addMessage(getSourceFilePosition(), MergingReport.Record.Severity.ERROR, String.format("'tools:selector=\"%1$s\"' is not a valid library identifier, " mergingReport.getLogger().verbose("Merging " + getId() + " with lower " + lowerPriorityNode.printPosition()); MergeType mergeType = getType().getMergeType(); if (isA(ManifestModel.NodeTypes.MANIFEST) && lowerPriorityNode.getDocument().getFileType() != XmlDocument.Type.LIBRARY) { mergeType = MergeType.MERGE; new ArrayList<AttributeModel>(lowerPriorityNode.getType().getAttributeModels()); for (XmlAttribute lowerPriorityAttribute : lowerPriorityNode.getAttributes()) { lowerPriorityAttribute.mergeInHigherPriorityElement(this, mergingReport); if (lowerPriorityAttribute.getModel() != null) { Optional<XmlAttribute> myAttribute = getAttribute(attributeModel.getName()); if (myAttribute.isPresent()) { myAttribute.get().mergeWithLowerPriorityDefaultValue( mergeChildren(lowerPriorityNode, mergingReport); } else { for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) { mergingReport.getActionRecorder().recordNodeAction(this,
if (getXml().getNamespaceURI() != null) { if (!getXml().getLocalName().equals(otherNode.getXml().getLocalName())) { return Optional.of( String.format("Element names do not match: %1$s versus %2$s", getXml().getLocalName(), otherNode.getXml().getLocalName())); String thisNS = getXml().getNamespaceURI(); String otherNS = otherNode.getXml().getNamespaceURI(); if ((thisNS == null && otherNS != null) || (thisNS != null && !thisNS.equals(otherNS))) { if (!getXml().getNodeName().equals(otherNode.getXml().getNodeName())) { return Optional.of(String.format("Element names do not match: %1$s versus %2$s", getXml().getNodeName(), otherNode.getXml().getNodeName())); Optional<String> message = checkAttributes(this, otherNode); if (message.isPresent()) { return message; message = checkAttributes(otherNode, this); if (message.isPresent()) { return message; @NonNull List<Node> expectedChildren = filterUninterestingNodes(getXml().getChildNodes()); @NonNull List<Node> actualChildren = filterUninterestingNodes(otherNode.getXml().getChildNodes()); if (expectedChildren.size() != actualChildren.size()) {
for (XmlElement childElement : xmlElement.getMergeableElements()) { if (childElement.getOperationType() == NodeOperationType.REMOVE_ALL) { validateRemoveAllOperation(mergingReport, childElement); } else { if (checkKeyPresence(mergingReport, childElement)) { XmlElement twin = childrenKeys.get(childElement.getId()); if (twin != null && !childElement.getType().areMultipleDeclarationAllowed()) { childElement.getId(), childElement.printPosition(), childrenKeys.get(childElement.getId()).printPosition()); if (twin.compareTo(childElement).isPresent()) { childElement.addMessage(mergingReport, ERROR, message); } else { childElement.addMessage(mergingReport, WARNING, message); childrenKeys.put(childElement.getId(), childElement);
@NonNull MergingReport.Builder mergingReport) { @NonNull NodeOperationType operationType = calculateNodeOperationType(higherPriority, lowerPriority); Actions.ActionType.MERGED, lowerPriority); higherPriority.mergeWithLowerPriorityNode(lowerPriority, mergingReport); break; case REMOVE: break; case STRICT: Optional<String> compareMessage = higherPriority.compareTo(lowerPriority); if (compareMessage.isPresent()) { addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format( "Node %1$s at %2$s is tagged with tools:node=\"strict\", yet " + "%3$s at %4$s is different : %5$s", higherPriority.getId(), higherPriority.printPosition(), lowerPriority.getId(), lowerPriority.printPosition(), compareMessage.get() )); "Unhandled node operation type %s", higherPriority.getOperationType()); break;
@NonNull MergingReport.Builder mergingReport) { NodeOperationType operationType = xmlElement.getOperationType(); switch (operationType) { case REPLACE: xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING, String.format( "%1$s was tagged at %2$s:%3$d to replace another declaration " + "but no other declaration present", xmlElement.getId(), xmlElement.getDocument().getSourceFile().print(true), xmlElement.getPosition().getStartLine() + 1 )); xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING, String.format( "%1$s was tagged at %2$s:%3$d to remove other declarations " + "but no other declaration present", xmlElement.getId(), xmlElement.getDocument().getSourceFile().print(true), xmlElement.getPosition().getStartLine() + 1 )); for (XmlElement child : xmlElement.getMergeableElements()) { validate(child, actions, mergingReport);
@NonNull MergingReport.Builder mergingReport, @NonNull XmlElement xmlElement) { ManifestModel.NodeKeyResolver nodeKeyResolver = xmlElement.getType().getNodeKeyResolver(); ImmutableList<String> keyAttributesNames = nodeKeyResolver.getKeyAttributesNames(); if (keyAttributesNames.isEmpty()) { return false; if (Strings.isNullOrEmpty(xmlElement.getKey())) { "Missing one of the key attributes '%1$s' on element %2$s at %3$s", Joiner.on(',').join(keyAttributesNames), xmlElement.getId(), xmlElement.printPosition()) : String.format( "Missing '%1$s' key attribute on element %2$s at %3$s", keyAttributesNames.get(0), xmlElement.getId(), xmlElement.printPosition()); xmlElement.addMessage(mergingReport, ERROR, message); return false;
= xmlElement.getAttributeOperations(); for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation : attributeOperations) { if (!isAttributeOperationPresent( xmlElement, attributeOperation, actions, ActionType.REJECTED)) { xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING, String.format( "%1$s@%2$s was tagged at %3$s:%4$d to remove other" + " declarations but no other declaration present", xmlElement.getId(), attributeOperation.getKey(), xmlElement.getDocument().getSourceFile().print(true), xmlElement.getPosition().getStartLine() + 1 )); if (!isAttributeOperationPresent( xmlElement, attributeOperation, actions, ActionType.REJECTED)) { xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING, String.format( "%1$s@%2$s was tagged at %3$s:%4$d to replace other" + " declarations but no other declaration present", xmlElement.getId(), attributeOperation.getKey(), xmlElement.getDocument().getSourceFile().print(true), xmlElement.getPosition().getStartLine() + 1 ));
xmlElement.getAttributeOperations()) { .getAttribute(attributeOperationTypeEntry.getKey()); switch(attributeOperationTypeEntry.getValue()) { case STRICT: xmlElement.addMessage(mergingReport, ERROR, String.format( "tools:remove specified at line:%d for attribute %s, but " + "attribute also declared at line:%d, " + "do you want to use tools:replace instead ?", xmlElement.getPosition().getStartLine() + 1, attributeOperationTypeEntry.getKey(), attribute.get().getPosition().getStartLine() + 1 xmlElement.addMessage(mergingReport, ERROR, String.format( "tools:replace specified at line:%d for attribute %s, but " + "no new value specified", xmlElement.getPosition().getStartLine() + 1, attributeOperationTypeEntry.getKey() ));
for (XmlElement childElement : xmlDocument.getRootNode().getMergeableElements()) { if (childElement.getType().equals(ManifestModel.NodeTypes.USES_FEATURE)) { Integer value = getGlEsVersion(childElement); if (value != null) { Attr requiredAttribute = glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS( ANDROID_URI, AndroidManifest.ATTRIBUTE_REQUIRED); if (glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(ANDROID_URI, SdkConstants.ATTR_NAME) != null) { glEsVersionDeclaration.getValue().getXml().removeAttributeNS(ANDROID_URI, AndroidManifest.ATTRIBUTE_GLESVERSION); mergingReport.getActionRecorder().recordAttributeAction( glEsVersionDeclaration.getValue().getAttribute(XmlNode.fromXmlName( "android:" + AndroidManifest.ATTRIBUTE_GLESVERSION)).get(), Actions.ActionType.REJECTED, xmlDocument.getRootNode().getXml().removeChild( glEsVersionDeclaration.getValue().getXml()); mergingReport.getActionRecorder().recordNodeAction( glEsVersionDeclaration.getValue(),
/** * Handles presence of custom elements (elements not part of the android or tools * namespaces). Such elements are merged unchanged into the resulting document, and * optionally, the namespace definition is added to the merged document root element. * @param customElement the custom element present in the lower priority document. * @param mergingReport the merging report to log errors and actions. */ private void handleCustomElement(@NonNull XmlElement customElement, @NonNull MergingReport.Builder mergingReport) { addElement(customElement, mergingReport); // add the custom namespace to the document generation. String nodeName = customElement.getXml().getNodeName(); if (!nodeName.contains(":")) { return; } @NonNull String prefix = nodeName.substring(0, nodeName.indexOf(':')); String namespace = customElement.getDocument().getRootNode() .getXml().getAttribute(SdkConstants.XMLNS_PREFIX + prefix); if (namespace != null) { getDocument().getRootNode().getXml().setAttributeNS( SdkConstants.XMLNS_URI, SdkConstants.XMLNS_PREFIX + prefix, namespace); } }
private static Optional<String> checkAttributes( @NonNull XmlElement expected, @NonNull XmlElement actual) { for (XmlAttribute expectedAttr : expected.getAttributes()) { XmlAttribute.NodeName attributeName = expectedAttr.getName(); if (attributeName.isInNamespace(SdkConstants.TOOLS_URI)) { continue; } Optional<XmlAttribute> actualAttr = actual.getAttribute(attributeName); if (actualAttr.isPresent()) { if (!expectedAttr.getValue().equals(actualAttr.get().getValue())) { return Optional.of( String.format("Attribute %1$s do not match: %2$s versus %3$s at %4$s", expectedAttr.getId(), expectedAttr.getValue(), actualAttr.get().getValue(), actual.printPosition())); } } else { return Optional.of(String.format("Attribute %1$s not found at %2$s", expectedAttr.getId(), actual.printPosition())); } } return Optional.absent(); }
@NonNull private static XmlElement createOrGetElement( @NonNull ActionRecorder actionRecorder, @NonNull XmlDocument document, @NonNull ManifestModel.NodeTypes nodeType, @NonNull String message) { Element manifest = document.getXml().getDocumentElement(); NodeList nodes = manifest.getElementsByTagName(nodeType.toXmlName()); if (nodes.getLength() == 0) { nodes = manifest.getElementsByTagNameNS( SdkConstants.ANDROID_URI, nodeType.toXmlName()); } if (nodes.getLength() == 0) { // create it first. Element node = manifest.getOwnerDocument().createElement(nodeType.toXmlName()); manifest.appendChild(node); XmlElement xmlElement = new XmlElement(node, document); Actions.NodeRecord nodeRecord = new Actions.NodeRecord( Actions.ActionType.INJECTED, new SourceFilePosition(xmlElement.getSourceFile(), SourcePosition.UNKNOWN), xmlElement.getId(), message, NodeOperationType.STRICT); actionRecorder.recordNodeAction(xmlElement, nodeRecord); return xmlElement; } else { return new XmlElement((Element) nodes.item(0), document); } }
higherPriorityElement.getAttribute(getName()); higherPriorityElement.getAttributeOperationType(getName()); getName().addToNode(higherPriorityElement.getXml(), mergedValue); this, Actions.ActionType.ADDED, getOwnerElement().getAttributeOperationType(getName()));
overlayDocument.getXmlDocument().getRootNode().getXml().setAttribute("package", mainPackageAttribute.get().getValue()); xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder); .getXml().getAttribute("package"); xmlDocumentOptional.get().getRootNode().getXml() .setAttribute("package", mainManifestPackageName); PostValidator.validate(finalMergedDocument, mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { finalMergedDocument.getRootNode().addMessage(mergingReportBuilder, MergingReport.Record.Severity.WARNING, "Post merge validation failed");