View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.utils.xml;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.WillClose;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.io.UnsupportedEncodingException;
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  import org.apache.maven.shared.utils.xml.pull.XmlPullParserException;
33  import org.xml.sax.Attributes;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.SAXException;
36  import org.xml.sax.XMLReader;
37  import org.xml.sax.helpers.DefaultHandler;
38  
39  /**
40   * @author Kristian Rosenvold
41   */
42  public class Xpp3DomBuilder {
43  
44      /**
45       * @param reader {@link Reader}
46       * @return the built DOM
47       * @throws XmlPullParserException in case of an error
48       */
49      public static Xpp3Dom build(@WillClose @Nonnull Reader reader) throws XmlPullParserException {
50          return build(reader, false);
51      }
52  
53      /**
54       * @param is {@link InputStream}
55       * @param encoding the encoding
56       * @return the built DOM
57       * @throws XmlPullParserException in case of an error
58       */
59      public static Xpp3Dom build(@WillClose InputStream is, @Nonnull String encoding) throws XmlPullParserException {
60          return build(is, encoding, false);
61      }
62  
63      /**
64       * @param is {@link InputStream}
65       * @param encoding the encoding
66       * @param noop vestigial argument with no effect
67       * @return the built DOM
68       * @throws XmlPullParserException in case of an error
69       * @deprecated use the two-arg variant
70       */
71      @Deprecated
72      public static Xpp3Dom build(@WillClose InputStream is, @Nonnull String encoding, boolean noop)
73              throws XmlPullParserException {
74          try {
75              Reader reader = new InputStreamReader(is, encoding);
76              return build(reader);
77          } catch (UnsupportedEncodingException e) {
78              throw new XmlPullParserException(e);
79          }
80      }
81  
82      /**
83       * @param in {@link Reader}
84       * @param noop vestigial argument with no effect
85       * @return the built DOM
86       * @throws XmlPullParserException in case of an error
87       * @deprecated use {#build(java.io.Reader)}
88       */
89      @Deprecated
90      public static Xpp3Dom build(@WillClose Reader in, boolean noop) throws XmlPullParserException {
91          try (Reader reader = in) {
92              DocHandler docHandler = parseSax(new InputSource(reader));
93              reader.close();
94              return docHandler.result;
95          } catch (final IOException e) {
96              throw new XmlPullParserException(e);
97          }
98      }
99  
100     private static DocHandler parseSax(@Nonnull InputSource inputSource) throws XmlPullParserException {
101         try {
102             DocHandler ch = new DocHandler();
103             XMLReader parser = createXmlReader();
104             parser.setContentHandler(ch);
105             parser.parse(inputSource);
106             return ch;
107         } catch (IOException e) {
108             throw new XmlPullParserException(e);
109         } catch (SAXException e) {
110             throw new XmlPullParserException(e);
111         }
112     }
113 
114     private static XMLReader createXmlReader() throws SAXException {
115         XMLReader comSunXmlReader = instantiate("com.sun.org.apache.xerces.internal.parsers.SAXParser");
116         if (comSunXmlReader != null) {
117             return comSunXmlReader;
118         }
119 
120         String key = "org.xml.sax.driver";
121         String oldParser = System.getProperty(key);
122         System.clearProperty(key); // There's a "slight" problem with this an parallel maven: It does not work ;)
123 
124         try {
125             return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
126         } finally {
127             if (oldParser != null) {
128                 System.setProperty(key, oldParser);
129             }
130         }
131     }
132 
133     private static XMLReader instantiate(String s) {
134         try {
135             Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass(s);
136             return (XMLReader) aClass.newInstance();
137         } catch (ClassNotFoundException e) {
138             return null;
139         } catch (InstantiationException e) {
140             return null;
141         } catch (IllegalAccessException e) {
142             return null;
143         }
144     }
145 
146     private static class DocHandler extends DefaultHandler {
147         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
148 
149         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
150 
151         Xpp3Dom result = null;
152 
153         private boolean spacePreserve = false;
154 
155         @Override
156         public void startElement(String uri, String localName, String qName, Attributes attributes)
157                 throws SAXException {
158             spacePreserve = false;
159             Xpp3Dom child = new Xpp3Dom(localName);
160 
161             attachToParent(child);
162             pushOnStack(child);
163 
164             values.add(new StringBuilder());
165 
166             int size = attributes.getLength();
167             for (int i = 0; i < size; i++) {
168                 String name = attributes.getQName(i);
169                 String value = attributes.getValue(i);
170                 child.setAttribute(name, value);
171                 spacePreserve = spacePreserve || ("xml:space".equals(name) && "preserve".equals(value));
172             }
173         }
174 
175         private boolean pushOnStack(Xpp3Dom child) {
176             return elemStack.add(child);
177         }
178 
179         private void attachToParent(Xpp3Dom child) {
180             int depth = elemStack.size();
181             if (depth > 0) {
182                 elemStack.get(depth - 1).addChild(child);
183             }
184         }
185 
186         private Xpp3Dom pop() {
187             int depth = elemStack.size() - 1;
188             return elemStack.remove(depth);
189         }
190 
191         @Override
192         public void endElement(String uri, String localName, String qName) throws SAXException {
193             int depth = elemStack.size() - 1;
194 
195             Xpp3Dom element = pop();
196 
197             /* this Object could be null if it is a singleton tag */
198             Object accumulatedValue = values.remove(depth);
199 
200             if (element.getChildCount() == 0) {
201                 if (accumulatedValue == null) {
202                     element.setValue(""); // null in xpp3dom, but we don't do that around here
203                 } else {
204                     element.setValue(accumulatedValue.toString());
205                 }
206             }
207 
208             if (depth == 0) {
209                 result = element;
210             }
211         }
212 
213         @Override
214         public void characters(char[] ch, int start, int length) throws SAXException {
215             String text = new String(ch, start, length);
216             appendToTopValue(text);
217         }
218 
219         private void appendToTopValue(String toAppend) {
220             // noinspection MismatchedQueryAndUpdateOfStringBuilder
221             StringBuilder stringBuilder = values.get(values.size() - 1);
222             stringBuilder.append(toAppend);
223         }
224     }
225 }