View Javadoc
1   package org.apache.maven.shared.utils.xml;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.shared.utils.io.IOUtil;
23  import org.apache.maven.shared.utils.xml.pull.XmlPullParserException;
24  import org.xml.sax.Attributes;
25  import org.xml.sax.InputSource;
26  import org.xml.sax.SAXException;
27  import org.xml.sax.SAXParseException;
28  import org.xml.sax.XMLReader;
29  import org.xml.sax.helpers.DefaultHandler;
30  
31  import javax.annotation.Nonnull;
32  import javax.annotation.WillClose;
33  
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.InputStreamReader;
37  import java.io.Reader;
38  import java.io.UnsupportedEncodingException;
39  import java.util.ArrayList;
40  import java.util.List;
41  
42  /**
43   * @author Kristian Rosenvold
44   */
45  public class Xpp3DomBuilder
46  {
47      private static final boolean DEFAULT_TRIM = true;
48  
49      /**
50       * @param reader {@link Reader}
51       * @return the built dom.
52       * @throws XmlPullParserException in case of an error.
53       */
54      public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
55          throws XmlPullParserException
56      {
57          return build( reader, DEFAULT_TRIM );
58      }
59  
60      /**
61       * @param is {@link InputStream}
62       * @param encoding The encoding.
63       * @return the built dom.
64       * @throws XmlPullParserException in case of an error.
65       */
66      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
67          throws XmlPullParserException
68      {
69          return build( is, encoding, DEFAULT_TRIM );
70      }
71  
72      /**
73       * @param is {@link InputStream}
74       * @param encoding The encoding.
75       * @param trim true/false.
76       * @return the built dom.
77       * @throws XmlPullParserException in case of an error.
78       */
79      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
80          throws XmlPullParserException
81      {
82          try
83          {
84              Reader reader = new InputStreamReader( is, encoding );
85              return build( reader, trim );
86          }
87          catch ( UnsupportedEncodingException e )
88          {
89              throw new RuntimeException( e );
90          }
91      }
92  
93      /**
94       * @param reader {@link Reader}
95       * @param trim true/false.
96       * @return the built dom.
97       * @throws XmlPullParserException in case of an error.
98       */
99      public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
100         throws XmlPullParserException
101     {
102         try
103         {
104             DocHandler docHandler = parseSax( new InputSource( reader ), trim );
105             return docHandler.result;
106         }
107         finally
108         {
109             IOUtil.close( reader );
110         }
111     }
112 
113     private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
114         throws XmlPullParserException
115     {
116         try
117         {
118             DocHandler ch = new DocHandler( trim );
119             XMLReader parser = createXmlReader();
120             parser.setContentHandler( ch );
121             parser.parse( inputSource );
122             return ch;
123         }
124         catch ( IOException e )
125         {
126             throw new XmlPullParserException( e );
127         }
128         catch ( SAXException e )
129         {
130             throw new XmlPullParserException( e );
131         }
132     }
133 
134 
135     private static XMLReader createXmlReader()
136         throws SAXException
137     {
138         XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
139         if ( comSunXmlReader != null )
140         {
141             return comSunXmlReader;
142         }
143 
144         String key = "org.xml.sax.driver";
145         String oldParser = System.getProperty( key );
146         System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
147 
148         try
149         {
150             return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
151         }
152         finally
153         {
154             if ( oldParser != null )
155             {
156                 System.setProperty( key, oldParser );
157             }
158         }
159 
160     }
161 
162     private static XMLReader instantiate( String s )
163     {
164         try
165         {
166             Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass( s );
167             return (XMLReader) aClass.newInstance();
168         }
169         catch ( ClassNotFoundException e )
170         {
171             return  null;
172         }
173         catch ( InstantiationException e )
174         {
175             return  null;
176         }
177         catch ( IllegalAccessException e )
178         {
179             return  null;
180         }
181     }
182 
183 
184     private static class DocHandler
185         extends DefaultHandler
186     {
187         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
188 
189         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
190 
191         // Todo: Use these for something smart !
192         private final List<SAXParseException> warnings = new ArrayList<SAXParseException>();
193 
194         private final List<SAXParseException> errors = new ArrayList<SAXParseException>();
195 
196         private final List<SAXParseException> fatals = new ArrayList<SAXParseException>();
197 
198 
199         Xpp3Dom result = null;
200 
201         private final boolean trim;
202 
203         private boolean spacePreserve = false;
204 
205         DocHandler( boolean trim )
206         {
207             this.trim = trim;
208         }
209 
210         @Override
211         public void startElement( String uri, String localName, String qName, Attributes attributes )
212             throws SAXException
213         {
214             spacePreserve = false;
215             Xpp3Dom child = new Xpp3Dom( localName );
216 
217             attachToParent( child );
218             pushOnStack( child );
219 
220             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
221             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
222             values.add( new StringBuilder() );
223 
224             int size = attributes.getLength();
225             for ( int i = 0; i < size; i++ )
226             {
227                 String name = attributes.getQName( i );
228                 String value = attributes.getValue( i );
229                 child.setAttribute( name, value );
230                 spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
231             }
232         }
233 
234         private boolean pushOnStack( Xpp3Dom child )
235         {
236             return elemStack.add( child );
237         }
238 
239         private void attachToParent( Xpp3Dom child )
240         {
241             int depth = elemStack.size();
242             if ( depth > 0 )
243             {
244                 elemStack.get( depth - 1 ).addChild( child );
245             }
246         }
247 
248         @Override
249         public void warning( SAXParseException e )
250             throws SAXException
251         {
252             warnings.add( e );
253         }
254 
255         @Override
256         public void error( SAXParseException e )
257             throws SAXException
258         {
259             errors.add( e );
260         }
261 
262         @Override
263         public void fatalError( SAXParseException e )
264             throws SAXException
265         {
266             fatals.add( e );
267         }
268 
269         private Xpp3Dom pop()
270         {
271             int depth = elemStack.size() - 1;
272             return elemStack.remove( depth );
273         }
274 
275         @Override
276         public void endElement( String uri, String localName, String qName )
277             throws SAXException
278         {
279             int depth = elemStack.size() - 1;
280 
281             Xpp3Dom element = pop();
282 
283             /* this Object could be null if it is a singleton tag */
284             Object accumulatedValue = values.remove( depth );
285 
286             if ( element.getChildCount() == 0 )
287             {
288                 if ( accumulatedValue == null )
289                 {
290                     element.setValue( "" ); // null in xpp3dom, but we don't do that around here
291                 }
292                 else
293                 {
294                     element.setValue( accumulatedValue.toString() );
295                 }
296             }
297 
298             if ( depth == 0 )
299             {
300                 result = element;
301             }
302         }
303 
304         @Override
305         public void characters( char[] ch, int start, int length )
306             throws SAXException
307         {
308             String text = new String( ch, start, length );
309             appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
310         }
311 
312         private void appendToTopValue( String toAppend )
313         {
314             // noinspection MismatchedQueryAndUpdateOfStringBuilder
315             StringBuilder stringBuilder = values.get( values.size() - 1 );
316             stringBuilder.append( toAppend );
317         }
318     }
319 
320 }