AgeRenameParticipant.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.ge.aadl2.internal.refactoring;
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.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.UsageCrossReferencer;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
import org.eclipse.xtext.resource.IFragmentProvider;
import org.eclipse.xtext.resource.IReferenceDescription;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.ui.refactoring.impl.RefactoringResourceSetProvider;
import org.eclipse.xtext.ui.refactoring.ui.IRenameElementContext;
import org.eclipse.xtext.ui.resource.LiveScopeResourceSetInitializer;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.ComponentTypeRename;
import org.osate.aadl2.Feature;
import org.osate.aadl2.Realization;
import org.osate.ge.CanonicalBusinessObjectReference;
import org.osate.ge.ProjectUtil;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.ui.AadlModelAccessUtil;
import org.osate.ge.internal.services.DiagramService;
import org.osate.ge.internal.services.ReferenceService;
import org.osate.xtext.aadl2.ui.internal.Aadl2Activator;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
@SuppressWarnings("restriction")
public class AgeRenameParticipant extends RenameParticipant {
private final Map<CanonicalBusinessObjectReference, UriAndRelativeReference> originalCanRefToNewInfoMap = new HashMap<>();
private IRenameElementContext ctx;
private EObject targetObject;
private ReferenceService referenceService;
private DiagramService diagramService;
private ResourceSet refactoringResourceSet;
private IProject project;
private DiagramService.ReferenceCollection originalReferences; // The original set of references during the refactoring. They are stored before the change
// because the model change will have occured before the change is executed.
private static class UriAndRelativeReference {
public final URI uri;
public final RelativeBusinessObjectReference relRef;
public UriAndRelativeReference(final URI uri, final RelativeBusinessObjectReference relRef) {
this.uri = Objects.requireNonNull(uri, "uri must not be null");
this.relRef = Objects.requireNonNull(relRef, "relRef must not be null");
}
}
@Override
protected boolean initialize(final Object element) {
originalCanRefToNewInfoMap.clear();
if (!(element instanceof IRenameElementContext)) {
return false;
}
ctx = (IRenameElementContext) element;
final URI targetElementUri = ctx.getTargetElementURI();
if (targetElementUri == null) {
return false;
}
final IResource resource = ResourcesPlugin.getWorkspace().getRoot()
.findMember(new Path(targetElementUri.toPlatformString(true)));
if (resource == null) {
return false;
}
final XtextResourceSet tmpRs = new XtextResourceSet();
Aadl2Activator.getInstance().getInjector(Aadl2Activator.ORG_OSATE_XTEXT_AADL2_AADL2)
.getInstance(LiveScopeResourceSetInitializer.class).initialize(tmpRs);
targetObject = tmpRs.getEObject(targetElementUri, true);
if (targetObject == null || !(targetObject.eResource() instanceof XtextResource)) {
return false;
}
final XtextResource xtextResource = (XtextResource) targetObject.eResource();
// Get the provider for the refactoring resource set
final RefactoringResourceSetProvider refactoringResourceSetProvider = xtextResource.getResourceServiceProvider()
.get(RefactoringResourceSetProvider.class);
project = ProjectUtil.getProjectOrNull(targetElementUri);
if (project == null) {
return false;
}
// Get the refactoring resource set
refactoringResourceSet = refactoringResourceSetProvider.get(project);
if (refactoringResourceSet == null) {
return false;
}
// Get global services
final Bundle bundle = FrameworkUtil.getBundle(getClass());
final IEclipseContext context = EclipseContextFactory.getServiceContext(bundle.getBundleContext());
referenceService = Objects.requireNonNull(context.get(ReferenceService.class),
"Unable to get reference service");
diagramService = Objects.requireNonNull(context.get(DiagramService.class), "Unable to get diagram service");
// Get projects with are affected by the refactoring.
final Set<IProject> relevantProjects = ProjectUtil.getAffectedProjects(project, new HashSet<>());
// Build a mapping between an EObject URI and the URIs of EObjects that it affects.
final Map<URI, Set<URI>> externalReferencesMap = buildExternalReferenceMap(relevantProjects);
// Find all dependent objects
final Set<EObject> dependentObjects = getDependentObjects(targetObject,
targetObject.eResource().getResourceSet(), externalReferencesMap);
dependentObjects.add(targetObject);
for (final EObject dirtyObject : dependentObjects) {
final URI uri = getNameIndependentUri(dirtyObject);
if (uri != null) {
final CanonicalBusinessObjectReference canonicalReference = referenceService
.getCanonicalReference(dirtyObject);
final RelativeBusinessObjectReference relativeReference = referenceService
.getRelativeReference(dirtyObject);
if (canonicalReference != null && relativeReference != null) {
originalCanRefToNewInfoMap.put(canonicalReference,
new UriAndRelativeReference(uri, relativeReference));
}
}
}
// Initialize is called many times so it would be best to do it before the change is made but not in the initialization phase
originalReferences = diagramService.getReferences(relevantProjects, originalCanRefToNewInfoMap.keySet());
return true;
}
@Override
public String getName() {
return "AgeRenameParticipant";
}
@Override
public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
throws OperationCanceledException {
final List<IFile> relatedReadOnlyDiagramFiles = originalReferences.getRelatedDiagramFiles().stream()
.filter(f -> f.isReadOnly()).collect(Collectors.toList());
final IFile[] relatedReadOnlyDiagramFilesArray = relatedReadOnlyDiagramFiles
.toArray(new IFile[relatedReadOnlyDiagramFiles.size()]);
if (relatedReadOnlyDiagramFilesArray.length > 0) {
final IStatus status = ResourcesPlugin.getWorkspace().validateEdit(relatedReadOnlyDiagramFilesArray,
IWorkspace.VALIDATE_PROMPT);
if (!status.isOK() || relatedReadOnlyDiagramFiles.stream().anyMatch(file -> file.isReadOnly())) {
final String extMessage = status.isOK() ? "" : status.getMessage();
final RefactoringStatus refactoringStatus = new RefactoringStatus();
refactoringStatus.addError(
"One or more related diagrams are read-only. Read-only diagrams will not be updated and broken linkages may result. "
+ extMessage);
return refactoringStatus;
}
}
return null;
}
@Override
public Change createChange(final IProgressMonitor pm) throws CoreException, OperationCanceledException {
return new Change() {
@Override
public String getName() {
return "OSATE Graphical Editor Diagram Change";
}
@Override
public void initializeValidationData(final IProgressMonitor pm) {
}
@Override
public RefactoringStatus isValid(final IProgressMonitor pm)
throws CoreException, OperationCanceledException {
return new RefactoringStatus();
}
@Override
public Change perform(final IProgressMonitor pm) throws CoreException {
// Build mappings between the canonical reference which identifies the original reference and the new canonical and relative reference for
// change and undo changes.
final Map<CanonicalBusinessObjectReference, CanonicalBusinessObjectReference> originalCanRefToNewCanRefMap = new HashMap<>();
final Map<CanonicalBusinessObjectReference, RelativeBusinessObjectReference> originalCanRefToNewRelRefMap = new HashMap<>();
final Map<CanonicalBusinessObjectReference, CanonicalBusinessObjectReference> undoOriginalCanRefToNewCanRefMap = new HashMap<>();
final Map<CanonicalBusinessObjectReference, RelativeBusinessObjectReference> undoOriginalCanRefToNewRelRefMap = new HashMap<>();
for (final Entry<CanonicalBusinessObjectReference, UriAndRelativeReference> entry : originalCanRefToNewInfoMap
.entrySet()) {
final EObject newObject = refactoringResourceSet.getEObject(entry.getValue().uri, true);
if (newObject != null) {
final CanonicalBusinessObjectReference originalCanRef = entry.getKey();
final CanonicalBusinessObjectReference newCanRef = referenceService
.getCanonicalReference(newObject);
final RelativeBusinessObjectReference newRelRef = referenceService
.getRelativeReference(newObject);
final RelativeBusinessObjectReference originalRelRef = entry.getValue().relRef;
if (newCanRef != null && newRelRef != null) {
originalCanRefToNewCanRefMap.put(originalCanRef, newCanRef);
originalCanRefToNewRelRefMap.put(originalCanRef, newRelRef);
undoOriginalCanRefToNewCanRefMap.put(originalCanRef, originalCanRef);
undoOriginalCanRefToNewRelRefMap.put(originalCanRef, originalRelRef);
}
}
}
// Update the references
final SimpleUpdatedReferenceValueProvider mapping = new SimpleUpdatedReferenceValueProvider(
originalCanRefToNewCanRefMap, originalCanRefToNewRelRefMap);
final SimpleUpdatedReferenceValueProvider undoMapping = new SimpleUpdatedReferenceValueProvider(
undoOriginalCanRefToNewCanRefMap, undoOriginalCanRefToNewRelRefMap);
final UpdateReferencesChange referenceUpdateChange = new UpdateReferencesChange(originalReferences,
mapping, undoMapping);
return referenceUpdateChange.perform(pm);
}
@Override
public Object getModifiedElement() {
return null;
}
};
}
// Builds a mapping between EObject URIs and the URIs of EObjects that it affects based on the EMF Index.
private static final Map<URI, Set<URI>> buildExternalReferenceMap(final Set<IProject> projects) {
final Map<URI, Set<URI>> externalReferencesMap = new HashMap<>();
for (final IResourceDescription resourceDescription : AadlModelAccessUtil
.calculateResourceDescriptions(projects)) {
for (final IReferenceDescription refDescription : resourceDescription.getReferenceDescriptions()) {
final EReference ref = refDescription.getEReference();
if (isHandledRefinedReference(ref)) {
if (refDescription.getSourceEObjectUri() != null && refDescription.getTargetEObjectUri() != null) {
Set<URI> affectedUris = externalReferencesMap.get(refDescription.getTargetEObjectUri());
if (affectedUris == null) {
affectedUris = new HashSet<>();
externalReferencesMap.put(refDescription.getTargetEObjectUri(), affectedUris);
}
affectedUris.add(refDescription.getSourceEObjectUri());
}
}
}
}
return externalReferencesMap;
}
/**
* Gets the URI for the object using the default referencing scheme. That is, it does not use the specialized fragment provider the resource may have.
* This is needed to allow creating URIs which do not include the name of the object being referenced but rather describe the position of the object in the resource.
* This type of URI is better suited to getting the new objects after renaming.
* @param obj
* @return
*/
private static URI getNameIndependentUri(final EObject obj) {
// Set the fragment processor of the resources to null so that the URI generated will be based on ordering and not actual names
IFragmentProvider oldFragmentProvider = null;
XtextResource res = null;
try {
if (obj.eResource() instanceof XtextResource) {
res = (XtextResource) obj.eResource();
oldFragmentProvider = res.getFragmentProvider();
res.setFragmentProvider(null);
}
// Store the URIs
return EcoreUtil.getURI(obj);
} finally {
// Restore the old fragment processor
if (oldFragmentProvider != null) {
res.setFragmentProvider(oldFragmentProvider);
}
}
}
private static Set<EObject> getDependentObjects(final EObject obj, final ResourceSet rs,
final Map<URI, Set<URI>> externalReferencesMap) {
final Set<EObject> results = new HashSet<>();
final EObject objectOfInterest;
// If the object is a component type rename, replace it with the component type it renames. This will result in additional objects being returned but it
// is the only known way of getting types related to the renames.
if (obj instanceof ComponentTypeRename) {
final ComponentType renamedComponentType = ((ComponentTypeRename) obj).getRenamedComponentType();
objectOfInterest = renamedComponentType == null ? null : renamedComponentType;
} else {
objectOfInterest = obj;
}
if (objectOfInterest != null) {
getRelatedObjects(Collections.singleton(objectOfInterest), rs, results, externalReferencesMap,
obj instanceof Feature);
}
return results;
}
// Gets objects related to the specified objects of interest
private static void getRelatedObjects(Collection<EObject> objectsOfInterest, final ResourceSet rs,
final Set<EObject> results, final Map<URI, Set<URI>> externalReferencesMap, final boolean recursive) {
final Collection<EObject> newObjects = new ArrayList<>();
for (final Collection<Setting> settings : UsageCrossReferencer.findAll(objectsOfInterest, rs).values()) {
for (final Setting s : settings) {
final EStructuralFeature sf = s.getEStructuralFeature();
if (isHandledRefinedReference(sf)) {
newObjects.add(s.getEObject());
} else if (sf == Aadl2Package.eINSTANCE.getRealization_Implemented()) {
// Get the component implementation from the realization
final Realization realization = (Realization) s.getEObject();
newObjects.add(realization.getSpecific());
}
}
}
for (final EObject objectOfInterest : objectsOfInterest) {
final URI objectOfInterestUri = EcoreUtil.getURI(objectOfInterest);
if (objectOfInterestUri != null) {
// Add objects references in the external references map
final Set<URI> affectedUris = externalReferencesMap.get(objectOfInterestUri);
if (affectedUris != null) {
for (final URI affectedUri : affectedUris) {
final EObject affectedObject = rs.getEObject(affectedUri, true);
if (affectedObject != null) {
newObjects.add(affectedObject);
}
}
}
}
}
if (results.addAll(newObjects)) {
if (recursive) {
getRelatedObjects(newObjects, rs, results, externalReferencesMap, recursive);
}
}
}
private static boolean isHandledRefinedReference(final EStructuralFeature sf) {
return sf == Aadl2Package.eINSTANCE.getFeature_Refined() || sf == Aadl2Package.eINSTANCE.getConnection_Refined()
|| sf == Aadl2Package.eINSTANCE.getSubcomponent_Refined()
|| sf == Aadl2Package.eINSTANCE.getFlowSpecification_Refined();
}
private static class SimpleUpdatedReferenceValueProvider implements DiagramService.UpdatedReferenceValueProvider {
private final Map<CanonicalBusinessObjectReference, CanonicalBusinessObjectReference> originalCanRefToNewCanRefMap;
private final Map<CanonicalBusinessObjectReference, RelativeBusinessObjectReference> originalCanRefToNewRelRefMap;
public SimpleUpdatedReferenceValueProvider(
final Map<CanonicalBusinessObjectReference, CanonicalBusinessObjectReference> originalCanRefToNewCanRefMap,
final Map<CanonicalBusinessObjectReference, RelativeBusinessObjectReference> originalCanRefToNewRelRefMap) {
this.originalCanRefToNewCanRefMap = originalCanRefToNewCanRefMap;
this.originalCanRefToNewRelRefMap = originalCanRefToNewRelRefMap;
}
@Override
public CanonicalBusinessObjectReference getNewCanonicalReference(
final CanonicalBusinessObjectReference originalCanonicalReference) {
return originalCanRefToNewCanRefMap.get(originalCanonicalReference);
}
@Override
public RelativeBusinessObjectReference getNewRelativeReference(
final CanonicalBusinessObjectReference originalCanonicalReference) {
return originalCanRefToNewRelRefMap.get(originalCanonicalReference);
}
};
private class UpdateReferencesChange extends Change {
private final DiagramService.ReferenceCollection references;
private final SimpleUpdatedReferenceValueProvider mapping;
private final SimpleUpdatedReferenceValueProvider undoMapping;
public UpdateReferencesChange(final DiagramService.ReferenceCollection references,
final SimpleUpdatedReferenceValueProvider mapping,
final SimpleUpdatedReferenceValueProvider undoMapping) {
this.references = Objects.requireNonNull(references, "references must not be null");
this.mapping = Objects.requireNonNull(mapping, "mapping must not be null");
this.undoMapping = Objects.requireNonNull(undoMapping, "undoMapping must not be null");
}
@Override
public String getName() {
return "Update OSATE Graphical Editor Diagram References";
}
@Override
public void initializeValidationData(final IProgressMonitor pm) {
}
@Override
public RefactoringStatus isValid(final IProgressMonitor pm) throws CoreException, OperationCanceledException {
return new RefactoringStatus();
}
@Override
public Change perform(final IProgressMonitor pm) throws CoreException {
references.update(mapping);
return new UpdateReferencesChange(references, undoMapping, mapping);
}
@Override
public Object getModifiedElement() {
return null;
}
}
}