View Javadoc
1   package org.apache.maven.model.transform.pull;
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 java.io.IOException;
23  import java.io.InputStream;
24  import java.io.Reader;
25  import java.util.ArrayDeque;
26  import java.util.Deque;
27  import java.util.Objects;
28  import java.util.regex.Pattern;
29  
30  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
31  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
32  
33  /**
34   * An xml pull parser filter base implementation.
35   *
36   * @author Guillaume Nodet
37   * @since 4.0.0
38   */
39  public class BufferingParser implements XmlPullParser
40  {
41  
42      private static final Pattern WHITESPACE_REGEX = Pattern.compile( "[ \r\t\n]+" );
43  
44      protected XmlPullParser xmlPullParser;
45      protected Deque<Event> events;
46      protected Event current;
47      protected boolean bypass;
48  
49      @SuppressWarnings( "checkstyle:VisibilityModifier" )
50      public static class Event
51      {
52          public int event;
53          public String name;
54          public String prefix;
55          public String namespace;
56          public boolean empty;
57          public String text;
58          public Attribute[] attributes;
59          public Namespace[] namespaces;
60      }
61  
62      @SuppressWarnings( "checkstyle:VisibilityModifier" )
63      public static class Namespace
64      {
65          public String prefix;
66          public String uri;
67      }
68  
69      @SuppressWarnings( "checkstyle:VisibilityModifier" )
70      public static class Attribute
71      {
72          public String name;
73          public String prefix;
74          public String namespace;
75          public String type;
76          public String value;
77          public boolean isDefault;
78      }
79  
80  
81      public BufferingParser( XmlPullParser xmlPullParser )
82      {
83          this.xmlPullParser = xmlPullParser;
84      }
85  
86      @Override
87      public void setFeature( String name, boolean state ) throws XmlPullParserException
88      {
89          xmlPullParser.setFeature( name, state );
90      }
91  
92      @Override
93      public boolean getFeature( String name )
94      {
95          return xmlPullParser.getFeature( name );
96      }
97  
98      @Override
99      public void setProperty( String name, Object value ) throws XmlPullParserException
100     {
101         xmlPullParser.setProperty( name, value );
102     }
103 
104     @Override
105     public Object getProperty( String name )
106     {
107         return xmlPullParser.getProperty( name );
108     }
109 
110     @Override
111     public void setInput( Reader in ) throws XmlPullParserException
112     {
113         xmlPullParser.setInput( in );
114     }
115 
116     @Override
117     public void setInput( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
118     {
119         xmlPullParser.setInput( inputStream, inputEncoding );
120     }
121 
122     @Override
123     public String getInputEncoding()
124     {
125         return xmlPullParser.getInputEncoding();
126     }
127 
128     @Override
129     public void defineEntityReplacementText( String entityName, String replacementText ) throws XmlPullParserException
130     {
131         xmlPullParser.defineEntityReplacementText( entityName, replacementText );
132     }
133 
134     @Override
135     public int getNamespaceCount( int depth ) throws XmlPullParserException
136     {
137 //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
138         return xmlPullParser.getNamespaceCount( depth );
139     }
140 
141     @Override
142     public String getNamespacePrefix( int pos ) throws XmlPullParserException
143     {
144 //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
145         return xmlPullParser.getNamespacePrefix( pos );
146     }
147 
148     @Override
149     public String getNamespaceUri( int pos ) throws XmlPullParserException
150     {
151 //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
152         return xmlPullParser.getNamespaceUri( pos );
153     }
154 
155     @Override
156     public String getNamespace( String prefix )
157     {
158 //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
159         return xmlPullParser.getNamespace( prefix );
160     }
161 
162     @Override
163     public int getDepth()
164     {
165 //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
166         return xmlPullParser.getDepth();
167     }
168 
169     @Override
170     public String getPositionDescription()
171     {
172         if ( current != null )
173         {
174             throw new IllegalStateException( "Not supported during events replay" );
175         }
176         return xmlPullParser.getPositionDescription();
177     }
178 
179     @Override
180     public int getLineNumber()
181     {
182         if ( current != null )
183         {
184             throw new IllegalStateException( "Not supported during events replay" );
185         }
186         return xmlPullParser.getLineNumber();
187     }
188 
189     @Override
190     public int getColumnNumber()
191     {
192         if ( current != null )
193         {
194             throw new IllegalStateException( "Not supported during events replay" );
195         }
196         return xmlPullParser.getColumnNumber();
197     }
198 
199     @Override
200     public boolean isWhitespace() throws XmlPullParserException
201     {
202         if ( current != null )
203         {
204             if ( current.event == TEXT || current.event == CDSECT )
205             {
206                 return WHITESPACE_REGEX.matcher( current.text ).matches();
207             }
208             else if ( current.event == IGNORABLE_WHITESPACE )
209             {
210                 return true;
211             }
212             else
213             {
214                 throw new XmlPullParserException( "no content available to check for whitespaces" );
215             }
216         }
217         return xmlPullParser.isWhitespace();
218     }
219 
220     @Override
221     public String getText()
222     {
223         return current != null ? current.text : xmlPullParser.getText();
224     }
225 
226     @Override
227     public char[] getTextCharacters( int[] holderForStartAndLength )
228     {
229         if ( current != null )
230         {
231             throw new IllegalStateException( "Not supported during events replay" );
232         }
233         return xmlPullParser.getTextCharacters( holderForStartAndLength );
234     }
235 
236     @Override
237     public String getNamespace()
238     {
239         return current != null ? current.namespace : xmlPullParser.getNamespace();
240     }
241 
242     @Override
243     public String getName()
244     {
245         return current != null ? current.name : xmlPullParser.getName();
246     }
247 
248     @Override
249     public String getPrefix()
250     {
251         return current != null ? current.prefix : xmlPullParser.getPrefix();
252     }
253 
254     @Override
255     public boolean isEmptyElementTag() throws XmlPullParserException
256     {
257         return current != null ? current.empty : xmlPullParser.isEmptyElementTag();
258     }
259 
260     @Override
261     public int getAttributeCount()
262     {
263         if ( current != null )
264         {
265             return current.attributes != null ? current.attributes.length : 0;
266         }
267         else
268         {
269             return xmlPullParser.getAttributeCount();
270         }
271     }
272 
273     @Override
274     public String getAttributeNamespace( int index )
275     {
276         if ( current != null )
277         {
278             return current.attributes[index].namespace;
279         }
280         else
281         {
282             return xmlPullParser.getAttributeNamespace( index );
283         }
284     }
285 
286     @Override
287     public String getAttributeName( int index )
288     {
289         if ( current != null )
290         {
291             return current.attributes[index].name;
292         }
293         else
294         {
295             return xmlPullParser.getAttributeName( index );
296         }
297     }
298 
299     @Override
300     public String getAttributePrefix( int index )
301     {
302         if ( current != null )
303         {
304             return current.attributes[index].prefix;
305         }
306         else
307         {
308             return xmlPullParser.getAttributePrefix( index );
309         }
310     }
311 
312     @Override
313     public String getAttributeType( int index )
314     {
315         if ( current != null )
316         {
317             return current.attributes[index].type;
318         }
319         else
320         {
321             return xmlPullParser.getAttributeType( index );
322         }
323     }
324 
325     @Override
326     public boolean isAttributeDefault( int index )
327     {
328         if ( current != null )
329         {
330             return current.attributes[index].isDefault;
331         }
332         else
333         {
334             return xmlPullParser.isAttributeDefault( index );
335         }
336     }
337 
338     @Override
339     public String getAttributeValue( int index )
340     {
341         if ( current != null )
342         {
343             return current.attributes[index].value;
344         }
345         else
346         {
347             return xmlPullParser.getAttributeValue( index );
348         }
349     }
350 
351     @Override
352     public String getAttributeValue( String namespace, String name )
353     {
354         if ( current != null )
355         {
356             if ( current.attributes != null )
357             {
358                 for ( Attribute attr : current.attributes )
359                 {
360                     if ( Objects.equals( namespace, attr.namespace )
361                             && Objects.equals( name, attr.name ) )
362                     {
363                         return attr.value;
364                     }
365                 }
366             }
367             return null;
368         }
369         else
370         {
371             return xmlPullParser.getAttributeValue( namespace, name );
372         }
373     }
374 
375     @Override
376     public void require( int type, String namespace, String name ) throws XmlPullParserException, IOException
377     {
378         if ( current != null )
379         {
380             throw new IllegalStateException( "Not supported during events replay" );
381         }
382         xmlPullParser.require( type, namespace, name );
383     }
384 
385     @Override
386     public int getEventType() throws XmlPullParserException
387     {
388         return current != null ? current.event : xmlPullParser.getEventType();
389     }
390 
391     @Override
392     public int next() throws XmlPullParserException, IOException
393     {
394         while ( true )
395         {
396             if ( events != null && !events.isEmpty() )
397             {
398                 current = events.removeFirst();
399                 return current.event;
400             }
401             else
402             {
403                 current = null;
404             }
405             if ( getEventType() == END_DOCUMENT )
406             {
407                 throw new XmlPullParserException( "already reached end of XML input", this, null );
408             }
409             int currentEvent = xmlPullParser.next();
410             if ( bypass() || accept() )
411             {
412                 return currentEvent;
413             }
414         }
415     }
416 
417     @Override
418     public int nextToken() throws XmlPullParserException, IOException
419     {
420         while ( true )
421         {
422             if ( events != null && !events.isEmpty() )
423             {
424                 current = events.removeFirst();
425                 return current.event;
426             }
427             else
428             {
429                 current = null;
430             }
431             if ( getEventType() == END_DOCUMENT )
432             {
433                 throw new XmlPullParserException( "already reached end of XML input", this, null );
434             }
435             int currentEvent = xmlPullParser.nextToken();
436             if ( bypass() || accept() )
437             {
438                 return currentEvent;
439             }
440         }
441     }
442 
443     @Override
444     public int nextTag() throws XmlPullParserException, IOException
445     {
446         int eventType = next();
447         if ( eventType == TEXT && isWhitespace() )
448         { // skip whitespace
449             eventType = next();
450         }
451         if ( eventType != START_TAG && eventType != END_TAG )
452         {
453             throw new XmlPullParserException( "expected START_TAG or END_TAG not "
454                     + TYPES[getEventType()], this, null );
455         }
456         return eventType;
457     }
458 
459     @Override
460     public String nextText() throws XmlPullParserException, IOException
461     {
462         int eventType = getEventType();
463         if ( eventType != START_TAG )
464         {
465             throw new XmlPullParserException( "parser must be on START_TAG to read next text", this, null );
466         }
467         eventType = next();
468         if ( eventType == TEXT )
469         {
470             final String result = getText();
471             eventType = next();
472             if ( eventType != END_TAG )
473             {
474                 throw new XmlPullParserException( "TEXT must be immediately followed by END_TAG and not "
475                         + TYPES[getEventType()], this, null );
476             }
477             return result;
478         }
479         else if ( eventType == END_TAG )
480         {
481             return "";
482         }
483         else
484         {
485             throw new XmlPullParserException( "parser must be on START_TAG or TEXT to read text", this, null );
486         }
487     }
488 
489     protected Event bufferEvent() throws XmlPullParserException
490     {
491         Event event = new Event();
492         XmlPullParser pp = xmlPullParser;
493         event.event = xmlPullParser.getEventType();
494         switch ( event.event )
495         {
496             case START_DOCUMENT:
497             case END_DOCUMENT:
498                 break;
499             case START_TAG:
500                 event.name = pp.getName();
501                 event.namespace = pp.getNamespace();
502                 event.prefix = pp.getPrefix();
503                 event.empty = pp.isEmptyElementTag();
504                 event.text = pp.getText();
505                 break;
506             case END_TAG:
507                 event.name = pp.getName();
508                 event.namespace = pp.getNamespace();
509                 event.prefix = pp.getPrefix();
510                 event.text = pp.getText();
511                 break;
512             case TEXT:
513             case COMMENT:
514             case IGNORABLE_WHITESPACE:
515                 event.text = pp.getText();
516                 break;
517             default:
518                 break;
519         }
520         return event;
521     }
522 
523     protected void pushEvent( Event event )
524     {
525         if ( events == null )
526         {
527             events = new ArrayDeque<>();
528         }
529         events.add( event );
530     }
531 
532     protected boolean accept() throws XmlPullParserException, IOException
533     {
534         return true;
535     }
536 
537     public void bypass( boolean bypass )
538     {
539         if ( bypass && events != null && !events.isEmpty() )
540         {
541             throw new IllegalStateException( "Can not disable filter while processing" );
542         }
543         this.bypass = bypass;
544     }
545 
546     public boolean bypass()
547     {
548         return bypass
549                 || ( xmlPullParser instanceof BufferingParser
550                         && ( (BufferingParser) xmlPullParser ).bypass() );
551     }
552 
553     protected static String nullSafeAppend( String originalValue, String charSegment )
554     {
555         if ( originalValue == null )
556         {
557             return charSegment;
558         }
559         else
560         {
561             return originalValue + charSegment;
562         }
563     }
564 }