blob: 8e392b8a97ff6b0fcad64915ec8bb25becc4a8fc [file] [log] [blame]
/**
* <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);
}