/******************************************************************************* * 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. *******************************************************************************/ package org.eclipse.wst.sse.sieditor.model.reconcile.adapters; import java.util.ArrayList; import java.util.List; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; import org.eclipse.xsd.XSDAnnotation; import org.eclipse.xsd.XSDAttributeDeclaration; import org.eclipse.xsd.XSDAttributeGroupContent; import org.eclipse.xsd.XSDAttributeGroupDefinition; import org.eclipse.xsd.XSDAttributeUse; import org.eclipse.xsd.XSDComplexTypeDefinition; import org.eclipse.xsd.XSDConcreteComponent; import org.eclipse.xsd.XSDFactory; import org.eclipse.xsd.impl.XSDAttributeDeclarationImpl; import org.eclipse.xsd.util.XSDConstants; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.eclipse.wst.sse.sieditor.model.reconcile.adapters.componentsource.IConcreteComponentSource; /** * Subclass of the {@link AbstractModelReconcileAdapter}. This adapter is * responsible for the fixing of problems caused by element annotations changes. * */ public class AnnotationsReconcileAdapter extends AbstractModelReconcileAdapter { public AnnotationsReconcileAdapter(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 != null && !(changedFeature instanceof Element)) { return; } if (notifier instanceof Element && oldValue instanceof Element && INodeNotifier.REMOVE == eventType) { fixAttributeAnnotationRemove((Element) notifier, (Element) changedFeature, oldValue, newValue); } else if (notifier instanceof Element && newValue instanceof Element && INodeNotifier.ADD == eventType) { fixAttributeAnnotationAdd((Element) notifier, changedFeature, oldValue, (Element) newValue); } } private void fixAttributeAnnotationRemove(final Element parentElement, final Element removedElement, final Object oldValue, final Object newValue) { // the changed feature should be the old (removed) value if (removedElement == null ? oldValue != null : !removedElement.equals(oldValue)) { return; } // if not from this NS if (!isPropperNamespace(removedElement, parentElement)) { return; } final String removedElementName = removedElement.getLocalName(); final String parentElementName = parentElement.getLocalName(); if (XSDConstants.ANNOTATION_ELEMENT_TAG.equals(removedElementName) && (XSDConstants.ATTRIBUTE_ELEMENT_TAG.equals(parentElementName))) { final EObject parentEObject = concreteComponentSource.getConcreteComponentFor(parentElement); if (parentEObject instanceof XSDAttributeDeclaration) { ((XSDAttributeDeclarationImpl) parentEObject).basicSetAnnotation(null, null); } } // the case of adding/removing annotation from attribute of an extension // of a simpleContent else if (XSDConstants.EXTENSION_ELEMENT_TAG.equals(parentElementName) && XSDConstants.ATTRIBUTE_ELEMENT_TAG.equals(removedElementName)) { processRareRemoveAttributeAnnotation(removedElement, parentElement); } } private void fixAttributeAnnotationAdd(final Element parentElement, final Object changedFeature, final Object oldValue, final Element addedElement) { if (changedFeature == null ? oldValue != null : !changedFeature.equals(oldValue)) { return; } final boolean propoerNamespace = isPropperNamespace(addedElement, parentElement); final String addedElementName = addedElement.getLocalName(); final String parentElementName = parentElement.getLocalName(); // the case where an annotation is added to an attribute of an extension // element. if (propoerNamespace && XSDConstants.EXTENSION_ELEMENT_TAG.equals(parentElementName) && XSDConstants.ATTRIBUTE_ELEMENT_TAG.equals(addedElementName)) { processRareAddAttributeAnnotation(parentElement, addedElement); } } /** * Add Annotation to : <xsd:complexType name="Amount"><br> * <xsd:simpleContent><br> * <xsd:extension base="AmountContent"><br> * <xsd:attribute name="currencyCode" type="xsd:token"><br> * <b><xsd:annotation>... </xsd:annotation></b><br> * </xsd:attribute><br> * </xsd:extension><br> * </xsd:simpleContent><br> * </xsd:complexType> * * @param removedElement * the attribute if removed * @param parentElement * the Extension element * @param newValue * the newly set attribute * @param eventType * {@link INodeNotifier#ADD} or {@link INodeNotifier#REMOVE} */ private void processRareAddAttributeAnnotation(final Element parentElement, final Element addedElement) { final List<XSDAttributeDeclaration> eAttributes = findAttributeInExtension(addedElement, parentElement); if (eAttributes.isEmpty()) { return; } // for all attributes with matching name of the added for (final XSDAttributeDeclaration eAttribute : eAttributes) { if (eAttribute.getAnnotation() != null || (eAttribute.getName() != null && !eAttribute.getName().equals( addedElement.getAttribute(XSDConstants.NAME_ATTRIBUTE)))) { continue; } // Apply fix only for the first annotation element found in the // attribute. // all other attributes will be ignored as invalid! final Element annotationElement = getAnnotationElement(addedElement, parentElement); if (annotationElement != null) { final XSDAnnotation eAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation(); eAnnotation.setElement(annotationElement); eAnnotation.elementChanged(eAnnotation.getElement()); eAttribute.setAnnotation(eAnnotation); } } } /** * Remove Annotation from : <xsd:complexType name="Amount"><br> * <xsd:simpleContent><br> * <xsd:extension base="AmountContent"><br> * <xsd:attribute name="currencyCode" type="xsd:token"><br> * <xsd:annotation>... </xsd:annotation><br> * </xsd:attribute><br> * </xsd:extension><br> * </xsd:simpleContent><br> * </xsd:complexType> * * @param removedElement * the attribute if removed * @param parentElement * the Extension element * @param newValue * the newly set attribute * @param eventType * {@link INodeNotifier#ADD} or {@link INodeNotifier#REMOVE} */ private void processRareRemoveAttributeAnnotation(final Element removedElement, final Element parentElement) { final List<XSDAttributeDeclaration> eAttributes = findAttributeInExtension(removedElement, parentElement); for (final XSDAttributeDeclaration eAttribute : eAttributes) { if (eAttribute == null) { return; } if (getAnnotationElement(removedElement, null) == null) { ((XSDAttributeDeclarationImpl) eAttribute).basicSetAnnotation(null, null); } } } /** * Finds the given eObject for the manipulatedAttribute which is * added/removed from the element of the xsd:extension. * * @param manipulatedAttribute * the attribute * @param extensionElement * the extension element * @return the found eObject for the given attribute or null if no such is * found. */ private List<XSDAttributeDeclaration> findAttributeInExtension(final Element manipulatedAttribute, final Element extensionElement) { final List<XSDAttributeDeclaration> attributes = new ArrayList<XSDAttributeDeclaration>(); final Node simpleContentNode = extensionElement.getParentNode(); final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) simpleContentNode.getParentNode()); if (!(eObject instanceof XSDComplexTypeDefinition)) { return null;// this is not the case } final XSDConcreteComponent complexType = (XSDConcreteComponent) eObject; final XSDComplexTypeDefinition eComplexType = (XSDComplexTypeDefinition) complexType; final EList<XSDAttributeGroupContent> eAttributes = eComplexType.getAttributeContents(); final XSDAttributeDeclaration eAttribute = null; for (final XSDAttributeGroupContent eAttGroupContent : eAttributes) { if (eAttGroupContent instanceof XSDAttributeUse) { attributes.add(checkAttributeUse(manipulatedAttribute, (XSDAttributeUse) eAttGroupContent)); } else if (eAttGroupContent instanceof XSDAttributeGroupDefinition) { attributes.addAll(findAttribute(manipulatedAttribute, (XSDAttributeGroupDefinition) eAttGroupContent)); } if (eAttribute != null) { attributes.add(eAttribute); } } return attributes; } private XSDAttributeDeclaration checkAttributeUse(final Element attrElement, final XSDAttributeUse attributeUse) { final XSDAttributeDeclaration currentAttribute = attributeUse.getAttributeDeclaration(); final Element element = currentAttribute.getElement(); if (element != null && element.getLocalName() != null && element.getLocalName().equals(attrElement.getLocalName())) { return currentAttribute; } return null; } private List<XSDAttributeDeclaration> findAttribute(final Element attrElement, final XSDAttributeGroupDefinition eAttGroupContent) { final List<XSDAttributeDeclaration> attributes = new ArrayList<XSDAttributeDeclaration>(); for (final XSDAttributeUse use : eAttGroupContent.getAttributeUses()) { final XSDAttributeDeclaration attribute = checkAttributeUse(attrElement, use); if (attribute != null) { attributes.add(attribute); } } return attributes; } /** * @param element * the element containing the annotation * @param parentElement * the parent of the element, containing the annotation. May be * null if not extension * @return the annotation element or null if no such is found */ private Element getAnnotationElement(final Element element, final Element parentElement) { Node child = element.getFirstChild(); if (child == null) { return null; } do { if (XSDConstants.ANNOTATION_ELEMENT_TAG.equals(child.getLocalName())) { return (Element) child; } } while ((child = child.getNextSibling()) != null); return null; } /** * Checks if the elements are from the w3 schema for schema NS * * @param element * @param parentElement * @return */ private boolean isPropperNamespace(final Node element, final Node parentElement) { final String removedElementNamespace = element.getNamespaceURI(); boolean propoerNamespace = removedElementNamespace == null || XSDConstants.isSchemaForSchemaNamespace(removedElementNamespace); final String parentNamespace = parentElement.getNamespaceURI(); // on undo/redo the element does not contain data about it's // namespace - this case should not be excluded propoerNamespace = propoerNamespace && (parentNamespace == null || XSDConstants.isSchemaForSchemaNamespace(parentNamespace)); return propoerNamespace; } }