1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.xml.stream;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.Arrays;
22  import javax.xml.stream.XMLInputFactory;
23  import javax.xml.stream.XMLStreamException;
24  
25  import junit.framework.TestCase;
26  import org.easymock.AbstractMatcher;
27  import org.easymock.MockControl;
28  import org.xml.sax.Attributes;
29  import org.xml.sax.ContentHandler;
30  import org.xml.sax.InputSource;
31  import org.xml.sax.Locator;
32  import org.xml.sax.SAXException;
33  import org.xml.sax.XMLReader;
34  import org.xml.sax.ext.LexicalHandler;
35  import org.xml.sax.helpers.AttributesImpl;
36  import org.xml.sax.helpers.XMLReaderFactory;
37  
38  import org.springframework.core.io.ClassPathResource;
39  import org.springframework.core.io.Resource;
40  import org.springframework.xml.sax.SaxUtils;
41  
42  public abstract class AbstractStaxXmlReaderTestCase extends TestCase {
43  
44      protected static XMLInputFactory inputFactory;
45  
46      private Resource testContentHandler;
47  
48      private XMLReader standardReader;
49  
50      private MockControl contentHandlerControl;
51  
52      private ContentHandler contentHandler;
53  
54      protected void setUp() throws Exception {
55          inputFactory = XMLInputFactory.newInstance();
56          standardReader = XMLReaderFactory.createXMLReader();
57          contentHandlerControl = MockControl.createStrictControl(ContentHandler.class);
58          contentHandlerControl.setDefaultMatcher(new SaxArgumentMatcher());
59          ContentHandler contentHandlerMock = (ContentHandler) contentHandlerControl.getMock();
60          contentHandler = new CopyingContentHandler(contentHandlerMock);
61          standardReader.setContentHandler(contentHandler);
62  
63          testContentHandler = new ClassPathResource("testContentHandler.xml", getClass());
64      }
65  
66      public void testContentHandlerNamespacesNoPrefixes() throws SAXException, IOException, XMLStreamException {
67          standardReader.setFeature("http://xml.org/sax/features/namespaces", true);
68          standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
69  
70          standardReader.parse(SaxUtils.createInputSource(testContentHandler));
71          contentHandlerControl.replay();
72  
73          AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testContentHandler.getInputStream());
74          staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true);
75          staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
76  
77          staxXmlReader.setContentHandler(contentHandler);
78          staxXmlReader.parse(new InputSource());
79          contentHandlerControl.verify();
80      }
81  
82      public void testContentHandlerNamespacesPrefixes() throws SAXException, IOException, XMLStreamException {
83          standardReader.setFeature("http://xml.org/sax/features/namespaces", true);
84          standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
85  
86          standardReader.parse(SaxUtils.createInputSource(testContentHandler));
87          contentHandlerControl.replay();
88  
89          AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testContentHandler.getInputStream());
90          staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true);
91          staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
92  
93          staxXmlReader.setContentHandler(contentHandler);
94          staxXmlReader.parse(new InputSource());
95          contentHandlerControl.verify();
96      }
97  
98      public void testContentHandlerNoNamespacesPrefixes() throws SAXException, IOException, XMLStreamException {
99          standardReader.setFeature("http://xml.org/sax/features/namespaces", false);
100         standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
101 
102         standardReader.parse(SaxUtils.createInputSource(testContentHandler));
103         contentHandlerControl.replay();
104 
105         AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testContentHandler.getInputStream());
106         staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", false);
107         staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
108 
109         staxXmlReader.setContentHandler(contentHandler);
110         staxXmlReader.parse(new InputSource());
111         contentHandlerControl.verify();
112     }
113 
114     public void testLexicalHandler() throws SAXException, IOException, XMLStreamException {
115 
116         MockControl lexicalHandlerControl = MockControl.createStrictControl(LexicalHandler.class);
117         lexicalHandlerControl.setDefaultMatcher(new SaxArgumentMatcher());
118         LexicalHandler lexicalHandlerMock = (LexicalHandler) lexicalHandlerControl.getMock();
119         LexicalHandler lexicalHandler = new CopyingLexicalHandler(lexicalHandlerMock);
120 
121         Resource testLexicalHandlerXml = new ClassPathResource("testLexicalHandler.xml", getClass());
122 
123         standardReader.setContentHandler(null);
124         standardReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler);
125         standardReader.parse(SaxUtils.createInputSource(testLexicalHandlerXml));
126         lexicalHandlerControl.replay();
127 
128         inputFactory.setProperty("javax.xml.stream.isCoalescing", Boolean.FALSE);
129         inputFactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE);
130         inputFactory.setProperty("javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE);
131         inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", Boolean.FALSE);
132 
133         AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testLexicalHandlerXml.getInputStream());
134 
135         staxXmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler);
136         staxXmlReader.parse(new InputSource());
137         lexicalHandlerControl.verify();
138     }
139 
140     protected abstract AbstractStaxXmlReader createStaxXmlReader(InputStream inputStream) throws XMLStreamException;
141 
142     /** Easymock <code>ArgumentMatcher</code> implementation that matches SAX arguments. */
143     protected static class SaxArgumentMatcher extends AbstractMatcher {
144 
145         public boolean matches(Object[] expected, Object[] actual) {
146             if (expected == actual) {
147                 return true;
148             }
149             if (expected == null || actual == null) {
150                 return false;
151             }
152             if (expected.length != actual.length) {
153                 throw new IllegalArgumentException("Expected and actual arguments must have the same size");
154             }
155             if (expected.length == 3 && expected[0] instanceof char[] && expected[1] instanceof Integer &&
156                     expected[2] instanceof Integer) {
157                 // handling of the character(char[], int, int) methods
158                 String expectedString = new String((char[]) expected[0], ((Integer) expected[1]).intValue(),
159                         ((Integer) expected[2]).intValue());
160                 String actualString = new String((char[]) actual[0], ((Integer) actual[1]).intValue(),
161                         ((Integer) actual[2]).intValue());
162                 return expectedString.equals(actualString);
163             }
164             else if (expected.length == 1 && (expected[0] instanceof Locator)) {
165                 return true;
166             }
167             else {
168                 return super.matches(expected, actual);
169             }
170         }
171 
172         protected boolean argumentMatches(Object expected, Object actual) {
173             if (expected instanceof char[]) {
174                 return Arrays.equals((char[]) expected, (char[]) actual);
175             }
176             else if (expected instanceof Attributes) {
177                 Attributes expectedAttributes = (Attributes) expected;
178                 Attributes actualAttributes = (Attributes) actual;
179                 if (expectedAttributes.getLength() != actualAttributes.getLength()) {
180                     return false;
181                 }
182                 for (int i = 0; i < expectedAttributes.getLength(); i++) {
183                     boolean found = false;
184                     for (int j = 0; j < actualAttributes.getLength(); j++) {
185                         if (expectedAttributes.getURI(i).equals(actualAttributes.getURI(j)) &&
186                                 expectedAttributes.getQName(i).equals(actualAttributes.getQName(j)) &&
187                                 expectedAttributes.getType(i).equals(actualAttributes.getType(j)) &&
188                                 expectedAttributes.getValue(i).equals(actualAttributes.getValue(j))) {
189                             found = true;
190                             break;
191                         }
192                     }
193                     if (!found) {
194                         return false;
195                     }
196                 }
197                 return true;
198             }
199             else {
200                 return super.argumentMatches(expected, actual);
201             }
202         }
203 
204         public String toString(Object[] arguments) {
205             if (arguments != null && arguments.length == 3 && arguments[0] instanceof char[] &&
206                     arguments[1] instanceof Integer && arguments[2] instanceof Integer) {
207                 return new String((char[]) arguments[0], ((Integer) arguments[1]).intValue(),
208                         ((Integer) arguments[2]).intValue());
209             }
210             else {
211                 return super.toString(arguments);
212             }
213         }
214 
215         protected String argumentToString(Object argument) {
216             if (argument instanceof char[]) {
217                 char[] array = (char[]) argument;
218                 StringBuffer buffer = new StringBuffer();
219                 for (int i = 0; i < array.length; i++) {
220                     buffer.append(array[i]);
221                 }
222                 return buffer.toString();
223             }
224             else if (argument instanceof Attributes) {
225                 Attributes attributes = (Attributes) argument;
226                 StringBuffer buffer = new StringBuffer("[");
227                 for (int i = 0; i < attributes.getLength(); i++) {
228                     if (attributes.getURI(i).length() != 0) {
229                         buffer.append('{');
230                         buffer.append(attributes.getURI(i));
231                         buffer.append('}');
232                     }
233 //                    if (attributes.getLocalName(i).length() != 0) {
234 //                        buffer.append('[');
235 //                        buffer.append(attributes.getLocalName(i));
236 //                        buffer.append(']');
237 //                    }
238                     if (attributes.getQName(i).length() != 0) {
239                         buffer.append(attributes.getQName(i));
240                     }
241                     buffer.append('=');
242                     buffer.append(attributes.getValue(i));
243                     if (i < attributes.getLength() - 1) {
244                         buffer.append(", ");
245                     }
246                 }
247                 buffer.append(']');
248                 return buffer.toString();
249             }
250             else if (argument instanceof Locator) {
251                 Locator locator = (Locator) argument;
252                 StringBuffer buffer = new StringBuffer("[");
253                 buffer.append(locator.getLineNumber());
254                 buffer.append(',');
255                 buffer.append(locator.getColumnNumber());
256                 buffer.append(']');
257                 return buffer.toString();
258             }
259             else {
260                 return super.argumentToString(argument);
261             }
262         }
263     }
264 
265     private static class CopyingContentHandler implements ContentHandler {
266 
267         private final ContentHandler wrappee;
268 
269         private CopyingContentHandler(ContentHandler wrappee) {
270             this.wrappee = wrappee;
271         }
272 
273         public void setDocumentLocator(Locator locator) {
274             wrappee.setDocumentLocator(locator);
275         }
276 
277         public void startDocument() throws SAXException {
278             wrappee.startDocument();
279         }
280 
281         public void endDocument() throws SAXException {
282             wrappee.endDocument();
283         }
284 
285         public void startPrefixMapping(String prefix, String uri) throws SAXException {
286             wrappee.startPrefixMapping(prefix, uri);
287         }
288 
289         public void endPrefixMapping(String prefix) throws SAXException {
290             wrappee.endPrefixMapping(prefix);
291         }
292 
293         public void startElement(String uri, String localName, String qName, Attributes attributes)
294                 throws SAXException {
295             wrappee.startElement(uri, localName, qName, new AttributesImpl(attributes));
296         }
297 
298         public void endElement(String uri, String localName, String qName) throws SAXException {
299             wrappee.endElement(uri, localName, qName);
300         }
301 
302         public void characters(char ch[], int start, int length) throws SAXException {
303             wrappee.characters(copy(ch), start, length);
304         }
305 
306         public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
307         }
308 
309         public void processingInstruction(String target, String data) throws SAXException {
310             wrappee.processingInstruction(target, data);
311         }
312 
313         public void skippedEntity(String name) throws SAXException {
314             wrappee.skippedEntity(name);
315         }
316     }
317 
318     private static class CopyingLexicalHandler implements LexicalHandler {
319 
320         private final LexicalHandler wrappee;
321 
322         private CopyingLexicalHandler(LexicalHandler wrappee) {
323             this.wrappee = wrappee;
324         }
325 
326         public void startDTD(String name, String publicId, String systemId) throws SAXException {
327             wrappee.startDTD("element", publicId, systemId);
328         }
329 
330         public void endDTD() throws SAXException {
331             wrappee.endDTD();
332         }
333 
334         public void startEntity(String name) throws SAXException {
335             wrappee.startEntity(name);
336         }
337 
338         public void endEntity(String name) throws SAXException {
339             wrappee.endEntity(name);
340         }
341 
342         public void startCDATA() throws SAXException {
343             wrappee.startCDATA();
344         }
345 
346         public void endCDATA() throws SAXException {
347             wrappee.endCDATA();
348         }
349 
350         public void comment(char ch[], int start, int length) throws SAXException {
351             wrappee.comment(copy(ch), start, length);
352         }
353     }
354 
355     private static char[] copy(char[] ch) {
356         char[] copy = new char[ch.length];
357         System.arraycopy(ch, 0, copy, 0, ch.length);
358         return copy;
359     }
360 
361 }