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