1 /* 2 * Copyright (c) 2022 Uber Technologies, Inc. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23 package com.uber.nullaway.fixserialization; 24 25 import java.io.File; 26 import javax.annotation.Nullable; 27 import javax.xml.parsers.DocumentBuilder; 28 import javax.xml.parsers.DocumentBuilderFactory; 29 import javax.xml.parsers.ParserConfigurationException; 30 import javax.xml.transform.Transformer; 31 import javax.xml.transform.TransformerException; 32 import javax.xml.transform.TransformerFactory; 33 import javax.xml.transform.dom.DOMSource; 34 import javax.xml.transform.stream.StreamResult; 35 import javax.xml.xpath.XPath; 36 import javax.xml.xpath.XPathConstants; 37 import javax.xml.xpath.XPathExpressionException; 38 import javax.xml.xpath.XPathFactory; 39 import org.jetbrains.annotations.Contract; 40 import org.w3c.dom.Document; 41 import org.w3c.dom.Element; 42 import org.w3c.dom.Node; 43 44 /** Helper for class for parsing/writing xml files. */ 45 public class XMLUtil { 46 47 /** 48 * Helper method for reading attributes of node located at /key_1/key_2/.../key_n (in the form of 49 * {@code Xpath} query) from a {@link Document}. 50 * 51 * @param doc XML object to read values from. 52 * @param key Key to locate the value, can be nested in the form of {@code Xpath} query (e.g. 53 * /key1/key2:.../key_n). 54 * @param klass Class type of the value in doc. 55 * @return The value in the specified keychain cast to the class type given in parameter. 56 */ getValueFromAttribute( Document doc, String key, String attr, Class<T> klass)57 public static <T> DefaultXMLValueProvider<T> getValueFromAttribute( 58 Document doc, String key, String attr, Class<T> klass) { 59 try { 60 XPath xPath = XPathFactory.newInstance().newXPath(); 61 Node node = (Node) xPath.compile(key).evaluate(doc, XPathConstants.NODE); 62 if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { 63 Element eElement = (Element) node; 64 return new DefaultXMLValueProvider<>(eElement.getAttribute(attr), klass); 65 } 66 } catch (XPathExpressionException ignored) { 67 return new DefaultXMLValueProvider<>(null, klass); 68 } 69 return new DefaultXMLValueProvider<>(null, klass); 70 } 71 72 /** 73 * Helper method for reading value of a node located at /key_1/key_2/.../key_n (in the form of 74 * {@code Xpath} query) from a {@link Document}. 75 * 76 * @param doc XML object to read values from. 77 * @param key Key to locate the value, can be nested in the form of {@code Xpath} query (e.g. 78 * /key1/key2/.../key_n). 79 * @param klass Class type of the value in doc. 80 * @return The value in the specified keychain cast to the class type given in parameter. 81 */ getValueFromTag( Document doc, String key, Class<T> klass)82 public static <T> DefaultXMLValueProvider<T> getValueFromTag( 83 Document doc, String key, Class<T> klass) { 84 try { 85 XPath xPath = XPathFactory.newInstance().newXPath(); 86 Node node = (Node) xPath.compile(key).evaluate(doc, XPathConstants.NODE); 87 if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { 88 Element eElement = (Element) node; 89 return new DefaultXMLValueProvider<>(eElement.getTextContent(), klass); 90 } 91 } catch (XPathExpressionException ignored) { 92 return new DefaultXMLValueProvider<>(null, klass); 93 } 94 return new DefaultXMLValueProvider<>(null, klass); 95 } 96 97 /** 98 * Writes the {@link FixSerializationConfig} in {@code XML} format. 99 * 100 * @param config Config file to write. 101 * @param path Path to write the config at. 102 */ writeInXMLFormat(FixSerializationConfig config, String path)103 public static void writeInXMLFormat(FixSerializationConfig config, String path) { 104 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 105 try { 106 DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 107 Document doc = docBuilder.newDocument(); 108 109 // Root 110 Element rootElement = doc.createElement("serialization"); 111 doc.appendChild(rootElement); 112 113 // Suggest 114 Element suggestElement = doc.createElement("suggest"); 115 suggestElement.setAttribute("active", String.valueOf(config.suggestEnabled)); 116 suggestElement.setAttribute("enclosing", String.valueOf(config.suggestEnclosing)); 117 rootElement.appendChild(suggestElement); 118 119 // Field Initialization 120 Element fieldInitInfoEnabled = doc.createElement("fieldInitInfo"); 121 fieldInitInfoEnabled.setAttribute("active", String.valueOf(config.fieldInitInfoEnabled)); 122 rootElement.appendChild(fieldInitInfoEnabled); 123 124 // Output dir 125 Element outputDir = doc.createElement("path"); 126 outputDir.setTextContent(config.outputDirectory); 127 rootElement.appendChild(outputDir); 128 129 // Serialization version 130 if (config.getSerializer() != null) { 131 Element serializationVersion = doc.createElement("version"); 132 serializationVersion.setTextContent( 133 String.valueOf(config.getSerializer().getSerializationVersion())); 134 rootElement.appendChild(serializationVersion); 135 } 136 137 // Writings 138 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 139 Transformer transformer = transformerFactory.newTransformer(); 140 DOMSource source = new DOMSource(doc); 141 StreamResult result = new StreamResult(new File(path)); 142 transformer.transform(source, result); 143 } catch (ParserConfigurationException | TransformerException e) { 144 throw new RuntimeException("Error happened in writing config.", e); 145 } 146 } 147 148 /** Helper class for setting default values when the key is not found. */ 149 static class DefaultXMLValueProvider<T> { 150 @Nullable final Object value; 151 final Class<T> klass; 152 DefaultXMLValueProvider(@ullable Object value, Class<T> klass)153 DefaultXMLValueProvider(@Nullable Object value, Class<T> klass) { 154 this.klass = klass; 155 if (value == null) { 156 this.value = null; 157 } else { 158 String content = value.toString(); 159 switch (klass.getSimpleName()) { 160 case "Integer": 161 this.value = Integer.valueOf(content); 162 break; 163 case "Boolean": 164 this.value = Boolean.valueOf(content); 165 break; 166 case "String": 167 this.value = String.valueOf(content); 168 break; 169 default: 170 throw new IllegalArgumentException( 171 "Cannot extract values of type: " 172 + klass 173 + ", only Double|Boolean|String accepted."); 174 } 175 } 176 } 177 178 @Contract("!null -> !null") 179 @Nullable orElse(@ullable T other)180 T orElse(@Nullable T other) { 181 return value == null ? other : klass.cast(this.value); 182 } 183 } 184 } 185