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