blob: 58726889be38cef4637efb6c7ed5dca1a3d91e04 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2004 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.ui.views.properties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySheetEntry;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;
import org.eclipse.wst.common.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.common.contentmodel.CMDataType;
import org.eclipse.wst.common.contentmodel.CMElementDeclaration;
import org.eclipse.wst.common.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.common.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.sse.core.INodeAdapter;
import org.eclipse.wst.sse.core.INodeNotifier;
import org.eclipse.wst.sse.ui.views.properties.IPropertySourceExtension;
import org.eclipse.wst.xml.core.document.DocumentTypeAdapter;
import org.eclipse.wst.xml.core.document.XMLNode;
import org.eclipse.wst.xml.core.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.ui.internal.Logger;
import org.eclipse.wst.xml.ui.internal.XMLUIPlugin;
import org.eclipse.wst.xml.ui.internal.properties.EnumeratedStringPropertyDescriptor;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* An IPropertySource implementation for a JFace viewer used to display
* properties of DOM nodes. Requires an adapter factory to create JFace
* adapters for the nodes in the tree.
*/
public class XMLPropertySourceAdapter implements INodeAdapter, IPropertySource, IPropertySourceExtension {
protected final static String CATEGORY_ATTRIBUTES = XMLUIPlugin.getResourceString("%XMLPropertySourceAdapter.0"); //$NON-NLS-1$
private static final boolean fSetExpertFilter = false;
// derive categories from CMDataTypes; disabled until display strings can
// be planned
private final static boolean fShouldDeriveCategories = false;
private final static boolean fSortEnumeratedValues = true;
protected boolean fCaseSensitive = true;
protected IPropertyDescriptor[] fDescriptors = null;
protected Node fNode = null;
protected Stack fValuesBeingSet = new Stack();
public XMLPropertySourceAdapter(INodeNotifier target) {
super();
fNode = (Node) target;
if (fNode instanceof XMLNode) {
Document ownerDocument = fNode.getOwnerDocument();
if (ownerDocument == null) {
// if ownerDocument is null, then it must be the Document Node
ownerDocument = (Document) fNode;
}
DocumentTypeAdapter adapter = (DocumentTypeAdapter) ((INodeNotifier) ownerDocument).getAdapterFor(DocumentTypeAdapter.class);
if (adapter != null)
fCaseSensitive = adapter.getTagNameCase() == DocumentTypeAdapter.STRICT_CASE;
}
}
private String[] _getValidFixedStrings(CMAttributeDeclaration attrDecl, CMDataType helper) {
String attributeName = attrDecl.getAttrName();
List values = new ArrayList(1);
String impliedValue = helper.getImpliedValue();
if (impliedValue != null)
values.add(impliedValue);
boolean checkIfCurrentValueIsIncluded = (fNode.getAttributes() != null && fNode.getAttributes().getNamedItem(attributeName) != null && fNode.getAttributes().getNamedItem(attributeName).getNodeValue() != null);
if (checkIfCurrentValueIsIncluded) {
String currentValue = null;
currentValue = fNode.getAttributes().getNamedItem(attributeName).getNodeValue();
if (!currentValue.equals(impliedValue))
values.add(currentValue);
}
String[] validStrings = new String[values.size()];
validStrings = (String[]) values.toArray(validStrings);
return validStrings;
}
private String[] _getValidStrings(CMAttributeDeclaration attrDecl, CMDataType valuesHelper) {
String attributeName = attrDecl.getAttrName();
List values = new ArrayList(1);
boolean currentValueKnown = false;
boolean checkIfCurrentValueIsKnown = (fNode.getAttributes() != null && fNode.getAttributes().getNamedItem(attributeName) != null && fNode.getAttributes().getNamedItem(attributeName).getNodeValue() != null);
String currentValue = null;
if (checkIfCurrentValueIsKnown)
currentValue = fNode.getAttributes().getNamedItem(attributeName).getNodeValue();
if (valuesHelper.getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED && valuesHelper.getImpliedValue() != null) {
// FIXED value
currentValueKnown = currentValue != null && valuesHelper.getImpliedValue().equals(currentValue);
values.add(valuesHelper.getImpliedValue());
} else {
// ENUMERATED values
String[] valueStrings = null;
// valueStrings = valuesHelper.getEnumeratedValues();
ModelQuery modelQuery = ModelQueryUtil.getModelQuery(fNode.getOwnerDocument());
if (modelQuery != null && fNode.getNodeType() == Node.ELEMENT_NODE) {
valueStrings = modelQuery.getPossibleDataTypeValues((Element) fNode, attrDecl);
} else {
valueStrings = attrDecl.getAttrType().getEnumeratedValues();
}
if (valueStrings != null) {
for (int i = 0; i < valueStrings.length; i++) {
if (checkIfCurrentValueIsKnown && valueStrings[i].equals(currentValue))
currentValueKnown = true;
values.add(valueStrings[i]);
}
}
}
if (valuesHelper.getImpliedValueKind() != CMDataType.IMPLIED_VALUE_NONE && valuesHelper.getImpliedValue() != null) {
if (!values.contains(valuesHelper.getImpliedValue()))
values.add(valuesHelper.getImpliedValue());
}
if (checkIfCurrentValueIsKnown && !currentValueKnown && currentValue != null && currentValue.length() > 0)
values.add(currentValue);
String[] validStrings = new String[values.size()];
validStrings = (String[]) values.toArray(validStrings);
return validStrings;
}
protected void clearDescriptors() {
fDescriptors = null;
}
protected IPropertyDescriptor createDefaultPropertyDescriptor(String attributeName) {
return createDefaultPropertyDescriptor(attributeName, false);
}
protected IPropertyDescriptor createDefaultPropertyDescriptor(String attributeName, boolean hideOnFilter) {
// The descriptor class used here is also used in
// updatePropertyDescriptors()
TextPropertyDescriptor descriptor = new TextPropertyDescriptor(attributeName, attributeName);
descriptor.setCategory(getCategory(null));
descriptor.setDescription(attributeName);
if (hideOnFilter && fSetExpertFilter)
descriptor.setFilterFlags(new String[]{IPropertySheetEntry.FILTER_ID_EXPERT});
return descriptor;
}
/**
* Creates a property descriptor for an attribute with ENUMERATED values -
* if the value does not exist, an editable combo box is returned - if the
* value exists but is not one in the enumerated list of value, a combo
* box featuring the current and correct values is returned - if the value
* exists and it is a valid value, a combo box featuring the correct
* values with the current one visible is returned
*/
private IPropertyDescriptor createEnumeratedPropertyDescriptor(CMAttributeDeclaration attrDecl, CMDataType valuesHelper) {
// the displayName MUST be set
EnumeratedStringPropertyDescriptor descriptor = new EnumeratedStringPropertyDescriptor(attrDecl.getAttrName(), attrDecl.getAttrName(), _getValidStrings(attrDecl, valuesHelper));
descriptor.setCategory(getCategory(attrDecl));
descriptor.setDescription(attrDecl.getAttrName());
if (attrDecl.getUsage() != CMAttributeDeclaration.REQUIRED && fSetExpertFilter)
descriptor.setFilterFlags(new String[]{IPropertySheetEntry.FILTER_ID_EXPERT});
return descriptor;
}
/**
* Creates a property descriptor for an attribute with a FIXED value - if
* the value does not exist, an editable combo box is returned - if the
* value exists but is not the fixed/default value, a combo box featuring
* the current and correct value is returned - if the value exists and it
* is the fixed/default value, no cell editor is provided "locking" the
* value in
*/
private IPropertyDescriptor createFixedPropertyDescriptor(CMAttributeDeclaration attrDecl, CMDataType helper) {
// the displayName MUST be set
EnumeratedStringPropertyDescriptor descriptor = new EnumeratedStringPropertyDescriptor(attrDecl.getNodeName(), attrDecl.getNodeName(), _getValidFixedStrings(attrDecl, helper));
descriptor.setCategory(getCategory(attrDecl));
descriptor.setDescription(attrDecl.getAttrName());
return descriptor;
}
protected IPropertyDescriptor createPropertyDescriptor(CMAttributeDeclaration attrDecl) {
IPropertyDescriptor descriptor = null;
CMDataType attrType = attrDecl.getAttrType();
if (attrType != null) {
// handle declarations that provide FIXED/ENUMERATED values
if (attrType.getEnumeratedValues() != null && attrType.getEnumeratedValues().length > 0) {
descriptor = createEnumeratedPropertyDescriptor(attrDecl, attrType);
} else if ((attrDecl.getUsage() == CMAttributeDeclaration.FIXED || attrType.getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED) && attrType.getImpliedValue() != null) {
descriptor = createFixedPropertyDescriptor(attrDecl, attrType);
} else {
// plain text
descriptor = createTextPropertyDescriptor(attrDecl);
}
} else {
// no extra information given
descriptor = createTextPropertyDescriptor(attrDecl);
}
return descriptor;
}
/**
* Returns the current collection of property descriptors.
*
* @return all valid descriptors.
*/
protected IPropertyDescriptor[] createPropertyDescriptors() {
CMNamedNodeMap attrMap = null;
CMElementDeclaration ed = getDeclaration();
if (ed != null) {
attrMap = ed.getAttributes();
}
List descriptorList = new ArrayList();
List names = new ArrayList();
IPropertyDescriptor descriptor;
CMAttributeDeclaration attrDecl = null;
// add descriptors for existing attributes
NamedNodeMap attributes = fNode.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
// if metainfo is present for this attribute, use the
// CMAttributeDeclaration to derive a descriptor
if (attrMap != null) {
String attrName = attr.getName();
if (fCaseSensitive)
attrDecl = (CMAttributeDeclaration) attrMap.getNamedItem(attrName);
else {
for (int j = 0; j < attrMap.getLength(); j++) {
if (!fCaseSensitive && attrMap.item(j).getNodeName().equalsIgnoreCase(attrName)) {
attrDecl = (CMAttributeDeclaration) attrMap.item(j);
break;
}
}
}
}
// be consistent: if there's metainfo, use *that* as the
// descriptor ID
if (attrDecl != null) {
descriptor = createPropertyDescriptor(attrDecl);
if (descriptor != null)
names.add(attrDecl.getNodeName());
} else {
descriptor = createDefaultPropertyDescriptor(attr.getName());
if (descriptor != null)
names.add(attr.getName());
}
if (descriptor != null)
descriptorList.add(descriptor);
}
}
// add descriptors from the metainfo that are not yet listed
if (attrMap != null) {
for (int i = 0; i < attrMap.getLength(); i++) {
attrDecl = (CMAttributeDeclaration) attrMap.item(i);
if (!names.contains(attrDecl.getAttrName())) {
IPropertyDescriptor holdDescriptor = createPropertyDescriptor(attrDecl);
if (holdDescriptor != null) {
descriptorList.add(holdDescriptor);
}
}
}
}
IPropertyDescriptor[] descriptors = new IPropertyDescriptor[descriptorList.size()];
for (int i = 0; i < descriptors.length; i++)
descriptors[i] = (IPropertyDescriptor) descriptorList.get(i);
return descriptors;
}
private IPropertyDescriptor createTextPropertyDescriptor(CMAttributeDeclaration attrDecl) {
TextPropertyDescriptor descriptor = new TextPropertyDescriptor(attrDecl.getAttrName(), attrDecl.getAttrName());
descriptor.setCategory(getCategory(attrDecl));
descriptor.setDescription(attrDecl.getAttrName());
if (attrDecl.getUsage() != CMAttributeDeclaration.REQUIRED && fSetExpertFilter)
descriptor.setFilterFlags(new String[]{IPropertySheetEntry.FILTER_ID_EXPERT});
return descriptor;
}
protected String getCategory(CMAttributeDeclaration attrDecl) {
if (attrDecl != null) {
if (attrDecl.supports("category")) { //$NON-NLS-1$
return (String) attrDecl.getProperty("category"); //$NON-NLS-1$
}
if (fShouldDeriveCategories && attrDecl.getAttrType() != null && attrDecl.getAttrType().getNodeName() != null && attrDecl.getAttrType().getNodeName().length() > 0) {
return attrDecl.getAttrType().getDataTypeName();
}
}
return CATEGORY_ATTRIBUTES;
}
protected CMElementDeclaration getDeclaration() {
if (fNode == null || fNode.getNodeType() != Node.ELEMENT_NODE)
return null;
ModelQuery modelQuery = ModelQueryUtil.getModelQuery(fNode.getOwnerDocument());
if (modelQuery != null) {
return modelQuery.getCMElementDeclaration((Element) fNode);
}
return null;
}
private Display getDisplay() {
return PlatformUI.getWorkbench().getDisplay();
}
/**
* Returns a value for this Node that can be editted in a property sheet.
*
* @return a value that can be editted
*/
public Object getEditableValue() {
return null;
}
/**
* Returns the current collection of property descriptors.
*
* @return all valid descriptors.
*/
public IPropertyDescriptor[] getPropertyDescriptors() {
if (fDescriptors == null || fDescriptors.length == 0) {
fDescriptors = createPropertyDescriptors();
} else {
updatePropertyDescriptors();
}
return fDescriptors;
}
/**
* Returns the current value for the named property.
*
*/
public Object getPropertyValue(Object nameObject) {
String name = nameObject.toString();
String returnedValue = null;
NamedNodeMap attrMap = fNode.getAttributes();
if (attrMap != null) {
Node attribute = attrMap.getNamedItem(name);
if (attribute != null) {
if (attribute instanceof XMLNode)
returnedValue = ((XMLNode) attribute).getValueSource();
else
returnedValue = attribute.getNodeValue();
}
}
if (returnedValue == null)
returnedValue = ""; //$NON-NLS-1$
return returnedValue;
}
protected String[] getValidValues(CMAttributeDeclaration attrDecl) {
if (attrDecl == null)
return new String[0];
String[] validValues = null;
CMDataType attrType = attrDecl.getAttrType();
if (attrType != null) {
validValues = _getValidStrings(attrDecl, attrType);
if (fSortEnumeratedValues)
Arrays.sort(validValues);
}
if (validValues == null)
validValues = new String[0];
return validValues;
}
/**
* Allowing the INodeAdapter to compare itself against the type allows it
* to return true in more than one case.
*/
public boolean isAdapterForType(Object type) {
return type == IPropertySource.class;
}
protected boolean isFixedValue(CMAttributeDeclaration attrDecl) {
if (attrDecl == null)
return true;
CMDataType attrType = attrDecl.getAttrType();
if (attrType != null) {
return attrType.getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED || attrDecl.getUsage() == CMAttributeDeclaration.FIXED;
}
return false;
}
/**
* Returns whether the property value has changed from the default.
*
* @return <code>true</code> if the value of the specified property has
* changed from its original default value; <code>false</code>
* otherwise.
*/
public boolean isPropertySet(Object propertyObject) {
String property = propertyObject.toString();
NamedNodeMap attrMap = fNode.getAttributes();
if (attrMap != null)
return attrMap.getNamedItem(property) != null;
return false;
}
public void notifyChanged(INodeNotifier notifier, int eventType, java.lang.Object changedFeature, java.lang.Object oldValue, java.lang.Object newValue, int pos) {
}
/**
* Remove the given attribute from the Node
*
* @param propertyObject
*/
public void removeProperty(Object propertyObject) {
NamedNodeMap attrMap = fNode.getAttributes();
if (attrMap != null) {
Node attribute = attrMap.getNamedItem(propertyObject.toString());
if (attribute != null) {
try {
attrMap.removeNamedItem(propertyObject.toString());
} catch (DOMException e) {
if (e.code != DOMException.INVALID_MODIFICATION_ERR) {
Logger.logException(e);
}
}
}
}
}
/**
* Resets the specified property's value to its default value.
*
*/
public void resetPropertyValue(Object propertyObject) {
String property = propertyObject.toString();
CMNamedNodeMap attrDecls = null;
CMElementDeclaration ed = getDeclaration();
if (ed != null) {
attrDecls = ed.getAttributes();
}
NamedNodeMap attrMap = fNode.getAttributes();
if (attrDecls != null) {
CMAttributeDeclaration attrDecl = (CMAttributeDeclaration) attrDecls.getNamedItem(property);
String defValue = null;
if (attrDecl != null) {
if (attrDecl.getAttrType() != null) {
CMDataType helper = attrDecl.getAttrType();
if (helper.getImpliedValueKind() != CMDataType.IMPLIED_VALUE_NONE && helper.getImpliedValue() != null)
defValue = helper.getImpliedValue();
}
}
if (defValue != null && defValue.length() > 0) {
((Attr) attrMap.getNamedItem(property)).setValue(defValue);
} else {
attrMap.removeNamedItem(property);
}
} else {
attrMap.removeNamedItem(property);
}
}
/**
* Sets the named property to the given value.
*
*/
public void setPropertyValue(Object nameObject, Object value) {
// Avoid cycling - can happen if a closing cell editor causes a
// refresh
// on the PropertySheet page and the setInput again asks the editor to
// close; besides, why apply the same value twice?
if (!fValuesBeingSet.isEmpty() && fValuesBeingSet.peek() == nameObject)
return;
fValuesBeingSet.push(nameObject);
String name = nameObject.toString();
String valueString = null;
if (value != null)
valueString = value.toString();
NamedNodeMap attrMap = fNode.getAttributes();
try {
if (attrMap != null) {
Attr attr = (Attr) attrMap.getNamedItem(name);
if (attr != null) {
// EXISTING VALUE
// potential out of control loop if updating the value
// triggers a viewer update, forcing the
// active cell editor to save its value and causing the
// loop to continue
if (attr.getValue() == null || !attr.getValue().equals(valueString)) {
if (attr instanceof XMLNode)
((XMLNode) attr).setValueSource(valueString);
else
attr.setValue(valueString);
}
} else {
// NEW(?) value
if (value != null) { // never create an empty attribute
Attr newAttr = fNode.getOwnerDocument().createAttribute(name);
if (newAttr instanceof XMLNode)
((XMLNode) newAttr).setValueSource(valueString);
else
newAttr.setValue(valueString);
attrMap.setNamedItem(newAttr);
}
}
} else {
if (fNode instanceof Element) {
((Element) fNode).setAttribute(name, valueString);
}
}
} catch (DOMException e) {
Display d = getDisplay();
if (d != null)
d.beep();
}
fValuesBeingSet.pop();
}
protected void updatePropertyDescriptors() {
if (fDescriptors == null || fDescriptors.length == 0)
// Nothing to update
return;
// List of all names encountered in the tag and defined by the element
List declaredNames = new ArrayList();
// New descriptor list that will become fDescriptors after all
// processing is done
List descriptors = new ArrayList();
// Names of the descriptors in the above List
List descriptorNames = new ArrayList();
// Update any descriptors derived from the metainfo
CMElementDeclaration ed = getDeclaration();
CMNamedNodeMap attrMap = null;
if (ed != null) {
attrMap = ed.getAttributes();
}
// Update exiting descriptors; not added to the final list here
if (attrMap != null) {
// Update existing descriptor types based on metainfo
CMAttributeDeclaration attrDecl = null;
for (int i = 0; i < attrMap.getLength(); i++) {
attrDecl = (CMAttributeDeclaration) attrMap.item(i);
String attrName = attrDecl.getAttrName();
if (!declaredNames.contains(attrName)) {
declaredNames.add(attrName);
}
for (int j = 0; j < fDescriptors.length; j++) {
boolean sameName = (fCaseSensitive && fDescriptors[j].getId().equals(attrDecl.getNodeName())) || (!fCaseSensitive && attrDecl.getNodeName().equals(fDescriptors[j].getId().toString()));
if (sameName) {
String[] validValues = getValidValues(attrDecl);
// Update the descriptor for this
// CMAttributeDeclaration (only enumerated values get
// updated for now)
if (fDescriptors[j] instanceof EnumeratedStringPropertyDescriptor) {
((EnumeratedStringPropertyDescriptor) fDescriptors[j]).updateValues(validValues);
}
// Replace with better descriptor
else if (validValues != null && validValues.length > 0) {
fDescriptors[j] = createPropertyDescriptor(attrDecl);
}
}
}
}
} else {
// Update existing descriptors based on not having any metainfo
for (int j = 0; j < fDescriptors.length; j++) {
// Replace with basic descriptor
if (!(fDescriptors[j] instanceof TextPropertyDescriptor)) {
fDescriptors[j] = createDefaultPropertyDescriptor((String) fDescriptors[j].getId());
}
}
}
NamedNodeMap attributes = fNode.getAttributes();
// Remove descriptors for attributes that aren't present AND aren't
// known through metainfo,
// do this by only reusing existing descriptors for attributes that
// are present or declared
for (int i = 0; i < fDescriptors.length; i++) {
if (fDescriptors[i] != null) {
String descriptorName = fDescriptors[i].getId().toString();
if ((declaredNames.contains(descriptorName) || (attributes.getNamedItem(descriptorName) != null)) && !descriptorNames.contains(descriptorName)) {
descriptorNames.add(descriptorName);
descriptors.add(fDescriptors[i]);
}
}
}
// Add descriptors for declared attributes that don't already have one
if (attrMap != null) {
// Update existing descriptor types based on metainfo
CMAttributeDeclaration attrDecl = null;
for (int i = 0; i < attrMap.getLength(); i++) {
attrDecl = (CMAttributeDeclaration) attrMap.item(i);
String attrName = attrDecl.getAttrName();
if (fCaseSensitive) {
if (!descriptorNames.contains(attrName)) {
IPropertyDescriptor descriptor = createPropertyDescriptor(attrDecl);
if (descriptor != null) {
descriptorNames.add(attrName);
descriptors.add(descriptor);
}
}
} else {
boolean exists = false;
for (int j = 0; j < descriptorNames.size(); j++)
exists = (descriptorNames.get(j).toString().equalsIgnoreCase(attrName)) || exists;
if (!exists) {
descriptorNames.add(attrName);
IPropertyDescriptor descriptor = createPropertyDescriptor(attrDecl);
if (descriptor != null) {
descriptorNames.add(attrName);
descriptors.add(descriptor);
}
}
}
}
}
// Add descriptors for existing attributes that don't already have one
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
String attrName = attr.getName();
if (fCaseSensitive) {
if (!descriptorNames.contains(attrName)) {
descriptorNames.add(attrName);
descriptors.add(createDefaultPropertyDescriptor(attrName));
}
} else {
boolean exists = false;
for (int j = 0; j < descriptorNames.size(); j++)
exists = (descriptorNames.get(j).toString().equalsIgnoreCase(attrName)) || exists;
if (!exists) {
descriptorNames.add(attrName);
descriptors.add(createDefaultPropertyDescriptor(attrName));
}
}
}
}
// Update fDescriptors
IPropertyDescriptor[] newDescriptors = new IPropertyDescriptor[descriptors.size()];
for (int i = 0; i < newDescriptors.length; i++)
newDescriptors[i] = (IPropertyDescriptor) descriptors.get(i);
fDescriptors = newDescriptors;
}
}