blob: 1662328cae9a2afa7ca501b6f688bb9f9e24d673 [file] [log] [blame]
/*
* Copyright 2009-2011,2013 Andreas Veithen
*
* 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.axiom.core.impl;
import org.apache.axiom.core.CoreAttribute;
import org.apache.axiom.core.CoreChildNode;
import org.apache.axiom.core.CoreDocument;
import org.apache.axiom.core.CoreElement;
import org.apache.axiom.core.CoreLeafNode;
import org.apache.axiom.core.CoreModelException;
import org.apache.axiom.core.CoreModelStreamException;
import org.apache.axiom.core.CoreNSAwareElement;
import org.apache.axiom.core.CoreNode;
import org.apache.axiom.core.CoreParentNode;
import org.apache.axiom.core.InputContext;
import org.apache.axiom.core.NodeConsumedException;
import org.apache.axiom.core.stream.DocumentElementExtractingFilterHandler;
import org.apache.axiom.core.stream.StreamException;
import org.apache.axiom.core.stream.XmlHandler;
import org.apache.axiom.core.stream.XmlInput;
import org.apache.axiom.core.stream.XmlReader;
public final class TreeWalkerImpl implements XmlReader {
private static final int STATE_NONE = 0;
/**
* Indicates that the serializer is synthesizing a start fragment event. This state can only be
* reached if the root node is not a document.
*/
private static final int STATE_START_FRAGMENT = 1;
/**
* Indicates that the current node is a leaf node.
*/
private static final int STATE_LEAF = 2;
/**
* Indicates that the current node is a parent node and that events for child nodes have not yet
* been generated.
*/
private static final int STATE_NOT_VISITED = 3;
/**
* Indicates that the current node is an element and that events for its attribute nodes have
* already been generated.
*/
private static final int STATE_ATTRIBUTES_VISITED = 4;
/**
* Indicates that the current node is a parent node and that events for child nodes have already
* been generated.
*/
private static final int STATE_VISITED = 5;
/**
* Indicates that the current node is a parent node for which the builder has been
* put into pass through mode. In this state, events are not synthesized from the
* object model but passed through from the underlying XML source used to build the
* tree. This state is only reachable if {@link #preserve} is <code>true</code>.
*/
private static final int STATE_PASS_THROUGH = 6;
private static final int STATE_STREAMING = 7;
private static final int STATE_ATTRIBUTE = 8;
private final XmlHandler handler;
private final CoreParentNode root;
private final boolean preserve;
private final boolean incremental;
private CoreNode node;
/**
* The stream from which events are included. This is only set if {@link #state} is
* {@link #STATE_STREAMING}.
*/
private XmlReader reader;
private int state = STATE_NONE;
public TreeWalkerImpl(XmlHandler handler, CoreParentNode root, boolean preserve, boolean incremental) {
this.handler = handler;
this.root = root;
this.preserve = preserve;
this.incremental = incremental;
}
@Override
public boolean proceed() throws StreamException {
if (incremental && !handler.drain()) {
return false;
}
try {
// Determine the next node (i.e. the node for which the next event is generated) and
// update the state
final CoreNode previousNode = node;
final CoreNode nextNode;
if (state == STATE_PASS_THROUGH || state == STATE_STREAMING) {
nextNode = previousNode;
} else if (previousNode == null) {
if (state == STATE_NONE && !(root instanceof CoreDocument)) {
nextNode = null;
state = STATE_START_FRAGMENT;
} else {
nextNode = root;
state = STATE_NOT_VISITED;
}
} else if (state == STATE_VISITED && previousNode == root) {
nextNode = null;
} else if (state == STATE_NOT_VISITED && previousNode instanceof CoreElement) {
final CoreElement element = (CoreElement)previousNode;
// TODO: handle case with preserve == false
CoreAttribute firstAttribute = element.coreGetFirstAttribute();
if (firstAttribute == null) {
nextNode = element;
state = STATE_ATTRIBUTES_VISITED;
} else {
nextNode = firstAttribute;
state = STATE_NOT_VISITED;
}
} else if (state == STATE_NOT_VISITED || state == STATE_ATTRIBUTES_VISITED) {
final CoreParentNode parent = (CoreParentNode)previousNode;
int nodeState = parent.getState();
if (preserve || nodeState == CoreParentNode.COMPLETE || nodeState == CoreParentNode.COMPACT) {
// TODO: bad because it will expand the node if the state is COMPACT
CoreChildNode child = parent.coreGetFirstChild();
if (child == null) {
nextNode = parent;
state = STATE_VISITED;
} else {
nextNode = child;
state = STATE_NOT_VISITED;
}
} else {
CoreChildNode child = parent.coreGetFirstChildIfAvailable();
if (child == null) {
nextNode = parent;
if (nodeState == CoreParentNode.DISCARDING || nodeState == CoreParentNode.DISCARDED) {
throw new NodeConsumedException();
}
parent.coreGetInputContext().setPassThroughHandler(handler);
state = STATE_PASS_THROUGH;
} else {
nextNode = child;
state = STATE_NOT_VISITED;
}
}
} else if (previousNode instanceof CoreChildNode) {
final CoreChildNode previousChildNode = (CoreChildNode)previousNode;
if (preserve) {
CoreChildNode sibling = previousChildNode.coreGetNextSibling();
if (sibling == null) {
nextNode = previousChildNode.coreGetParent();
state = STATE_VISITED;
} else {
nextNode = sibling;
state = STATE_NOT_VISITED;
}
} else {
CoreChildNode sibling = previousChildNode.coreGetNextSiblingIfAvailable();
if (sibling == null) {
CoreParentNode parent = previousChildNode.coreGetParent();
nextNode = parent;
int nodeState = parent.getState();
// TODO: <hack>
if (nodeState == CoreParentNode.INCOMPLETE && parent.coreGetInputContext() == null) {
nodeState = CoreParentNode.COMPLETE;
}
// </hack>
if (nodeState == CoreParentNode.COMPLETE) {
state = STATE_VISITED;
} else if (nodeState == CoreParentNode.DISCARDING || nodeState == CoreParentNode.DISCARDED) {
throw new NodeConsumedException();
} else {
parent.coreGetInputContext().setPassThroughHandler(handler);
state = STATE_PASS_THROUGH;
}
} else {
nextNode = sibling;
state = STATE_NOT_VISITED;
}
}
} else {
final CoreAttribute attribute = (CoreAttribute)previousNode;
// TODO: handle case with preserve == false
CoreAttribute nextAttribute = attribute.coreGetNextAttribute();
if (nextAttribute == null) {
nextNode = attribute.coreGetOwnerElement();
state = STATE_ATTRIBUTES_VISITED;
} else {
nextNode = nextAttribute;
state = STATE_NOT_VISITED;
}
}
// More closely examine the case where we move to a node that has not
// been visited yet. It may be a sourced element or a leaf node
if (state == STATE_NOT_VISITED) {
if (nextNode instanceof CoreNSAwareElement) {
XmlInput input = ((CoreNSAwareElement)nextNode).getXmlInput(preserve, incremental);
if (input != null) {
reader = input.createReader(new DocumentElementExtractingFilterHandler(handler));
state = STATE_STREAMING;
}
} else if (nextNode instanceof CoreLeafNode) {
state = STATE_LEAF;
} else if (nextNode instanceof CoreAttribute) {
state = STATE_ATTRIBUTE;
}
}
switch (state) {
case STATE_START_FRAGMENT:
handler.startFragment();
break;
case STATE_LEAF:
((CoreLeafNode)nextNode).internalSerialize(handler, preserve);
break;
case STATE_ATTRIBUTE:
((CoreAttribute)nextNode).internalSerialize(handler, preserve);
break;
case STATE_NOT_VISITED:
((CoreParentNode)nextNode).serializeStartEvent(handler);
break;
case STATE_ATTRIBUTES_VISITED:
handler.attributesCompleted();
break;
case STATE_VISITED:
if (nextNode == null) {
handler.completed();
} else {
((CoreParentNode)nextNode).serializeEndEvent(handler);
}
break;
case STATE_PASS_THROUGH: {
CoreParentNode parent = (CoreParentNode)nextNode;
parent.coreGetInputContext().getBuilder().next();
if (parent.coreGetInputContext() == null) {
state = STATE_VISITED;
}
break;
}
case STATE_STREAMING:
if (reader.proceed()) {
state = STATE_VISITED;
reader = null;
}
break;
default:
throw new IllegalStateException();
}
node = nextNode;
return state == STATE_VISITED && (nextNode == null || nextNode instanceof CoreDocument);
} catch (CoreModelException ex) {
throw new CoreModelStreamException(ex);
}
}
@Override
public void dispose() {
if (!preserve && node != null) {
CoreParentNode parent = node instanceof CoreParentNode ? (CoreParentNode)node : ((CoreChildNode)node).coreGetParent();
while (true) {
InputContext context = parent.coreGetInputContext();
if (context != null) {
context.discard();
}
if (parent == root) {
break;
}
parent = ((CoreChildNode)parent).coreGetParent();
}
}
if (reader != null) {
reader.dispose();
}
}
}