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.xml.pull.XmlPullParserException;
23  import org.xml.sax.Attributes;
24  import org.xml.sax.InputSource;
25  import org.xml.sax.SAXException;
26  import org.xml.sax.XMLReader;
27  import org.xml.sax.helpers.DefaultHandler;
28  
29  import javax.annotation.Nonnull;
30  import javax.annotation.WillClose;
31  
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.InputStreamReader;
35  import java.io.Reader;
36  import java.io.UnsupportedEncodingException;
37  import java.util.ArrayList;
38  import java.util.List;
39  
40  /**
41   * @author Kristian Rosenvold
42   */
43  public class Xpp3DomBuilder
44  {
45      private static final boolean DEFAULT_TRIM = true;
46  
47      /**
48       * @param reader {@link Reader}
49       * @return the built dom.
50       * @throws XmlPullParserException in case of an error.
51       */
52      public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
53          throws XmlPullParserException
54      {
55          return build( reader, DEFAULT_TRIM );
56      }
57  
58      /**
59       * @param is {@link InputStream}
60       * @param encoding The encoding.
61       * @return the built dom.
62       * @throws XmlPullParserException in case of an error.
63       */
64      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
65          throws XmlPullParserException
66      {
67          return build( is, encoding, DEFAULT_TRIM );
68      }
69  
70      /**
71       * @param is {@link InputStream}
72       * @param encoding The encoding.
73       * @param trim true/false.
74       * @return the built dom.
75       * @throws XmlPullParserException in case of an error.
76       */
77      public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
78          throws XmlPullParserException
79      {
80          try
81          {
82              Reader reader = new InputStreamReader( is, encoding );
83              return build( reader, trim );
84          }
85          catch ( UnsupportedEncodingException e )
86          {
87              throw new RuntimeException( e );
88          }
89      }
90  
91      /**
92       * @param reader {@link Reader}
93       * @param trim true/false
94       * @return the built dom
95       * @throws XmlPullParserException in case of an error
96       */
97      public static Xpp3Dom build( @WillClose Reader in, boolean trim )
98          throws XmlPullParserException
99      {
100         try ( Reader reader = in )  
101         {
102             DocHandler docHandler = parseSax( new InputSource( reader ), trim );
103             reader.close();
104             return docHandler.result;
105         }
106         catch ( final IOException e )
107         {
108             throw new XmlPullParserException( e );
109         }
110     }
111 
112     private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
113         throws XmlPullParserException
114     {
115         try
116         {
117             DocHandler ch = new DocHandler( trim );
118             XMLReader parser = createXmlReader();
119             parser.setContentHandler( ch );
120             parser.parse( inputSource );
121             return ch;
122         }
123         catch ( IOException e )
124         {
125             throw new XmlPullParserException( e );
126         }
127         catch ( SAXException e )
128         {
129             throw new XmlPullParserException( e );
130         }
131     }
132 
133 
134     private static XMLReader createXmlReader()
135         throws SAXException
136     {
137         XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
138         if ( comSunXmlReader != null )
139         {
140             return comSunXmlReader;
141         }
142 
143         String key = "org.xml.sax.driver";
144         String oldParser = System.getProperty( key );
145         System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
146 
147         try
148         {
149             return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
150         }
151         finally
152         {
153             if ( oldParser != null )
154             {
155                 System.setProperty( key, oldParser );
156             }
157         }
158 
159     }
160 
161     private static XMLReader instantiate( String s )
162     {
163         try
164         {
165             Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass( s );
166             return (XMLReader) aClass.newInstance();
167         }
168         catch ( ClassNotFoundException e )
169         {
170             return  null;
171         }
172         catch ( InstantiationException e )
173         {
174             return  null;
175         }
176         catch ( IllegalAccessException e )
177         {
178             return  null;
179         }
180     }
181 
182 
183     private static class DocHandler
184         extends DefaultHandler
185     {
186         private final List<Xpp3Dom> elemStack = new ArrayList<Xpp3Dom>();
187 
188         private final List<StringBuilder> values = new ArrayList<StringBuilder>();
189 
190         Xpp3Dom result = null;
191 
192         private final boolean trim;
193 
194         private boolean spacePreserve = false;
195 
196         DocHandler( boolean trim )
197         {
198             this.trim = trim;
199         }
200 
201         @Override
202         public void startElement( String uri, String localName, String qName, Attributes attributes )
203             throws SAXException
204         {
205             spacePreserve = false;
206             Xpp3Dom child = new Xpp3Dom( localName );
207 
208             attachToParent( child );
209             pushOnStack( child );
210 
211             // Todo: Detecting tags that close immediately seem to be impossible in sax ?
212             // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
213             values.add( new StringBuilder() );
214 
215             int size = attributes.getLength();
216             for ( int i = 0; i < size; i++ )
217             {
218                 String name = attributes.getQName( i );
219                 String value = attributes.getValue( i );
220                 child.setAttribute( name, value );
221                 spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
222             }
223         }
224 
225         private boolean pushOnStack( Xpp3Dom child )
226         {
227             return elemStack.add( child );
228         }
229 
230         private void attachToParent( Xpp3Dom child )
231         {
232             int depth = elemStack.size();
233             if ( depth > 0 )
234             {
235                 elemStack.get( depth - 1 ).addChild( child );
236             }
237         }
238 
239         private Xpp3Dom pop()
240         {
241             int depth = elemStack.size() - 1;
242             return elemStack.remove( depth );
243         }
244 
245         @Override
246         public void endElement( String uri, String localName, String qName )
247             throws SAXException
248         {
249             int depth = elemStack.size() - 1;
250 
251             Xpp3Dom element = pop();
252 
253             /* this Object could be null if it is a singleton tag */
254             Object accumulatedValue = values.remove( depth );
255 
256             if ( element.getChildCount() == 0 )
257             {
258                 if ( accumulatedValue == null )
259                 {
260                     element.setValue( "" ); // null in xpp3dom, but we don't do that around here
261                 }
262                 else
263                 {
264                     element.setValue( accumulatedValue.toString() );
265                 }
266             }
267 
268             if ( depth == 0 )
269             {
270                 result = element;
271             }
272         }
273 
274         @Override
275         public void characters( char[] ch, int start, int length )
276             throws SAXException
277         {
278             String text = new String( ch, start, length );
279             appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
280         }
281 
282         private void appendToTopValue( String toAppend )
283         {
284             // noinspection MismatchedQueryAndUpdateOfStringBuilder
285             StringBuilder stringBuilder = values.get( values.size() - 1 );
286             stringBuilder.append( toAppend );
287         }
288     }
289 
290 }