AbstractAaxlHandler.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.ui.handlers;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;
import org.osate.aadl2.Element;
import org.osate.aadl2.EnumerationLiteral;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.Property;
import org.osate.aadl2.PropertyConstant;
import org.osate.aadl2.PropertyType;
import org.osate.aadl2.UnitLiteral;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.modelsupport.AadlConstants;
import org.osate.aadl2.modelsupport.WriteToFile;
import org.osate.aadl2.modelsupport.errorreporting.AnalysisErrorReporterFactory;
import org.osate.aadl2.modelsupport.errorreporting.AnalysisErrorReporterManager;
import org.osate.aadl2.modelsupport.errorreporting.MarkerAnalysisErrorReporter;
import org.osate.aadl2.modelsupport.util.AadlUtil;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.ui.dialogs.Dialog;
import org.osate.xtext.aadl2.properties.util.GetProperties;
/**
* Abstract superclass for {@link org.osate.ui.handlers.AaxlReadOnlyHandlerAsJob}.
* Contains all the utility methods {@link #lookupPropertyDefinition(String, String)},
* {@link #lookupPropertyType(String, String)}, etc. Abstracts the Eclipse
* job creation process. Calls {@link #createJob(Element)} to get the
* job to run. It is expected that the job invoke {@link #actionBody(IProgressMonitor, Element)}.
* This method initializes the OSATE action by creating the error reporter
* manager, calling {@link #initPropertyReferences()}, etc., and finally
* invokes {@link #doAaxlAction(IProgressMonitor, Element)} to run the
* specific analysis action body.
*
* <p>Users should not extend this class directly, but should extend
* {@link org.osate.ui.actions.AaxlReadOnlyHandlerAsJob}.
*
* @author aarong
*/
public abstract class AbstractAaxlHandler extends AbstractHandler {
private static final String ERROR_SEPARATOR = ", ";
private static final String ERROR_MESSAGE = "Unable to find ";
private static final String PREDECLARED = "predeclared ";
private static final String PROP_DEF = "property definition ";
private static final String PROP_CONST = "property constant ";
private static final String PROP_TYPE = "property type ";
private static final String COLON_COLON = "::";
private static final String ERROR_TITLE = "Plug-in Initialization Error";
private ExecutionEvent event;
/** Get the name of the action to display in the Job, etc. */
protected abstract String getActionName();
/**
* The manager of error reporters. Set by the run action to the resource of
* the selected item
*/
protected AnalysisErrorReporterManager errManager;
protected WriteToFile csvlog = null;
protected StringBuffer summaryReport;
/**
* sets up a CSV log in the report folder using report type as subfolder
*/
public void setCSVLog(String reporttype, EObject root) {
csvlog = new WriteToFile(reporttype, root);
}
/**
* sets up a CSV log in the report folder using report type as subfolder
*/
public void setTXTLog(String reporttype, EObject root) {
csvlog = new WriteToFile(reporttype, root, "txt");
}
private String issuePrefix = "";
public void setIssuePrefix(String prefix) {
issuePrefix = prefix;
}
/**
* Use by the property reference initialization process to keep track
* of the property references that could not be found.
*/
private final List<String> notFound = new LinkedList<>();
/**
* The model object that controls the property set name space.
* See {@link OsateResourceManager#findPropertySet(String, Element)}.
*/
private Element context;
/**
* The action has been activated. Indirectly invokes the body of the
* action, {@link #doAaxlAction(IProgressMonitor, Element)}, by setting up a
* {@link org.eclipse.ui.progress.UIJob} that invokes
* (default visibility method) <code>processAaxlAction</code>.
*/
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
this.event = event;
Element root = null;
root = AadlUtil.getElement(getCurrentSelection(event));
if (root != null) {
/*
* Here we create the job, and then do two very important things:
* (1) set the scheduling rule so that the job locks up the
* entire workspace so that nobody messes with it while run.
* (2) set the job to be a user job so that we get the
* nice progress dialog box AND the option to run the job in the
* background.
*/
final Job job = createJob(root);
job.setRule(ResourcesPlugin.getWorkspace().getRoot());
job.setUser(true); // important!
job.schedule();
}
return null;
}
protected final void actionBody(final IProgressMonitor monitor, final Element root) {
final Resource resource = root.eResource();
errManager = new AnalysisErrorReporterManager(getAnalysisErrorReporterFactory());
summaryReport = new StringBuffer();
// Root cannot be null (see above)
// init the context object. It is used by the lookup methods for initializing property references
context = root instanceof SystemInstance ? ((SystemInstance) root).getComponentImplementation() : root;
// Init the properties
notFound.clear();
initPropertyReferences();
initializeAction((NamedElement) root);
if (suppressErrorMessages() || !reportPropertyLookupErrors()) {
// Run the command (indirectly)
processAaxlAction(monitor, resource, root);
}
finalizeAction();
}
protected abstract Job createJob(Element root);
// --- BEGIN: Property Reference management -------------------------------
/**
* Plug-ins override this to initialize references to property
* definitions and constants that they use. The default implementation
* does nothing
*
*/
protected void initPropertyReferences() {
// Default implementation does nothing.
}
/**
* Lookup a particular property definition, updating the error list if it is
* not found.
*
* @return The property defintion, or <code>null</code> if it is not
* found.
*/
protected final Property lookupPropertyDefinition(final String ps, final String name) {
final Property pd = GetProperties.lookupPropertyDefinition(context, ps, name);
if (Aadl2Util.isNull(pd)) {
notFound.add(PROP_DEF + ps + COLON_COLON + name);
}
return pd;
}
/**
* Lookup a particular optional property definition. Does not update the
* error list if the definition is not found. It is assumed the plug-in is
* written in such a way that it works correctly even when the definition is
* absent.
*
* @return The property definition or <code>null</code> if it is not
* found.
*/
protected final Property lookupOptionalPropertyDefinition(final String ps, final String name) {
return GetProperties.lookupPropertyDefinition(context, ps, name);
}
/**
* Lookup a particular predeclared property definition, updating the error
* list if it is not found.
*
* @return The property defintion, or <code>null</code> if it is not
* found.
*/
protected final Property lookupPropertyDefinition(final String name) {
final Property pd = GetProperties.lookupPropertyDefinition(context, null, name);
if (pd == null) {
notFound.add(PREDECLARED + PROP_DEF + name);
}
return pd;
}
/**
* Lookup a particular property type, updating the error list if it is not
* found.
*
* @return The property type, or <code>null</code> if it is not found.
*/
protected final PropertyType lookupPropertyType(final String ps, final String name) {
final PropertyType pt = GetProperties.lookupPropertyType(context, ps, name);
if (pt == null) {
notFound.add(PROP_TYPE + ps + COLON_COLON + name);
}
return pt;
}
/**
* Lookup a particular unit literal, updating the error list if it or its
* declaring unit type is not found.
*
* @return The unit literal, or <code>null</code> if it or it's
* declaring unit type is not found.
*/
protected final UnitLiteral lookupUnitLiteral(final String ps, final String unitType, final String literalName) {
final UnitLiteral literal = GetProperties.findUnitLiteral(context, ps + "::" + unitType, literalName);
if (literal == null) {
notFound.add(MessageFormat.format("unit literal {0} in type {1}::{2}",
new Object[] { literalName, ps, unitType }));
}
return literal;
}
/**
* Lookup a particular enumeration literal, updating the error list if it or its
* declaring enumeration type is not found.
*
* @return The enumeration literal, or <code>null</code> if it or it's
* declaring enumeration type is not found.
*/
protected final EnumerationLiteral lookupEnumerationLiteral(final String ps, final String enumType,
final String literalName) {
final EnumerationLiteral literal = GetProperties.findEnumerationLiteral(context, ps + "::" + enumType,
literalName);
if (literal == null) {
notFound.add(MessageFormat.format("enumeration literal {0} in type {1}::{2}",
new Object[] { literalName, ps, enumType }));
}
return literal;
}
/**
* Lookup a particular optional property type definition. Does not update
* the error list if the type is not found. It is assumed the plug-in is
* written in such a way that it works correctly even when the type is
* absent.
*
* @return The property type or <code>null</code> if it is not found.
*/
protected final PropertyType lookupOptionalPropertyType(final String ps, final String name) {
return GetProperties.lookupPropertyType(context, ps, name);
}
/**
* Lookup a particular predeclared property type, updating the error list
* if it is not found.
*
* @return The property type or <code>null</code> if it is not found.
*/
protected final PropertyType lookupPropertyType(final String name) {
final PropertyType pt = GetProperties.lookupPropertyType(context, name);
if (pt == null) {
notFound.add(PREDECLARED + PROP_TYPE + name);
}
return pt;
}
/**
* Lookup a particular unit literal from a predeclared property type,
* updating the error list if it or its declaring unit type is not found.
*
* @return The unit literal, or <code>null</code> if it or it's
* declaring unit type is not found.
*/
protected final UnitLiteral lookupUnitLiteral(final String unitType, final String literalName) {
final UnitLiteral literal = GetProperties.findUnitLiteral(context, unitType, literalName);
if (literal == null) {
notFound.add(MessageFormat.format("unit literal {0} in predeclared type {1}",
new Object[] { literalName, unitType }));
}
return literal;
}
/**
* Lookup a particular enumeration literal from a predeclared property type,
* updating the error list if it or its declaring enumeration type is not
* found.
*
* @return The enumeration literal, or <code>null</code> if it or it's
* declaring enumeration type is not found.
*/
protected final EnumerationLiteral lookupEnumerationLiteral(final String enumType, final String literalName) {
final EnumerationLiteral literal = GetProperties.findEnumerationLiteral(context, enumType, literalName);
if (literal == null) {
notFound.add(MessageFormat.format("enumeration literal {0} in predeclared type {1}",
new Object[] { literalName, enumType }));
}
return literal;
}
/**
* Lookup a particular property constant, updating the error list if it is
* not found.
*
* @return The property constant or <code>null</code> if it is not found.
*/
protected final PropertyConstant lookupPropertyConstant(final String name) {
final PropertyConstant pc = GetProperties.lookupPropertyConstant(context, name);
if (pc == null) {
notFound.add(PREDECLARED + PROP_CONST + name);
}
return pc;
}
/**
* Lookup a particular predeclared property constant, updating the error
* list if it is not found.
*
* @return The property constant or <code>null</code> if it is not found.
*/
protected final PropertyConstant lookupPropertyConstant(final String ps, final String name) {
final PropertyConstant pc = GetProperties.lookupPropertyConstant(context, ps, name);
if (pc == null) {
notFound.add(PROP_CONST + ps + COLON_COLON + name);
}
return pc;
}
/**
* Lookup a particular optional property constant definition. Does not
* update the error list if the type is not found. It is assumed the plug-in
* is written in such a way that it works correctly even when the constant
* is absent.
*
* @return The property constant or <code>null</code> if it is not found.
*/
protected final PropertyConstant lookupOptionalPropertyConstant(final String ps, final String name) {
return GetProperties.lookupPropertyConstant(context, ps, name);
}
/**
* Find out if there were any errors initializing the property references.
*/
protected final boolean hasPropertyLookupErrors() {
return !notFound.isEmpty();
}
/**
* Get the property lookup errors.
* @return A List of strings, where each string names the property
* refernece or property constant that could not be found. Meant
* to be used to construct a larger error message.
*/
protected final List<String> getPropertyLookupErrors() {
return Collections.unmodifiableList(new ArrayList<>(notFound));
}
/**
* Override this to return <code>true</code> if the default error
* reporting mechanism should not be used. By default the action will report
* those property references that could not be found and avoids invoking
* {@link #doAaxlAction(IProgressMonitor, Element)}. If this method returns
* <code>true</code> then the error reporting will be suppressed and
* {@link #doAaxlAction(IProgressMonitor, Element)}will be invoked. The
* intent here, however, is that the implementation of
* {@link #doAaxlAction(IProgressMonitor, Element)}will check whether there
* were errors and report them in its own way. This is useful if the plug-in
* has a more sophisticated initialziation process and could have additional
* start up errors that it needs to report. The lookup errors are available
* to the body of the action via {@link #getPropertyLookupErrors()}.
*
* @return The default implementation returns <code>false</code>.
*/
protected boolean suppressErrorMessages() {
return false;
}
/**
* Check if there were property lookup errors and put up an
* error dialog box if there were.
* @return Whether there any lookup errors to be reported.
*/
protected final boolean reportPropertyLookupErrors() {
if (hasPropertyLookupErrors()) {
final StringBuffer sb = new StringBuffer();
sb.append(ERROR_MESSAGE);
for (final Iterator<String> i = notFound.iterator(); i.hasNext();) {
final String s = i.next();
sb.append(s);
if (i.hasNext()) {
sb.append(ERROR_SEPARATOR);
}
}
Dialog.showError(ERROR_TITLE, sb.toString());
return true;
} else {
return false;
}
}
// --- END: Property Reference management ---------------------------------
/**
* This method allows subclasses to wrap the call of
* {@link #doAaxlAction(Element)} with additional processing. For example,
* {@link AaxlModifyHandlerAsJob} wrap the call
* to make sure that the resource is saved if it was changed.
*
* <p>
* An implementation of this method must make sure that
* {@link #doAaxlAction(Element)} is invoked.
*
* <p>This initial implementation just invokes does nothing other than
* call {@link #doAaxlAction(Element)}.
*
* @param rsrc
* The resource containing the Element to be processed.
* @param root
* The Element to be processed by the action.
*/
// "default" visibility so that it cannot be used outside of the package
void processAaxlAction(final IProgressMonitor monitor, final Resource rsrc, final Element root) {
doAaxlAction(monitor, root);
}
/**
* This method will be defined by the ultimate subclass, and implements the
* true body of the action. It is invoked along a call chain from the
* {@link #execute(ExecutionEvent)} method, which first makes sure the OSATE
* environment is loaded and other house cleaning things.
*
* <p>
* It is a good idea, although not required, for the action to check to see
* if the {@link IProgressMonitor#isCanceled() action has been cancelled}.
* If cancellation is detected, this method should throw the unchecked
* exception {@link org.eclipse.core.runtime.OperationCanceledException}.
*
* @param monitor
* The progress monitor to use to provide feedback about the
* action and to check for cancellation.
* @param root
* The currently selected Element in the workspace.
* @exception org.eclipse.core.runtime.OperationCanceledException
* Thrown if the method detected that the action has been
* cancelled.
*/
protected abstract void doAaxlAction(IProgressMonitor monitor, Element root);
/** the current selection in the AADL model
*
*/
protected Object getCurrentSelection(ExecutionEvent event) {
ISelection selection = HandlerUtil.getCurrentSelection(event);
if (selection instanceof IStructuredSelection && ((IStructuredSelection) selection).size() == 1) {
Object object = ((IStructuredSelection) selection).getFirstElement();
return object;
} else {
return null;
}
}
protected final IWorkbenchWindow getWindow() {
return HandlerUtil.getActiveWorkbenchWindow(event);
}
protected Shell getShell() {
return HandlerUtil.getActiveShell(event);
}
/**
* Get the type of the markers that the action might create. This is used to
* create a new {@link MarkerAnalysisErrorReporter}for that marker type for
* the resource being analyzed. Any existing markers of that type on the
* resource will be removed.
*
* <p>
* Subclasses should override this method to change the marker type used by
* the action. The initial implementation returns the generic
* <code>"AadlObjectMarker"</code> marker type, and will therefore cause
* all existing OSATE markers to be removed from the resource.
*
* @see #getDefaultAnalysisErrorReporterFactory()
* @see #getAnalysisErrorReporterFactory()
*
* @return The marker type to use for the action's results. This initial
* implementation specifically returns
* <code>"AadlObjectMarker"</code>.
*/
protected String getMarkerType() {
return AadlConstants.AADLOBJECTMARKER;
}
/**
* Generate an error reporter factory that creates the default error reporters
* used by the analysis. The default
* error reporter is a {@link MarkerAnalysisErrorReporter} that generates
* markers with the marker type determined by {@link #getMarkerType()}.
*
* <p>This method exists so that subclasses can override
* {@link #getAnalysisErrorReporterFactory()} to create
* factories that return instances of
* {@link edu.cmu.sei.aadl.model.pluginsupport.ChainedAnalysisErrorReporter}
* where one of the delegate error reporters is the default error reporter.
*
* @see #getMarkerType()
* @see #getAnalysisErrorReporterFactory()
*/
protected final AnalysisErrorReporterFactory getDefaultAnalysisErrorReporterFactory() {
return new MarkerAnalysisErrorReporter.Factory(getMarkerType());
}
/**
* Get the factory to be used to generate error reporters for this action.
*
* @see #getMarkerType()
* @see #getDefaultAnalysisErrorReporterFactory()
*/
protected AnalysisErrorReporterFactory getAnalysisErrorReporterFactory() {
return getDefaultAnalysisErrorReporterFactory();
}
/**
* Get the error mananger used by the action.
* @return Error Reporter
*/
protected final AnalysisErrorReporterManager getErrorManager() {
return errManager;
}
/**
* Get the error manager used by the action.
* @return Error Reporter
*/
protected final WriteToFile getCSVLog() {
return csvlog;
}
/**
* Report error message on object as result of action as marker and in csv log.
* @param obj Element that has been processed by the action
* @param msg The error message
*/
public void error(final Element obj, final String msg) {
errManager.error(obj, msg);
logError(obj, msg);
}
/**
* log error message on object as result of action.
* @param msg The error message
*/
public final void logError(final String msg) {
if (csvlog != null) {
csvlog.addOutputNewline(issuePrefix + "ERROR: " + msg);
}
}
public final void logError(Element ci, final String msg) {
if (csvlog != null) {
String name = ci instanceof NamedElement ? " " + ((NamedElement) ci).getName() + ": " : ": ";
csvlog.addOutputNewline(issuePrefix + "ERROR: " + name + msg);
}
}
/**
* Log warning message on object as result of action.
* @param msg The warning message
*/
public final void logWarning(final String msg) {
if (csvlog != null) {
csvlog.addOutputNewline(issuePrefix + "Warning! " + msg);
}
}
public final void logWarning(final NamedElement e, final String msg) {
if (csvlog != null) {
csvlog.addOutputNewline(issuePrefix + "Warning! " + e.getName() + ": " + msg);
}
}
/**
* Record warning message on object as result of action as marker and in csv log
* @param obj Element that has been processed by the action
* @param msg The warning message
*/
public final void warning(final Element obj, final String msg) {
errManager.warning(obj, msg);
if (obj instanceof NamedElement)
logWarning((NamedElement) obj, msg);
else
logWarning(msg);
}
/**
* log an informative message on object as result of action.
* @param msg The informative message
*/
public final void logInfoNoNewLine(final String msg) {
if (csvlog != null) {
csvlog.addOutput(msg);
}
}
/**
* log an informative message on object as result of action.
* @param msg The informative message
*/
public final void logInfo(final String msg) {
if (csvlog != null) {
csvlog.addOutputNewline(msg);
}
}
public final void logInfo(final NamedElement e, final String msg) {
if (csvlog != null) {
csvlog.addOutputNewline(issuePrefix + e.getName() + ": " + msg);
}
}
/**
* Record an informative message on object as result of action as Marker and in CSV log
* @param obj Element that has been processed by the action
* @param msg The informative message
*/
public final void info(final Element obj, final String msg) {
errManager.info(obj, msg);
if (obj instanceof NamedElement)
logInfo((NamedElement) obj, msg);
else
logInfo(msg);
}
public void errorSummary(final NamedElement obj, String somName, String msg) {
if (somName != null && !somName.isEmpty() && !somName.equalsIgnoreCase("No Modes")) {
msg = "In SystemMode " + somName + ": " + msg;
}
errManager.error(obj, msg);
summaryReport.append("** " + msg + "\n");
}
public void warningSummary(final NamedElement obj, String somName, String msg) {
if (somName != null && !somName.isEmpty() && !somName.equalsIgnoreCase("No Modes")) {
msg = "In SystemMode " + somName + ": " + msg;
}
errManager.warning(obj, msg);
summaryReport.append("* " + msg + "\n");
}
public void infoSummary(final NamedElement obj, String somName, String msg) {
if (somName != null && !somName.isEmpty() && !somName.equalsIgnoreCase("No Modes")) {
msg = " in SystemMode " + somName + ": " + msg;
}
errManager.info(obj, msg);
summaryReport.append(msg + "\n");
}
public void infoSummaryReportOnly(Element obj, SystemOperationMode som, String msg) {
if (som != null && !som.getName().equalsIgnoreCase("No Modes")) {
msg = "In SystemMode " + som.getName() + ": " + msg;
}
summaryReport.append(msg + "\n");
}
public String getResultsMessages() {
synchronized (summaryReport) {
return summaryReport.toString();
}
}
/**
* Report an internal error in the operation of the action.
*/
protected final void internalError(final String msg) {
errManager.internalError(msg);
}
/**
* Report an internal error in the operation of the action.
*/
protected final void internalError(final Exception e) {
errManager.internalError(e);
}
/**
* Initialize the state of the action. For example,
* this can open a dialog box to get additional parameters to the
* analysis. The analysis state should be initialized by setting
* fields that are then used by {@link #analyzeDeclarativeModel}
* and {@link #analyzeInstanceModel}.
*
* <p>The default implementation of this method simply returns
* <code>true</code>.
*
* @return <code>true</code> if the analysis should proceed or
* <code>false</code> if the user cancelled the analysis.
*/
protected boolean initializeAction(NamedElement object) {
return true;
}
/**
* finalize the state of analysis. For example,
* this can close a report being generated.
* <p>The default implementation of this method simply returns
* <code>true</code>.
*
* @return <code>true</code> if the analysis should proceed or
* <code>false</code> if the user cancelled the analysis.
*/
protected boolean finalizeAction() {
if (csvlog != null) {
csvlog.saveToFile(getActionName() + " Report\n\n" + summaryReport.toString());
}
return true;
}
public String getAnalysisMarkerType() {
return getMarkerType();
}
}