/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
/**
*
*/
package org.ebayopensource.turmeric.runtime.binding.impl.parser.json;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import org.ebayopensource.turmeric.runtime.binding.BindingConstants;
import org.ebayopensource.turmeric.runtime.binding.impl.parser.ChildNodeStreamingIterator;
import org.ebayopensource.turmeric.runtime.binding.impl.parser.ParseException;
import org.ebayopensource.turmeric.runtime.binding.objectnode.ObjectNode;
import org.ebayopensource.turmeric.runtime.binding.objectnode.StreamableObjectNode;
import org.ebayopensource.turmeric.runtime.binding.objectnode.impl.ObjectNodeImpl;
/**
* @author wdeng
*
*/
public class JSONStreamObjectNodeImpl extends ObjectNodeImpl implements
StreamableObjectNode {
private JSONStreamReadContext m_context;
private NodeBuildingStage m_stage;
private QName m_nameOfPreviousElement = null;
private QName m_nameOfCurrentElement = null;
private int m_indexOfCurrentElement = -1;
/**
* This is used to built the root node.
*
* @param m_name
* @param lexer
*/
public JSONStreamObjectNodeImpl(JSONStreamReadContext ctx) {
this(ROOT_NODE_QNAME, null, ctx);
}
private JSONStreamObjectNodeImpl(QName name, ObjectNode parent,
JSONStreamReadContext ctx) {
super(name, parent);
m_stage = NodeBuildingStage.NameFilled;
m_context = ctx;
ctx.newNodeCreated(this);
}
@Override
public List<ObjectNode> getChildNodes() throws XMLStreamException {
if (m_stage != NodeBuildingStage.ValueBuilt) {
buildValue();
}
return super.getChildNodes();
}
@Override
public List<ObjectNode> getChildNodes(QName name) throws XMLStreamException {
List<ObjectNode> children = super.getChildNodes(name);
if (children != null) {
return children;
}
if (m_stage != NodeBuildingStage.ValueBuilt) {
buildValue(name, false);
}
return super.getChildNodes(name);
}
@Override
public ObjectNode getChildNode(int index) throws XMLStreamException {
while (m_children.size() <= index && m_stage != NodeBuildingStage.ValueBuilt) {
m_context.buildValue(this, null, Integer.MAX_VALUE, true);
}
return super.getChildNode(index);
}
@Override
public ObjectNode getChildNode(QName name, int index) throws XMLStreamException {
ObjectNode child = super.getChildNode(name, index);
if (null != child) {
return child;
}
if (m_stage != NodeBuildingStage.ValueBuilt) {
m_context.buildValue(this, name, index, false);
}
return super.getChildNode(name, index);
}
@Override
public boolean hasChildNodes() throws XMLStreamException {
if (m_stage == NodeBuildingStage.NameFilled) {
m_context.buildNextChild(this);
}
return super.hasChildNodes();
}
@Override
public boolean hasAttributes() {
if (m_stage != NodeBuildingStage.ValueBuilt) {
m_context.buildNextChild(this);
}
return super.hasAttributes();
}
@Override
public String getNodeValue() {
if (m_stage != NodeBuildingStage.ValueBuilt) {
m_context.buildNextChild(this);
}
return super.getNodeValue();
}
public StreamableObjectNode nextChild() throws XMLStreamException {
if (m_stage != NodeBuildingStage.ValueBuilt) {
m_context.buildNextChild(this);
}
if (-1 == m_indexOfCurrentElement || m_indexOfCurrentElement >= m_children.size()) {
return null;
}
return (StreamableObjectNode) getChildNode(m_indexOfCurrentElement);
}
@Override
public Iterator<ObjectNode> getChildrenIterator() throws XMLStreamException {
if (m_stage == NodeBuildingStage.NameFilled) {
m_context.buildNextChild(this);
}
return new ChildNodeStreamingIterator(this);
}
@Override
public int getChildNodesSize() throws XMLStreamException {
if (m_stage != NodeBuildingStage.ValueBuilt) {
buildValue();
}
return super.getChildNodesSize();
}
/**
* Node is allow to be cloned only after it is built.
*/
@Override
public ObjectNode cloneNode() throws XMLStreamException {
if (m_stage != NodeBuildingStage.ValueBuilt) {
buildValue();
}
return super.cloneNode();
}
private void buildValue() {
buildValue(null, false);
}
/**
* build all the children until elements with QName name are found.
*
* @param name
*/
private void buildValue(QName name, boolean singleChildPolicyApplied) {
m_context.buildValue(this, name, Integer.MAX_VALUE, singleChildPolicyApplied);
}
void buildValue(QName key, int idxTofind, boolean singleChildPolicyApplied) {
buildValue(key, idxTofind, singleChildPolicyApplied, false);
}
/**
* Build children until the given 'index'th named element
*
* @param key
* @param idxTofind
* @param singleChildPolicyApplied
* @param isArrayElement
*/
private void buildValue(QName key, int idxTofind, boolean singleChildPolicyApplied, boolean isArrayElement) {
// Any json node can either have value or have children, not both of
// them.
if (buildNonCollectionValue(singleChildPolicyApplied)) {
return;
}
if (m_stage != NodeBuildingStage.NameFilled
&& !isBuilding(m_stage)) {
return;
}
JSONTokenType type = m_context.getCurrentTokenType();
validateChildrenBoundary(type);
if ((JSONTokenType.COMMA == type && isBuilding(m_stage))
|| JSONTokenType.LCURLY == type) {
m_stage = NodeBuildingStage.Building;
buildObject(key, idxTofind, singleChildPolicyApplied, isArrayElement);
return;
}
if (JSONTokenType.LBLANKET == type) {
JSONStreamObjectNodeImpl parent = (JSONStreamObjectNodeImpl)getParentNode();
parent.m_stage = NodeBuildingStage.BuildingArray;
buildArray(key, idxTofind, singleChildPolicyApplied);
return;
}
if (JSONTokenType.RBLANKET == type) {
m_context.getNextToken();
m_indexOfCurrentElement++;
if (m_stage == NodeBuildingStage.BuildingArray) {
m_stage = NodeBuildingStage.Building;
buildValue(key, idxTofind, singleChildPolicyApplied, isArrayElement);
return;
}
JSONStreamObjectNodeImpl parent = (JSONStreamObjectNodeImpl)getParentNode();
if (null != parent) {
parent.m_stage = NodeBuildingStage.Building;
}
return;
}
if (JSONTokenType.RCURLY == type) {
m_context.getNextToken();
nodeBuildingCompleted(singleChildPolicyApplied);
return;
}
throw createException(m_context.getCurrentToken(), "'{', '}', '[',']', or ',' is expected.");
}
private void validateChildrenBoundary(JSONTokenType type) {
if (JSONTokenType.COMMA == type) {
if (isBuilding(m_stage)) {
m_context.getNextToken();
return;
}
throw createException(m_context.getCurrentToken(),
"',' is unexpected.");
}
if (JSONTokenType.LCURLY == type) {
if (m_stage == NodeBuildingStage.NameFilled /*|| m_stage == NodeBuildingStage.BuildingArray*/) {
m_context.getNextToken();
return;
}
throw createException(m_context.getCurrentToken(),
"'{' is unexpected.");
}
if (JSONTokenType.LBLANKET == type) {
if (m_stage == NodeBuildingStage.NameFilled) {
m_context.getNextToken();
return;
}
throw createException(m_context.getCurrentToken(),
"'[' is unexpected.");
}
}
private void buildObject(QName key, int idxTofind, boolean singleChildPolicyApplied, boolean isArrayElement) {
boolean isAttribute = false;
do {
if (JSONTokenType.COMMA == m_context.getCurrentTokenType()) {
m_context.getNextToken();
}
isAttribute = false;
JSONStreamObjectNodeImpl child = buildChild(key, idxTofind, singleChildPolicyApplied);
if (null != child) {
QName qName = child.getNodeName();
isAttribute = child.isAttribute();
if (isAttribute) {
addAttribute(child);
} else if (BindingConstants.JSON_VALUE_KEY.equals(qName.getLocalPart())) {
this.setNodeValue(child.getNodeValue());
} else {
int childIdx = addChild(child);
m_nameOfPreviousElement = m_nameOfCurrentElement;
m_nameOfCurrentElement = qName;
m_indexOfCurrentElement = childIdx;
if (!singleChildPolicyApplied) {
if (key == null || idxTofind == Integer.MAX_VALUE) {
child.buildValue(null, Integer.MAX_VALUE, false, false);
}
}
}
} else { // child == null
if (JSONTokenType.RBLANKET == m_context.getCurrentTokenType()) {
//If it is "]", it is a curly braces mismatch. Advance the token to avoid
// infinite loop
m_context.getNextToken();
}
}
} while ((isAttribute || !singleChildPolicyApplied) && !foundChild(key, idxTofind)
&& JSONTokenType.RCURLY != m_context.getCurrentTokenType());
if (JSONTokenType.RCURLY == m_context.getCurrentTokenType()) {
m_context.getNextToken();
nodeBuildingCompleted(singleChildPolicyApplied);
}
}
void nodeBuildingCompleted(boolean singleChildPolicyApplied) {
if (m_stage == NodeBuildingStage.ValueBuilt) {
return;
}
m_stage = NodeBuildingStage.ValueBuilt;
m_nameOfPreviousElement = m_nameOfCurrentElement;
m_nameOfCurrentElement = null;
m_context.nodeBuildingCompleted(this);
JSONStreamObjectNodeImpl parent = (JSONStreamObjectNodeImpl)getParentNode();
if ( singleChildPolicyApplied && null != parent &&
parent.m_stage == NodeBuildingStage.BuildingArray &&
m_context.getCurrentTokenType() != JSONTokenType.RBLANKET) {
createNewArrayElementNodeIfNeeded();
}
}
private void buildArray(QName key, int idxTofind, boolean singleChildPolicyApplied) {
JSONStreamObjectNodeImpl parentNode = (JSONStreamObjectNodeImpl) getParentNode();
JSONTokenType type;
type = m_context.getCurrentTokenType();
if (JSONTokenType.RBLANKET == type) {
this.setNodeValue(null);
parentNode.m_stage = NodeBuildingStage.Building;
nodeBuildingCompleted(singleChildPolicyApplied);
return;
}
// Handled the first array element first
buildValue(null, Integer.MAX_VALUE, singleChildPolicyApplied, true);
if (singleChildPolicyApplied) {
return;
}
type = m_context.getCurrentTokenType();
while (JSONTokenType.COMMA == type) {
JSONStreamObjectNodeImpl node = createNewArrayElementNodeIfNeeded();
if (null == node) {
break;
}
node.buildValue(null, Integer.MAX_VALUE, singleChildPolicyApplied, true);
type = m_context.getCurrentTokenType();
}
if (JSONTokenType.RBLANKET == type) {
m_context.getNextToken();
parentNode.m_stage = NodeBuildingStage.Building;
}
}
private JSONStreamObjectNodeImpl createNewArrayElementNodeIfNeeded() {
JSONStreamObjectNodeImpl parentNode = (JSONStreamObjectNodeImpl) getParentNode();
JSONTokenType type = m_context.getCurrentTokenType();
if (JSONTokenType.RBLANKET == type) {
// Skip the right blanket;
m_context.getNextToken();
parentNode.m_indexOfCurrentElement++;
return null;
}
if (JSONTokenType.COMMA != type) {
throw createException(m_context.getCurrentToken(),
"Expecting array end");
}
QName nodeName = getNodeName();
JSONStreamObjectNodeImpl node = new JSONStreamObjectNodeImpl(
nodeName, parentNode, m_context);
int index = parentNode.addChild(node);
parentNode.m_nameOfPreviousElement = parentNode.m_nameOfCurrentElement;
parentNode.m_indexOfCurrentElement = index;
// Skip the comma; Why need to skip?
m_context.getNextToken();
return node;
}
/**
* Returns the local name of the child.
*
* @param key
* @param idxTofind
* @return
*/
private JSONStreamObjectNodeImpl buildChild(QName key, int idxTofind, boolean singleChildPolicyApplied) {
JSONToken token = m_context.getCurrentToken();
JSONTokenType type = token.m_type;
if (JSONTokenType.RBLANKET == type || JSONTokenType.RCURLY == type) {
return null;
}
if (JSONTokenType.STRING != type) {
throw createException(token, "QName expected.");
}
QName name = buildName();
// buildName() always returns non-null QName otherwise exception is thrown.
JSONStreamObjectNodeImpl child = new JSONStreamObjectNodeImpl(name,
this, m_context);
type = m_context.getCurrentTokenType();
if (JSONTokenType.COLON == type) {
m_context.getNextToken();
}
child.buildNonCollectionValue(singleChildPolicyApplied);
return child;
}
private boolean foundChild(QName searchKey, int searchIdx) {
// searchKey == null, we want to find all children
if (null == searchKey) {
return false;
}
// This is to find all the children with the searchKey
if (searchIdx == Integer.MAX_VALUE
&& searchKey.equals(m_nameOfPreviousElement)
&& !searchKey.equals(m_nameOfCurrentElement)) {
return true;
}
return hasChildNode(searchKey, searchIdx);
}
private QName buildName() {
JSONToken token = m_context.getCurrentToken();
String prefix = "";
String name = null;
if (token.m_prefixEnd > 0) {
prefix = token.getPrefix();
name = token.getName();
} else {
name = token.getText();
}
if (null == name) {
throw createException(token, "QName expected.");
}
m_context.getNextToken();
return m_context.createQName(prefix, name, getParentNode() == null);
}
/**
* builds json value: JSON value can be string, number, object, array, true,
* false.
*
* @param canTakeArray
* @return true, if the current node is a leaf node that has value.
*/
private boolean buildNonCollectionValue(boolean singleChildPolicyApplied) {
if (m_stage == NodeBuildingStage.ValueBuilt) {
return true;
}
if (isBuilding(m_stage)) {
return false;
}
JSONToken token = m_context.getCurrentToken();
if (null == token) {
throw createException(JSONToken.END_TOKEN, "Unexpected EOF during the construction of value for " + m_nameOfCurrentElement);
}
JSONTokenType type = token.m_type;
boolean shouldGetNextToken = true;
if (JSONTokenType.STRING == type) {
this.setNodeValue(token.getText());
} else if (JSONTokenType.NUMBER == type) {
this.setNodeValue(token.getText());
} else if (JSONTokenType.TRUE == type) {
this.setNodeValue("true");
} else if (JSONTokenType.FALSE == type) {
this.setNodeValue("false");
} else if (JSONTokenType.NULL == type) {
this.setIsNull(true);
addNilAttribute();
} else if (JSONTokenType.RBLANKET == type || JSONTokenType.RCURLY == type || JSONTokenType.COMMA == type) {
this.setNodeValue("");
shouldGetNextToken = false;
} else {
return false;
}
if (shouldGetNextToken) {
m_context.getNextToken();
}
nodeBuildingCompleted(singleChildPolicyApplied);
return true;
}
private ParseException createException(JSONToken token, String msg) {
return new ParseException(token.getText(), token.m_line, token
.m_column, msg);
}
private boolean isBuilding(NodeBuildingStage stage) {
return m_stage == NodeBuildingStage.Building
|| m_stage == NodeBuildingStage.BuildingArray;
}
private void addNilAttribute() {
ObjectNodeImpl attr = new ObjectNodeImpl(BindingConstants.NILLABLE_ATTRIBUTE_QNAME, this);
attr.setNodeValue(Boolean.TRUE.toString());
addAttribute(attr);
}
private static enum NodeBuildingStage {
NameFilled, // The name of the node has been filled
Building, // The node is in the middle of building the object structure (Name-Value)
BuildingArray, // The node is in the middle of building as an element in an json array
ValueBuilt // The node is completely built
}
}