/* * Copyright 2011, 2012 Odysseus Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.synapse.commons.staxon.core.json; import java.io.IOException; import javax.xml.XMLConstants; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import org.apache.synapse.commons.staxon.core.base.AbstractXMLStreamReader; import org.apache.synapse.commons.staxon.core.base.XMLStreamReaderScope; import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamSource; import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamSource.Value; import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamToken; /** * JSON XML stream reader. * <p/> * <h4>Limitations</h4> * <ul> * <li>Mixed content (e.g. <code><alice>bob<edgar/></alice></code>) is not supported.</li> * </ul> * <p/> * <p>The reader may produce processing instructions * <code><?xml-multiple element-name?></code> * to indicate array starts (<code>'['</code>).</p> */ public class JsonXMLStreamReader extends AbstractXMLStreamReader<JsonXMLStreamReader.ScopeInfo> { static class ScopeInfo extends JsonXMLStreamScopeInfo { private String currentTagName; } private final JsonStreamSource source; private final boolean multiplePI; private final char namespaceSeparator; private boolean documentArray = false; /** * Create reader instance. * * @param source stream source * @param multiplePI whether to produce <code><xml-multiple?></code> PIs to signal array start * @param namespaceSeparator namespace prefix separator * @throws XMLStreamException */ public JsonXMLStreamReader(JsonStreamSource source, boolean multiplePI, char namespaceSeparator) throws XMLStreamException { super(new ScopeInfo(), source); this.source = source; this.multiplePI = multiplePI; this.namespaceSeparator = namespaceSeparator; initialize(); } private void readStartElementTag(String name) throws XMLStreamException { int separator = name.indexOf(namespaceSeparator); if (separator < 0) { readStartElementTag(XMLConstants.DEFAULT_NS_PREFIX, name, null, new ScopeInfo()); } else { readStartElementTag(name.substring(0, separator), name.substring(separator + 1), null, new ScopeInfo()); } } private void readAttrNsDecl(String name, String value) throws XMLStreamException { int separator = name.indexOf(namespaceSeparator); if (separator < 0) { if (XMLConstants.XMLNS_ATTRIBUTE.equals(name)) { readNsDecl(XMLConstants.DEFAULT_NS_PREFIX, value); } else { readAttr(XMLConstants.DEFAULT_NS_PREFIX, name, null, value); } } else { if (name.startsWith(XMLConstants.XMLNS_ATTRIBUTE) && separator == XMLConstants.XMLNS_ATTRIBUTE.length()) { readNsDecl(name.substring(separator + 1), value); } else { readAttr(name.substring(0, separator), name.substring(separator + 1), null, value); } } } private void readData(Value value, int type) throws XMLStreamException { readData(value.text, value.data, type); } private void consumeName(ScopeInfo info) throws XMLStreamException, IOException { String fieldName = source.name(); if (fieldName.startsWith("@")) { fieldName = fieldName.substring(1); if (source.peek() == JsonStreamToken.VALUE) { readAttrNsDecl(fieldName, source.value().text); } else if (XMLConstants.XMLNS_ATTRIBUTE.equals(fieldName)) { // badgerfish source.startObject(); while (source.peek() == JsonStreamToken.NAME) { String prefix = source.name(); if ("$".equals(prefix)) { readNsDecl(XMLConstants.DEFAULT_NS_PREFIX, source.value().text); } else { readNsDecl(prefix, source.value().text); } } source.endObject(); } else { throw new IllegalStateException("Expected attribute value"); } } else if ("$".equals(fieldName)) { readData(source.value(), XMLStreamConstants.CHARACTERS); } else { info.currentTagName = fieldName; } } @Override protected boolean consume() throws XMLStreamException, IOException { XMLStreamReaderScope<ScopeInfo> scope = getScope(); switch (source.peek()) { case NAME: consumeName(scope.getInfo()); return consume(); case START_ARRAY: source.startArray(); if (scope.getInfo().isArray()) { throw new IOException("Array start inside array"); } if (scope.isRoot() && !isStartDocumentRead()) { documentArray = true; } else { if (scope.getInfo().currentTagName == null) { throw new IOException("Array name missing"); } scope.getInfo().startArray(scope.getInfo().currentTagName); } if (multiplePI) { readPI(JsonXMLStreamConstants.MULTIPLE_PI_TARGET, scope.getInfo().currentTagName); } return consume(); case START_OBJECT: source.startObject(); if (scope.isRoot() && !isStartDocumentRead()) { readStartDocument(null, null, null); } else { if (scope.getInfo().isArray()) { scope.getInfo().incArraySize(); } if (scope.getInfo().currentTagName != null) { readStartElementTag(scope.getInfo().currentTagName); } } return consume(); case END_OBJECT: source.endObject(); if (scope.isRoot() && isStartDocumentRead()) { readEndDocument(); return documentArray; } else { readEndElementTag(); return true; } case VALUE: String name = scope.getInfo().currentTagName; if (scope.getInfo().isArray()) { scope.getInfo().incArraySize(); name = scope.getInfo().getArrayName(); } if (getScope().isRoot() && !isStartDocumentRead()) { // hack: allow to read simple value readData(source.value(), XMLStreamConstants.CHARACTERS); } else { readStartElementTag(name); Value value = source.value(); if (value != JsonStreamSource.NULL) { readData(value, XMLStreamConstants.CHARACTERS); } readEndElementTag(); } return true; case END_ARRAY: source.endArray(); if (getScope().isRoot() && documentArray) { return false; } if (!scope.getInfo().isArray()) { throw new IllegalStateException("Array end without matching start"); } scope.getInfo().endArray(); return true; case NONE: return false; default: throw new IOException("Unexpected token: " + source.peek()); } } /** * @return <code>true</code> iff the current event data is a number primitive */ public boolean hasNumber() { return getEventData() instanceof Number; } /** * @return number primitive * @throws ClassCastException */ public Number getNumber() { return (Number) getEventData(); } /** * @return <code>true</code> iff the current event data is a boolean primitive */ public boolean hasBoolean() { return getEventData() instanceof Boolean; } /** * @return boolean primitive * @throws ClassCastException */ public Boolean getBoolean() { return (Boolean) getEventData(); } @Override public void close() throws XMLStreamException { super.close(); try { source.close(); } catch (IOException e) { throw new XMLStreamException(e); } } }