1/*
2 * Copyright (c) 2014, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package util;
25
26import java.io.ByteArrayInputStream;
27import java.io.File;
28import java.io.FileReader;
29import java.io.IOException;
30import java.io.StringReader;
31import java.io.UnsupportedEncodingException;
32import java.util.HashMap;
33
34import javax.xml.stream.XMLEventFactory;
35import javax.xml.stream.XMLInputFactory;
36import javax.xml.stream.XMLOutputFactory;
37import javax.xml.stream.XMLResolver;
38import javax.xml.stream.XMLStreamConstants;
39import javax.xml.stream.XMLStreamException;
40import javax.xml.stream.XMLStreamReader;
41import javax.xml.stream.events.XMLEvent;
42
43import org.testng.Assert;
44
45/**
46 * Base class for all StaxTest unit test classes. Contains shared
47 * functionality for many common set up tasks, as well as for
48 * outputting diagnostics.
49 *
50 */
51public class BaseStAXUT implements XMLStreamConstants {
52    /**
53     * This is the de facto standard property that enables accurate reporting of
54     * CDATA events.
55     */
56    final static String PROP_REPORT_CDATA = "http://java.sun.com/xml/stream/properties/report-cdata-event";
57
58    final static HashMap mTokenTypes = new HashMap();
59    static {
60        mTokenTypes.put(new Integer(START_ELEMENT), "START_ELEMENT");
61        mTokenTypes.put(new Integer(END_ELEMENT), "END_ELEMENT");
62        mTokenTypes.put(new Integer(START_DOCUMENT), "START_DOCUMENT");
63        mTokenTypes.put(new Integer(END_DOCUMENT), "END_DOCUMENT");
64        mTokenTypes.put(new Integer(CHARACTERS), "CHARACTERS");
65        mTokenTypes.put(new Integer(CDATA), "CDATA");
66        mTokenTypes.put(new Integer(COMMENT), "COMMENT");
67        mTokenTypes.put(new Integer(PROCESSING_INSTRUCTION), "PROCESSING_INSTRUCTION");
68        mTokenTypes.put(new Integer(DTD), "DTD");
69        mTokenTypes.put(new Integer(SPACE), "SPACE");
70        mTokenTypes.put(new Integer(ENTITY_REFERENCE), "ENTITY_REFERENCE");
71        mTokenTypes.put(new Integer(NAMESPACE), "NAMESPACE_DECLARATION");
72        mTokenTypes.put(new Integer(NOTATION_DECLARATION), "NOTATION_DECLARATION");
73        mTokenTypes.put(new Integer(ENTITY_DECLARATION), "ENTITY_DECLARATION");
74    }
75
76    /*
77     * /////////////////////////////////////////////////// // Consts for
78     * expected values ///////////////////////////////////////////////////
79     */
80
81    /**
82     * Expected return value for streamReader.getNamespaceURI() in
83     * non-namespace-aware mode.
84     */
85    protected final String DEFAULT_URI_NON_NS = "";
86
87    protected final String DEFAULT_URI_NS = "";
88
89    /*
90     * /////////////////////////////////////////////////// // Other consts
91     * ///////////////////////////////////////////////////
92     */
93
94    /*
95     * /////////////////////////////////////////////////// // Cached instances
96     * ///////////////////////////////////////////////////
97     */
98
99    XMLInputFactory mInputFactory;
100    XMLOutputFactory mOutputFactory;
101    XMLEventFactory mEventFactory;
102
103    protected XMLInputFactory getInputFactory() {
104        if (mInputFactory == null) {
105            mInputFactory = getNewInputFactory();
106        }
107        return mInputFactory;
108    }
109
110    protected static XMLInputFactory getNewInputFactory() {
111        return XMLInputFactory.newInstance();
112    }
113
114    protected XMLOutputFactory getOutputFactory() {
115        if (mOutputFactory == null) {
116            mOutputFactory = getNewOutputFactory();
117        }
118        return mOutputFactory;
119    }
120
121    protected static XMLOutputFactory getNewOutputFactory() {
122        return XMLOutputFactory.newInstance();
123    }
124
125    protected XMLEventFactory getEventFactory() {
126        if (mEventFactory == null) {
127            mEventFactory = XMLEventFactory.newInstance();
128        }
129        return mEventFactory;
130    }
131
132    protected static XMLStreamReader constructStreamReader(XMLInputFactory f, String content) throws XMLStreamException {
133        // return f.createXMLStreamReader(new StringReader(content));
134        try {
135            byte[] data = content.getBytes("UTF-8");
136            return constructStreamReader(f, data);
137        } catch (UnsupportedEncodingException e) {
138            throw new RuntimeException(e);
139        }
140    }
141
142    protected static XMLStreamReader constructStreamReader(XMLInputFactory f, byte[] b) throws XMLStreamException {
143        return f.createXMLStreamReader(new ByteArrayInputStream(b));
144    }
145
146    protected static XMLStreamReader constructStreamReaderForFile(XMLInputFactory f, String filename) throws IOException, XMLStreamException {
147        File inf = new File(filename);
148        XMLStreamReader sr = f.createXMLStreamReader(inf.toURL().toString(), new FileReader(inf));
149        Assert.assertEquals(START_DOCUMENT, sr.getEventType());
150        return sr;
151    }
152
153    protected XMLStreamReader constructNsStreamReader(String content) throws XMLStreamException {
154        XMLInputFactory f = getInputFactory();
155        setNamespaceAware(f, true);
156        return f.createXMLStreamReader(new StringReader(content));
157    }
158
159    protected XMLStreamReader constructNsStreamReader(String content, boolean coal) throws XMLStreamException {
160        XMLInputFactory f = getInputFactory();
161        setNamespaceAware(f, true);
162        setCoalescing(f, coal);
163        return f.createXMLStreamReader(new StringReader(content));
164    }
165
166    /*
167     * ////////////////////////////////////////////////// // Configuring input
168     * factory //////////////////////////////////////////////////
169     */
170
171    protected static boolean isCoalescing(XMLInputFactory f) throws XMLStreamException {
172        return ((Boolean) f.getProperty(XMLInputFactory.IS_COALESCING)).booleanValue();
173    }
174
175    protected static void setCoalescing(XMLInputFactory f, boolean state) throws XMLStreamException {
176        Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
177        f.setProperty(XMLInputFactory.IS_COALESCING, b);
178        // Let's just double-check it...
179        Assert.assertEquals(state, isCoalescing(f));
180    }
181
182    protected static boolean isValidating(XMLInputFactory f) throws XMLStreamException {
183        return ((Boolean) f.getProperty(XMLInputFactory.IS_VALIDATING)).booleanValue();
184    }
185
186    protected static void setValidating(XMLInputFactory f, boolean state) throws XMLStreamException {
187        try {
188            Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
189            f.setProperty(XMLInputFactory.IS_VALIDATING, b);
190        } catch (IllegalArgumentException iae) {
191            Assert.fail("Could not set DTD validating mode to " + state + ": " + iae);
192            // throw new XMLStreamException(iae.getMessage(), iae);
193        }
194        Assert.assertEquals(state, isValidating(f));
195    }
196
197    protected static boolean isNamespaceAware(XMLInputFactory f) throws XMLStreamException {
198        return ((Boolean) f.getProperty(XMLInputFactory.IS_NAMESPACE_AWARE)).booleanValue();
199    }
200
201    /**
202     * @return True if setting succeeded, and property supposedly was
203     *         succesfully set to the value specified; false if there was a
204     *         problem.
205     */
206    protected static boolean setNamespaceAware(XMLInputFactory f, boolean state) throws XMLStreamException {
207        try {
208            f.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, state ? Boolean.TRUE : Boolean.FALSE);
209
210            /*
211             * 07-Sep-2005, TSa: Let's not assert, but instead let's see if it
212             * sticks. Some implementations might choose to silently ignore
213             * setting, at least for 'false'?
214             */
215            return (isNamespaceAware(f) == state);
216        } catch (IllegalArgumentException e) {
217            /*
218             * Let's assume, then, that the property (or specific value for it)
219             * is NOT supported...
220             */
221            return false;
222        }
223    }
224
225    protected static void setReplaceEntities(XMLInputFactory f, boolean state) throws XMLStreamException {
226        Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
227        f.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, b);
228        Assert.assertEquals(b, f.getProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES));
229    }
230
231    protected static void setSupportDTD(XMLInputFactory f, boolean state) throws XMLStreamException {
232        Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
233        f.setProperty(XMLInputFactory.SUPPORT_DTD, b);
234        Assert.assertEquals(b, f.getProperty(XMLInputFactory.SUPPORT_DTD));
235    }
236
237    protected static boolean setSupportExternalEntities(XMLInputFactory f, boolean state) throws XMLStreamException {
238        Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
239        try {
240            f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, b);
241            Object act = f.getProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES);
242            return (act instanceof Boolean) && ((Boolean) act).booleanValue() == state;
243        } catch (IllegalArgumentException e) {
244            /*
245             * Let's assume, then, that the property (or specific value for it)
246             * is NOT supported...
247             */
248            return false;
249        }
250    }
251
252    protected static void setResolver(XMLInputFactory f, XMLResolver resolver) throws XMLStreamException {
253        f.setProperty(XMLInputFactory.RESOLVER, resolver);
254    }
255
256    protected static boolean setReportCData(XMLInputFactory f, boolean state) throws XMLStreamException {
257
258        Boolean b = state ? Boolean.TRUE : Boolean.FALSE;
259        if (f.isPropertySupported(PROP_REPORT_CDATA)) {
260            f.setProperty(PROP_REPORT_CDATA, b);
261            return true;
262        }
263        return false;
264    }
265
266    /*
267     * ////////////////////////////////////////////////// // Stream reader
268     * accessors //////////////////////////////////////////////////
269     */
270
271    /**
272     * Method that not only gets currently available text from the reader, but
273     * also checks that its consistenly accessible using different StAX methods.
274     */
275    protected static String getAndVerifyText(XMLStreamReader sr) throws XMLStreamException {
276        String text = sr.getText();
277
278        /*
279         * 05-Apr-2006, TSa: Although getText() is available for DTD and
280         * ENTITY_REFERENCE, getTextXxx() are not. Thus, can not do more checks
281         * for those types.
282         */
283        int type = sr.getEventType();
284        if (type != ENTITY_REFERENCE && type != DTD) {
285            Assert.assertNotNull("getText() should never return null.", text);
286            int expLen = sr.getTextLength();
287            /*
288             * Hmmh. Can only return empty text for CDATA (since empty blocks
289             * are legal).
290             */
291            /*
292             * !!! 01-Sep-2004, TSa: note: theoretically, in coalescing mode, it
293             * could be possible to have empty CDATA section(s) get converted to
294             * CHARACTERS, which would be empty... may need to enhance this to
295             * check that mode is not coalescing? Or something
296             */
297            if (sr.getEventType() == CHARACTERS) {
298                if (expLen == 0) {
299                    Assert.fail("Stream reader should never return empty Strings.");
300                }
301            }
302            Assert.assertEquals(expLen, text.length(), "Expected text length of " + expLen + ", got " + text.length());
303            char[] textChars = sr.getTextCharacters();
304            int start = sr.getTextStart();
305            String text2 = new String(textChars, start, expLen);
306            Assert.assertEquals("Expected getText() and getTextCharacters() to return same value for event of type (" + tokenTypeDesc(sr.getEventType()) + ")",
307                    text, text2);
308        } else { // DTD or ENTITY_REFERENCE
309            // not sure if null is legal for these either, but...
310            if (text == null) { // let's prevent an NPE at caller
311                text = "";
312            }
313        }
314        return text;
315    }
316
317    protected static String getAllText(XMLStreamReader sr) throws XMLStreamException {
318        StringBuffer sb = new StringBuffer();
319        while (true) {
320            int tt = sr.getEventType();
321            if (tt != CHARACTERS && tt != SPACE) {
322                break;
323            }
324            sb.append(getAndVerifyText(sr));
325            sr.next();
326        }
327        return sb.toString();
328    }
329
330    protected static String getAllCData(XMLStreamReader sr) throws XMLStreamException {
331        StringBuffer sb = new StringBuffer();
332        while (true) {
333            /*
334             * Note: CDATA sections CAN be reported as CHARACTERS, but not as
335             * SPACE
336             */
337            int tt = sr.getEventType();
338            if (tt != CHARACTERS && tt != CDATA) {
339                break;
340            }
341            sb.append(getAndVerifyText(sr));
342            sr.next();
343        }
344        return sb.toString();
345    }
346
347    /*
348     * ////////////////////////////////////////////////// // Derived assert/fail
349     * methods //////////////////////////////////////////////////
350     */
351
352    protected static void assertTokenType(int expType, int actType) {
353        if (expType == actType) {
354            return;
355        }
356        Assert.fail("Expected token " + tokenTypeDesc(expType) + "; got " + tokenTypeDesc(actType) + ".");
357    }
358
359    protected static void assertTokenType(int expType, int actType, XMLStreamReader sr) {
360        if (expType == actType) {
361            return;
362        }
363        Assert.fail("Expected token " + tokenTypeDesc(expType) + "; got " + tokenTypeDesc(actType, sr) + ".");
364    }
365
366    protected static void assertTextualTokenType(int actType) {
367        if (actType != CHARACTERS && actType != SPACE && actType != CDATA) {
368            Assert.fail("Expected textual token (CHARACTERS, SPACE or CDATA)" + "; got " + tokenTypeDesc(actType) + ".");
369        }
370    }
371
372    protected static void failStrings(String msg, String exp, String act) {
373        // !!! TODO: Indicate position where Strings differ
374        Assert.fail(msg + ": expected " + quotedPrintable(exp) + ", got " + quotedPrintable(act));
375    }
376
377    /**
378     * Specific method makes sense, since earlier it was not clear whether null
379     * or empty string (or perhaps both) would be the right answer when there is
380     * no prefix.
381     * <p>
382     * However: as per javadocs of {@link XMLStreamReader#getPrefix}, from JDK
383     * 1.6 indicate, the current understanding is that <b>null</b> is the
384     * ultimate right answer here.
385     */
386    protected static void assertNoPrefix(XMLStreamReader sr) throws XMLStreamException {
387        String prefix = sr.getPrefix();
388        if (prefix != null) {
389            if (prefix.length() != 0) {
390                Assert.fail("Current element should not have a prefix: got '" + prefix + "'");
391            } else {
392                Assert.fail("Expected null to signify missing prefix (see XMLStreamReader#getPrefix() JavaDocs): got empty String");
393            }
394        }
395    }
396
397    protected static void assertNoAttrPrefix(String attrPrefix) throws XMLStreamException {
398        if (attrPrefix != null) {
399            if (attrPrefix.length() != 0) {
400                Assert.fail("Attribute should not have a prefix: got '" + attrPrefix + "'");
401            } else {
402                Assert.fail("Expected null to signify missing attribute prefix (see XMLStreamReader#getAttributePrefix() JavaDocs): got empty String");
403            }
404        }
405    }
406
407    /**
408     * Similar to {@link #assertNoPrefix}, but here we do know that unbound
409     * namespace URI should be indicated as empty String.
410     */
411    protected static void assertNoNsURI(XMLStreamReader sr) throws XMLStreamException {
412        String uri = sr.getNamespaceURI();
413        if (uri == null) {
414            Assert.fail("Expected empty String to indicate \"no namespace\": got null");
415        } else if (uri.length() != 0) {
416            Assert.fail("Expected empty String to indicate \"no namespace\": got '" + uri + "'");
417        }
418    }
419
420    protected static void assertNoAttrNamespace(String attrNsURI) throws XMLStreamException {
421        if (attrNsURI == null) {
422            // refer to 6903561; accept null for now.
423            // fail("Expected empty String to indicate \"no namespace\" (for attribute): got null");
424        } else if (attrNsURI.length() != 0) {
425            Assert.fail("Expected empty String to indicate \"no namespace\" (for attribute): got '" + attrNsURI + "'");
426        }
427    }
428
429    protected static void assertNoPrefixOrNs(XMLStreamReader sr) throws XMLStreamException {
430        assertNoPrefix(sr);
431        assertNoNsURI(sr);
432    }
433
434    /**
435     * Helper assertion that assert that the String is either null or empty
436     * ("").
437     */
438    protected static void assertNullOrEmpty(String str) {
439        if (str != null && str.length() > 0) {
440            Assert.fail("Expected String to be empty or null; was '" + str + "' (length " + str.length() + ")");
441        }
442    }
443
444    /*
445     * ////////////////////////////////////////////////// // Debug/output
446     * helpers //////////////////////////////////////////////////
447     */
448
449    protected static String tokenTypeDesc(int tt) {
450        String desc = (String) mTokenTypes.get(new Integer(tt));
451        if (desc == null) {
452            return "[" + tt + "]";
453        }
454        return desc;
455    }
456
457    protected static String tokenTypeDesc(XMLEvent evt) {
458        return tokenTypeDesc(evt.getEventType());
459    }
460
461    final static int MAX_DESC_TEXT_CHARS = 8;
462
463    protected static String tokenTypeDesc(int tt, XMLStreamReader sr) {
464        String desc = tokenTypeDesc(tt);
465        // Let's show first 8 chars or so...
466        if (tt == CHARACTERS || tt == SPACE || tt == CDATA) {
467            String str = sr.getText();
468            if (str.length() > MAX_DESC_TEXT_CHARS) {
469                desc = "\"" + str.substring(0, MAX_DESC_TEXT_CHARS) + "\"[...]";
470            } else {
471                desc = "\"" + desc + "\"";
472            }
473            desc = " (" + desc + ")";
474        }
475        return desc;
476    }
477
478    protected static String valueDesc(String value) {
479        if (value == null) {
480            return "[NULL]";
481        }
482        return "\"" + value + "\"";
483    }
484
485    protected static String printable(char ch) {
486        if (ch == '\n') {
487            return "\\n";
488        }
489        if (ch == '\r') {
490            return "\\r";
491        }
492        if (ch == '\t') {
493            return "\\t";
494        }
495        if (ch == ' ') {
496            return "_";
497        }
498        if (ch > 127 || ch < 32) {
499            StringBuffer sb = new StringBuffer(6);
500            sb.append("\\u");
501            String hex = Integer.toHexString((int) ch);
502            for (int i = 0, len = 4 - hex.length(); i < len; i++) {
503                sb.append('0');
504            }
505            sb.append(hex);
506            return sb.toString();
507        }
508        return null;
509    }
510
511    protected static String printable(String str) {
512        if (str == null || str.length() == 0) {
513            return str;
514        }
515
516        int len = str.length();
517        StringBuffer sb = new StringBuffer(len + 64);
518        for (int i = 0; i < len; ++i) {
519            char c = str.charAt(i);
520            String res = printable(c);
521            if (res == null) {
522                sb.append(c);
523            } else {
524                sb.append(res);
525            }
526        }
527        return sb.toString();
528    }
529
530    protected static String quotedPrintable(String str) {
531        if (str == null || str.length() == 0) {
532            return "[0]''";
533        }
534        return "[len: " + str.length() + "] '" + printable(str) + "'";
535    }
536
537    protected void reportNADueToProperty(String method, String prop) {
538        String clsName = getClass().getName();
539        /*
540         * 27-Sep-2005, TSa: Should probably use some other mechanism for
541         * reporting this. Does JUnit have something applicable?
542         */
543        System.err.println("Skipping " + clsName + "#" + method + ": property '" + prop + "' (or one of its values) not supported.");
544    }
545
546    protected void reportNADueToNS(String method) {
547        reportNADueToProperty(method, "IS_NAMESPACE_AWARE");
548    }
549
550    protected void reportNADueToExtEnt(String method) {
551        reportNADueToProperty(method, "IS_SUPPORTING_EXTERNAL_ENTITIES");
552    }
553
554    protected void reportNADueToEntityExpansion(String method, int type) {
555        String clsName = getClass().getName();
556        String msg = (type > 0) ? " (next event: " + tokenTypeDesc(type) + ")" : "";
557        System.err.println("Skipping " + clsName + "#" + method + ": entity expansion does not seem to be functioning properly" + msg + ".");
558    }
559}
560