| /** |
| * <copyright> |
| * |
| * Copyright (c) 2010 Eclipse Modeling Project 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: |
| * E.D.Willink - Initial API and implementation |
| * |
| * </copyright> |
| * |
| * $Id: OCLInEcoreSynchronizer.java,v 1.4 2010/03/22 01:23:49 ewillink Exp $ |
| */ |
| package org.eclipse.ocl.examples.editor.ocl.ui.ecore; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import lpg.runtime.IToken; |
| |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.EMap; |
| import org.eclipse.emf.ecore.EAnnotation; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EModelElement; |
| import org.eclipse.emf.ecore.ENamedElement; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EParameter; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.ETypedElement; |
| import org.eclipse.emf.ecore.EcoreFactory; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.ocl.cst.CSTNode; |
| import org.eclipse.ocl.ecore.CallOperationAction; |
| import org.eclipse.ocl.ecore.Constraint; |
| import org.eclipse.ocl.ecore.EcoreEnvironmentFactory; |
| import org.eclipse.ocl.ecore.ExpressionInOCL; |
| import org.eclipse.ocl.ecore.SendSignalAction; |
| import org.eclipse.ocl.examples.common.utils.ClassUtils; |
| import org.eclipse.ocl.examples.common.utils.EcoreUtils; |
| import org.eclipse.ocl.examples.common.utils.StringUtils; |
| import org.eclipse.ocl.examples.common.utils.TracingAdapter; |
| import org.eclipse.ocl.lpg.BasicEnvironment; |
| import org.eclipse.ocl.lpg.BasicEnvironment2; |
| import org.eclipse.ocl.utilities.UMLReflection; |
| |
| public abstract class OCLInEcoreSynchronizer extends TracingAdapter |
| { |
| public static String getSourceText(BasicEnvironment environment, Object astNode) { |
| CSTNode astMapping = environment.getASTMapping(astNode); |
| IToken startToken = astMapping.getStartToken(); |
| IToken prevToken = startToken.getIPrsStream().getIToken(startToken.getTokenIndex()-1); |
| IToken endToken = astMapping.getEndToken(); |
| IToken[] followingAdjuncts = endToken.getFollowingAdjuncts(); |
| if ((followingAdjuncts != null) && (followingAdjuncts.length > 0)) { |
| endToken = followingAdjuncts[followingAdjuncts.length-1]; |
| } |
| return startToken.getILexStream().toString(prevToken.getEndOffset()+1, endToken.getEndOffset()); |
| } |
| |
| private static final String ECORE_URI = org.eclipse.emf.ecore.EcorePackage.eNS_URI; |
| private static final String OCL_URI = ECORE_URI + "/OCL"; |
| private static final String CONSTRAINTS_KEY = "constraints"; |
| private static final String BODY_KEY = "body"; |
| private static final String DERIVATION_KEY = "derivation"; |
| private static final String INITIAL_KEY = "initial"; |
| private static final String POSTCONDITION_KEY = "postcondition"; |
| private static final String PRECONDITION_KEY = "precondition"; |
| |
| private static final UMLReflection<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> umlReflection = |
| EcoreEnvironmentFactory.INSTANCE.createEnvironment().getUMLReflection(); |
| |
| public static class Detail |
| { |
| protected final EAnnotation annotation; |
| protected final String key; |
| |
| public Detail(EAnnotation annotation, String key) { |
| this.annotation = annotation; |
| this.key = key; |
| } |
| |
| public EAnnotation getAnnotation() { |
| return annotation; |
| } |
| |
| public EModelElement getContainer() { |
| return (EModelElement) annotation.eContainer(); |
| } |
| |
| public String getKey() { |
| return key; |
| } |
| |
| @Override |
| public String toString() { |
| return key + " => "; |
| } |
| } |
| |
| public static class Exporter |
| { |
| protected void addConstraintName(EModelElement constrainedElement, String constraintName) { |
| Set<String> constraintsNameSet = getConstraintNames(constrainedElement); |
| constraintsNameSet.add(constraintName); |
| setConstraintNames(constrainedElement, constraintsNameSet); |
| } |
| |
| public void exportConstraint(BasicEnvironment2 environment, EModelElement constrainedElement, Constraint constraint) { |
| EList<EModelElement> constrainedElements = constraint.getConstrainedElements(); |
| if (constrainedElements.isEmpty()) { |
| return; |
| } |
| // EModelElement constrainedElement = constrainedElements.get(0); |
| ExpressionInOCL specification = (ExpressionInOCL) constraint.getSpecification(); |
| if (specification == null) { |
| return; |
| } |
| org.eclipse.ocl.expressions.OCLExpression<?> bodyExpression = specification.getBodyExpression(); |
| if (bodyExpression == null) { |
| return; |
| } |
| String text = getSourceText(environment, bodyExpression); |
| EOperation ecoreInvariant; |
| if ((constrainedElement instanceof EClass) |
| && ((ecoreInvariant = EcoreUtils.getEcoreInvariant((EClass)constrainedElement, constraint.getName())) != null)) { |
| setEAnnotation(environment, constraint, ecoreInvariant, OCL_URI, BODY_KEY, text); |
| } |
| else if (constrainedElement instanceof EClassifier) { |
| addConstraintName(constrainedElement, constraint.getName()); |
| setEAnnotation(environment, constraint, constrainedElement, OCL_URI, constraint.getName(), text); |
| } |
| else if (constrainedElement instanceof EOperation) { |
| setEAnnotation(environment, constraint, constrainedElement, OCL_URI, BODY_KEY, text); |
| } |
| else if (constrainedElement instanceof EStructuralFeature) { |
| setEAnnotation(environment, constraint, constrainedElement, OCL_URI, DERIVATION_KEY, text); |
| } |
| } |
| |
| protected Set<String> getConstraintNames(EModelElement constrainedElement) { |
| EAnnotation ecoreAnnotation = getEAnnotation(constrainedElement, ECORE_URI); |
| String oldConstraintsDetails = ecoreAnnotation.getDetails().get(CONSTRAINTS_KEY); |
| Set<String> constraintsNameSet = new HashSet<String>(); |
| if (oldConstraintsDetails != null) { |
| String[] constraintsNameArray = oldConstraintsDetails.split(" "); //$NON-NLS-1$ |
| for (String constraintsName : constraintsNameArray) { |
| constraintsNameSet.add(constraintsName); |
| } |
| } |
| return constraintsNameSet; |
| } |
| |
| protected EAnnotation getEAnnotation(EModelElement element, String source) { |
| EAnnotation ecoreAnnotation = element.getEAnnotation(source); |
| if (ecoreAnnotation == null) { |
| ecoreAnnotation = EcoreFactory.eINSTANCE.createEAnnotation(); |
| ecoreAnnotation.setSource(source); |
| element.getEAnnotations().add(ecoreAnnotation); |
| } |
| return ecoreAnnotation; |
| } |
| |
| protected void removeConstraintName(EModelElement constrainedElement, String constraintName) { |
| Set<String> constraintsNameSet = getConstraintNames(constrainedElement); |
| constraintsNameSet.remove(constraintName); |
| setConstraintNames(constrainedElement, constraintsNameSet); |
| } |
| |
| protected void setEAnnotation(BasicEnvironment2 environment, Constraint constraint, EModelElement constrainedElement, String source, String key, String newValue) { |
| EAnnotation eAnnotation = getEAnnotation(constrainedElement, source); |
| EMap<String, String> details = eAnnotation.getDetails(); |
| String oldValue = details.get(key); |
| if (!ClassUtils.equals(oldValue, newValue)) { |
| details.put(key, newValue); |
| } |
| int indexOfKey = details.indexOfKey(key); |
| if (indexOfKey >= 0) { |
| Entry<String, String> entry = details.get(indexOfKey); |
| CSTNode cstConstraint = environment.getASTMapping(constraint); |
| environment.initASTMapping(entry, cstConstraint, null); |
| } |
| } |
| |
| protected void setConstraintNames(EModelElement constrainedElement, Set<String> constraintsNameSet) { |
| EAnnotation ecoreAnnotation = getEAnnotation(constrainedElement, ECORE_URI); |
| String oldConstraintsDetails = ecoreAnnotation.getDetails().get(CONSTRAINTS_KEY); |
| List<String> constraintNameList = new ArrayList<String>(constraintsNameSet); |
| Collections.sort(constraintNameList); |
| String newConstraintDetails = StringUtils.splice(constraintNameList, " "); //$NON-NLS-1$ |
| if (!ClassUtils.equals(oldConstraintsDetails, newConstraintDetails)) { |
| ecoreAnnotation.getDetails().put(CONSTRAINTS_KEY, newConstraintDetails); |
| } |
| } |
| } |
| |
| /** |
| * Importer creates a textual OCL document from the embedded OCL in a collection of resources. |
| * <p> |
| * The text form is available with getText() |
| * <p> |
| * A contents mapping for use during synchronization with getContents(). |
| */ |
| public static class Importer |
| { |
| private StringBuffer s = new StringBuffer(); |
| |
| /** |
| * The contents identifies every detail of every model element that contributed OCL content that needs |
| * synchronization on an export back to the ResourceSet. |
| */ |
| private Map<EObject, List<Detail>> contents = new HashMap<EObject, List<Detail>>(); |
| |
| public Importer(Collection<? extends Resource> resources) { |
| for (Resource resource : resources) { |
| importEObjectList(resource.getContents()); |
| } |
| } |
| |
| protected void appendContent(EObject eContainer, EAnnotation eAnnotation, String detailKey) { |
| List<Detail> details = contents.get(eContainer); |
| if (details == null) { |
| details = new ArrayList<Detail>(); |
| contents.put(eContainer, details); |
| } |
| details.add(new Detail(eAnnotation, detailKey)); |
| } |
| |
| protected void appendName(ENamedElement eNamedElement) { |
| appendText(eNamedElement.getName()); |
| } |
| |
| protected void appendQualifiedName(EOperation eOperation) { |
| appendName(eOperation.getEContainingClass()); |
| appendText("::"); |
| appendName(eOperation); |
| } |
| |
| protected void appendQualifiedName(EStructuralFeature eStructuralFeature) { |
| appendName(eStructuralFeature.getEContainingClass()); |
| appendText("::"); |
| appendName(eStructuralFeature); |
| } |
| |
| protected void appendText(String text) { |
| s.append(text); |
| } |
| |
| protected void appendTypeName(ETypedElement eTypedElement) { |
| Object oclType = umlReflection.getOCLType(eTypedElement); |
| appendName((ENamedElement) oclType); |
| } |
| |
| public Map<EObject, List<Detail>> getContents() { |
| return contents; |
| } |
| |
| public String getText() { |
| return s.toString(); |
| } |
| |
| protected void importEClassifier(EClassifier eClassifier) { |
| String constraintsString = EcoreUtil.getAnnotation(eClassifier, ECORE_URI, CONSTRAINTS_KEY); |
| if (constraintsString != null) { |
| EAnnotation eAnnotation = eClassifier.getEAnnotation(OCL_URI); |
| if (eAnnotation == null) { |
| return; |
| } |
| EMap<String, String> details = eAnnotation.getDetails(); |
| for (String constraintName : constraintsString.split(" ")) { |
| String invariant = details.get(constraintName); |
| if (invariant != null) { |
| appendContent(eClassifier, eAnnotation, constraintName); |
| appendText("\ncontext "); |
| appendName(eClassifier); |
| appendText("\ninv "); |
| appendText(constraintName); |
| appendText(":"); |
| appendText(invariant); |
| appendText("\n"); |
| } |
| } |
| } |
| if (eClassifier instanceof EClass) { |
| for (EOperation eOperation : ((EClass)eClassifier).getEOperations()) { |
| if (EcoreUtil.isInvariant(eOperation)) { |
| EAnnotation eAnnotation = eOperation.getEAnnotation(OCL_URI); |
| if (eAnnotation != null) { |
| EMap<String, String> details = eAnnotation.getDetails(); |
| String invariant = details.get(BODY_KEY); |
| if (invariant != null) { |
| appendContent(eClassifier, eAnnotation, eOperation.getName()); |
| appendText("\ncontext "); |
| appendName(eClassifier); |
| appendText("\ninv "); |
| appendName(eOperation); |
| appendText(":"); |
| appendText(invariant); |
| appendText("\n"); |
| } |
| } |
| } |
| } |
| for (EStructuralFeature eStructuralFeature : ((EClass)eClassifier).getEStructuralFeatures()) { |
| importEStructuralFeature(eStructuralFeature); |
| } |
| for (EOperation eOperation : ((EClass)eClassifier).getEOperations()) { |
| if (!EcoreUtil.isInvariant(eOperation)) { |
| importEOperation(eOperation); |
| } |
| } |
| } |
| } |
| |
| protected void importEObjectList(EList<? extends EObject> contents) { |
| for (EObject eObject : contents) { |
| if (eObject instanceof EPackage) { |
| EPackage ePackage = (EPackage)eObject; |
| importEPackage(ePackage); |
| EList<EPackage> eSubpackages = ePackage.getESubpackages(); |
| if (!eSubpackages.isEmpty()) { |
| appendText("\n"); |
| } |
| importEObjectList(eSubpackages); |
| } |
| } |
| } |
| |
| protected void importEOperation(EOperation eOperation) { |
| EAnnotation eAnnotation = eOperation.getEAnnotation(OCL_URI); |
| if (eAnnotation == null) { |
| return; |
| } |
| EMap<String, String> details = eAnnotation.getDetails(); |
| String pre = details.get(PRECONDITION_KEY); |
| String body = details.get(BODY_KEY); |
| String post = details.get(POSTCONDITION_KEY); |
| if ((pre != null) || (body != null) || (post != null)) { |
| appendText("\ncontext "); |
| appendQualifiedName(eOperation); |
| appendText("("); |
| String prefix = ""; |
| for (EParameter eParameter : eOperation.getEParameters()) { |
| appendText(prefix); |
| appendName(eParameter); |
| appendText(" : "); |
| appendTypeName(eParameter); |
| prefix = ", "; |
| } |
| appendText(") : "); |
| appendTypeName(eOperation); |
| appendText("\n"); |
| } |
| if (pre != null) { |
| appendContent(eOperation, eAnnotation, PRECONDITION_KEY); |
| appendText("pre:"); |
| appendText(pre); |
| appendText("\n"); |
| } |
| if (body != null) { |
| appendContent(eOperation, eAnnotation, BODY_KEY); |
| appendText("body:"); |
| appendText(body); |
| appendText("\n"); |
| } |
| if (post != null) { |
| appendContent(eOperation, eAnnotation, POSTCONDITION_KEY); |
| appendText("post:"); |
| appendText(post); |
| appendText("\n"); |
| } |
| } |
| |
| protected void importEPackage(EPackage ePackage) { |
| List<String> delegates = new ArrayList<String>(); |
| delegates.addAll(EcoreUtil.getInvocationDelegates(ePackage)); |
| delegates.addAll(EcoreUtil.getSettingDelegates(ePackage)); |
| delegates.addAll(EcoreUtil.getValidationDelegates(ePackage)); |
| if (!delegates.contains(OCL_URI)) { |
| return; |
| } |
| appendText("package "); |
| appendName(ePackage); |
| ResourceSet resourceSet = ePackage.eResource().getResourceSet(); |
| resourceSet.getPackageRegistry().put(ePackage.getNsURI(), ePackage); |
| appendText("\n"); |
| for (EClassifier eClassifier : ePackage.getEClassifiers()) { |
| importEClassifier(eClassifier); |
| } |
| appendText("\nendpackage\n"); |
| } |
| |
| protected void importEStructuralFeature(EStructuralFeature eStructuralFeature) { |
| EAnnotation eAnnotation = eStructuralFeature.getEAnnotation(OCL_URI); |
| if (eAnnotation == null) { |
| return; |
| } |
| EMap<String, String> details = eAnnotation.getDetails(); |
| String derivation = details.get(DERIVATION_KEY); |
| String initial = details.get(INITIAL_KEY); |
| if ((derivation != null) || (initial != null)) { |
| appendText("\ncontext "); |
| appendQualifiedName(eStructuralFeature); |
| appendText(" : "); |
| appendTypeName(eStructuralFeature); |
| appendText("\n"); |
| } |
| if (initial != null) { |
| appendContent(eStructuralFeature, eAnnotation, INITIAL_KEY); |
| appendText("init:"); |
| appendText(initial); |
| appendText("\n"); |
| } |
| if (derivation != null) { |
| appendContent(eStructuralFeature, eAnnotation, DERIVATION_KEY); |
| appendText("derive:"); |
| appendText(derivation); |
| appendText("\n"); |
| } |
| } |
| } |
| |
| public class ResourceWatcher extends TracingAdapter |
| { |
| @Override |
| protected void handleNotification(Notification notification) { |
| int featureID = notification.getFeatureID(Resource.class); |
| if (featureID == Resource.RESOURCE__IS_MODIFIED) { |
| if (notification.getEventType() == Notification.SET) { |
| importFromResources(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isAdapterForType(Object type) { |
| return type == ResourceWatcher.class; |
| } |
| } |
| |
| public static OCLInEcoreSynchronizer getAdapter(ResourceSet resourceSet) { |
| return EcoreUtils.getAdapter(resourceSet.eAdapters(), OCLInEcoreSynchronizer.class); |
| } |
| |
| private Exporter exporter = null; // Non-null while export in progress |
| private Importer importer = null; // Non-null while import in progress |
| /** |
| * The contents identifies every detail of every model element that contributed OCL content that needs |
| * synchronization on an export back to the ResourceSet. |
| */ |
| private Map<EObject, List<Detail>> contents = new HashMap<EObject, List<Detail>>(); |
| |
| protected void addWatcher(Object object) { |
| if (object instanceof Resource) { |
| Resource resource = (Resource)object; |
| resource.setTrackingModification(true); |
| ResourceWatcher watcher = EcoreUtils.getAdapter(resource, ResourceWatcher.class); |
| if (watcher == null) { |
| resource.eAdapters().add(new ResourceWatcher()); |
| } |
| } |
| } |
| |
| public void exportConstraintsToResource(BasicEnvironment2 environment, List<Constraint> constraints) { |
| try { |
| exporter = new Exporter(); |
| Set<Detail> residualDetails = new HashSet<Detail>(); |
| for (List<Detail> details : contents.values()) { |
| residualDetails.addAll(details); |
| } |
| // |
| // First export the constraints that align with imported details. |
| // |
| List<Constraint> residualConstraints = new ArrayList<Constraint>(); |
| for (Constraint constraint : constraints) { |
| EList<EModelElement> constrainedElements = constraint.getConstrainedElements(); |
| if (!constrainedElements.isEmpty()) { |
| EModelElement constrainedElement = constrainedElements.get(0); |
| String key = constrainedElement instanceof EClassifier ? constraint.getName() : constraint.getStereotype(); |
| List<Detail> details = contents.get(constrainedElement); |
| if (details != null) { |
| for (Detail detail : details){ |
| if (key.equals(detail.getKey())) { |
| residualDetails.remove(detail); |
| exporter.exportConstraint(environment, detail.getContainer(), constraint); |
| constrainedElement = null; |
| break; |
| } |
| } |
| } |
| if (constrainedElement != null) { |
| residualConstraints.add(constraint); |
| } |
| } |
| } |
| // |
| // Now export the residual constraints by aligning the unmatched details in |
| // sequential order element-wise. |
| // |
| for (Constraint constraint : residualConstraints) { |
| EList<EModelElement> constrainedElements = constraint.getConstrainedElements(); |
| EModelElement constrainedElement = constrainedElements.get(0); |
| List<Detail> details = contents.get(constrainedElement); |
| if (details != null) { |
| for (Detail detail : details) { |
| // if (!residualDetails.contains(detail)) { |
| // residualDetails.remove(detail); |
| // break; |
| // } |
| exporter.exportConstraint(environment, detail.getContainer(), constraint); |
| } |
| } |
| } |
| // |
| // Finally delete the obsolete details |
| // |
| for (Detail detail : residualDetails) { |
| EAnnotation eAnnotation = detail.getAnnotation(); |
| EObject eObject = eAnnotation.eContainer(); |
| if (eObject instanceof EClassifier) { |
| exporter.removeConstraintName((EClassifier)eObject, detail.getKey()); |
| } |
| eAnnotation.getDetails().remove(detail.getKey()); |
| } |
| } finally { |
| exporter = null; |
| } |
| importFromResources(); |
| } |
| |
| @Override |
| public ResourceSet getTarget() { |
| return (ResourceSet) super.getTarget(); |
| } |
| |
| @Override |
| protected void handleNotification(Notification notification) { |
| int featureID = notification.getFeatureID(ResourceSet.class); |
| if (featureID == ResourceSet.RESOURCE_SET__RESOURCES) |
| { |
| int eventType = notification.getEventType(); |
| if (eventType == Notification.ADD) { |
| addWatcher(notification.getNewValue()); |
| } |
| else if (eventType == Notification.ADD_MANY) { |
| for (Object resource : (List<?>)notification.getNewValue()) { |
| addWatcher(resource); |
| } |
| } |
| else if (eventType == Notification.REMOVE) { |
| removeWatcher(notification.getOldValue()); |
| } |
| else if (eventType == Notification.REMOVE_MANY) { |
| for (Object resource : (List<?>)notification.getOldValue()) { |
| removeWatcher(resource); |
| } |
| } |
| } |
| } |
| |
| public void importFromResources() { |
| if (exporter != null) { |
| return; |
| } |
| try { |
| importer = new Importer(getTarget().getResources()); |
| contents = importer.getContents(); |
| String newText = importer.getText(); |
| updateDocument(newText); |
| } finally { |
| importer = null; |
| } |
| } |
| |
| @Override |
| public boolean isAdapterForType(Object type) { |
| return type == OCLInEcoreSynchronizer.class; |
| } |
| |
| protected void removeWatcher(Object object) { |
| if (object instanceof Resource) { |
| Resource resource = (Resource)object; |
| ResourceWatcher watcher = EcoreUtils.getAdapter(resource, ResourceWatcher.class); |
| if (watcher != null) { |
| resource.eAdapters().remove(watcher); |
| } |
| } |
| } |
| |
| @Override |
| public void setTarget(Notifier newTarget) { |
| if (getTarget() != null) { |
| for (Resource resource : getTarget().getResources()) { |
| removeWatcher(resource); |
| } |
| } |
| if (newTarget != null) { |
| for (Resource resource : ((ResourceSet)newTarget).getResources()) { |
| addWatcher(resource); |
| } |
| } |
| super.setTarget(newTarget); |
| } |
| |
| @Override |
| public void unsetTarget(Notifier oldTarget) { |
| if (oldTarget != null) { |
| for (Resource resource : ((ResourceSet)oldTarget).getResources()) { |
| removeWatcher(resource); |
| } |
| } |
| super.unsetTarget(oldTarget); |
| } |
| |
| protected abstract void updateDocument(String newText); |
| } |