blob: a4a9dc97985ab398cde0b9ac44484af38fc5792b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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;
import org.apache.axiom.om.NodeUnavailableException;
import org.apache.axiom.om.OMContainer;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMXMLParserWrapper;
import org.apache.axiom.om.impl.builder.StAXBuilder;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
public aspect CoreParentNodeSupport {
private Object CoreParentNode.content;
// TODO: rename & make final
public int CoreParentNode.getState() {
return flags & Flags.STATE_MASK;
}
public final void CoreParentNode.coreSetState(int state) {
flags = (flags & ~Flags.STATE_MASK) | state;
}
public boolean CoreParentNode.isExpanded() {
return true;
}
public void CoreParentNode.forceExpand() {}
final Content CoreParentNode.getContent(boolean create) {
if (getState() == COMPACT) {
Content content = new Content();
CoreCharacterDataNode cdata = coreGetNodeFactory().createNode(CoreCharacterDataNode.class);
cdata.internalSetParent(this);
cdata.coreSetCharacterData((String)this.content);
content.firstChild = cdata;
content.lastChild = cdata;
this.content = content;
coreSetState(COMPLETE);
return content;
} else {
Content content = (Content)this.content;
if (content == null && create) {
content = new Content();
this.content = content;
}
return content;
}
}
/**
* Get the first child if it is available. The child is available if it is complete or
* if the builder has started building the node. In the latter case,
* {@link OMNode#isComplete()} may return <code>false</code> when called on the child.
* In contrast to {@link OMContainer#getFirstOMChild()}, this method will never modify
* the state of the underlying parser.
*
* @return the first child or <code>null</code> if the container has no children or
* the builder has not yet started to build the first child
*/
public final CoreChildNode CoreParentNode.coreGetFirstChildIfAvailable() {
forceExpand();
Content content = getContent(false);
return content == null ? null : content.firstChild;
}
public CoreChildNode CoreParentNode.coreGetLastKnownChild() {
Content content = getContent(false);
return content == null ? null : content.lastChild;
}
public void CoreParentNode.buildNext() {
OMXMLParserWrapper builder = getBuilder();
if (builder == null) {
throw new IllegalStateException("The node has no builder");
} else if (((StAXOMBuilder)builder).isClosed()) {
throw new OMException("The builder has already been closed");
} else if (!builder.isCompleted()) {
builder.next();
} else {
// If the builder is suddenly complete, but the completion status of the node
// doesn't change, then this means that we built the wrong nodes
throw new IllegalStateException("Builder is already complete");
}
}
public CoreChildNode CoreParentNode.coreGetFirstChild() {
CoreChildNode firstChild = coreGetFirstChildIfAvailable();
if (firstChild == null) {
switch (getState()) {
case CoreParentNode.DISCARDED:
((StAXBuilder)getBuilder()).debugDiscarded(this);
throw new NodeUnavailableException();
case CoreParentNode.INCOMPLETE:
do {
buildNext();
} while (getState() == CoreParentNode.INCOMPLETE
&& (firstChild = coreGetFirstChildIfAvailable()) == null);
}
}
return firstChild;
}
public final CoreChildNode CoreParentNode.coreGetFirstChild(NodeFilter filter) {
CoreChildNode child = coreGetFirstChild();
while (child != null && !filter.accept(child)) {
child = child.coreGetNextSibling();
}
return child;
}
public final CoreChildNode CoreParentNode.coreGetLastChild() {
build();
return coreGetLastKnownChild();
}
public final CoreChildNode CoreParentNode.coreGetLastChild(NodeFilter filter) {
CoreChildNode child = coreGetLastChild();
while (child != null && !filter.accept(child)) {
child = child.coreGetPreviousSibling();
}
return child;
}
public final void CoreParentNode.coreAppendChild(CoreChildNode child, boolean fromBuilder) {
CoreParentNode parent = child.coreGetParent();
if (!fromBuilder) {
// TODO: this is wrong; we only need to build the node locally, but build() builds incomplete children as well
build();
}
Content content = getContent(true);
if (parent == this && child == content.lastChild) {
// The child is already the last node.
// We don't need to detach and re-add it.
return;
}
child.internalDetach(null, this);
if (content.firstChild == null) {
content.firstChild = child;
} else {
child.previousSibling = content.lastChild;
content.lastChild.nextSibling = child;
}
content.lastChild = child;
}
public final void CoreParentNode.coreAppendChildren(CoreDocumentFragment fragment) {
fragment.build();
Content fragmentContent = fragment.getContent(false);
if (fragmentContent == null || fragmentContent.firstChild == null) {
// Fragment is empty; nothing to do
return;
}
build();
CoreChildNode child = fragmentContent.firstChild;
while (child != null) {
child.internalSetParent(this);
child = child.nextSibling;
}
Content content = getContent(true);
if (content.firstChild == null) {
content.firstChild = fragmentContent.firstChild;
} else {
fragmentContent.firstChild.previousSibling = content.lastChild;
content.lastChild.nextSibling = fragmentContent.firstChild;
}
content.lastChild = fragmentContent.lastChild;
fragmentContent.firstChild = null;
fragmentContent.lastChild = null;
}
public final void CoreParentNode.coreRemoveChildren(Semantics semantics) {
if (getState() == COMPACT) {
coreSetState(COMPLETE);
content = null;
} else {
// We need to call this first because if may modify the state (applies to OMSourcedElements)
CoreChildNode child = coreGetFirstChildIfAvailable();
boolean updateState;
if (getState() == INCOMPLETE && getBuilder() != null) {
CoreChildNode lastChild = coreGetLastKnownChild();
if (lastChild instanceof CoreParentNode) {
((CoreParentNode)lastChild).build();
}
((StAXOMBuilder)getBuilder()).discard((OMContainer)this);
updateState = true;
} else {
updateState = false;
}
if (child != null) {
CoreDocument newOwnerDocument = semantics.getDetachPolicy().getNewOwnerDocument(this);
do {
CoreChildNode nextSibling = child.nextSibling;
child.previousSibling = null;
child.nextSibling = null;
child.internalUnsetParent(newOwnerDocument);
child = nextSibling;
} while (child != null);
}
content = null;
if (updateState) {
coreSetState(COMPLETE);
}
}
}
final Object CoreParentNode.internalGetCharacterData(ElementAction elementAction) {
if (getState() == COMPACT) {
return (String)content;
} else {
Object textContent = null;
StringBuilder buffer = null;
int depth = 0;
CoreChildNode child = coreGetFirstChild();
boolean visited = false;
while (child != null) {
if (visited) {
visited = false;
} else if (child instanceof CoreElement) {
switch (elementAction) {
case RETURN_NULL:
return null;
case RECURSE:
CoreChildNode firstChild = ((CoreElement)child).coreGetFirstChild();
if (firstChild != null) {
child = firstChild;
depth++;
continue;
}
// Fall through
case SKIP:
// Just continue
}
} else {
if (child instanceof CoreCharacterDataNode || child instanceof CoreCDATASection) {
Object textValue = ((CoreCharacterDataContainer)child).coreGetCharacterData();
if (textValue instanceof CharacterData || ((String)textValue).length() != 0) {
if (textContent == null) {
// This is the first non empty text node. Just save the string.
textContent = textValue;
} else {
// We've already seen a non empty text node before. Concatenate using
// a StringBuilder.
if (buffer == null) {
// This is the first text node we need to append. Initialize the
// StringBuilder.
buffer = new StringBuilder(textContent.toString());
}
buffer.append(textValue.toString());
}
}
}
}
CoreChildNode nextSibling = child.coreGetNextSibling();
if (depth > 0 && nextSibling == null) {
depth--;
child = (CoreChildNode)child.coreGetParent();
visited = true;
} else {
child = nextSibling;
}
}
if (textContent == null) {
// We didn't see any text nodes. Return an empty string.
return "";
} else if (buffer != null) {
return buffer.toString();
} else {
return textContent;
}
}
}
public final void CoreParentNode.coreSetCharacterData(Object data, Semantics semantics) {
coreRemoveChildren(semantics);
if (data != null && (data instanceof CharacterData || ((String)data).length() > 0)) {
coreSetState(COMPACT);
content = data;
}
}
public final <T> NodeIterator<T> CoreParentNode.coreGetNodes(Axis axis, Class<T> type, Semantics semantics) {
return new AbstractNodeIterator<T>(this, axis, type, semantics) {
@Override
protected boolean matches(CoreNode node) throws CoreModelException {
return true;
}
};
}
public final <T extends CoreElement> NodeIterator<T> CoreParentNode.coreGetElements(Axis axis, Class<T> type, ElementMatcher<? super T> matcher, String namespaceURI, String name, Semantics semantics) {
return new ElementsIterator<T>(this, axis, type, matcher, namespaceURI, name, semantics);
}
public final <T> void CoreParentNode.cloneChildrenIfNecessary(ClonePolicy<T> policy, T options, CoreNode clone) {
CoreParentNode targetParent = (CoreParentNode)clone;
if (policy.cloneChildren(options, coreGetNodeType()) && targetParent.isExpanded()) {
if (getState() == COMPACT) {
Object content = this.content;
if (content instanceof CharacterData) {
content = ((CharacterData)content).clone(policy, options);
}
targetParent.coreSetCharacterData(content, null);
} else {
CoreChildNode child = coreGetFirstChild();
while (child != null) {
child.coreClone(policy, options, targetParent);
child = child.coreGetNextSibling();
}
}
}
}
}