blob: 41c97e2d0917b3b4c16bd358f5fe6d20b4c764a1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2010 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.resource.xml;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.jpt.core.utility.AbstractTextRange;
import org.eclipse.jpt.core.utility.TextRange;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapter;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* Provisional API: This interface is part of an interim API that is still
* under development and expected to change significantly before reaching
* stability. It is available at this early stage to solicit feedback from
* pioneering adopters on the understanding that any code that uses this API
* will almost certainly be broken (repeatedly) as the API evolves.
*
* @version 3.0
* @since 2.2
*/
public abstract class AbstractJpaEObject
extends EObjectImpl
implements JpaEObject
{
protected IDOMNode node;
/**
* Sets of "insignificant" feature ids, keyed by class.
* This is built up lazily, as the objects are modified.
*/
private static final Hashtable<Class<? extends AbstractJpaEObject>, HashSet<Integer>> insignificantFeatureIdSets = new Hashtable<Class<? extends AbstractJpaEObject>, HashSet<Integer>>();
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
protected AbstractJpaEObject() {
super();
}
// ********** JpaEObject implementation **********
public boolean isUnset() {
for (EStructuralFeature feature : this.eClass().getEAllStructuralFeatures()) {
if (this.eIsSet(feature)) {
return false;
}
}
return true;
}
// ********** change notification **********
/**
* override to build a custom list for the adapters
*/
@Override
public EList<Adapter> eAdapters() {
if (this.eAdapters == null) {
this.eAdapters = new XmlEAdapterList<Adapter>(this);
}
return this.eAdapters;
}
/**
* override to prevent notification when the object's state is unchanged
*/
@Override
public void eNotify(Notification notification) {
if ( ! notification.isTouch()) {
super.eNotify(notification);
this.featureChanged(notification.getFeatureID(this.getClass()));
}
}
protected void featureChanged(int featureId) {
if (this.featureIsSignificant(featureId)) {
this.getXmlResource().resourceModelChanged();
}
}
protected JpaXmlResource getXmlResource() {
return (JpaXmlResource) this.eResource();
}
protected boolean featureIsSignificant(int featureId) {
return ! this.featureIsInsignificant(featureId);
}
protected boolean featureIsInsignificant(int featureId) {
return this.insignificantFeatureIds().contains(Integer.valueOf(featureId));
}
/**
* Return a set of the object's "insignificant" feature ids.
* These are the EMF features that will not be used to determine if all
* the features are unset. We use this to determine when to remove
* an element from the xml.
*
* If you need instance-based calculation of your xml "insignificant" aspects,
* override this method. If class-based calculation is sufficient,
* override #addInsignificantXmlFeatureIdsTo(Set).
*
* @see isAllFeaturesUnset()
*/
protected Set<Integer> insignificantFeatureIds() {
synchronized (insignificantFeatureIdSets) {
HashSet<Integer> insignificantXmlFeatureIds = insignificantFeatureIdSets.get(this.getClass());
if (insignificantXmlFeatureIds == null) {
insignificantXmlFeatureIds = new HashSet<Integer>();
this.addInsignificantXmlFeatureIdsTo(insignificantXmlFeatureIds);
insignificantFeatureIdSets.put(this.getClass(), insignificantXmlFeatureIds);
}
return insignificantXmlFeatureIds;
}
}
/**
* Add the object's "insignificant" feature ids to the specified set.
* These are the EMF features that, when they change, will NOT cause the
* object (or its containing tree) to be updated, i.e. defaults calculated.
* If class-based calculation of your "insignificant" features is sufficient,
* override this method. If you need instance-based calculation,
* override #insignificantXmlFeatureIds().
*/
protected void addInsignificantXmlFeatureIdsTo(@SuppressWarnings("unused") Set<Integer> insignificantXmlFeatureIds) {
// when you override this method, don't forget to include:
// super.addInsignificantXmlFeatureIdsTo(insignificantXmlFeatureIds);
}
// ********** text ranges **********
/**
* Return a text range for the "text" node.
* If the text node does not exist, return a text range for this object's node
*/
protected TextRange getTextTextRange() {
IDOMNode textNode = this.getTextNode();
return (textNode != null) ? buildTextRange(textNode) : this.getValidationTextRange();
}
protected IDOMNode getTextNode() {
if (this.node == null) return null; // virtual objects have no node
return getTextNode(this.node);
}
protected static IDOMNode getTextNode(IDOMNode node) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i ++) {
IDOMNode child = (IDOMNode) children.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
return child;
}
}
return null;
}
/**
* Return a text range for the specified attribute node.
* If the attribute node does not exist, return a text range for this object's
* node
*/
protected TextRange getAttributeTextRange(String attributeName) {
IDOMNode attributeNode = this.getAttributeNode(attributeName);
return (attributeNode != null) ? buildTextRange(attributeNode) : this.getValidationTextRange();
}
protected IDOMAttr getAttributeNode(String attributeName) {
return (this.node == null) ? // virtual objects have no node
null : (IDOMAttr) this.node.getAttributes().getNamedItem(attributeName);
}
/**
* Return a text range for the specified element node.
* If the element node does not exist, return a text range for this object's
* node
*/
protected TextRange getElementTextRange(String elementName) {
IDOMNode elementNode = this.getElementNode(elementName);
return (elementNode != null) ? buildTextRange(elementNode) : this.getValidationTextRange();
}
/**
* Returns the first element node with the given name, if one exists
*/
protected IDOMNode getElementNode(String elementName) {
if (this.node == null) return null; // virtual objects have no node
NodeList children = this.node.getChildNodes();
for (int i = 0; i < children.getLength(); i ++) {
IDOMNode child = (IDOMNode) children.item(i);
if ((child.getNodeType() == Node.ELEMENT_NODE)
&& elementName.equals(child.getNodeName())) {
return child;
}
}
return null;
}
public TextRange getValidationTextRange() {
return this.getFullTextRange();
}
public TextRange getSelectionTextRange() {
return this.getFullTextRange();
}
protected TextRange getFullTextRange() {
return buildTextRange(this.node);
}
protected static TextRange buildTextRange(IDOMNode domNode) {
return (domNode == null) ? null : new DOMNodeTextRange(domNode);
}
public boolean containsOffset(int textOffset) {
return (this.node == null) ? false : this.node.contains(textOffset);
}
// ********** Refactoring TextEdits **********
public DeleteEdit createDeleteEdit() {
int deletionOffset = getDeletionOffset();
int deletionLength = this.node.getEndOffset() - deletionOffset;
return new DeleteEdit(deletionOffset, deletionLength);
}
public int getNodeEndOffset() {
return this.node.getEndOffset();
}
/**
* deletion offset needs to include any text that is before the node
*/
protected int getDeletionOffset() {
int emptyTextLength = 0;
Node previousSibling = this.node.getPreviousSibling();
if (previousSibling != null && previousSibling.getNodeType() == Node.TEXT_NODE) {
emptyTextLength = ((Text) previousSibling).getLength();
}
return this.node.getStartOffset() - emptyTextLength;
}
// ********** custom adapter list **********
protected static class XmlEAdapterList<E extends Object & Adapter>
extends EAdapterList<E>
{
public XmlEAdapterList(AbstractJpaEObject jpaEObject) {
super(jpaEObject);
}
@Override
protected void didAdd(int index, E newObject) {
super.didAdd(index, newObject);
if (newObject instanceof EMF2DOMAdapter) {
Object n = ((EMF2DOMAdapter) newObject).getNode();
if (n instanceof IDOMNode) {
((AbstractJpaEObject) this.notifier).node = (IDOMNode) n;
}
}
}
@Override
protected void didRemove(int index, E oldObject) {
if ((oldObject instanceof EMF2DOMAdapter) &&
(((EMF2DOMAdapter) oldObject).getNode() == ((AbstractJpaEObject) this.notifier).node)) {
((AbstractJpaEObject) this.notifier).node = null;
}
super.didRemove(index, oldObject);
}
}
// ********** DOM node text range **********
/**
* Adapt an IDOMNode to the TextRange interface.
*/
protected static class DOMNodeTextRange
extends AbstractTextRange
{
private final IDOMNode node;
DOMNodeTextRange(IDOMNode node) {
super();
this.node = node;
}
public int getOffset() {
return this.node.getStartOffset();
}
public int getLength() {
if (this.node.getNodeType() == Node.ELEMENT_NODE) {
return ((IDOMElement) this.node).getStartEndOffset() - this.node.getStartOffset();
}
return this.node.getLength();
}
public int getLineNumber() {
return this.node.getStructuredDocument().getLineOfOffset(this.getOffset()) + 1;
}
}
}