View Javadoc
1   /*
2    * Copyright (C) 2016 Uwe Plonus
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package org.sw4j.tool.annotation.jpa.test.util;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.StringWriter;
22  import java.io.Writer;
23  import java.nio.charset.Charset;
24  import java.util.Arrays;
25  import java.util.HashSet;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Set;
29  import javax.tools.JavaCompiler;
30  import javax.tools.JavaFileObject;
31  import javax.tools.StandardJavaFileManager;
32  import javax.tools.ToolProvider;
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.parsers.ParserConfigurationException;
36  import javax.xml.xpath.XPath;
37  import javax.xml.xpath.XPathConstants;
38  import javax.xml.xpath.XPathExpressionException;
39  import javax.xml.xpath.XPathFactory;
40  import org.testng.Assert;
41  import org.w3c.dom.Attr;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.Element;
44  import org.w3c.dom.NamedNodeMap;
45  import org.w3c.dom.Node;
46  import org.w3c.dom.NodeList;
47  import org.xml.sax.SAXException;
48  
49  /**
50   * An utility class to support testing of the annotation processor.
51   *
52   * @author Uwe Plonus
53   */
54  public class ITUtil {
55  
56      /** The target folder for the compiled classes. */
57      private static final String TARGET_FOLDER = "target/jpa-classes/";
58  
59      /** The document that is used to testResultFile the result. */
60      private Document resultDocument;
61  
62      /** */
63      private final Set<Node> visitedNodes;
64  
65      public ITUtil() {
66          visitedNodes = new HashSet<>();
67      }
68  
69      /**
70       * Method to compile all classes in the given folder. The generated classes are placed in the
71       * folder {@code target/jpa-classes/}. After compilation the resulting XML file will be read.
72       *
73       * @param folder the folder that contains the JPA classes to process.
74       * @param resultFileName the resulting file of the generator. Must match the option of the
75       *  annotation processor.
76       * @param options the options for the compiling (primary the options for the annotation
77       *  processor).
78       * @throws ParserConfigurationException when the parser cannot be created.
79       * @throws SAXException when the parsing fails.
80       * @throws IOException when the IO fails.
81       */
82      public void compileClasses(String folder, String resultFileName, String[] options)
83              throws ParserConfigurationException, SAXException, IOException {
84          try {
85              File entityFolder = new File(folder);
86              List<File> files = new LinkedList<>();
87              getFiles(entityFolder, files);
88  
89              JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
90              Assert.assertNotNull(compiler, "Need a java compiler for executing the tests.");
91              StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, Charset.forName("UTF-8"));
92  
93              File classesFolder = new File(TARGET_FOLDER);
94              classesFolder.mkdirs();
95  
96              Iterable<? extends JavaFileObject> entities = fileManager.getJavaFileObjectsFromFiles(files);
97              List<String> opts = new LinkedList<>();
98              opts.add("-d");
99              opts.add(TARGET_FOLDER);
100             opts.addAll(Arrays.asList(options));
101             Writer writer = new StringWriter();
102             compiler.getTask(writer, fileManager, null, opts, null, entities).call();
103 
104             if (resultFileName != null) {
105                 File resultFile = new File(resultFileName);
106                 if (resultFile.exists()) {
107                     DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
108                     resultDocument = builder.parse(resultFile);
109                 }
110             }
111         } catch (Exception ex) {
112             ex.printStackTrace();
113         }
114     }
115 
116     /**
117      * Helper method to collect all java files (with suffix {@code .java}) inside a folder. If the
118      * folder contains subfolders this are also scanned for java classes.
119      *
120      * @param folder the folder to scan for java classes.
121      * @param javaFiles the resulting list that contains all java files.
122      */
123     private void getFiles(File folder, List<File> javaFiles) {
124         if (folder.isDirectory()) {
125             File[] files = folder.listFiles();
126             for (File file: files) {
127                 getFiles(file, javaFiles);
128             }
129         } else if (folder.getName().endsWith(".java")) {
130             javaFiles.add(folder);
131         }
132     }
133 
134     /**
135      * Traverses the complete DOM and checks that every node is retrieved with {@link #getNode(java.lang.String)}. The
136      * check is done with {@link Assert}.
137      */
138     public void checkVisitedNodes() {
139         StringBuilder sb = new StringBuilder();
140         if (this.resultDocument != null) {
141             checkVisitedNodes(resultDocument.getDocumentElement(), sb);
142             if (sb.length() > 0) {
143                 Assert.fail(sb.toString());
144             }
145         }
146     }
147 
148     /**
149      * Check that the given node is tested with the method {@link #getNode(java.lang.String)}. If the current node is an
150      * element then each embedded element will recursively be checked.
151      *
152      * @param node the node to check.
153      */
154     private void checkVisitedNodes(final Element node, final StringBuilder sb) {
155         if (!visitedNodes.contains(node)) {
156             sb.append("Expected the element \"").append(getPathToNode(node)).append("\" to be tested.\n");
157         }
158         NamedNodeMap attributes = node.getAttributes();
159         for (int i = 0; i < attributes.getLength(); i++) {
160             Attr attr = (Attr)attributes.item(i);
161             if (!visitedNodes.contains(attr)) {
162                 sb.append("Expected the attribute \"").append(getPathToNode(attr)).append("\" to be tested.\n");
163             }
164         }
165         NodeList children = node.getChildNodes();
166         for (int i = 0; i < children.getLength(); i++) {
167             Node child = children.item(i);
168             if (child.getNodeType() == Node.ELEMENT_NODE) {
169                 checkVisitedNodes((Element)child, sb);
170             }
171         }
172     }
173 
174     /**
175      * Returns the XPath to the given node.
176      *
177      * @param node the node to get the path for.
178      * @return the path to the node.
179      */
180     private String getPathToNode(final Node node) {
181         StringBuilder path = new StringBuilder();
182         Node parent;
183         if (node.getNodeType() == Node.ELEMENT_NODE) {
184             parent = node;
185         } else {
186             parent = ((Attr)node).getOwnerElement();
187             path.append("/@").append(node.getNodeName());
188         }
189         while (parent != null) {
190             if (parent.getNodeType() != Node.DOCUMENT_NODE) {
191                 path.insert(0, handleElementAttributes(parent));
192                 path.insert(0, parent.getNodeName());
193                 path.insert(0, "/");
194             }
195             parent = parent.getParentNode();
196         }
197         return path.toString();
198     }
199 
200     /**
201      * Handle the attributes for an element in an XPath expression.
202      *
203      * @param node the node to get the attributes from.
204      * @return the attributes of the node as XPath expression.
205      */
206     private StringBuilder handleElementAttributes(Node node) {
207         StringBuilder attributePath = new StringBuilder();
208         if (node.hasAttributes()) {
209             NamedNodeMap attributes = node.getAttributes();
210             if (attributes.getLength() > 0) {
211                 attributePath.append("[");
212                 for (int i = 0; i < attributes.getLength(); i++) {
213                     if (i > 0) {
214                         attributePath.append(" and ");
215                     }
216                     Node attribute = attributes.item(i);
217                     attributePath.append("@").append(attribute.getNodeName()).append("='")
218                             .append(attribute.getNodeValue()).append("'");
219                 }
220                 attributePath.append("]");
221             }
222         }
223         return attributePath;
224     }
225 
226     /**
227      * Returns the root element of the document.
228      *
229      * @return the root element.
230      */
231     public Element getRootElement() {
232         Element root = resultDocument.getDocumentElement();
233         visitedNodes.add(root);
234         getAttribute("xmlns:xsi", root);
235         getAttribute("xsi:schemaLocation", root);
236         return root;
237     }
238 
239     /**
240      * Returns the node with the given XPath.
241      *
242      * @param path the XPath of the node to retrieve.
243      * @return the node denoted by the XPath.
244      * @throws XPathExpressionException if the XPath expression is invalid.
245      */
246     public Node getNode(String path) throws XPathExpressionException {
247         XPath xpath = XPathFactory.newInstance().newXPath();
248         Node result = (Node)xpath.evaluate(path, resultDocument, XPathConstants.NODE);
249         visitedNodes.add(result);
250         return result;
251     }
252 
253     /**
254      * Returns the given attribute of the given element.
255      *
256      * @param name the name of the attribute to get.
257      * @param node the element to get the attribute from.
258      * @return the named attribute.
259      */
260     public Node getAttribute(String name, Node node) {
261         Node attribute = node.getAttributes().getNamedItem(name);
262         if (attribute != null) {
263             visitedNodes.add(attribute);
264         }
265         return attribute;
266     }
267 
268 }