View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.transform.pull;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.Reader;
24  import java.util.ArrayDeque;
25  import java.util.Deque;
26  import java.util.Objects;
27  import java.util.regex.Pattern;
28  
29  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
30  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
31  
32  /**
33   * An xml pull parser filter base implementation.
34   *
35   * @author Guillaume Nodet
36   * @since 4.0.0
37   */
38  public class BufferingParser implements XmlPullParser {
39  
40      private static final Pattern WHITESPACE_REGEX = Pattern.compile("[ \r\t\n]+");
41  
42      protected XmlPullParser xmlPullParser;
43      protected Deque<Event> events;
44      protected Event current;
45      protected boolean bypass;
46  
47      @SuppressWarnings("checkstyle:VisibilityModifier")
48      public static class Event {
49          public int event;
50          public String name;
51          public String prefix;
52          public String namespace;
53          public boolean empty;
54          public String text;
55          public Attribute[] attributes;
56          public Namespace[] namespaces;
57      }
58  
59      @SuppressWarnings("checkstyle:VisibilityModifier")
60      public static class Namespace {
61          public String prefix;
62          public String uri;
63      }
64  
65      @SuppressWarnings("checkstyle:VisibilityModifier")
66      public static class Attribute {
67          public String name;
68          public String prefix;
69          public String namespace;
70          public String type;
71          public String value;
72          public boolean isDefault;
73      }
74  
75      public BufferingParser(XmlPullParser xmlPullParser) {
76          this.xmlPullParser = xmlPullParser;
77      }
78  
79      @Override
80      public void setFeature(String name, boolean state) throws XmlPullParserException {
81          xmlPullParser.setFeature(name, state);
82      }
83  
84      @Override
85      public boolean getFeature(String name) {
86          return xmlPullParser.getFeature(name);
87      }
88  
89      @Override
90      public void setProperty(String name, Object value) throws XmlPullParserException {
91          xmlPullParser.setProperty(name, value);
92      }
93  
94      @Override
95      public Object getProperty(String name) {
96          return xmlPullParser.getProperty(name);
97      }
98  
99      @Override
100     public void setInput(Reader in) throws XmlPullParserException {
101         xmlPullParser.setInput(in);
102     }
103 
104     @Override
105     public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
106         xmlPullParser.setInput(inputStream, inputEncoding);
107     }
108 
109     @Override
110     public String getInputEncoding() {
111         return xmlPullParser.getInputEncoding();
112     }
113 
114     @Override
115     public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
116         xmlPullParser.defineEntityReplacementText(entityName, replacementText);
117     }
118 
119     @Override
120     public int getNamespaceCount(int depth) throws XmlPullParserException {
121         //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
122         return xmlPullParser.getNamespaceCount(depth);
123     }
124 
125     @Override
126     public String getNamespacePrefix(int pos) throws XmlPullParserException {
127         //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
128         return xmlPullParser.getNamespacePrefix(pos);
129     }
130 
131     @Override
132     public String getNamespaceUri(int pos) throws XmlPullParserException {
133         //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
134         return xmlPullParser.getNamespaceUri(pos);
135     }
136 
137     @Override
138     public String getNamespace(String prefix) {
139         //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
140         return xmlPullParser.getNamespace(prefix);
141     }
142 
143     @Override
144     public int getDepth() {
145         //  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
146         return xmlPullParser.getDepth();
147     }
148 
149     @Override
150     public String getPositionDescription() {
151         if (current != null) {
152             throw new IllegalStateException("Not supported during events replay");
153         }
154         return xmlPullParser.getPositionDescription();
155     }
156 
157     @Override
158     public int getLineNumber() {
159         if (current != null) {
160             throw new IllegalStateException("Not supported during events replay");
161         }
162         return xmlPullParser.getLineNumber();
163     }
164 
165     @Override
166     public int getColumnNumber() {
167         if (current != null) {
168             throw new IllegalStateException("Not supported during events replay");
169         }
170         return xmlPullParser.getColumnNumber();
171     }
172 
173     @Override
174     public boolean isWhitespace() throws XmlPullParserException {
175         if (current != null) {
176             if (current.event == TEXT || current.event == CDSECT) {
177                 return WHITESPACE_REGEX.matcher(current.text).matches();
178             } else if (current.event == IGNORABLE_WHITESPACE) {
179                 return true;
180             } else {
181                 throw new XmlPullParserException("no content available to check for whitespaces");
182             }
183         }
184         return xmlPullParser.isWhitespace();
185     }
186 
187     @Override
188     public String getText() {
189         return current != null ? current.text : xmlPullParser.getText();
190     }
191 
192     @Override
193     public char[] getTextCharacters(int[] holderForStartAndLength) {
194         if (current != null) {
195             throw new IllegalStateException("Not supported during events replay");
196         }
197         return xmlPullParser.getTextCharacters(holderForStartAndLength);
198     }
199 
200     @Override
201     public String getNamespace() {
202         return current != null ? current.namespace : xmlPullParser.getNamespace();
203     }
204 
205     @Override
206     public String getName() {
207         return current != null ? current.name : xmlPullParser.getName();
208     }
209 
210     @Override
211     public String getPrefix() {
212         return current != null ? current.prefix : xmlPullParser.getPrefix();
213     }
214 
215     @Override
216     public boolean isEmptyElementTag() throws XmlPullParserException {
217         return current != null ? current.empty : xmlPullParser.isEmptyElementTag();
218     }
219 
220     @Override
221     public int getAttributeCount() {
222         if (current != null) {
223             return current.attributes != null ? current.attributes.length : 0;
224         } else {
225             return xmlPullParser.getAttributeCount();
226         }
227     }
228 
229     @Override
230     public String getAttributeNamespace(int index) {
231         if (current != null) {
232             return current.attributes[index].namespace;
233         } else {
234             return xmlPullParser.getAttributeNamespace(index);
235         }
236     }
237 
238     @Override
239     public String getAttributeName(int index) {
240         if (current != null) {
241             return current.attributes[index].name;
242         } else {
243             return xmlPullParser.getAttributeName(index);
244         }
245     }
246 
247     @Override
248     public String getAttributePrefix(int index) {
249         if (current != null) {
250             return current.attributes[index].prefix;
251         } else {
252             return xmlPullParser.getAttributePrefix(index);
253         }
254     }
255 
256     @Override
257     public String getAttributeType(int index) {
258         if (current != null) {
259             return current.attributes[index].type;
260         } else {
261             return xmlPullParser.getAttributeType(index);
262         }
263     }
264 
265     @Override
266     public boolean isAttributeDefault(int index) {
267         if (current != null) {
268             return current.attributes[index].isDefault;
269         } else {
270             return xmlPullParser.isAttributeDefault(index);
271         }
272     }
273 
274     @Override
275     public String getAttributeValue(int index) {
276         if (current != null) {
277             return current.attributes[index].value;
278         } else {
279             return xmlPullParser.getAttributeValue(index);
280         }
281     }
282 
283     @Override
284     public String getAttributeValue(String namespace, String name) {
285         if (current != null) {
286             if (current.attributes != null) {
287                 for (Attribute attr : current.attributes) {
288                     if (Objects.equals(namespace, attr.namespace) && Objects.equals(name, attr.name)) {
289                         return attr.value;
290                     }
291                 }
292             }
293             return null;
294         } else {
295             return xmlPullParser.getAttributeValue(namespace, name);
296         }
297     }
298 
299     @Override
300     public void require(int type, String namespace, String name) throws XmlPullParserException, IOException {
301         if (current != null) {
302             throw new IllegalStateException("Not supported during events replay");
303         }
304         xmlPullParser.require(type, namespace, name);
305     }
306 
307     @Override
308     public int getEventType() throws XmlPullParserException {
309         return current != null ? current.event : xmlPullParser.getEventType();
310     }
311 
312     @Override
313     public int next() throws XmlPullParserException, IOException {
314         while (true) {
315             if (events != null && !events.isEmpty()) {
316                 current = events.removeFirst();
317                 return current.event;
318             } else {
319                 current = null;
320             }
321             if (getEventType() == END_DOCUMENT) {
322                 throw new XmlPullParserException("already reached end of XML input", this, null);
323             }
324             int currentEvent = xmlPullParser.next();
325             if (bypass() || accept()) {
326                 return currentEvent;
327             }
328         }
329     }
330 
331     @Override
332     public int nextToken() throws XmlPullParserException, IOException {
333         while (true) {
334             if (events != null && !events.isEmpty()) {
335                 current = events.removeFirst();
336                 return current.event;
337             } else {
338                 current = null;
339             }
340             if (getEventType() == END_DOCUMENT) {
341                 throw new XmlPullParserException("already reached end of XML input", this, null);
342             }
343             int currentEvent = xmlPullParser.nextToken();
344             if (bypass() || accept()) {
345                 return currentEvent;
346             }
347         }
348     }
349 
350     @Override
351     public int nextTag() throws XmlPullParserException, IOException {
352         int eventType = next();
353         if (eventType == TEXT && isWhitespace()) { // skip whitespace
354             eventType = next();
355         }
356         if (eventType != START_TAG && eventType != END_TAG) {
357             throw new XmlPullParserException("expected START_TAG or END_TAG not " + TYPES[getEventType()], this, null);
358         }
359         return eventType;
360     }
361 
362     @Override
363     public String nextText() throws XmlPullParserException, IOException {
364         int eventType = getEventType();
365         if (eventType != START_TAG) {
366             throw new XmlPullParserException("parser must be on START_TAG to read next text", this, null);
367         }
368         eventType = next();
369         if (eventType == TEXT) {
370             final String result = getText();
371             eventType = next();
372             if (eventType != END_TAG) {
373                 throw new XmlPullParserException(
374                         "TEXT must be immediately followed by END_TAG and not " + TYPES[getEventType()], this, null);
375             }
376             return result;
377         } else if (eventType == END_TAG) {
378             return "";
379         } else {
380             throw new XmlPullParserException("parser must be on START_TAG or TEXT to read text", this, null);
381         }
382     }
383 
384     protected Event bufferEvent() throws XmlPullParserException {
385         Event event = new Event();
386         XmlPullParser pp = xmlPullParser;
387         event.event = xmlPullParser.getEventType();
388         switch (event.event) {
389             case START_DOCUMENT:
390             case END_DOCUMENT:
391                 break;
392             case START_TAG:
393                 event.name = pp.getName();
394                 event.namespace = pp.getNamespace();
395                 event.prefix = pp.getPrefix();
396                 event.empty = pp.isEmptyElementTag();
397                 event.text = pp.getText();
398                 break;
399             case END_TAG:
400                 event.name = pp.getName();
401                 event.namespace = pp.getNamespace();
402                 event.prefix = pp.getPrefix();
403                 event.text = pp.getText();
404                 break;
405             case TEXT:
406             case COMMENT:
407             case IGNORABLE_WHITESPACE:
408                 event.text = pp.getText();
409                 break;
410             default:
411                 break;
412         }
413         return event;
414     }
415 
416     protected void pushEvent(Event event) {
417         if (events == null) {
418             events = new ArrayDeque<>();
419         }
420         events.add(event);
421     }
422 
423     protected boolean accept() throws XmlPullParserException, IOException {
424         return true;
425     }
426 
427     public void bypass(boolean bypass) {
428         if (bypass && events != null && !events.isEmpty()) {
429             throw new IllegalStateException("Can not disable filter while processing");
430         }
431         this.bypass = bypass;
432     }
433 
434     public boolean bypass() {
435         return bypass || (xmlPullParser instanceof BufferingParser && ((BufferingParser) xmlPullParser).bypass());
436     }
437 
438     protected static String nullSafeAppend(String originalValue, String charSegment) {
439         if (originalValue == null) {
440             return charSegment;
441         } else {
442             return originalValue + charSegment;
443         }
444     }
445 }