DiagramContextChecker.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.internal.ui.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.window.Window;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.instance.InstanceFactory;
import org.osate.ge.CanonicalBusinessObjectReference;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.internal.AadlReferenceUtil;
import org.osate.ge.aadl2.ui.AadlModelAccessUtil;
import org.osate.ge.aadl2.ui.internal.dialogs.ElementSelectionDialog;
import org.osate.ge.internal.diagram.runtime.AgeDiagram;
import org.osate.ge.internal.diagram.runtime.DiagramConfigurationBuilder;
import org.osate.ge.internal.services.ProjectReferenceService;
import org.osate.ge.internal.services.SystemInstanceLoadingService;
import org.osate.ge.internal.services.impl.DeclarativeReferenceType;
import com.google.common.base.Strings;
public class DiagramContextChecker {
private final IProject project;
private final ProjectReferenceService refService;
private final SystemInstanceLoadingService systemInstanceLoader;
public static class Result {
private final boolean contextIsValid;
private final boolean relinked;
public Result(final boolean contextIsValid, final boolean relinked) {
this.contextIsValid = contextIsValid;
this.relinked = relinked;
}
public final boolean isContextValid() {
return contextIsValid;
}
public final boolean wasContextUpdated() {
return relinked;
}
}
public DiagramContextChecker(final IProject project, final ProjectReferenceService refService,
final SystemInstanceLoadingService systemInstanceLoader) {
this.project = Objects.requireNonNull(project, "project must not be null");
this.refService = Objects.requireNonNull(refService, "refService must not be null");
this.systemInstanceLoader = Objects.requireNonNull(systemInstanceLoader,
"systemInstanceLoader must not be null");
}
public Result checkContextFullBuild(final AgeDiagram diagram, final boolean promptToRelinkIfMissing) {
return checkContext(diagram, promptToRelinkIfMissing, IncrementalProjectBuilder.FULL_BUILD);
}
public Result checkContextIncrementalBuild(final AgeDiagram diagram, final boolean promptToRelinkIfMissing) {
return checkContext(diagram, promptToRelinkIfMissing, IncrementalProjectBuilder.INCREMENTAL_BUILD);
}
/**
* Checks the diagram context and optionally prompts the user to fix missing context.
* Throws an exception if the context is invalid.
* @param diagram
* @param promptToRelinkIfMissing
*/
private Result checkContext(final AgeDiagram diagram, final boolean promptToRelinkIfMissing,
final int buildKind) {
Objects.requireNonNull(diagram, "diagram must not be null");
boolean relinked = false;
// Contextless diagrams cannot have a broken context
if(diagram.getConfiguration().getContextBoReference() != null) {
Object contextBo = refService.resolve(diagram.getConfiguration().getContextBoReference());
// If unable to resolve the context, rebuild the project
if (contextBo == null) {
try {
project.build(buildKind, new NullProgressMonitor());
contextBo = refService.resolve(diagram.getConfiguration().getContextBoReference());
} catch (CoreException e) {
throw new RuntimeException(e);
}
}
// If the context is still not valid and the prompt flag is set, prompt the user for a new context and relink the diagram.
if (contextBo == null && promptToRelinkIfMissing) {
relinked = promptToRelink(diagram);
}
// Check the context again
contextBo = refService.resolve(diagram.getConfiguration().getContextBoReference());
if (contextBo == null) {
return new Result(false, false);
}
}
return new Result(true, relinked);
}
/**
* Returns whether or not the diagram context was adjusted.
* @param diagram
* @param missingContextRef
* @return
*/
private boolean promptToRelink(final AgeDiagram diagram) {
final CanonicalBusinessObjectReference missingContextRef = diagram.getConfiguration().getContextBoReference();
final List<String> refSegs = missingContextRef.getSegments();
if (refSegs.size() < 2) {
return false;
}
final boolean isPackageRef = DeclarativeReferenceType.PACKAGE.getId().equals(refSegs.get(0));
final boolean isClassifierRef = DeclarativeReferenceType.CLASSIFIER.getId().equals(refSegs.get(0));
final boolean isSystemInstance = AadlReferenceUtil.isSystemInstanceReference(missingContextRef);
if (!isPackageRef && !isClassifierRef && !isSystemInstance) {
return false;
}
// Determine the options to present to the user
final Collection<?> options;
String searchPrefix = "";
if (isPackageRef || isClassifierRef) {
// Find all packages
final Collection<IEObjectDescription> packageDescriptions = AadlModelAccessUtil
.getAllEObjectsByType(project, Aadl2Package.eINSTANCE.getAadlPackage());
if (isPackageRef) {
options = packageDescriptions;
} else { // isClassifierRef
options = AadlModelAccessUtil.getAllEObjectsByType(project,
Aadl2Package.eINSTANCE.getClassifier());
// Check if the package portion of the qualified name is a valid package.
// If so, use it as the initial filter
final String referencedClassifierQualifiedName = refSegs.get(1);
final String[] qualifiedNameParts = referencedClassifierQualifiedName.split("::");
if (qualifiedNameParts.length == 2) {
final String pkgName = qualifiedNameParts[0];
for (final IEObjectDescription desc : packageDescriptions) {
if (desc.getName().toString("::").equalsIgnoreCase(pkgName)) {
searchPrefix = pkgName.toLowerCase() + "::";
}
}
}
}
} else if (isSystemInstance) {
options = findInstanceModelFiles(project, new ArrayList<IPath>());
} else {
// Unexpected case: there is already a short circuit for the case where the reference isn't a package or classifier reference
throw new RuntimeException("Unexpected case");
}
// Don't prompt if there aren't any options.
if (options.size() == 0) {
return false;
}
final ElementSelectionDialog dlg = new ElementSelectionDialog(null, "Missing Diagram Context",
"Unable to find diagram context \"" + refService.getLabel(
missingContextRef)
+ "\".\nIf the model element has been renamed, select the new name for the model element.",
options);
dlg.setFilter(searchPrefix);
if (dlg.open() != Window.OK) {
return false;
}
final CanonicalBusinessObjectReference newContextCanonicalRef;
final RelativeBusinessObjectReference newContextRelativeRef;
final Object newContext;
if (isSystemInstance) {
final IPath systemInstancePath = (IPath) dlg.getFirstSelectedElement();
newContextCanonicalRef = AadlReferenceUtil
.getCanonicalBusinessObjectReferenceForSystemInstance(systemInstanceLoader, systemInstancePath);
newContextRelativeRef = AadlReferenceUtil
.getRelativeBusinessObjectReferenceForSystemInstance(systemInstanceLoader, systemInstancePath);
// Create a dummy system instance. It will be replaced as part of the diagram updating process.
newContext = InstanceFactory.eINSTANCE.createSystemInstance();
} else {
final EObject newContextProxy = (EObject) dlg.getFirstSelectedElement();
// Find the live object
final ResourceSet liveResourceSet = AadlModelAccessUtil.getLiveResourceSet(project);
newContext = EcoreUtil.resolve(newContextProxy, liveResourceSet);
if (((EObject) newContext).eIsProxy()) {
throw new RuntimeException("Unable to retrieve non-proxy object for selection");
}
// Find canonical and relative reference
newContextCanonicalRef = refService
.getCanonicalReference(newContext);
if (newContextCanonicalRef == null) {
throw new RuntimeException("Unable to retrieve reference for new diagram context: " + newContext);
}
newContextRelativeRef = refService.getRelativeReference(newContext);
if (newContextRelativeRef == null) {
throw new RuntimeException(
"Unable to retrieve relative reference for new diagram context: " + newContext);
}
}
// Update the diagram
diagram.modify("Update Diagram Context", m -> {
// Update the diagram's context
m.setDiagramConfiguration(new DiagramConfigurationBuilder(diagram.getConfiguration())
.contextBoReference(newContextCanonicalRef).build());
// Update the root element
if (diagram.getChildren().size() == 1) {
m.updateBusinessObject(diagram.getChildren().stream().findAny().get(), newContext,
newContextRelativeRef);
}
});
return true;
}
/**
*
* @param parent
* @param results
* @return results
*/
private List<IPath> findInstanceModelFiles(final IResource res, final List<IPath> results) {
if (res instanceof IFile && Strings.emptyToNull(res.getFileExtension()).equalsIgnoreCase("aaxl2")) {
results.add(res.getFullPath());
} else if (res instanceof IContainer) {
final IContainer container = (IContainer) res;
try {
for (final IResource child : container.members()) {
findInstanceModelFiles(child, results);
}
} catch (CoreException e) {
throw new RuntimeException(e);
}
}
return results;
}
}