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