/*******************************************************************************
* Copyright (c) 2010 SAP AG.
* 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:
* Emil Simeonov - initial API and implementation.
* Dimitar Donchev - initial API and implementation.
* Dimitar Tenev - initial API and implementation.
* Nevena Manova - initial API and implementation.
* Georgi Konstantinov - initial API and implementation.
* Stanislav Nichev - initial API and implementation.
*******************************************************************************/
package org.eclipse.wst.sse.sieditor.model.reconcile.adapters;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.eclipse.wst.wsdl.Operation;
import org.eclipse.wst.wsdl.Part;
import org.eclipse.wst.wsdl.WSDLPackage;
import org.eclipse.wst.wsdl.XSDSchemaExtensibilityElement;
import org.eclipse.wst.wsdl.util.WSDLConstants;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDFeature;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDPackage;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.impl.XSDImportImpl;
import org.eclipse.xsd.util.XSDConstants;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.eclipse.wst.sse.sieditor.model.reconcile.adapters.componentsource.IConcreteComponentSource;
import org.eclipse.wst.sse.sieditor.model.utils.EmfXsdUtils;
/**
* Subclass of the {@link AbstractModelReconcileAdapter}. This adapter is
* responsible for the fixing of problems caused by DOM element attributes
* changes.
*/
public class AttributesReconcileAdapter extends AbstractModelReconcileAdapter {
public AttributesReconcileAdapter(final IConcreteComponentSource concreteComponentSource) {
super(concreteComponentSource);
}
@Override
protected void processNotifyChanged(final INodeNotifier notifier, final int eventType, final Object changedFeature,
final Object oldValue, final Object newValue, final int pos) {
if (changedFeature instanceof Attr) {
processAttributeChangeFeature(notifier, (Attr) changedFeature, oldValue, newValue);
}
}
private void processAttributeChangeFeature(final INodeNotifier notifier, final Attr attr, final Object oldValue,
final Object newValue) {
processNameAttribute(notifier, attr.getLocalName());
processRefAttribute(notifier, attr.getLocalName(), newValue);
processSchemaLocationAttribute(notifier, attr.getLocalName(), newValue);
processAttributeStartingWithXMLNS(notifier, oldValue, newValue, attr);
processParameterOrderAttribute(notifier, attr.getLocalName());
processTargetNamespaceAttribute(notifier, attr.getName());
processNamespaceAttribute(notifier, attr.getName());
processElementAttribute(notifier, attr.getLocalName(), newValue);
processTypeAttribute(notifier, attr.getLocalName(), newValue);
processDefaultAttribute(notifier, attr.getLocalName(), newValue);
}
private void processNameAttribute(final INodeNotifier notifier, final String attributeName) {
if (XSDConstants.NAME_ATTRIBUTE.equals(attributeName)) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof XSDElementDeclaration) {
this.getModelReconcileRegistry().setNeedsReconciling(true);
}
}
}
private void processRefAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) {
if (XSDConstants.REF_ATTRIBUTE.equals(attributeName) && newValue != null) {
/*
* we are in the following scenario: we are setting "ref" to element
* attribute. if the element we are updating has "type" attribute,
* the EMF model does not set it to null. that way, the updated
* element has both "type" and "ref" attributes, which is invalid
*/
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof XSDConcreteComponent) {
final XSDConcreteComponent element = (XSDConcreteComponent) eObject;
if (element instanceof XSDElementDeclaration) {
((XSDElementDeclaration) element).setTypeDefinition(null);
} else if (element instanceof XSDAttributeDeclaration) {
((XSDAttributeDeclaration) element).setTypeDefinition(null);
}
}
}
}
private void processSchemaLocationAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) {
// **************************************************
// This method is mostly duplicated functionality from the
// com.sap.tc.esmp.tools.sieditor.model.reconcile.utils.XsdReconcileUtils.reconcileSchemaContentsInternal(XSDSchema,
// Map<String, String>, ObjectsForResolveContainer, Set<XSDSchema>, int,
// NamespaceResolverType)
// line : 116
// The main difference is that the reconciler recursively reaches a
// certain level in the tree of schema imports.
// ******************************
// if (XSDConstants.SCHEMALOCATION_ATTRIBUTE.equals(attributeName)) {
// final EObject eObject =
// concreteComponentSource.getConcreteComponentFor((Element) notifier);
//
// if (eObject instanceof XSDImportImpl) {
// final XSDSchema resolvedSchema = resolveImportedSchema(newValue,
// eObject);
// getModelReconcileRegistry().addChangedSchema(resolvedSchema);
// getModelReconcileRegistry().setNeedsReconciling(true);
// }
// }
}
// ******************************
// Obsolete code, as in the method before
// ******************************
// private XSDSchema resolveImportedSchema(final Object newValue, final
// EObject eObject) {
// XSDSchema schema = null;
//
// final XSDImportImpl xsdImport = (XSDImportImpl) eObject;
// if (xsdImport.getResolvedSchema() == null && newValue == null) {
// xsdImport.reset();
// xsdImport.importSchema();
// }
// schema = xsdImport.getResolvedSchema();
// return schema;
// }
private void processAttributeStartingWithXMLNS(final INodeNotifier notifier, final Object oldValue, final Object newValue,
final Attr attr) {
if (attr.getName().startsWith(EmfXsdUtils.XMLNS_PREFIX)) {
/*
* here we are updating the "xmlns" attribute. if that's the case,
* we are either updating the schema element of a XSD document, or a
* WSDL definition element, or a WSDL types schema
*/
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
XSDSchema schema = null;
if (eObject instanceof XSDSchema) {
schema = (XSDSchema) eObject;
} else if (eObject instanceof XSDSchemaExtensibilityElement) {
schema = ((XSDSchemaExtensibilityElement) eObject).getSchema();
}
if (schema != null) {
this.getModelReconcileRegistry().setNeedsReconciling(true);
this.getModelReconcileRegistry().addChangedSchema(schema);
fixSchemaForSchemaState(oldValue, newValue, attr, schema);
}
}
}
private void fixSchemaForSchemaState(final Object oldValue, final Object newValue, final Attr attr, final XSDSchema xsdSchema) {
final String prefix = getPrefix(attr);
if (newValue == null) {
/*
* The new value is null, so we need to remove the prefix/namespace
* from the EMF model map. In some cases (on undo/redo) the EMF
* didn't do that.
*
* We also need to check the number of times this attribute is
* appearing in the schema element's body, because we don't want to
* update the map if more than one attribute with the same name
* exists. This is causing an unwanted DOM update of all the
* attribute values with the same name, instead of just the
* currently edited one.
*
* Please note here that the currently edited attribute is already
* removed from DOM and that's why we check if the current count is
* zero
*/
if (countAttributeWithSameName(attr.getName(), xsdSchema.getElement()) == 0
&& xsdSchema.getQNamePrefixToNamespaceMap().containsKey(prefix)) {
xsdSchema.getQNamePrefixToNamespaceMap().remove(prefix);
}
/*
* notify for the change anyway, since we want to run the
* validation. the EMF model didn't do that as well
*/
xsdSchema.eNotify(new ENotificationImpl((InternalEObject) xsdSchema, Notification.REMOVE, null, null, null));
} else {
/*
* we have non-null value, so we must be updating existing
* namespace, or adding a new one
*/
final String newNamespaceValue = attr.getValue();
final String oldNamespaceValue = xsdSchema.getQNamePrefixToNamespaceMap().get(prefix);
// we should not be updating the map when there are more than one
// attribute with same name in the element
if (countAttributeWithSameName(attr.getName(), xsdSchema.getElement()) <= 1
&& !newNamespaceAndOldNamespaceAreTheSame(newNamespaceValue, oldNamespaceValue)) {
xsdSchema.getQNamePrefixToNamespaceMap().put(prefix, newNamespaceValue);
}
/*
* notify for the change anyway, since we want to run the
* validation. the EMF model didn't do that as well
*/
xsdSchema.eNotify(new ENotificationImpl((InternalEObject) xsdSchema, Notification.ADD, null, null, null));
}
}
/**
* @return the number of times the given attribute name is appearing in the
* element attributes collection.
*/
private int countAttributeWithSameName(final String attributeName, final Element element) {
final NamedNodeMap attributes = element.getAttributes();
final int attributesSize = attributes.getLength();
int count = 0;
for (int i = 0; i < attributesSize; i++) {
final Node item = attributes.item(i);
if (attributeName.equals(item.getNodeName())) {
count++;
}
}
return count;
}
private boolean newNamespaceAndOldNamespaceAreTheSame(final String newNamespaceValue, final String oldNamespaceValue) {
if (newNamespaceValue == null && oldNamespaceValue == null) {
return true;
}
if (oldNamespaceValue == null && newNamespaceValue.isEmpty()) {
// generally, "null" namespace and empty one will be treated the
// same
return true;
}
// XSDConstants.isSchemaForSchemaNamespace(attr.getValue()) &&
return newNamespaceValue.equals(oldNamespaceValue);
}
private void processParameterOrderAttribute(final INodeNotifier notifier, final String attributeName) {
if (WSDLConstants.PARAMETER_ORDER_ATTRIBUTE.equals(attributeName)) {
/* we need to check if we are updating a part name */
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof Operation) {
/*
* we need to tell the operation EMF object to update itself
* from its corresponding DOM element. this updates the
* parameterOrdering collection of the operation
*/
((Operation) eObject).elementChanged((Element) notifier);
}
}
}
private void processTargetNamespaceAttribute(final INodeNotifier notifier, final String attributeName) {
if (XSDConstants.TARGETNAMESPACE_ATTRIBUTE.equals(attributeName)) {
/*
* we need to prepare the reconciling information. setting
* tagetNamespace "breaks" the EMF WSDL references
*/
this.getModelReconcileRegistry().setNeedsReconciling(true);
if (notifier instanceof Element) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof XSDSchema) {
this.getModelReconcileRegistry().addChangedSchema((XSDSchema) eObject);
} else if (eObject instanceof XSDSchemaExtensibilityElement) {
this.getModelReconcileRegistry().addChangedSchema(((XSDSchemaExtensibilityElement) eObject).getSchema());
}
}
}
}
private void processNamespaceAttribute(final INodeNotifier notifier, final String attributeName) {
if (WSDLConstants.NAMESPACE_ATTRIBUTE.equals(attributeName)) {
this.getModelReconcileRegistry().setNeedsReconciling(true);
/*
* we are updating schema import. this breaks the schema references
* to the changed import. we need to update the information for
* resolve.
*/
if (notifier instanceof Element) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof XSDImport) {
this.getModelReconcileRegistry().addChangedSchema(((XSDImport) eObject).getSchema());
}
}
}
}
private void processElementAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) {
if (WSDLConstants.ELEMENT_ATTRIBUTE.equals(attributeName)) {
processPartAttribute(notifier, newValue, WSDLPackage.PART__ELEMENT_DECLARATION);
}
}
private void processTypeAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) {
if (WSDLConstants.TYPE_ATTRIBUTE.equals(attributeName)) {
processPartAttribute(notifier, newValue, WSDLPackage.PART__TYPE_DEFINITION);
processElementTypeAttribute(notifier, newValue);
}
}
private void processPartAttribute(final INodeNotifier notifier, final Object newValue, final int eStructuralFeatureId) {
if (notifier instanceof Element) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof Part) {
this.getModelReconcileRegistry().setNeedsReconciling(true);
final Part part = (Part) eObject;
if (newValue == null) {
part
.eNotify(new ENotificationImpl((InternalEObject) part, Notification.SET, eStructuralFeatureId, null,
null));
}
part.elementChanged(part.getElement());
}
}
}
private void processElementTypeAttribute(final INodeNotifier notifier, final Object newValue) {
if (notifier instanceof Element && newValue != null) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
if (eObject instanceof XSDElementDeclaration) {
final XSDElementDeclaration elementDeclaration = (XSDElementDeclaration) eObject;
if (elementDeclaration.getTypeDefinition() != null && elementDeclaration.getTypeDefinition().eContainer() == null) {
getModelReconcileRegistry().setNeedsReconciling(true);
getModelReconcileRegistry().addChangedSchema(elementDeclaration.getSchema());
}
}
}
}
private void processDefaultAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) {
if (XSDConstants.DEFAULT_ATTRIBUTE.equals(attributeName)) {
if (notifier instanceof Element) {
final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier);
((XSDFeature)eObject).setLexicalValue((String)newValue);
eObject.eNotify(new ENotificationImpl((InternalEObject) eObject, Notification.SET, XSDPackage.XSD_FEATURE__VALUE, null,
newValue));
}
}
}
}