| /******************************************************************************* |
| * Copyright (c) 2007, 2010 BMW Car IT, Technische Universitaet Muenchen, and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * BMW Car IT - Initial API and implementation |
| * Technische Universitaet Muenchen - Major refactoring and extension |
| *******************************************************************************/ |
| package org.eclipse.emf.edapt.history.reconstruction; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import org.eclipse.emf.ecore.EAttribute; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.ETypedElement; |
| import org.eclipse.emf.ecore.EcorePackage; |
| import org.eclipse.emf.ecore.util.EcoreSwitch; |
| import org.eclipse.emf.edapt.internal.common.MetamodelExtent; |
| import org.eclipse.emf.edapt.internal.common.TypeUtils; |
| import org.eclipse.emf.edapt.spi.history.Change; |
| import org.eclipse.emf.edapt.spi.history.CompositeChange; |
| import org.eclipse.emf.edapt.spi.history.Create; |
| import org.eclipse.emf.edapt.spi.history.Delete; |
| import org.eclipse.emf.edapt.spi.history.InitializerChange; |
| import org.eclipse.emf.edapt.spi.history.MigrationChange; |
| import org.eclipse.emf.edapt.spi.history.Move; |
| import org.eclipse.emf.edapt.spi.history.OperationChange; |
| import org.eclipse.emf.edapt.spi.history.PrimitiveChange; |
| import org.eclipse.emf.edapt.spi.history.Release; |
| import org.eclipse.emf.edapt.spi.history.Remove; |
| import org.eclipse.emf.edapt.spi.history.util.HistorySwitch; |
| |
| /** |
| * A reconstructor that follows the history and checks whether changes are |
| * breaking |
| * |
| * @author herrmama |
| * @author $Author$ |
| * @version $Rev$ |
| * @levd.rating RED Rev: |
| */ |
| public class HistoryValidator extends ReconstructorBase { |
| |
| /** |
| * Switch to distinguish between different types of changes |
| */ |
| private ValidatorSwitch validatorSwitch; |
| |
| /** |
| * Mapping |
| */ |
| private Mapping mapping; |
| |
| /** |
| * Elements created within a certain period |
| */ |
| private final Set<EObject> createdElements; |
| |
| /** |
| * Whether current release is the first release |
| */ |
| private boolean inFirstRelease; |
| |
| /** |
| * Whether reconstruction currently is inside a migration change |
| */ |
| private boolean inMigrationChange; |
| |
| /** |
| * Whether reconstruction currently is inside an operation change |
| */ |
| private boolean inOperationChange; |
| |
| /** |
| * Whether reconstruction currently is inside an initializer change |
| */ |
| private boolean inInitializerChange; |
| |
| /** |
| * Default constructor |
| * |
| */ |
| public HistoryValidator() { |
| createdElements = new HashSet<EObject>(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void init(Mapping mapping, MetamodelExtent extent) { |
| this.mapping = mapping; |
| validatorSwitch = new ValidatorSwitch(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void startRelease(Release originalRelease) { |
| inFirstRelease = originalRelease.isFirstRelease(); |
| reset(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void startChange(Change originalChange) { |
| |
| if (canIgnore()) { |
| originalChange.setBreaking(false); |
| return; |
| } |
| |
| originalChange.setBreaking(validatorSwitch.doSwitch(originalChange)); |
| |
| startIgnore(originalChange); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void endChange(Change change) { |
| |
| endIgnore(change); |
| |
| if (canIgnore()) { |
| return; |
| } |
| |
| endUpdate(change); |
| } |
| |
| /** |
| * Check whether the current change can be ignored |
| */ |
| private boolean canIgnore() { |
| return inFirstRelease || inMigrationChange || inOperationChange |
| || inInitializerChange; |
| } |
| |
| /** |
| * Set ignore status when starting a change |
| */ |
| private void startIgnore(Change change) { |
| if (change instanceof MigrationChange) { |
| inMigrationChange = true; |
| change.setBreaking(false); |
| reset(); |
| } else if (change instanceof OperationChange) { |
| inOperationChange = true; |
| change.setBreaking(false); |
| reset(); |
| } else if (change instanceof InitializerChange) { |
| inInitializerChange = true; |
| change.setBreaking(false); |
| } |
| } |
| |
| /** |
| * Unset ignore status when ending a change |
| */ |
| private void endIgnore(Change change) { |
| if (change instanceof MigrationChange) { |
| inMigrationChange = false; |
| reset(); |
| } else if (change instanceof OperationChange) { |
| inOperationChange = false; |
| reset(); |
| } else if (change instanceof InitializerChange) { |
| inInitializerChange = false; |
| } |
| } |
| |
| /** |
| * Perform updates when ending a change |
| */ |
| private void endUpdate(Change change) { |
| if (change instanceof Create) { |
| // add created element |
| final Create createChild = (Create) change; |
| final EObject created = mapping.resolveTarget(createChild.getElement()); |
| created(created); |
| } else if (change instanceof CompositeChange |
| && !(change instanceof OperationChange)) { |
| // update breaking status of composite change |
| final CompositeChange compositeChange = (CompositeChange) change; |
| boolean breaking = false; |
| for (final PrimitiveChange primitiveChange : compositeChange.getChanges()) { |
| breaking |= primitiveChange.isBreaking(); |
| } |
| compositeChange.setBreaking(breaking); |
| reset(); |
| } |
| } |
| |
| /** |
| * Register a created element |
| */ |
| private void created(EObject element) { |
| createdElements.add(element); |
| } |
| |
| /** |
| * Check whether an element was recently created |
| * |
| * @param element |
| * Element |
| * @return true if the element was recently created, false otherwise |
| */ |
| private boolean isCreated(EObject element) { |
| return createdElements.contains(element); |
| } |
| |
| /** |
| * Reset the registry of recently created elements |
| */ |
| private void reset() { |
| createdElements.clear(); |
| } |
| |
| /** |
| * Switch to distinguish between different types of changes |
| */ |
| private class ValidatorSwitch extends HistorySwitch<Boolean> { |
| |
| /** |
| * Ecore package |
| */ |
| private final EcorePackage p = EcorePackage.eINSTANCE; |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Boolean caseSet(final org.eclipse.emf.edapt.spi.history.Set set) { |
| final EStructuralFeature feature = set.getFeature(); |
| final EObject element = mapping.resolveTarget(set.getElement()); |
| if (isCreated(element)) { |
| return false; |
| } |
| |
| /** |
| * Switch distinguish element types |
| */ |
| final EcoreSwitch<Boolean> setValidatorSwitch = new EcoreSwitch<Boolean>() { |
| @Override |
| public Boolean caseETypedElement(ETypedElement object) { |
| |
| if (object instanceof EOperation) { |
| return false; |
| } else if (object instanceof EStructuralFeature |
| && feature.isDerived()) { |
| return false; |
| } |
| |
| switch (feature.getFeatureID()) { |
| // ETypedElement.unique -> true |
| case EcorePackage.ETYPED_ELEMENT__UNIQUE: { |
| final boolean value = (Boolean) set.getValue(); |
| if (value) { |
| return true; |
| } |
| break; |
| } |
| // ETypedElement.upperBound |
| case EcorePackage.ETYPED_ELEMENT__UPPER_BOUND: { |
| final int value = (Integer) set.getValue(); |
| final int oldValue = (Integer) set.getOldValue(); |
| // decrease |
| if (oldValue < 0) { |
| return true; |
| } else if (value >= 0 && value < oldValue) { |
| return true; |
| } |
| break; |
| } |
| // ETypedElement.eType |
| case EcorePackage.ETYPED_ELEMENT__ETYPE: { |
| if (set.getElement() instanceof EReference) { |
| final EClass oldValue = (EClass) mapping |
| .resolveTarget(set.getOldValue()); |
| final EClass newValue = (EClass) mapping.resolveTarget(set |
| .getValue()); |
| if (!TypeUtils.ancestor(oldValue, newValue)) { |
| return true; |
| } |
| } else { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean caseEStructuralFeature(EStructuralFeature object) { |
| switch (feature.getFeatureID()) { |
| // EStructuralFeature.transient -> true |
| case EcorePackage.ESTRUCTURAL_FEATURE__TRANSIENT: { |
| final boolean value = (Boolean) set.getValue(); |
| if (value) { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean caseEAttribute(EAttribute object) { |
| switch (feature.getFeatureID()) { |
| // EAttribute.iD -> true |
| case EcorePackage.EATTRIBUTE__ID: { |
| final boolean value = (Boolean) set.getValue(); |
| if (value) { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean caseEReference(EReference object) { |
| switch (feature.getFeatureID()) { |
| // EReference.containment |
| case EcorePackage.EREFERENCE__CONTAINMENT: { |
| return true; |
| } |
| // EReference.eOpposite |
| case EcorePackage.EREFERENCE__EOPPOSITE: { |
| return true; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean caseEClass(EClass eClass) { |
| switch (feature.getFeatureID()) { |
| case EcorePackage.ECLASS__ABSTRACT: { |
| // EClass.abstract -> true |
| if (feature == p.getEClass_Abstract()) { |
| final boolean value = (Boolean) set.getValue(); |
| if (value && !eClass.isInterface()) { |
| return true; |
| } |
| } |
| break; |
| } |
| // EClass.interface -> true |
| case EcorePackage.ECLASS__INTERFACE: { |
| final boolean value = (Boolean) set.getValue(); |
| if (value && !eClass.isAbstract()) { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean defaultCase(EObject object) { |
| return false; |
| } |
| }; |
| |
| return setValidatorSwitch.doSwitch(element); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Boolean caseRemove(Remove remove) { |
| final EStructuralFeature feature = remove.getFeature(); |
| final EObject element = mapping.resolveTarget(remove.getElement()); |
| if (isCreated(element)) { |
| return false; |
| } |
| |
| // EClass.eSuperTypes |
| if (element instanceof EClass) { |
| switch (feature.getFeatureID()) { |
| case EcorePackage.ECLASS__ESUPER_TYPES: |
| final EClass eClass = (EClass) remove.getValue(); |
| if (!eClass.getEAllStructuralFeatures().isEmpty()) { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Boolean caseDelete(Delete delete) { |
| final EObject element = mapping.resolveTarget(delete.getElement()); |
| if (isCreated(element)) { |
| return false; |
| } |
| |
| if (element instanceof EStructuralFeature) { |
| return true; |
| } else if (!delete.getChanges().isEmpty()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Boolean caseMove(Move move) { |
| final EObject element = move.getElement(); |
| if (element instanceof EStructuralFeature) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Boolean defaultCase(EObject object) { |
| return false; |
| } |
| } |
| } |