blob: aa4e3ea18fd7c2d512a09c88fa1bfce234c1e9d2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jens Lukowski/Innoopract - initial renaming/restructuring
* David Carver (STAR) - bug 296999 - Inefficient use of new String()
* Angelo Zerr <angelo.zerr@gmail.com> - copied from org.eclipse.wst.xml.core.internal.document.NodeContainer
* modified in order to process JSON Objects.
*******************************************************************************/
package org.eclipse.wst.json.core.internal.document;
import org.eclipse.wst.json.core.document.IJSONDocument;
import org.eclipse.wst.json.core.document.IJSONNode;
import org.eclipse.wst.json.core.document.IJSONStructure;
import org.eclipse.wst.json.core.document.JSONException;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
/**
* Base class for JSON structure (Object and Array).
*
*/
public abstract class JSONStructureImpl extends JSONValueImpl implements
IJSONStructure {
private IStructuredDocumentRegion endStructuredDocumentRegion = null;
/**
*/
private class ChildNodesCache {
private IJSONNode curChild = null;
private int curIndex = -1;
private int length = 0;
ChildNodesCache() {
initializeCache();
}
public int getLength() {
// atomic
return this.length;
}
private void initializeCache() {
// note we use the outter objects lockobject
// (since we are using their "children".
synchronized (lockObject) {
for (IJSONNode child = firstChild; child != null; child = child
.getNextSibling()) {
this.length++;
}
}
}
public IJSONNode item(int index) {
synchronized (lockObject) {
if (this.length == 0)
return null;
if (index < 0)
return null;
if (index >= this.length)
return null;
if (this.curIndex < 0) { // first time
if (index * 2 >= this.length) { // search from the last
this.curIndex = this.length - 1;
this.curChild = lastChild;
} else { // search from the first
this.curIndex = 0;
this.curChild = firstChild;
}
}
if (index == this.curIndex)
return this.curChild;
if (index > this.curIndex) {
while (index > this.curIndex) {
this.curIndex++;
this.curChild = this.curChild.getNextSibling();
}
} else { // index < this.curIndex
while (index < this.curIndex) {
this.curIndex--;
this.curChild = this.curChild.getPreviousSibling();
}
}
return this.curChild;
}
}
}
private ChildNodesCache childNodesCache = null;
private boolean fChildEditable = true;
JSONNodeImpl firstChild = null;
JSONNodeImpl lastChild = null;
Object lockObject = new byte[0];
/**
* NodeContainer constructor
*/
protected JSONStructureImpl() {
super();
}
/**
* NodeContainer constructor
*
* @param that
* NodeContainer
*/
protected JSONStructureImpl(JSONStructureImpl that) {
super(that);
}
/**
* appendChild method
*
* @return org.w3c.dom.Node
* @param newChild
* org.w3c.dom.Node
*/
public IJSONNode appendChild(IJSONNode newChild) throws JSONException {
return insertBefore(newChild, null);
}
/**
* cloneChildNodes method
*
* @param container
* org.w3c.dom.Node
* @param deep
* boolean
*/
protected void cloneChildNodes(IJSONNode newParent, boolean deep) {
if (newParent == null || newParent == this)
return;
if (!(newParent instanceof JSONStructureImpl))
return;
JSONStructureImpl container = (JSONStructureImpl) newParent;
container.removeChildNodes();
for (IJSONNode child = getFirstChild(); child != null; child = child
.getNextSibling()) {
IJSONNode cloned = child.cloneNode(deep);
if (cloned != null)
container.appendChild(cloned);
}
}
/**
* getChildNodes method
*
* @return org.w3c.dom.NodeList
*/
// public NodeList getChildNodes() {
// return this;
// }
/**
* getFirstChild method
*
* @return org.w3c.dom.Node
*/
public IJSONNode getFirstChild() {
return this.firstChild;
}
/**
* getLastChild method
*
* @return org.w3c.dom.Node
*/
public IJSONNode getLastChild() {
return this.lastChild;
}
/**
* getLength method
*
* @return int
*/
public int getLength() {
if (this.firstChild == null)
return 0;
synchronized (lockObject) {
if (this.childNodesCache == null)
this.childNodesCache = new ChildNodesCache();
return this.childNodesCache.getLength();
}
}
/**
*/
public String getSource() {
StringBuffer buffer = new StringBuffer();
IStructuredDocumentRegion startStructuredDocumentRegion = getStartStructuredDocumentRegion();
if (startStructuredDocumentRegion != null) {
String source = startStructuredDocumentRegion.getText();
if (source != null)
buffer.append(source);
}
for (JSONNodeImpl child = firstChild; child != null; child = (JSONNodeImpl) child
.getNextSibling()) {
String source = child.getSource();
if (source != null)
buffer.append(source);
}
IStructuredDocumentRegion endStructuredDocumentRegion = getEndStructuredDocumentRegion();
if (endStructuredDocumentRegion != null) {
String source = endStructuredDocumentRegion.getText();
if (source != null)
buffer.append(source);
}
return buffer.toString();
}
/**
* hasChildNodes method
*
* @return boolean
*/
public boolean hasChildNodes() {
return (this.firstChild != null);
}
/**
* insertBefore method
*
* @return org.w3c.dom.Node
* @param newChild
* org.w3c.dom.Node
* @param refChild
* org.w3c.dom.Node
*/
public IJSONNode insertBefore(IJSONNode newChild, IJSONNode refChild)
throws JSONException {
if (newChild == null)
return null; // nothing to do
if (refChild != null && refChild.getParentNode() != this) {
// throw new JSONException(JSONException.NOT_FOUND_ERR,
// JSONMessages.NOT_FOUND_ERR);
}
if (!isChildEditable()) {
// throw new
// JSONException(JSONException.NO_MODIFICATION_ALLOWED_ERR,
// JSONMessages.NO_MODIFICATION_ALLOWED_ERR);
}
if (newChild == refChild)
return newChild; // nothing to do
// new child can not be a parent of this, would cause cycle
if (isParent(newChild)) {
// throw new JSONException(JSONException.HIERARCHY_REQUEST_ERR,
// JSONMessages.HIERARCHY_REQUEST_ERR);
}
// if (newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE) {
// // insert child nodes instead
// for (IJSONNode child = newChild.getFirstChild(); child != null; child
// = newChild.getFirstChild()) {
// newChild.removeChild(child);
// insertBefore(child, refChild);
// }
// return newChild;
// }
// synchronized in case another thread is getting item, or length
synchronized (lockObject) {
this.childNodesCache = null; // invalidate child nodes cache
}
JSONNodeImpl child = (JSONNodeImpl) newChild;
JSONNodeImpl next = (JSONNodeImpl) refChild;
JSONNodeImpl prev = null;
IJSONNode oldParent = child.getParentNode();
if (oldParent != null)
oldParent.removeChild(child);
if (next == null) {
prev = this.lastChild;
this.lastChild = child;
} else {
prev = (JSONNodeImpl) next.getPreviousSibling();
next.setPreviousSibling(child);
}
if (prev == null)
this.firstChild = child;
else
prev.setNextSibling(child);
child.setPreviousSibling(prev);
child.setNextSibling(next);
child.setParentNode(this);
// make sure having the same owner document
if (child.getOwnerDocument() == null) {
if (getNodeType() == DOCUMENT_NODE) {
child.setOwnerDocument((IJSONDocument) this);
} else {
child.setOwnerDocument(getOwnerDocument());
}
}
notifyChildReplaced(child, null);
return child;
}
public boolean isChildEditable() {
if (!fChildEditable) {
JSONModelImpl model = (JSONModelImpl) getModel();
if (model != null && model.isReparsing()) {
return true;
}
}
return fChildEditable;
}
/**
* isContainer method
*
* @return boolean
*/
public boolean isContainer() {
return true;
}
/**
* item method
*
* @return org.w3c.dom.Node
* @param index
* int
*/
public IJSONNode item(int index) {
if (this.firstChild == null)
return null;
synchronized (lockObject) {
if (this.childNodesCache == null)
this.childNodesCache = new ChildNodesCache();
return this.childNodesCache.item(index);
}
}
/**
* notifyChildReplaced method
*
* @param newChild
* org.w3c.dom.Node
* @param oldChild
* org.w3c.dom.Node
*/
protected void notifyChildReplaced(IJSONNode newChild, IJSONNode oldChild) {
JSONDocumentImpl document = (JSONDocumentImpl) getContainerDocument();
if (document == null)
return;
// syncChildEditableState(newChild);
JSONModelImpl model = (JSONModelImpl) document.getModel();
if (model == null)
return;
model.childReplaced(this, newChild, oldChild);
}
/**
* removeChild method
*
* @return org.w3c.dom.Node
* @param oldChild
* org.w3c.dom.Node
*/
public IJSONNode removeChild(IJSONNode oldChild) throws JSONException {
if (oldChild == null)
return null;
if (oldChild.getParentNode() != null && oldChild.getParentNode() != this) {
// throw new JSONException(JSONException.NOT_FOUND_ERR,
// JSONMessages.NOT_FOUND_ERR);
throw new JSONException();
}
if (!isChildEditable()) {
// throw new
// JSONException(JSONException.NO_MODIFICATION_ALLOWED_ERR,
// JSONMessages.NO_MODIFICATION_ALLOWED_ERR);
throw new JSONException();
}
// synchronized in case another thread is getting item, or length
synchronized (lockObject) {
this.childNodesCache = null; // invalidate child nodes cache
}
JSONNodeImpl child = (JSONNodeImpl) oldChild;
if (oldChild.getParentNode() == this) {
JSONNodeImpl prev = (JSONNodeImpl) child.getPreviousSibling();
JSONNodeImpl next = (JSONNodeImpl) child.getNextSibling();
// child.setEditable(true, true); // clear ReadOnly flags
if (prev == null)
this.firstChild = next;
else
prev.setNextSibling(next);
if (next == null)
this.lastChild = prev;
else
next.setPreviousSibling(prev);
}
child.setPreviousSibling(null);
child.setNextSibling(null);
child.setParentNode(null);
notifyChildReplaced(null, child);
return child;
}
/**
* removeChildNodes method
*/
public void removeChildNodes() {
if (!isChildEditable()) {
// throw new
// JSONException(JSONException.NO_MODIFICATION_ALLOWED_ERR,
// JSONMessages.NO_MODIFICATION_ALLOWED_ERR);
throw new JSONException();
}
IJSONNode nextChild = null;
for (IJSONNode child = getFirstChild(); child != null; child = nextChild) {
nextChild = child.getNextSibling();
removeChild(child);
}
}
/**
* replaceChild method
*
* @return org.w3c.dom.Node
* @param newChild
* org.w3c.dom.Node
* @param oldChild
* org.w3c.dom.Node
*/
public IJSONNode replaceChild(IJSONNode newChild, IJSONNode oldChild)
throws JSONException {
if (!isChildEditable()) {
// throw new
// JSONException(JSONException.NO_MODIFICATION_ALLOWED_ERR,
// JSONMessages.NO_MODIFICATION_ALLOWED_ERR);
throw new JSONException();
}
if (oldChild == null || oldChild == newChild)
return newChild;
if (newChild != null)
insertBefore(newChild, oldChild);
return removeChild(oldChild);
}
// public void setChildEditable(boolean editable) {
// if (fChildEditable == editable) {
// return;
// }
//
// ReadOnlyController roc = ReadOnlyController.getInstance();
// IJSONNode node;
// if (editable) {
// for (node = getFirstChild(); node != null; node = node.getNextSibling())
// {
// roc.unlockNode(node);
// }
// } else {
// for (node = getFirstChild(); node != null; node = node.getNextSibling())
// {
// roc.lockNode( node);
// }
// }
//
// fChildEditable = editable;
// notifyEditableChanged();
// }
//
// protected void syncChildEditableState(IJSONNode child) {
// ReadOnlyController roc = ReadOnlyController.getInstance();
// if (fChildEditable) {
// roc.unlockNode((JSONNodeImpl) child);
// } else {
// roc.lockNode((JSONNodeImpl) child);
// }
// }
/**
* <p>
* Checks to see if the given <code>Node</code> is a parent of
* <code>this</code> node
* </p>
*
* @param possibleParent
* the possible parent <code>Node</code> of <code>this</code>
* node
* @return <code>true</code> if <code>possibleParent</code> is the parent of
* <code>this</code>, <code>false</code> otherwise.
*/
private boolean isParent(IJSONNode possibleParent) {
IJSONNode parent = this.getParentNode();
while (parent != null && parent != possibleParent) {
parent = parent.getParentNode();
}
return parent == possibleParent;
}
void setStartStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
setStructuredDocumentRegion(flatNode);
}
void setEndStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
this.endStructuredDocumentRegion = flatNode;
}
@Override
public int getEndOffset() {
if (this.endStructuredDocumentRegion != null)
return this.endStructuredDocumentRegion.getEnd();
return super.getEndOffset();
}
public IStructuredDocumentRegion getEndStructuredDocumentRegion() {
return this.endStructuredDocumentRegion;
}
public int getStartEndOffset() {
IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
if (flatNode != null)
return flatNode.getEnd();
return super.getStartOffset();
}
@Override
public boolean isClosed() {
return getEndStructuredDocumentRegion() != null;
}
}