/** * Compares this document to another {@link com.android.manifmerger.XmlDocument} ignoring all * attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace. * * @param other the other document to compare against. * @return a {@link String} describing the differences between the two XML elements or * {@link Optional#absent()} if they are equals. */ @SuppressWarnings("CovariantCompareTo") public Optional<String> compareTo(@NonNull XmlDocument other) { return getRootNode().compareTo(other.getRootNode()); }
public Optional<XmlAttribute> getPackage() { Optional<XmlAttribute> packageAttribute = getRootNode().getAttribute(XmlNode.fromXmlName("package")); return packageAttribute.isPresent() ? packageAttribute : getRootNode().getAttribute(XmlNode.fromNSName( SdkConstants.ANDROID_URI, "android", "package")); }
/** * Removes the android namespace from all nodes. */ public void clearNodeNamespaces() { clearNodeNamespaces(getRootNode().getXml()); }
public Optional<XmlElement> getByTypeAndKey( ManifestModel.NodeTypes type, @Nullable String keyValue) { return getRootNode().getNodeByTypeAndKey(type, keyValue); }
/** * Visits a document's entire tree and check each attribute for a placeholder existence. * If one is found, encode its name so tools like aapt will not object invalid characters and * such. * <p> * * @param xmlDocument the xml document to visit */ public static void visit(@NonNull XmlDocument xmlDocument) { visit(xmlDocument.getRootNode()); }
@Override public void addTo(@NonNull ActionRecorder actionRecorder, @NonNull XmlDocument document, @NonNull String value) { addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode()); } },
@Override public void addTo(@NonNull ActionRecorder actionRecorder, @NonNull XmlDocument document, @NonNull String value) { addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode()); } },
@Override public void addTo(@NonNull ActionRecorder actionRecorder, @NonNull XmlDocument document, @NonNull String value) { addToElement(this, actionRecorder, value, document.getRootNode()); } },
/** * shorten all fully qualified class name that belong to the same package as the manifest's * package attribute value. * @param finalMergedDocument the AndroidManifest.xml document. */ private static void extractFcqns(@NonNull XmlDocument finalMergedDocument) { extractFcqns(finalMergedDocument.getPackageName(), finalMergedDocument.getRootNode()); }
/** * Enforces {@link com.android.SdkConstants#ANDROID_URI} declaration in the top level element. * It is possible that the original manifest file did not contain any attribute declaration, * therefore not requiring a xmlns: declaration. Yet the implicit elements handling may have * added attributes requiring the namespace declaration. */ private static void enforceAndroidNamespaceDeclaration(@NonNull XmlDocument xmlDocument) { XmlElement manifest = xmlDocument.getRootNode(); for (XmlAttribute xmlAttribute : manifest.getAttributes()) { if (xmlAttribute.getXml().getName().startsWith(SdkConstants.XMLNS) && SdkConstants.ANDROID_URI.equals(xmlAttribute.getValue())) { return; } } // if we are here, we did not find the namespace declaration, add it. manifest.getXml().setAttribute(SdkConstants.XMLNS + ":" + "android", SdkConstants.ANDROID_URI); }
/** * Post validation of the merged document. This will essentially check that all merging * instructions were applied at least once. * * @param xmlDocument merged document to check. * @param mergingReport report for errors and warnings. */ public static void validate( @NonNull XmlDocument xmlDocument, @NonNull MergingReport.Builder mergingReport) { Preconditions.checkNotNull(xmlDocument); Preconditions.checkNotNull(mergingReport); enforceAndroidNamespaceDeclaration(xmlDocument); reOrderElements(xmlDocument.getRootNode()); validate(xmlDocument.getRootNode(), mergingReport.getActionRecorder().build(), mergingReport); }
/** * 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); } }
/** * merge this higher priority document with a higher priority document. * @param lowerPriorityDocument the lower priority document to merge in. * @param mergingReportBuilder the merging report to record errors and actions. * @return a new merged {@link com.android.manifmerger.XmlDocument} or * {@link Optional#absent()} if there were errors during the merging activities. */ @NonNull public Optional<XmlDocument> merge( @NonNull XmlDocument lowerPriorityDocument, @NonNull MergingReport.Builder mergingReportBuilder) { if (getFileType() == Type.MAIN) { mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(getRootNode()); } getRootNode().mergeWithLowerPriorityNode( lowerPriorityDocument.getRootNode(), mergingReportBuilder); addImplicitElements(lowerPriorityDocument, mergingReportBuilder); // force re-parsing as new nodes may have appeared. return mergingReportBuilder.hasErrors() ? Optional.<XmlDocument>absent() : Optional.of(reparse()); }
public ImmutableMultimap<Integer, Record> getResultingSourceMapping(@NonNull XmlDocument xmlDocument) throws ParserConfigurationException, SAXException, IOException { SourceFile inMemory = SourceFile.UNKNOWN; XmlDocument loadedWithLineNumbers = XmlLoader.load( xmlDocument.getSelectors(), xmlDocument.getSystemPropertyResolver(), inMemory, xmlDocument.prettyPrint(), XmlDocument.Type.MAIN, Optional.<String>absent() /* mainManifestPackageName */); ImmutableMultimap.Builder<Integer, Record> mappingBuilder = ImmutableMultimap.builder(); for (XmlElement xmlElement : loadedWithLineNumbers.getRootNode().getMergeableElements()) { parse(xmlElement, mappingBuilder); } return mappingBuilder.build(); }
/** * Validates a loaded {@link com.android.manifmerger.XmlDocument} and return a status of the * merging model. * * Will return one the following status : * <ul> * <li>{@link com.android.manifmerger.MergingReport.Result#SUCCESS} : the merging model is * correct, merging should be attempted</li> * <li>{@link com.android.manifmerger.MergingReport.Result#WARNING} : the merging model * contains non fatal error, user should be notified, merging can be attempted</li> * <li>{@link com.android.manifmerger.MergingReport.Result#ERROR} : the merging model * contains errors, user must be notified, merging should not be attempted</li> * </ul> * * A successful validation does not mean that the merging will be successful, it only means * that the {@link com.android.SdkConstants#TOOLS_URI} instructions are correct and consistent. * * @param mergingReport report to log warnings and errors. * @param xmlDocument the loaded xml part. * @return one the {@link com.android.manifmerger.MergingReport.Result} value. */ @NonNull public static MergingReport.Result validate( @NonNull MergingReport.Builder mergingReport, @NonNull XmlDocument xmlDocument) { validateManifestAttribute( mergingReport, xmlDocument.getRootNode(), xmlDocument.getFileType()); return validate(mergingReport, xmlDocument.getRootNode()); }
/** * Cleans all attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace. * * @param document the xml document to clean * @param logger logger to use in case of errors and warnings. * @return the cleaned document or null if an error occurred. */ @Nullable public static XmlDocument cleanToolsReferences( @NonNull ManifestMerger2.MergeType mergeType, @NonNull XmlDocument document, @NonNull ILogger logger) { Preconditions.checkNotNull(document); Preconditions.checkNotNull(logger); MergingReport.Result result = cleanToolsReferences( mergeType, document.getRootNode().getXml(), logger); return result == MergingReport.Result.SUCCESS ? document.reparse() : null; }
/** * Returns a pretty string representation of this document. */ @NonNull public String prettyPrint() { return XmlPrettyPrinter.prettyPrint( getXml(), XmlFormatPreferences.defaults(), XmlFormatStyle.get(getRootNode().getXml()), null, /* endOfLineSeparator */ false /* endWithNewLine */); }
/** * Returns true if the minSdkVersion of the application and the library are compatible, false * otherwise. */ private boolean checkUsesSdkMinVersion(@NonNull XmlDocument lowerPriorityDocument, MergingReport.Builder mergingReport) { int thisMinSdk = getApiLevelFromAttribute(getMinSdkVersion()); int libraryMinSdk = getApiLevelFromAttribute( lowerPriorityDocument.getRawMinSdkVersion()); // the merged document minSdk cannot be lower than a library if (thisMinSdk < libraryMinSdk) { // check if this higher priority document has any tools instructions for the node Optional<XmlElement> xmlElementOptional = getByTypeAndKey(USES_SDK, null); if (!xmlElementOptional.isPresent()) { return false; } XmlElement xmlElement = xmlElementOptional.get(); // if we find a selector that applies to this library. the users wants to explicitly // allow this higher version library to be allowed. for (Selector selector : xmlElement.getOverrideUsesSdkLibrarySelectors()) { if (selector.appliesTo(lowerPriorityDocument.getRootNode())) { return true; } } return false; } return true; }
private Optional<XmlDocument> merge( @NonNull Optional<XmlDocument> xmlDocument, @NonNull LoadedManifestInfo lowerPriorityDocument, @NonNull MergingReport.Builder mergingReportBuilder) throws MergeFailureException { MergingReport.Result validationResult = PreValidator .validate(mergingReportBuilder, lowerPriorityDocument.getXmlDocument()); if (validationResult == MergingReport.Result.ERROR) { mergingReportBuilder.addMessage( lowerPriorityDocument.getXmlDocument().getSourceFile(), MergingReport.Record.Severity.ERROR, "Validation failed, exiting"); return Optional.absent(); } Optional<XmlDocument> result; if (xmlDocument.isPresent()) { result = xmlDocument.get().merge( lowerPriorityDocument.getXmlDocument(), mergingReportBuilder); } else { mergingReportBuilder.getActionRecorder().recordDefaultNodeAction( lowerPriorityDocument.getXmlDocument().getRootNode()); result = Optional.of(lowerPriorityDocument.getXmlDocument()); } // if requested, dump each intermediary merging stage into the report. if (mOptionalFeatures.contains(Invoker.Feature.KEEP_INTERMEDIARY_STAGES) && result.isPresent()) { mergingReportBuilder.addMergingStage(result.get().prettyPrint()); } return result; }
/** * Record a node that was added due to an implicit presence in earlier SDK release but requires * an explicit declaration in the application targeted SDK. * @param xmlElement the implied element that was added to the resulting xml. * @param reason optional contextual information whey the implied element was added. */ synchronized void recordImpliedNodeAction(@NonNull XmlElement xmlElement, @Nullable String reason) { NodeKey storageKey = xmlElement.getOriginalId(); Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey); if (nodeDecisionTree == null) { nodeDecisionTree = new Actions.DecisionTreeRecord(); mRecords.put(storageKey, nodeDecisionTree); } Actions.NodeRecord record = new Actions.NodeRecord(Actions.ActionType.IMPLIED, new SourceFilePosition( xmlElement.getDocument().getSourceFile(), xmlElement.getDocument().getRootNode().getPosition()), xmlElement.getOriginalId(), reason, xmlElement.getOperationType() ); nodeDecisionTree.addNodeRecord(record); }