View Javadoc
1   /*
2    * Copyright (c) 2012-2022, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.xml;
31  
32  import com.jcabi.log.Logger;
33  import java.io.ByteArrayInputStream;
34  import java.io.IOException;
35  import java.nio.charset.StandardCharsets;
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  import lombok.EqualsAndHashCode;
40  import lombok.ToString;
41  import org.w3c.dom.Document;
42  import org.xml.sax.SAXException;
43  
44  /**
45   * Convenient parser of XML to DOM.
46   *
47   * <p>Objects of this class are immutable and thread-safe.
48   *
49   * @since 0.1
50   */
51  @ToString
52  @EqualsAndHashCode(of = "xml")
53  final class DomParser {
54  
55      /**
56       * The XML as a text.
57       */
58      private final transient byte[] xml;
59  
60      /**
61       * Document builder factory to use for parsing.
62       */
63      private final transient DocumentBuilderFactory factory;
64  
65      /**
66       * Public ctor.
67       *
68       * <p>An {@link IllegalArgumentException} may be thrown if the parameter
69       * passed is not in XML format. It doesn't perform a strict validation
70       * and is not guaranteed that an exception will be thrown whenever
71       * the parameter is not XML.
72       *
73       * <p>It is assumed that the text is in UTF-8.
74       *
75       * @param fct Document builder factory to use
76       * @param txt The XML in text (in UTF-8)
77       */
78      DomParser(final DocumentBuilderFactory fct, final String txt) {
79          this(fct, txt.getBytes(StandardCharsets.UTF_8));
80      }
81  
82      /**
83       * Public ctor.
84       *
85       * <p>An {@link IllegalArgumentException} may be thrown if the parameter
86       * passed is not in XML format. It doesn't perform a strict validation
87       * and is not guaranteed that an exception will be thrown whenever
88       * the parameter is not XML.
89       *
90       * @param fct Document builder factory to use
91       * @param bytes The XML in bytes
92       */
93      @SuppressWarnings("PMD.ArrayIsStoredDirectly")
94      DomParser(final DocumentBuilderFactory fct, final byte[] bytes) {
95          this.xml = bytes;
96          this.factory = fct;
97      }
98  
99      /**
100      * Get document of body.
101      * @return The document
102      */
103     public Document document() {
104         final DocumentBuilder builder;
105         try {
106             builder = this.factory.newDocumentBuilder();
107         } catch (final ParserConfigurationException ex) {
108             throw new IllegalArgumentException(
109                 String.format(
110                     "Failed to create document builder by %s",
111                     this.factory.getClass().getName()
112                 ),
113                 ex
114             );
115         }
116         final long start = System.nanoTime();
117         final Document doc;
118         try {
119             doc = builder.parse(new ByteArrayInputStream(this.xml));
120         } catch (final IOException | SAXException ex) {
121             throw new IllegalArgumentException(
122                 String.format(
123                     "Can't parse by %s, most probably the XML is invalid",
124                     builder.getClass().getName()
125                 ),
126                 ex
127             );
128         }
129         if (Logger.isTraceEnabled(this)) {
130             Logger.trace(
131                 this,
132                 "%s parsed %d bytes of XML in %[nano]s",
133                 builder.getClass().getName(),
134                 this.xml.length,
135                 System.nanoTime() - start
136             );
137         }
138         return doc;
139     }
140 
141 }