• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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