1/*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.ws.streaming;
27
28import javax.xml.namespace.QName;
29import static javax.xml.stream.XMLStreamConstants.*;
30import javax.xml.stream.XMLStreamException;
31import javax.xml.stream.XMLStreamReader;
32import javax.xml.stream.XMLStreamConstants;
33
34/**
35 * <p> XMLStreamReaderUtil provides some utility methods intended to be used
36 * in conjunction with a StAX XMLStreamReader. </p>
37 *
38 * @author WS Development Team
39 */
40public class XMLStreamReaderUtil {
41
42    private XMLStreamReaderUtil() {
43    }
44
45    public static void close(XMLStreamReader reader) {
46        try {
47            reader.close();
48        } catch (XMLStreamException e) {
49            throw wrapException(e);
50        }
51    }
52
53    public static void readRest(XMLStreamReader reader) {
54        try {
55            while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {
56                reader.next();
57            }
58        } catch (XMLStreamException e) {
59            throw wrapException(e);
60        }
61    }
62
63    public static int next(XMLStreamReader reader) {
64        try {
65            int readerEvent = reader.next();
66
67            while (readerEvent != END_DOCUMENT) {
68                switch (readerEvent) {
69                    case START_ELEMENT:
70                    case END_ELEMENT:
71                    case CDATA:
72                    case CHARACTERS:
73                    case PROCESSING_INSTRUCTION:
74                        return readerEvent;
75                    default:
76                        // falls through ignoring event
77                }
78                readerEvent = reader.next();
79            }
80
81            return readerEvent;
82        }
83        catch (XMLStreamException e) {
84            throw wrapException(e);
85        }
86    }
87
88    public static int nextElementContent(XMLStreamReader reader) {
89        int state = nextContent(reader);
90        if (state == CHARACTERS) {
91            throw new XMLStreamReaderException(
92                "xmlreader.unexpectedCharacterContent", reader.getText());
93        }
94        return state;
95    }
96
97    public static void toNextTag(XMLStreamReader reader, QName name) {
98        // skip any whitespace
99        if (reader.getEventType() != XMLStreamConstants.START_ELEMENT &&
100                reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
101            XMLStreamReaderUtil.nextElementContent(reader);
102        }
103        if(reader.getEventType() == XMLStreamConstants.END_ELEMENT && name.equals(reader.getName())) {
104            XMLStreamReaderUtil.nextElementContent(reader);
105        }
106    }
107
108    /**
109     * Moves next and read spaces from the reader as long as to the next element.
110     * Comments are ignored
111     * @param reader
112     * @return
113     */
114    public static String nextWhiteSpaceContent(XMLStreamReader reader) {
115        next(reader);
116        return currentWhiteSpaceContent(reader);
117    }
118
119    /**
120     * Read spaces from the reader as long as to the next element, starting from
121     * current position. Comments are ignored.
122     * @param reader
123     * @return
124     */
125    public static String currentWhiteSpaceContent(XMLStreamReader reader) {
126
127        // since the there might be several valid chunks (spaces/comment/spaces)
128        // StringBuilder must be used; it's initialized lazily, only when needed
129        StringBuilder whiteSpaces = null;
130
131        for (;;) {
132            switch (reader.getEventType()) {
133                case START_ELEMENT:
134                case END_ELEMENT:
135                case END_DOCUMENT:
136                    return whiteSpaces == null ? null : whiteSpaces.toString();
137                case CHARACTERS:
138                    if (reader.isWhiteSpace()) {
139                        if (whiteSpaces == null) {
140                            whiteSpaces = new StringBuilder();
141                        }
142                        whiteSpaces.append(reader.getText());
143                    } else {
144                        throw new XMLStreamReaderException(
145                                "xmlreader.unexpectedCharacterContent", reader.getText());
146                    }
147            }
148            next(reader);
149        }
150    }
151
152    public static int nextContent(XMLStreamReader reader) {
153        for (;;) {
154            int state = next(reader);
155            switch (state) {
156                case START_ELEMENT:
157                case END_ELEMENT:
158                case END_DOCUMENT:
159                    return state;
160                case CHARACTERS:
161                    if (!reader.isWhiteSpace()) {
162                        return CHARACTERS;
163                    }
164            }
165        }
166    }
167
168    /**
169     * Skip current element, leaving the cursor at END_ELEMENT of
170     * current element.
171     */
172    public static void skipElement(XMLStreamReader reader) {
173        assert reader.getEventType() == START_ELEMENT;
174        skipTags(reader, true);
175        assert reader.getEventType() == END_ELEMENT;
176    }
177
178    /**
179     * Skip following siblings, leaving cursor at END_ELEMENT of
180     * parent element.
181     */
182    public static void skipSiblings(XMLStreamReader reader, QName parent) {
183        skipTags(reader, reader.getName().equals(parent));
184        assert reader.getEventType() == END_ELEMENT;
185    }
186
187    private static void skipTags(XMLStreamReader reader, boolean exitCondition) {
188        try {
189            int state, tags = 0;
190            while ((state = reader.next()) != END_DOCUMENT) {
191                if (state == START_ELEMENT) {
192                    tags++;
193                }
194                else if (state == END_ELEMENT) {
195                    if (tags == 0 && exitCondition) return;
196                    tags--;
197                }
198            }
199        }
200        catch (XMLStreamException e) {
201            throw wrapException(e);
202        }
203    }
204
205    /*
206    * Get the text of an element
207    */
208    public static String getElementText(XMLStreamReader reader) {
209        try {
210            return reader.getElementText();
211        } catch (XMLStreamException e) {
212            throw wrapException(e);
213        }
214    }
215
216    /*
217    * Get a QName with 'someUri' and 'localname' from an
218    * element of qname type:
219    * <xyz xmlns:ns1="someUri">ns1:localname</xyz>
220    */
221    public static QName getElementQName(XMLStreamReader reader) {
222        try {
223            String text = reader.getElementText().trim();
224            String prefix = text.substring(0,text.indexOf(':'));
225            String namespaceURI = reader.getNamespaceContext().getNamespaceURI(prefix);
226            if (namespaceURI == null) {
227                namespaceURI = "";
228            }
229            String localPart = text.substring(
230                    text.indexOf(':') + 1, text.length());
231            return new QName(namespaceURI, localPart);
232        } catch (XMLStreamException e) {
233            throw wrapException(e);
234        }
235    }
236
237    /**
238     * Read all attributes into an data structure. Note that this method cannot
239     * be called multiple times to get the same list of attributes.
240     */
241    public static Attributes getAttributes(XMLStreamReader reader) {
242        return (reader.getEventType() == START_ELEMENT ||
243                reader.getEventType() == ATTRIBUTE) ?
244                new AttributesImpl(reader) : null;
245    }
246
247    public static void verifyReaderState(XMLStreamReader reader, int expectedState) {
248        int state = reader.getEventType();
249        if (state != expectedState) {
250            throw new XMLStreamReaderException(
251                "xmlreader.unexpectedState",
252                getStateName(expectedState), getStateName(state));
253        }
254    }
255
256    public static void verifyTag(XMLStreamReader reader, String namespaceURI, String localName) {
257        if (!localName.equals(reader.getLocalName()) || !namespaceURI.equals(reader.getNamespaceURI())) {
258            throw new XMLStreamReaderException(
259                "xmlreader.unexpectedState.tag",
260                    "{" + namespaceURI + "}" + localName,
261                    "{" + reader.getNamespaceURI() + "}" + reader.getLocalName());
262        }
263    }
264
265    public static void verifyTag(XMLStreamReader reader, QName name) {
266        verifyTag(reader, name.getNamespaceURI(), name.getLocalPart());
267    }
268
269    public static String getStateName(XMLStreamReader reader) {
270        return getStateName(reader.getEventType());
271    }
272
273    public static String getStateName(int state) {
274        switch (state) {
275            case ATTRIBUTE:
276                return "ATTRIBUTE";
277            case CDATA:
278                return "CDATA";
279            case CHARACTERS:
280                return "CHARACTERS";
281            case COMMENT:
282                return "COMMENT";
283            case DTD:
284                return "DTD";
285            case END_DOCUMENT:
286                return "END_DOCUMENT";
287            case END_ELEMENT:
288                return "END_ELEMENT";
289            case ENTITY_DECLARATION:
290                return "ENTITY_DECLARATION";
291            case ENTITY_REFERENCE:
292                return "ENTITY_REFERENCE";
293            case NAMESPACE:
294                return "NAMESPACE";
295            case NOTATION_DECLARATION:
296                return "NOTATION_DECLARATION";
297            case PROCESSING_INSTRUCTION:
298                return "PROCESSING_INSTRUCTION";
299            case SPACE:
300                return "SPACE";
301            case START_DOCUMENT:
302                return "START_DOCUMENT";
303            case START_ELEMENT:
304                return "START_ELEMENT";
305            default :
306                return "UNKNOWN";
307        }
308    }
309
310    private static XMLStreamReaderException wrapException(XMLStreamException e) {
311        return new XMLStreamReaderException("xmlreader.ioException",e);
312    }
313
314    // -- Auxiliary classes ----------------------------------------------
315
316    /**
317     * AttributesImpl class copied from old StAXReader. This class is used to implement
318     * getAttributes() on a StAX Reader.
319     */
320    public static class AttributesImpl implements Attributes {
321
322        static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
323
324        static class AttributeInfo {
325
326            private QName name;
327            private String value;
328
329            public AttributeInfo(QName name, String value) {
330                this.name = name;
331                if (value == null) {
332                    // e.g., <return xmlns=""> -- stax returns null
333                    this.value = "";
334                } else {
335                    this.value = value;
336                }
337            }
338
339            QName getName() {
340                return name;
341            }
342
343            String getValue() {
344                return value;
345            }
346
347            /*
348             * Return "xmlns:" as part of name if namespace.
349             */
350            String getLocalName() {
351                if (isNamespaceDeclaration()) {
352                    if (name.getLocalPart().equals("")) {
353                        return "xmlns";
354                    }
355                    return "xmlns:" + name.getLocalPart();
356                }
357                return name.getLocalPart();
358            }
359
360            boolean isNamespaceDeclaration() {
361                return (name.getNamespaceURI() == XMLNS_NAMESPACE_URI);
362            }
363        }
364
365        // stores qname and value for each attribute
366        AttributeInfo [] atInfos;
367
368        /*
369         * Will create a list that contains the namespace declarations
370         * as well as the other attributes.
371         */
372        public AttributesImpl(XMLStreamReader reader) {
373            if (reader == null) {
374
375                // this is the case when we call getAttributes() on the
376                // reader when it is not on a start tag
377                atInfos = new AttributeInfo[0];
378            } else {
379
380                // this is the normal case
381                int index = 0;
382                int namespaceCount = reader.getNamespaceCount();
383                int attributeCount = reader.getAttributeCount();
384                atInfos = new AttributeInfo[namespaceCount + attributeCount];
385                for (int i=0; i<namespaceCount; i++) {
386                    String namespacePrefix = reader.getNamespacePrefix(i);
387
388                    // will be null if default prefix. QName can't take null
389                    if (namespacePrefix == null) {
390                        namespacePrefix = "";
391                    }
392                    atInfos[index++] = new AttributeInfo(
393                        new QName(XMLNS_NAMESPACE_URI,
394                            namespacePrefix,
395                            "xmlns"),
396                        reader.getNamespaceURI(i));
397                }
398                for (int i=0; i<attributeCount; i++) {
399                    atInfos[index++] = new AttributeInfo(
400                        reader.getAttributeName(i),
401                        reader.getAttributeValue(i));
402                }
403            }
404        }
405
406        public int getLength() {
407            return atInfos.length;
408        }
409
410        public String getLocalName(int index) {
411            if (index >= 0 && index < atInfos.length) {
412                return atInfos[index].getLocalName();
413            }
414            return null;
415        }
416
417        public QName getName(int index) {
418            if (index >= 0 && index < atInfos.length) {
419                return atInfos[index].getName();
420            }
421            return null;
422        }
423
424        public String getPrefix(int index) {
425            if (index >= 0 && index < atInfos.length) {
426                return atInfos[index].getName().getPrefix();
427            }
428            return null;
429        }
430
431        public String getURI(int index) {
432            if (index >= 0 && index < atInfos.length) {
433                return atInfos[index].getName().getNamespaceURI();
434            }
435            return null;
436        }
437
438        public String getValue(int index) {
439            if (index >= 0 && index < atInfos.length) {
440                return atInfos[index].getValue();
441            }
442            return null;
443        }
444
445        public String getValue(QName name) {
446            int index = getIndex(name);
447            if (index != -1) {
448                return atInfos[index].getValue();
449            }
450            return null;
451        }
452
453        public String getValue(String localName) {
454            int index = getIndex(localName);
455            if (index != -1) {
456                return atInfos[index].getValue();
457            }
458            return null;
459        }
460
461        public String getValue(String uri, String localName) {
462            int index = getIndex(uri, localName);
463            if (index != -1) {
464                return atInfos[index].getValue();
465            }
466            return null;
467        }
468
469        public boolean isNamespaceDeclaration(int index) {
470            if (index >= 0 && index < atInfos.length) {
471                return atInfos[index].isNamespaceDeclaration();
472            }
473            return false;
474        }
475
476        public int getIndex(QName name) {
477            for (int i=0; i<atInfos.length; i++) {
478                if (atInfos[i].getName().equals(name)) {
479                    return i;
480                }
481            }
482            return -1;
483        }
484
485        public int getIndex(String localName) {
486            for (int i=0; i<atInfos.length; i++) {
487                if (atInfos[i].getName().getLocalPart().equals(localName)) {
488                    return i;
489                }
490            }
491            return -1;
492        }
493
494        public int getIndex(String uri, String localName) {
495            QName qName;
496            for (int i=0; i<atInfos.length; i++) {
497                qName = atInfos[i].getName();
498                if (qName.getNamespaceURI().equals(uri) &&
499                    qName.getLocalPart().equals(localName)) {
500
501                    return i;
502                }
503            }
504            return -1;
505        }
506    }
507}
508