1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.processor.compat.changeid; 18 19 import org.w3c.dom.Document; 20 import org.w3c.dom.Element; 21 22 import java.io.IOException; 23 import java.io.OutputStream; 24 25 import javax.xml.parsers.DocumentBuilder; 26 import javax.xml.parsers.DocumentBuilderFactory; 27 import javax.xml.parsers.ParserConfigurationException; 28 import javax.xml.transform.Transformer; 29 import javax.xml.transform.TransformerException; 30 import javax.xml.transform.TransformerFactory; 31 import javax.xml.transform.dom.DOMSource; 32 import javax.xml.transform.stream.StreamResult; 33 34 /** 35 * <p>Writes an XML config file containing provided changes.</p> 36 * <p>Output example:</p> 37 * <pre> 38 * {@code 39 * <config> 40 * <compat-change id="111" name="change-name1"> 41 * <meta-data definedIn="java.package.ClassName" sourcePosition="java/package/ClassName 42 * .java:10" /> 43 * </compat-change> 44 * <compat-change disabled="true" id="222" loggingOnly= "true" name="change-name2" 45 * description="my change"> 46 * <meta-data .../> 47 * </compat-change> 48 * <compat-change enableAfterTargetSdk="28" id="333" name="change-name3"> 49 * <meta-data .../> 50 * </compat-change> 51 * </config> 52 * } 53 * 54 * </pre> 55 * 56 * The inner {@code meta-data} tags are intended to be stripped before embedding the config on a 57 * device. They are intended for use by intermediate build tools only. 58 */ 59 final class XmlWriter { 60 //XML tags 61 private static final String XML_ROOT = "config"; 62 private static final String XML_CHANGE_ELEMENT = "compat-change"; 63 private static final String XML_NAME_ATTR = "name"; 64 private static final String XML_ID_ATTR = "id"; 65 private static final String XML_DISABLED_ATTR = "disabled"; 66 private static final String XML_LOGGING_ATTR = "loggingOnly"; 67 private static final String XML_ENABLED_AFTER_ATTR = "enableAfterTargetSdk"; 68 private static final String XML_ENABLED_SINCE_ATTR = "enableSinceTargetSdk"; 69 private static final String XML_DESCRIPTION_ATTR = "description"; 70 private static final String XML_OVERRIDABLE_ATTR = "overridable"; 71 private static final String XML_METADATA_ELEMENT = "meta-data"; 72 private static final String XML_DEFINED_IN = "definedIn"; 73 private static final String XML_SOURCE_POSITION = "sourcePosition"; 74 75 private Document mDocument; 76 private Element mRoot; 77 XmlWriter()78 XmlWriter() { 79 mDocument = createDocument(); 80 mRoot = mDocument.createElement(XML_ROOT); 81 mDocument.appendChild(mRoot); 82 } 83 addChange(Change change)84 void addChange(Change change) { 85 Element newElement = mDocument.createElement(XML_CHANGE_ELEMENT); 86 newElement.setAttribute(XML_NAME_ATTR, change.name); 87 newElement.setAttribute(XML_ID_ATTR, change.id.toString()); 88 if (change.disabled) { 89 newElement.setAttribute(XML_DISABLED_ATTR, "true"); 90 } 91 if (change.loggingOnly) { 92 newElement.setAttribute(XML_LOGGING_ATTR, "true"); 93 } 94 if (change.enabledAfter != null) { 95 newElement.setAttribute(XML_ENABLED_AFTER_ATTR, change.enabledAfter.toString()); 96 } 97 if (change.enabledSince != null) { 98 newElement.setAttribute(XML_ENABLED_SINCE_ATTR, change.enabledSince.toString()); 99 } 100 if (change.description != null) { 101 newElement.setAttribute(XML_DESCRIPTION_ATTR, change.description); 102 } 103 if (change.overridable) { 104 newElement.setAttribute(XML_OVERRIDABLE_ATTR, "true"); 105 } 106 Element metaData = mDocument.createElement(XML_METADATA_ELEMENT); 107 if (change.qualifiedClass != null) { 108 metaData.setAttribute(XML_DEFINED_IN, change.qualifiedClass); 109 } 110 if (change.sourcePosition != null) { 111 metaData.setAttribute(XML_SOURCE_POSITION, change.sourcePosition); 112 } 113 if (metaData.hasAttributes()) { 114 newElement.appendChild(metaData); 115 } 116 mRoot.appendChild(newElement); 117 } 118 write(OutputStream output)119 void write(OutputStream output) throws IOException { 120 try { 121 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 122 Transformer transformer = transformerFactory.newTransformer(); 123 DOMSource domSource = new DOMSource(mDocument); 124 125 StreamResult result = new StreamResult(output); 126 127 transformer.transform(domSource, result); 128 } catch (TransformerException e) { 129 throw new IOException("Failed to write output", e); 130 } 131 } 132 createDocument()133 private Document createDocument() { 134 try { 135 DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance(); 136 DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder(); 137 return documentBuilder.newDocument(); 138 } catch (ParserConfigurationException e) { 139 throw new RuntimeException("Failed to create a new document", e); 140 } 141 } 142 } 143