blob: 50a032a18d3bae40e6dbcc57d80d5c008217b00f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002 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
*
*******************************************************************************/
package org.eclipse.wst.xml.core.internal.contentmodel.modelqueryimpl;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAnyElement;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.modelqueryimpl.ModelQueryExtensionManagerImpl;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.CMDataTypeValueHelper;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.DOMValidator;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.CMDocumentManager;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAssociationProvider;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.extension.ModelQueryExtensionManager;
import org.eclipse.wst.xml.core.internal.contentmodel.util.CMVisitor;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceHelper;
import org.eclipse.wst.xml.core.internal.contentmodel.util.NamespaceInfo;
import org.eclipse.wst.xml.core.internal.contentmodel.util.NamespaceTable;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* This class implements a large portion of the ModelQuery interfaces.
* Some work is delegated to the DOMHelper, CMDocumentManager, and DOMValidator.
*/
public class ModelQueryImpl implements ModelQuery
{
protected ModelQueryAssociationProvider modelQueryAssociationProvider;
protected ModelQueryActionHelper modelQueryActionHelper;
protected DOMValidator validator;
protected ModelQueryExtensionManagerImpl extensionManager;
protected CMDataTypeValueHelper valueHelper;
protected int editMode = EDIT_MODE_CONSTRAINED_STRICT;
public ModelQueryImpl(ModelQueryAssociationProvider modelQueryAssociationProvider)
{
this.modelQueryAssociationProvider = modelQueryAssociationProvider;
modelQueryActionHelper = createModelQueryActionHelper();
validator = new DOMValidator();
extensionManager = new ModelQueryExtensionManagerImpl();
valueHelper = new CMDataTypeValueHelper();
}
public int getEditMode()
{
return editMode;
}
public void setEditMode(int editMode)
{
this.editMode =editMode;
}
// factory methods
public ModelQueryActionHelper createModelQueryActionHelper()
{
return new ModelQueryActionHelper(this);
}
public DOMValidator getValidator()
{
return validator;
}
public CMDocument getCorrespondingCMDocument(Node node)
{
return modelQueryAssociationProvider.getCorrespondingCMDocument(node);
}
public CMNode getCMNode(Node node)
{
return modelQueryAssociationProvider.getCMNode(node);
}
public CMDataType getCMDataType(Text text)
{
return modelQueryAssociationProvider.getCMDataType(text);
}
public CMAttributeDeclaration getCMAttributeDeclaration(Attr attr)
{
return modelQueryAssociationProvider.getCMAttributeDeclaration(attr);
}
public CMElementDeclaration getCMElementDeclaration(Element element)
{
return modelQueryAssociationProvider.getCMElementDeclaration(element);
}
public CMDocumentManager getCMDocumentManager()
{
CMDocumentManager result = null;
if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
{
XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
result = xmlAssociationProvider.getCMDocumentManager();
}
return result;
}
/**
* @deprected - use 3 arg version below
*/
public List getCMDocumentList(Element element, String uri)
{
return Collections.EMPTY_LIST;
}
public List getCMDocumentList(Element element, CMElementDeclaration ed, String uri)
{
List result = new Vector();
if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
{
XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
// todo... revist... handle each ##thing explicitly
//
if (uri == null)
{
uri = "##any"; //$NON-NLS-1$
}
if (uri.equals("##targetNamespace")) //$NON-NLS-1$
{
CMDocument cmDocument = (CMDocument)ed.getProperty("CMDocument"); //$NON-NLS-1$
if (cmDocument != null)
{
result.add(cmDocument);
}
}
else if (uri.equals("##any") || uri.equals("##other")) //$NON-NLS-1$ //$NON-NLS-2$
{
String excludedURI = null;
if (uri.equals("##other")) //$NON-NLS-1$
{
CMDocument cmDocument = (CMDocument)ed.getProperty("CMDocument"); //$NON-NLS-1$
if (cmDocument != null)
{
excludedURI = (String)cmDocument.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI"); //$NON-NLS-1$
}
}
// in this case we should consider all of the schema related to this document
//
NamespaceTable namespaceTable = new NamespaceTable(element.getOwnerDocument());
namespaceTable.addElementLineage(element);
List list = namespaceTable.getNamespaceInfoList();
for (Iterator i = list.iterator(); i.hasNext();)
{
NamespaceInfo info = (NamespaceInfo)i.next();
if (info.uri != null && !info.uri.equals(excludedURI))
{
CMDocument document = xmlAssociationProvider.getCMDocument(info.uri, info.locationHint, "XSD"); //$NON-NLS-1$
if (document != null)
{
result.add(document);
}
}
}
}
else
{
CMDocument document = xmlAssociationProvider.getCMDocument(element, uri);
if (document != null)
{
result.add(document);
}
}
}
return result;
}
public CMDocument getCMDocument(Element element, String uri)
{
CMDocument result = null;
if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
{
XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
result = xmlAssociationProvider.getCMDocument(element, uri);
}
//ContentModelManager.println("ModelQueryImpl.getCMDocument(" + element.getNodeName() + ", " + uri + ") = " + result);
return result;
}
public boolean isContentValid(Element element)
{
CMElementDeclaration ed = getCMElementDeclaration(element);
return isContentValid(ed, element);
}
public boolean isContentValid(CMElementDeclaration ed, Element element)
{
boolean result = true;
if (ed != null)
{
// first check to see if all the required attributes are present
//
CMNamedNodeMap map = ed.getAttributes();
int mapLength = map.getLength();
for (int i = 0; i < mapLength; i++)
{
CMAttributeDeclaration ad = (CMAttributeDeclaration)map.item(i);
String attributeName = DOMNamespaceHelper.computeName(ad, element, null);
if (ad.getUsage() == CMAttributeDeclaration.REQUIRED)
{
Attr attr = element.getAttributeNode(attributeName);
if (attr == null)
{
result = false;
break;
}
}
}
// now check to see of the children validate properly
//
if (result)
{
CMNode[] originArray = getOriginArray(element);
result = originArray != null && originArray.length == element.getChildNodes().getLength();
}
}
return result;
}
public CMNode getOrigin(Node node)
{
CMNode result = null;
// todo... make sure parent is right
//
Node parentNode = getParentOrOwnerNode(node);
if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE)
{
Element parentElement = (Element)parentNode;
CMNode[] array = getOriginArray(parentElement);
if (array != null)
{
int index = getIndexOfNode(parentElement.getChildNodes(), node);
if (index < array.length)
{
result = array[index];
}
}
}
return result;
}
public CMNode[] getOriginArray(Element element)
{
CMElementDeclaration ed = getCMElementDeclaration(element);
return (ed != null) ? getValidator().getOriginArray(ed, element) : null;
}
public int getIndexOfNode(NodeList nodeList, Node node)
{
int result = -1;
int size = nodeList.getLength();
for (int i = 0; i < size; i++)
{
if (nodeList.item(i) == node)
{
result = i;
break;
}
}
return result;
}
/**
* Returns a list of all CMNode 'meta data' that may be potentially added to the element.
*/
public List getAvailableContent(Element element, CMElementDeclaration ed, int includeOptions)
{
AvailableContentCMVisitor visitor = new AvailableContentCMVisitor(element, ed);
List list = visitor.computeAvailableContent(includeOptions);
if (extensionManager != null)
{
extensionManager.filterAvailableElementContent(list, element, ed);
}
return list;
}
public boolean canInsert(Element parent, CMNode cmNode, int index, int validityChecking)
{
boolean result = true;
CMElementDeclaration ed = getCMElementDeclaration(parent);
if (ed != null)
{
result = canInsert(parent, ed, cmNode, index, validityChecking);
}
return result;
}
public boolean canInsert(Element parent, CMElementDeclaration ed, CMNode cmNode, int index, int validityChecking)
{
return canInsert(parent, ed, cmNode, index, validityChecking, null);
}
protected boolean canInsert(Element parent, CMElementDeclaration ed, CMNode cmNode, int index, int validityChecking, Object reuseableData)
{
boolean result = true;
switch (cmNode.getNodeType())
{
case CMNode.ATTRIBUTE_DECLARATION :
{
String attributeName = DOMNamespaceHelper.computeName(cmNode, parent, null);
result = parent.getAttributeNode(attributeName) == null;
break;
}
case CMNode.ELEMENT_DECLARATION :
case CMNode.GROUP :
{
if (validityChecking == VALIDITY_STRICT)
{
// create list
List contentSpecificationList = null;
if (reuseableData != null)
{
contentSpecificationList = (List)reuseableData;
}
else
{
contentSpecificationList = getValidator().createContentSpecificationList(parent, ed);
}
result = getValidator().canInsert(ed, contentSpecificationList, index, cmNode);
}
break;
}
case CMNode.DATA_TYPE :
{
int contentType = ed.getContentType();
result = (contentType == CMElementDeclaration.MIXED ||
contentType == CMElementDeclaration.PCDATA ||
contentType == CMElementDeclaration.ANY);
break;
}
default :
{
result = false;
break;
}
}
return result;
}
public boolean canInsert(Element parent, List cmNodeList, int index, int validityChecking)
{
// todo
return true;
}
public boolean canRemove(Node node, int validityChecking)
{
boolean result = true;
if (validityChecking == VALIDITY_STRICT)
{
int nodeType = node.getNodeType();
switch (nodeType)
{
case Node.ATTRIBUTE_NODE:
{
CMAttributeDeclaration ad = getCMAttributeDeclaration((Attr)node);
if (ad != null)
{
result = (ad.getUsage() == CMAttributeDeclaration.OPTIONAL);
}
break;
}
case Node.ELEMENT_NODE:
{
Node parentNode = node.getParentNode();
if (parentNode.getNodeType() == Node.ELEMENT_NODE)
{
Element parentElement = (Element)parentNode;
CMElementDeclaration ed = getCMElementDeclaration(parentElement);
if (ed != null)
{
List contentSpecificationList = getValidator().createContentSpecificationList(parentElement, ed);
int index = getIndexOfNode(parentElement.getChildNodes(), node);
result = getValidator().canRemove(ed, contentSpecificationList, index);
}
}
break;
}
}
}
return result;
}
public boolean canRemove(List nodeList, int validityChecking)
{
boolean result = true;
if (validityChecking == VALIDITY_STRICT)
{
Element parentElement = null;
List childList = null;
for (Iterator i = nodeList.iterator(); i.hasNext(); )
{
Node node = (Node)i.next();
if (parentElement == null)
{
parentElement = getParentOrOwnerElement(node);
}
else if (parentElement != getParentOrOwnerElement(node))
{
// make sure the parent are the same
result = false;
break;
}
if (parentElement == null)
{
result = true;
break;
}
int nodeType = node.getNodeType();
if (nodeType == Node.ATTRIBUTE_NODE)
{
if (!canRemove(node, validityChecking))
{
result = false;
break;
}
}
else
{
if (childList == null)
{
childList = nodeListToList(parentElement.getChildNodes());
}
childList.remove(node);
}
}
if (result && childList != null)
{
CMElementDeclaration ed = getCMElementDeclaration(parentElement);
if (ed != null)
{
List contentSpecificationList = getValidator().createContentSpecificationList(childList, ed);
result = getValidator().isValid(ed, contentSpecificationList);
}
}
}
return result;
}
public boolean canReplace(Element parent, int startIndex, int endIndex, CMNode cmNode, int validityChecking)
{
return true;
}
public boolean canReplace(Element parent, int startIndex, int endIndex, List cmNodeList, int validityChecking)
{
return true;
}
/**
* This method is experimental... use at your own risk
*/
public boolean canWrap(Element childElement, CMElementDeclaration wrapElement, int validityChecking)
{
boolean result = true;
Node parentNode = childElement.getParentNode();
if (parentNode.getNodeType() == Node.ELEMENT_NODE)
{
Element parentElement = (Element)parentNode;
CMElementDeclaration parentEd = getCMElementDeclaration(parentElement);
if (parentEd != null)
{
if (validityChecking == VALIDITY_STRICT)
{
int index = getIndexOfNode(parentElement.getChildNodes(), childElement);
List contentSpecificationList = getValidator().createContentSpecificationList(parentElement, parentEd);
List subList = contentSpecificationList.subList(index, index + 1);
result = getValidator().canReplace(parentEd, contentSpecificationList, index, index, wrapElement);
if (result)
{
result = getValidator().isValid(wrapElement, subList);
}
}
}
}
else
{
result = false;
}
return result;
}
public void getInsertActions(Element parent, CMElementDeclaration ed, int index, int includeOptions, int validityChecking, List actionList)
{
modelQueryActionHelper.getInsertActions(parent, ed, index, includeOptions, validityChecking, actionList);
}
public void getInsertActions(Document parent, CMDocument cmDocument, int index, int includeOptions, int validityChecking, List actionList)
{
modelQueryActionHelper.getInsertActions(parent, cmDocument, index, includeOptions, validityChecking, actionList);
}
public void getReplaceActions(Element parent, CMElementDeclaration ed, int includeOptions, int validityChecking, List actionList)
{
modelQueryActionHelper.getReplaceActions(parent, ed, includeOptions, validityChecking, actionList);
}
public void getReplaceActions(Element parent, CMElementDeclaration ed, List selectedChildren, int includeOptions, int validityChecking, List actionList)
{
modelQueryActionHelper.getReplaceActions(parent, ed, selectedChildren, includeOptions, validityChecking, actionList);
}
public void getInsertChildNodeActionTable(Element parent, CMElementDeclaration ed, int validityChecking, Hashtable actionTable)
{
modelQueryActionHelper.getInsertChildNodeActionTable(parent, ed, validityChecking, actionTable);
}
public void getActionTable(Element parent, CMElementDeclaration ed, int index, int validityChecking, Hashtable actionTable)
{
//modelQueryActionHelper.getAllActions(parent, ed, validityChecking, actionList);
}
// some helper methods
//
protected Node getParentOrOwnerNode(Node node)
{
return (node.getNodeType() == Node.ATTRIBUTE_NODE) ?
((Attr)node).getOwnerElement() :
node.getParentNode();
}
protected Element getParentOrOwnerElement(Node node)
{
Node parent = getParentOrOwnerNode(node);
return (parent.getNodeType() == Node.ELEMENT_NODE) ? (Element)parent : null;
}
protected List nodeListToList(NodeList nodeList)
{
int size = nodeList.getLength();
Vector v = new Vector(size);
for (int i = 0; i < size; i++)
{
v.add(nodeList.item(i));
}
return v;
}
/**
protected List getCMNodeList(NodeList nodeList)
{
int size = nodeList.getLength();
Vector v = new Vector(size);
for (int i = 0; i < size; i++)
{
v.add(getCMNode(nodeList.item(i));
}
return v;
}
*/
public class AvailableContentCMVisitor extends CMVisitor
{
public Hashtable childNodeTable = new Hashtable();
public Hashtable attributeTable = new Hashtable();
public Element rootElement;
public CMElementDeclaration rootElementDeclaration;
public boolean isRootVisited;
protected boolean includeSequenceGroups;
public AvailableContentCMVisitor(Element rootElement, CMElementDeclaration rootElementDeclaration)
{
this.rootElement = rootElement;
this.rootElementDeclaration = rootElementDeclaration;
}
protected String getKey(CMNode cmNode)
{
String key = cmNode.getNodeName();
CMDocument cmDocument = (CMDocument)cmNode.getProperty("CMDocument"); //$NON-NLS-1$
if (cmDocument != null)
{
String namespaceURI = (String)cmDocument.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI"); //$NON-NLS-1$
if (namespaceURI != null)
{
key = "[" + namespaceURI + "]" + key; //$NON-NLS-1$ //$NON-NLS-2$
}
}
return key;
}
protected void addToTable(Hashtable table, CMNode cmNode)
{
String nodeName = cmNode.getNodeName();
if (nodeName != null && nodeName.length() > 0)
{
table.put(getKey(cmNode), cmNode);
}
}
public List computeAvailableContent(int includeOptions)
{
Vector v = new Vector();
int contentType = rootElementDeclaration.getContentType();
includeSequenceGroups = ((includeOptions & INCLUDE_SEQUENCE_GROUPS) != 0);
visitCMNode(rootElementDeclaration);
if ((includeOptions & INCLUDE_ATTRIBUTES) != 0)
{
v.addAll(attributeTable.values());
CMAttributeDeclaration nillableAttribute = (CMAttributeDeclaration)rootElementDeclaration.getProperty("http://org.eclipse.wst/cm/properties/nillable"); //$NON-NLS-1$
if (nillableAttribute != null)
{
v.add(nillableAttribute);
}
}
if ((includeOptions & INCLUDE_CHILD_NODES) != 0)
{
if (contentType == CMElementDeclaration.MIXED ||
contentType == CMElementDeclaration.ELEMENT)
{
v.addAll(childNodeTable.values());
}
else if (contentType == CMElementDeclaration.ANY)
{
CMDocument cmDocument = (CMDocument)rootElementDeclaration.getProperty("CMDocument"); //$NON-NLS-1$
if (cmDocument != null)
{
CMNamedNodeMap elements = cmDocument.getElements();
for (Iterator i = elements.iterator(); i.hasNext(); )
{
v.add(i.next());
}
}
}
if (contentType == CMElementDeclaration.MIXED ||
contentType == CMElementDeclaration.PCDATA ||
contentType == CMElementDeclaration.ANY)
{
CMDataType dataType = rootElementDeclaration.getDataType();
if (dataType != null)
{
v.add(dataType);
}
}
}
return v;
}
public void visitCMAnyElement(CMAnyElement anyElement)
{
String uri = anyElement.getNamespaceURI();
List list = getCMDocumentList(rootElement, rootElementDeclaration, uri);
for (Iterator iterator = list.iterator(); iterator.hasNext(); )
{
CMDocument cmdocument = (CMDocument)iterator.next();
if (cmdocument != null)
{
CMNamedNodeMap map = cmdocument.getElements();
int size = map.getLength();
for (int i = 0; i < size; i++)
{
CMNode ed = map.item(i);
addToTable(childNodeTable,ed);
}
}
}
}
public void visitCMAttributeDeclaration(CMAttributeDeclaration ad)
{
super.visitCMAttributeDeclaration(ad);
attributeTable.put(ad.getNodeName(), ad);
}
public void visitCMElementDeclaration(CMElementDeclaration ed)
{
if (ed == rootElementDeclaration && !isRootVisited)
{
isRootVisited = true;
super.visitCMElementDeclaration(ed);
}
else
{
if (!Boolean.TRUE.equals(ed.getProperty("Abstract"))) //$NON-NLS-1$
{
addToTable(childNodeTable,ed);
}
CMNodeList substitutionGroup = (CMNodeList)ed.getProperty("SubstitutionGroup"); //$NON-NLS-1$
if (substitutionGroup != null)
{
handleSubstitutionGroup(substitutionGroup);
}
}
}
protected void handleSubstitutionGroup(CMNodeList substitutionGroup)
{
int substitutionGroupLength = substitutionGroup.getLength();
if (substitutionGroupLength > 1)
{
for (int i = 0; i < substitutionGroupLength; i++)
{
CMNode ed = substitutionGroup.item(i);
if (!Boolean.TRUE.equals(ed.getProperty("Abstract"))) //$NON-NLS-1$
{
addToTable(childNodeTable,ed);
}
}
}
}
public void visitCMGroup(CMGroup group)
{
if (includeSequenceGroups)
{
if (group.getOperator() == CMGroup.SEQUENCE &&
group.getChildNodes().getLength() > 1 &&
includesRequiredContent(group))
{
childNodeTable.put(group, group);
}
}
super.visitCMGroup(group);
}
public boolean includesRequiredContent(CMGroup group)
{
List list = getValidator().createContentSpecificationList(group);
return list.size() > 1;
}
}
/**
* @deprected - use getPossibleDataTypeValues()
*/
public List getDataTypeValues(Element element, CMNode cmNode)
{
return Arrays.asList(getPossibleDataTypeValues(element, cmNode));
}
/**
* This methods return an array of possible values corresponding to the datatype of the CMNode (either an CMAttributeDeclaration or a CMElementDeclaration)
*/
public String[] getPossibleDataTypeValues(Element element, CMNode cmNode)
{
List list = new Vector();
if (cmNode != null)
{
CMDataType dataType = null;
if (cmNode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION)
{
dataType = ((CMAttributeDeclaration)cmNode).getAttrType();
}
else if (cmNode.getNodeType() == CMNode.ELEMENT_DECLARATION)
{
dataType = ((CMElementDeclaration)cmNode).getDataType();
}
String[] enumeratedValues = dataType != null ? dataType.getEnumeratedValues() : null;
if (enumeratedValues != null)
{
for (int i = 0; i < enumeratedValues.length; i++)
{
list.add(enumeratedValues[i]);
}
}
}
addValuesForXSIType(element, cmNode, list);
if (extensionManager != null)
{
list.addAll(extensionManager.getDataTypeValues(element, cmNode));
}
int listSize = list.size();
String[] result = new String[listSize];
for (int i = 0; i < listSize; i++)
{
result[i] = (String)list.get(i);
}
return result;
}
protected void addValuesForXSIType(Element element, CMNode cmNode, List list)
{
if (cmNode != null && cmNode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION)
{
CMAttributeDeclaration ad = (CMAttributeDeclaration)cmNode;
if (valueHelper.isXSIType(ad))
{
NamespaceTable table = new NamespaceTable(element.getOwnerDocument());
table.addElementLineage(element);
list.addAll(valueHelper.getQualifiedXSITypes(ad, table));
}
}
}
public ModelQueryExtensionManager getExtensionManager()
{
return extensionManager;
}
}