/*******************************************************************************
* Copyright (c) 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 v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
* Balazs Banfai: Bug 154737 getUserData/setUserData support for Node
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=154737
*
* David Carver (STAR) - bug 295127 - implement isSameNode and compareDocumentPosition methods.
* Unit Tests covered in wst.xsl XPath 2.0 tests.
* 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.NodeImpl
* modified in order to process JSON Objects.
* Alina Marin <alina@mx1.ibm.com> - fixed some stuff to improve the synch between the editor and the model.
*******************************************************************************/
package org.eclipse.wst.json.core.internal.document;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.json.jsonpath.IJSONPath;
import org.eclipse.json.jsonpath.JSONPath;
import org.eclipse.wst.json.core.document.IJSONDocument;
import org.eclipse.wst.json.core.document.IJSONModel;
import org.eclipse.wst.json.core.document.IJSONNode;
import org.eclipse.wst.json.core.document.IJSONPair;
import org.eclipse.wst.json.core.document.IJSONValue;
import org.eclipse.wst.json.core.document.JSONException;
import org.eclipse.wst.sse.core.internal.model.FactoryRegistry;
import org.eclipse.wst.sse.core.internal.provisional.AbstractNotifier;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.w3c.dom.NamedNodeMap;
/**
* NodeImpl class
*/
public abstract class JSONNodeImpl extends AbstractNotifier implements
IJSONNode, IAdaptable {
// define one empty nodelist, for repeated use
// private final static NodeList EMPTY_NODE_LIST = new NodeListImpl();
// DocumentPosition
// private final static short DOCUMENT_POSITION_DISCONNECTED = 0x01;
private final static short DOCUMENT_POSITION_PRECEDING = 0x02;
private final static short DOCUMENT_POSITION_FOLLOWING = 0x04;
// private final static short DOCUMENT_POSITION_CONTAINS = 0x08;
// private final static short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
private final static short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
private boolean fDataEditable = true;
private IStructuredDocumentRegion flatNode = null;
private JSONNodeImpl nextSibling = null;
private JSONDocumentImpl ownerDocument = null;
private JSONNodeImpl parentNode = null;
private JSONNodeImpl previousSibling = null;
// define one empty String constant for repeated use
static final String EMPTY_STRING = "";
/**
* NodeImpl constructor
*/
protected JSONNodeImpl() {
super();
}
/**
* NodeImpl constructor
*
* @param that
* NodeImpl
*/
protected JSONNodeImpl(JSONNodeImpl that) {
if (that != null) {
this.ownerDocument = that.ownerDocument;
}
}
@Override
public Object getAdapter(Class adapter) {
final IStructuredModel model = getModel();
return model != null ? Platform.getAdapterManager().getAdapter(model,
adapter) : null;
}
/**
* contains method
*
* @return boolean
* @param offset
* int
*/
@Override
public boolean contains(int offset) {
return (offset >= getStartOffset() && offset < getEndOffset());
}
/**
* @param s
* @param tagName
* @return
*/
protected String createJSONExceptionMessage(short s, String tagName) {
String result = null;
// TODO: Should localize these messages, and provide /u escaped
// version of tagName
result = lookupMessage(s) + " " + tagName; //$NON-NLS-1$
return result;
}
/**
* getAttributes method
*
* @return org.w3c.dom.NamedNodeMap
*/
public NamedNodeMap getAttributes() {
return null;
}
/**
*/
protected String getCharValue(String name) {
JSONDocumentImpl document = (JSONDocumentImpl) getOwnerDocument();
if (document == null)
return null;
return document.getCharValue(name);
}
/**
* getChildNodes method
*
* @return org.w3c.dom.NodeList
*/
// public NodeList getChildNodes() {
// // As per JSON spec, correct behavior for getChildNodes is to return a
// // zero length NodeList, not null, when there are no children.
// // We'll use a common instance of an empty node list, just to prevent
// // creating a trival object many many times.
//
// return EMPTY_NODE_LIST;
// }
/**
* getContainerDocument method
*
* @return org.w3c.dom.Document
*/
public IJSONDocument getContainerDocument() {
IJSONNode parent = null;
for (IJSONNode node = this; node != null; node = parent) {
if (node.getNodeType() == IJSONNode.DOCUMENT_NODE) {
return (IJSONDocument) node;
}
/* Break out of a bad hierarchy */
if ((parent = node.getParentNode()) == node)
break;
}
return null;
}
/**
* getEndOffset method
*
* @return int
*/
@Override
public int getEndOffset() {
IJSONNode node = this;
while (node != null) {
if (node.getNodeType() == IJSONNode.OBJECT_NODE) {
JSONObjectImpl element = (JSONObjectImpl) node;
IStructuredDocumentRegion endStructuredDocumentRegion = element
.getEndStructuredDocumentRegion();
if (endStructuredDocumentRegion != null)
return endStructuredDocumentRegion.getEnd();
}
IJSONNode last = node.getLastChild();
if (last != null) { // dig into the last
node = last;
continue;
}
IStructuredDocumentRegion lastStructuredDocumentRegion = ((JSONNodeImpl) node)
.getStructuredDocumentRegion();
if (lastStructuredDocumentRegion != null)
return lastStructuredDocumentRegion.getEnd();
IJSONNode prev = node.getPreviousSibling();
if (prev != null) { // move to the previous
node = prev;
continue;
}
IJSONNode parent = node.getParentNode();
node = null;
while (parent != null) {
if (parent.getNodeType() == IJSONNode.OBJECT_NODE) {
JSONObjectImpl element = (JSONObjectImpl) parent;
IStructuredDocumentRegion startStructuredDocumentRegion = element
.getStartStructuredDocumentRegion();
if (startStructuredDocumentRegion != null)
return startStructuredDocumentRegion.getEnd();
}
IJSONNode parentPrev = parent.getPreviousSibling();
if (parentPrev != null) { // move to the previous
node = parentPrev;
break;
}
parent = parent.getParentNode();
}
}
return 0;
}
@Override
public IStructuredDocumentRegion getEndStructuredDocumentRegion() {
return null;
}
/**
*/
@Override
public FactoryRegistry getFactoryRegistry() {
IJSONModel model = getModel();
if (model != null) {
FactoryRegistry reg = model.getFactoryRegistry();
if (reg != null)
return reg;
}
return null;
}
@Override
public IJSONNode getFirstChild() {
return null;
}
/**
* getFirstStructuredDocumentRegion method
*
*/
@Override
public IStructuredDocumentRegion getFirstStructuredDocumentRegion() {
return StructuredDocumentRegionUtil
.getStructuredDocumentRegion(this.flatNode);
}
/**
*/
public int getIndex() {
IJSONNode parent = getParentNode();
if (parent == null)
return -1; // error
int index = 0;
for (IJSONNode child = parent.getFirstChild(); child != null; child = child
.getNextSibling()) {
if (child == this)
return index;
index++;
}
return -1; // error
}
@Override
public IJSONNode getLastChild() {
return null;
}
/**
* getLastStructuredDocumentRegion method
*
*/
@Override
public IStructuredDocumentRegion getLastStructuredDocumentRegion() {
return StructuredDocumentRegionUtil
.getStructuredDocumentRegion(this.flatNode);
}
/**
*/
public String getLocalName() {
return null;
}
/**
* the default implementation can just refer to the owning document
*/
@Override
public IJSONModel getModel() {
if (this.ownerDocument == null)
return null;
return this.ownerDocument.getModel();
}
/**
* all but attr return null
*/
public ITextRegion getNameRegion() {
return null;
}
/**
*/
public String getNamespaceURI() {
return null;
}
@Override
public IJSONNode getNextSibling() {
return this.nextSibling;
}
/**
* getNodeAt method
*
* @return org.w3c.dom.Node
* @param offset
* int
*/
IJSONNode getNodeAt(int offset) {
IJSONNode parent = this;
IJSONNode child = getFirstChild();
while (child != null) {
if (child.getEndOffset() == offset) {
return child;
}
if (child.getEndOffset() <= offset) {
child = child.getNextSibling();
continue;
}
if (child.getStartOffset() > offset) {
break;
}
IStructuredDocumentRegion startStructuredDocumentRegion = child
.getStartStructuredDocumentRegion();
if (startStructuredDocumentRegion != null) {
if (startStructuredDocumentRegion.getEnd() > offset)
return child;
}
// dig more
parent = child;
// In case parent is a JSONPair ensure the value is not a container
// JSONNode like JSONObject or JSONArray
if (parent instanceof JSONPairImpl) {
IJSONValue value = ((JSONPairImpl) parent).getValue();
if (value instanceof JSONObjectImpl || value instanceof JSONArrayImpl) {
parent = value;
}
}
child = parent.getFirstChild();
}
return parent;
}
@Override
public IJSONDocument getOwnerDocument() {
return this.ownerDocument;
}
@Override
public IJSONNode getParentNode() {
return this.parentNode;
}
/**
*/
public String getPrefix() {
return null;
}
@Override
public IJSONNode getPreviousSibling() {
return this.previousSibling;
}
/**
*/
public String getSource() {
if (this.flatNode == null)
return JSONNodeImpl.EMPTY_STRING;
return this.flatNode.getText();
}
/**
* getStartOffset method
*
* @return int
*/
@Override
public int getStartOffset() {
if (this.flatNode != null)
return this.flatNode.getStart();
JSONNodeImpl prev = (JSONNodeImpl) getPreviousSibling();
if (prev != null)
return prev.getEndOffset();
IJSONNode parent = getParentNode();
if (parent != null && parent.getNodeType() == IJSONNode.OBJECT_NODE) {
JSONObjectImpl element = (JSONObjectImpl) parent;
if (element.hasStartTag())
return element.getStartEndOffset();
return element.getStartOffset();
}
// final fallback to look into first child
JSONNodeImpl child = (JSONNodeImpl) getFirstChild();
while (child != null) {
IStructuredDocumentRegion childStructuredDocumentRegion = child
.getStructuredDocumentRegion();
if (childStructuredDocumentRegion != null)
return childStructuredDocumentRegion.getStart();
child = (JSONNodeImpl) child.getFirstChild();
}
return 0;
}
@Override
public IStructuredDocumentRegion getStartStructuredDocumentRegion() {
return getFirstStructuredDocumentRegion();
}
/**
* Every node (indirectly) knows its structuredDocument
*/
@Override
public IStructuredDocument getStructuredDocument() {
return getModel().getStructuredDocument();
}
/**
*/
IStructuredDocumentRegion getStructuredDocumentRegion() {
return this.flatNode;
}
/**
* all but attr return null
*/
public ITextRegion getValueRegion() {
return null;
}
/**
* @throws JSONException
*/
public String getValueSource() throws JSONException {
return getNodeValue();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasAttributes()
*/
public boolean hasAttributes() {
return false;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasChildNodes()
*/
@Override
public boolean hasChildNodes() {
return false;
}
/**
* hasProperties method
*
* @return boolean
*/
public boolean hasProperties() {
return false;
}
/**
* insertBefore method
*
* @return org.w3c.dom.Node
* @param newChild
* org.w3c.dom.Node
* @param refChild
* org.w3c.dom.Node
*/
@Override
public IJSONNode insertBefore(IJSONNode newChild, IJSONNode refChild)
throws JSONException {
// throw new JSONException(JSONException.HIERARCHY_REQUEST_ERR,
// JSONMessages.HIERARCHY_REQUEST_ERR);
throw new JSONException();
}
public boolean isChildEditable() {
return false;
}
/**
*
*/
public boolean isClosed() {
return true;
}
/**
* isContainer method
*
* @return boolean
*/
public boolean isContainer() {
return false;
}
public boolean isDataEditable() {
if (!fDataEditable) {
JSONModelImpl model = (JSONModelImpl) getModel();
if (model != null && model.isReparsing()) {
return true;
}
}
return fDataEditable;
}
/**
* @param s
* @return
*/
private String lookupMessage(short s) {
// TODO: make localized version
String result = null;
switch (s) {
// case JSONException.JSONSTRING_SIZE_ERR:
// result = JSONMessages.JSONSTRING_SIZE_ERR;
// break;
// case JSONException.HIERARCHY_REQUEST_ERR:
// result = JSONMessages.HIERARCHY_REQUEST_ERR;
// break;
// case JSONException.INDEX_SIZE_ERR:
// result = JSONMessages.INDEX_SIZE_ERR;
// break;
// case JSONException.INUSE_ATTRIBUTE_ERR:
// result = JSONMessages.INUSE_ATTRIBUTE_ERR;
// break;
// case JSONException.INVALID_ACCESS_ERR:
// result = JSONMessages.INVALID_ACCESS_ERR;
// break;
// case JSONException.INVALID_CHARACTER_ERR:
// result = JSONMessages.INVALID_CHARACTER_ERR;
// break;
// case JSONException.INVALID_MODIFICATION_ERR:
// result = JSONMessages.INVALID_MODIFICATION_ERR;
// break;
// case JSONException.INVALID_STATE_ERR:
// result = JSONMessages.INVALID_STATE_ERR;
// break;
// case JSONException.NAMESPACE_ERR:
// result = JSONMessages.NAMESPACE_ERR;
// break;
// case JSONException.NO_DATA_ALLOWED_ERR:
// result = JSONMessages.NO_DATA_ALLOWED_ERR;
// break;
// case JSONException.NO_MODIFICATION_ALLOWED_ERR:
// result = JSONMessages.NO_MODIFICATION_ALLOWED_ERR;
// break;
// case JSONException.NOT_FOUND_ERR:
// result = JSONMessages.NOT_FOUND_ERR;
// break;
// case JSONException.NOT_SUPPORTED_ERR:
// result = JSONMessages.NOT_SUPPORTED_ERR;
// break;
// case JSONException.SYNTAX_ERR:
// result = JSONMessages.SYNTAX_ERR;
// break;
// case 17:// JSONException.TYPE_MISMATCH_ERR :
// result = JSONMessages.TYPE_MISMATCH_ERR;
// break;
// case 16:// JSONException.VALIDATION_ERR :
// result = JSONMessages.VALIDATION_ERR;
// break;
// case JSONException.WRONG_DOCUMENT_ERR:
// result = JSONMessages.WRONG_DOCUMENT_ERR;
// break;
default:
result = JSONNodeImpl.EMPTY_STRING;
break;
}
return result;
}
// protected void notifyEditableChanged() {
// JSONDocumentImpl document = (JSONDocumentImpl) getContainerDocument();
// if (document == null)
// return;
// JSONModelImpl model = (JSONModelImpl) document.getModel();
// if (model == null)
// return;
// model.editableChanged(this);
// }
/**
* notifyValueChanged method
*/
protected void notifyValueChanged() {
JSONDocumentImpl document = (JSONDocumentImpl) getContainerDocument();
if (document == null)
return;
// syncDataEditableState();
JSONModelImpl model = (JSONModelImpl) document.getModel();
if (model == null)
return;
model.valueChanged(this);
}
/**
* removeChild method
*
* @return org.w3c.dom.Node
* @param oldChild
* org.w3c.dom.Node
*/
@Override
public IJSONNode removeChild(IJSONNode oldChild) throws JSONException {
throw new JSONException();
// throw new JSONException(JSONException.NOT_FOUND_ERR,
// JSONMessages.NOT_FOUND_ERR);
}
/**
* removeChildNodes method
*/
public void removeChildNodes() {
}
/**
* removeChildNodes method
*
* @return org.w3c.dom.DocumentFragment
* @param firstChild
* org.w3c.dom.Node
* @param lastChild
* org.w3c.dom.Node
*/
// public DocumentFragment removeChildNodes(Node firstChild, Node lastChild)
// {
// return null;
// }
/**
* 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 {
// throw new JSONException(JSONException.HIERARCHY_REQUEST_ERR,
// JSONMessages.HIERARCHY_REQUEST_ERR);
return null;
}
/**
* Resets children values from IStructuredDocumentRegion.
*/
void resetStructuredDocumentRegions() {
for (JSONNodeImpl child = (JSONNodeImpl) getFirstChild(); child != null; child = (JSONNodeImpl) child
.getNextSibling()) {
child.resetStructuredDocumentRegions();
}
this.flatNode = null;
}
public void setChildEditable(boolean editable) {
// nop
}
// public void setDataEditable(boolean editable) {
// if (fDataEditable == editable) {
// return;
// }
//
// ReadOnlyController roc = ReadOnlyController.getInstance();
// if (editable) {
// roc.unlockData(this);
// } else {
// roc.lockData(this);
// }
//
// fDataEditable = editable;
//
// notifyEditableChanged();
// }
// public void setEditable(boolean editable, boolean deep) {
// if (deep) {
// IJSONNode node = (IJSONNode) getFirstChild();
// while (node != null) {
// node.setEditable(editable, deep);
// node = (IJSONNode) node.getNextSibling();
// }
// }
// setChildEditable(editable);
// setDataEditable(editable);
// }
/**
* setNextSibling method
*
* @param nextSibling
* org.w3c.dom.Node
*/
protected void setNextSibling(IJSONNode nextSibling) {
this.nextSibling = (JSONNodeImpl) nextSibling;
}
/**
* setNodeValue method
*
* @param nodeValue
* java.lang.String
*/
@Override
public void setNodeValue(String nodeValue) {
}
protected void setOwnerDocument(IJSONDocument ownerDocument) {
this.ownerDocument = (JSONDocumentImpl) ownerDocument;
}
/**
*/
protected void setOwnerDocument(IJSONDocument ownerDocument, boolean deep) {
this.ownerDocument = (JSONDocumentImpl) ownerDocument;
if (deep) {
for (JSONNodeImpl child = (JSONNodeImpl) getFirstChild(); child != null; child = (JSONNodeImpl) child
.getNextSibling()) {
child.setOwnerDocument(ownerDocument, deep);
}
}
}
/**
* setParentNode method
*
* @param parentNode
* org.w3c.dom.Node
*/
protected void setParentNode(IJSONNode parentNode) {
this.parentNode = (JSONNodeImpl) parentNode;
}
/**
* setPreviousSibling method
*
* @param previousSibling
* org.w3c.dom.Node
*/
protected void setPreviousSibling(IJSONNode previousSibling) {
this.previousSibling = (JSONNodeImpl) previousSibling;
}
/**
*/
public void setSource(String source) {
// not supported
}
/**
*/
void setStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
this.flatNode = flatNode;
}
/**
*/
public void setValueSource(String source) {
setNodeValue(source);
}
// protected void syncDataEditableState() {
// ReadOnlyController roc = ReadOnlyController.getInstance();
// if (fDataEditable) {
// roc.unlockData(this);
// } else {
// roc.lockData(this);
// }
// }
@Override
public int getLength() {
int result = -1;
int start = getStartOffset();
if (start >= 0) {
int end = getEndOffset();
if (end >= 0) {
result = end - start;
if (result < -1) {
result = -1;
}
}
}
return result;
}
@Override
public IJSONPath getPath() {
List<String> names = new LinkedList<String>();
getNames(names, this);
List<String> copy = names.subList(0, names.size());
Collections.reverse(copy);
String[] segments = copy.toArray(new String[0]);
return new JSONPath(segments);
}
private void getNames(List<String> names, IJSONNode node) {
if (node == null) {
return;
}
if (node instanceof IJSONPair) {
names.add(((IJSONPair)node).getName());
}
getNames(names, node.getParentOrPairNode());
}
}