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             reader.close();
106             reader = null;
107             return docHandler.result;
108         }
109         catch ( final IOException e )
110         {
111             throw new XmlPullParserException( e );
112         }
113         finally
114         {
115             IOUtil.close( reader );
116         }
117     }
118 
119     private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
120         throws XmlPullParserException
121     {
122         try
123         {
124             DocHandler ch = new DocHandler( trim );
125             XMLReader parser = createXmlReader();
126             parser.setContentHandler( ch );
127             parser.parse( inputSource );
128             return ch;
129         }
130         catch ( IOException e )
131         {
132             throw new XmlPullParserException( e );
133         }
134         catch ( SAXException e )
135         {
136             throw new XmlPullParserException( e );
137         }
138     }
139 
140 
141     private static XMLReader createXmlReader()
142         throws SAXException
143     {
144         XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
145         if ( comSunXmlReader != null )
146         {
147             return comSunXmlReader;
148         }
149 
150         String key = "org.xml.sax.driver";
151         String oldParser = System.getProperty( key );
152         System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
153 
154         try
155         {
156             return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
157         }
158         finally
159         {
160             if ( oldParser != null )
161             {
162                 System.setProperty( key, oldParser );
163             }
164         }
165 
166     }
167 
168     private static XMLReader instantiate( String s )
169     {
170         try
171         {
172             Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass( s );
173             return (XMLReader) aClass.newInstance();
174         }
175         catch ( ClassNotFoundException e )
176         {
177             return  null;
178         }
179         catch ( InstantiationException e )
180         {
181             return  null;
182         }
183         catch ( IllegalAccessException e )
184         {
185             return  null;
186         }
187     }
188 
189 
190     private static class DocHandler
191         extends DefaultHandler
192     {
193         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
194 
195         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
196 
197         // Todo: Use these for something smart !
198         private final List<SAXParseException> warnings = new ArrayList<SAXParseException>();
199 
200         private final List<SAXParseException> errors = new ArrayList<SAXParseException>();
201 
202         private final List<SAXParseException> fatals = new ArrayList<SAXParseException>();
203 
204 
205         Xpp3Dom result = null;
206 
207         private final boolean trim;
208 
209         private boolean spacePreserve = false;
210 
211         DocHandler( boolean trim )
212         {
213             this.trim = trim;
214         }
215 
216         @Override
217         public void startElement( String uri, String localName, String qName, Attributes attributes )
218             throws SAXException
219         {
220             spacePreserve = false;
221             Xpp3Dom child = new Xpp3Dom( localName );
222 
223             attachToParent( child );
224             pushOnStack( child );
225 
226             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
227             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
228             values.add( new StringBuilder() );
229 
230             int size = attributes.getLength();
231             for ( int i = 0; i < size; i++ )
232             {
233                 String name = attributes.getQName( i );
234                 String value = attributes.getValue( i );
235                 child.setAttribute( name, value );
236                 spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
237             }
238         }
239 
240         private boolean pushOnStack( Xpp3Dom child )
241         {
242             return elemStack.add( child );
243         }
244 
245         private void attachToParent( Xpp3Dom child )
246         {
247             int depth = elemStack.size();
248             if ( depth > 0 )
249             {
250                 elemStack.get( depth - 1 ).addChild( child );
251             }
252         }
253 
254         @Override
255         public void warning( SAXParseException e )
256             throws SAXException
257         {
258             warnings.add( e );
259         }
260 
261         @Override
262         public void error( SAXParseException e )
263             throws SAXException
264         {
265             errors.add( e );
266         }
267 
268         @Override
269         public void fatalError( SAXParseException e )
270             throws SAXException
271         {
272             fatals.add( e );
273         }
274 
275         private Xpp3Dom pop()
276         {
277             int depth = elemStack.size() - 1;
278             return elemStack.remove( depth );
279         }
280 
281         @Override
282         public void endElement( String uri, String localName, String qName )
283             throws SAXException
284         {
285             int depth = elemStack.size() - 1;
286 
287             Xpp3Dom element = pop();
288 
289             /* this Object could be null if it is a singleton tag */
290             Object accumulatedValue = values.remove( depth );
291 
292             if ( element.getChildCount() == 0 )
293             {
294                 if ( accumulatedValue == null )
295                 {
296                     element.setValue( "" ); // null in xpp3dom, but we don't do that around here
297                 }
298                 else
299                 {
300                     element.setValue( accumulatedValue.toString() );
301                 }
302             }
303 
304             if ( depth == 0 )
305             {
306                 result = element;
307             }
308         }
309 
310         @Override
311         public void characters( char[] ch, int start, int length )
312             throws SAXException
313         {
314             String text = new String( ch, start, length );
315             appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
316         }
317 
318         private void appendToTopValue( String toAppend )
319         {
320             // noinspection MismatchedQueryAndUpdateOfStringBuilder
321             StringBuilder stringBuilder = values.get( values.size() - 1 );
322             stringBuilder.append( toAppend );
323         }
324     }
325 
326 }