blob: 7d6cc1f79bc68860955ff5241d9de7f29590d255 [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2020-2022 Robert Bosch GmbH.
*
* 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
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.app4mc.amalthea.visualization.hw;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Named;
import org.eclipse.app4mc.amalthea.model.HWModel;
import org.eclipse.app4mc.amalthea.model.HwStructure;
import org.eclipse.app4mc.amalthea.visualization.hw.templates.HWBlockDiagramCreator;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.di.extensions.Preference;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.services.IServiceConstants;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;
import net.sourceforge.plantuml.core.DiagramDescription;
import net.sourceforge.plantuml.eclipse.utils.PlantumlConstants;
public class VisualizationHandler {
private static final String SYSTEM_PROPERTY = "SystemProperty";
private static final String PLANT_UML_PREFERENCE = "PlantUMLPreference";
private static final String PROPERTY_GRAPHVIZ_DOT = "GRAPHVIZ_DOT";
private static final Logger LOGGER = LoggerFactory.getLogger(VisualizationHandler.class);
@Execute
public void execute(
Shell shell,
UISynchronize sync,
@Optional
@Preference(
nodePath = "net.sourceforge.plantuml.eclipse",
value = PlantumlConstants.GRAPHVIZ_PATH)
String dotPath,
@Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection selection) {
// **** Check selection and set HW model
if (selection.isEmpty()) {
LOGGER.info("No EObject selected!");
return;
}
if (selection.getFirstElement() instanceof EObject) {
EObject eObject = ((EObject) selection.getFirstElement());
DiagramLocation diagramLocation = new DiagramLocation(eObject);
if (!diagramLocation.isValid()) return;
// Execute job
List<HwStructure> structures = new ArrayList<>();
if (eObject instanceof HWModel) {
structures.addAll(((HWModel) eObject).getStructures());
} else if (eObject instanceof HwStructure) {
structures.add((HwStructure) eObject);
}
execute(shell, sync, dotPath, structures, diagramLocation, true, null);
}
}
/**
*
* @param shell The active Shell needed for info/error dialog creation.
* @param dotPathFromPlantUMLPreferencePage The path to Graphviz dot.exe needed for visualization.
* @param hwModel The {@link HWModel} that should be visualized.
* @param listener optional listener that is notified on diagram generation job changes.
* @return The path to the SVG.
*/
public void execute(
Shell shell,
UISynchronize sync,
String dotPathFromPlantUMLPreferencePage,
List<HwStructure> structures,
DiagramLocation diagramLocation,
boolean showSuccessInfo,
IJobChangeListener listener) {
// **** Check dot path and set GRAPHVIZ_DOT property
String dotPathFromSystemProperty = System.getenv(PROPERTY_GRAPHVIZ_DOT);
File dotFile = null;
if (dotPathFromPlantUMLPreferencePage != null) {
dotFile = ensureValidDotFile(shell, sync, dotPathFromPlantUMLPreferencePage, PLANT_UML_PREFERENCE);
} else if (dotPathFromSystemProperty != null) {
dotFile = ensureValidDotFile(shell, sync, dotPathFromSystemProperty, SYSTEM_PROPERTY);
} else {
showErrorDialog(shell, sync, "Missing Graphviz dot.exe location."
+ "\nPlease specify location via Window - Preferences - PlantUML - Path to the dot executable of Graphviz.");
}
if (dotFile == null)
return;
System.setProperty(PROPERTY_GRAPHVIZ_DOT, dotFile.getAbsolutePath());
LOGGER.info("GRAPHVIZ_DOT: {}", System.getProperty(PROPERTY_GRAPHVIZ_DOT));
// **** Generate Plant-UML file
Job job = Job.create("Generate diagram", monitor -> {
ModelToTextResult umlOutput = HWBlockDiagramCreator.generatePlantUML(structures);
if (umlOutput.error()) {
showErrorDialog(shell, sync, "Errors on generating HW Visualization for selected AMALTHEA model file: " + umlOutput.getErrorMessage());
return Status.CANCEL_STATUS;
}
// **** Write to UML to file
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(diagramLocation.getPlantUmlFilePath()))) {
bufferedWriter.write(umlOutput.getOutput().toString());
} catch (IOException e) {
return Status.CANCEL_STATUS;
}
LOGGER.info("{} created: {}", diagramLocation.getPlantUmlFileName(), umlOutput.getOutput());
// **** Render SVG output
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
FileOutputStream fileStream = new FileOutputStream(diagramLocation.getDiagramFilePath())) {
SourceStringReader reader = new SourceStringReader(umlOutput.getOutput().toString());
DiagramDescription description = reader.outputImage(outputStream, new FileFormatOption(FileFormat.SVG));
byte[] svgBytes = outputStream.toByteArray();
String svgString = new String(svgBytes, StandardCharsets.UTF_8);
fileStream.write(svgBytes);
fileStream.flush();
if (description == null || svgString.contains("An error has occured")) {
showErrorDialog(shell, sync, "SVG rendering failed."
+ "\nCheck Graphviz dot.exe location via Window - Preferences - PlantUML - Path to the dot executable of Graphviz..");
return Status.CANCEL_STATUS;
}
LOGGER.info("{} created.", diagramLocation.getDiagramFileName());
if (showSuccessInfo) {
showInfoDialog(shell, sync, "Successfully generated HW Visualization for amalthea model file.");
}
} catch (Exception e) {
showErrorDialog(shell, sync, "Unable to generate HW Visualization for selected AMALTHEA model file.");
LOGGER.error("Unable to generate HW Visualization for selected AMALTHEA model file.", e);
} finally {
try {
diagramLocation.getDiagramFolder().refreshLocal(1, new NullProgressMonitor());
} catch (CoreException ex) {
// no action required
}
}
return Status.OK_STATUS;
});
if (listener != null) {
job.addJobChangeListener(listener);
}
job.schedule();
}
private File ensureValidDotFile(Shell shell, UISynchronize sync, String dotPath, String origin) {
if (dotPath == null || dotPath.equals("")) {
showErrorDialog(shell, sync, "Missing Graphviz dot.exe location."
+ "\nPlease specify location via Window - Preferences - PlantUML - Path to the dot executable of Graphviz.");
return null;
}
final File dotFile = new File(dotPath);
if (!dotFile.canExecute()) {
if (origin.equals(PLANT_UML_PREFERENCE)) {
showErrorDialog(shell, sync, "Invalid Graphviz dot.exe location: \'" + dotFile.getAbsolutePath()+"\'"
+ "\nPlease set location via Window - Preferences - PlantUML - Path to the 'dot executable of Graphviz'.");
} else {
showErrorDialog(shell, sync, "Invalid Graphviz dot.exe location found in System Property 'GRAPHVIZ_DOT' : \'" + dotFile.getAbsolutePath()+"\'"
+ "\nPlease update the System Property 'GRAPHVIZ_DOT' with valid Path to the 'dot executable of Graphviz'.");
}
return null;
}
return dotFile;
}
private void showInfoDialog(Shell shell, UISynchronize sync, String message) {
sync.asyncExec(() ->
MessageDialog.openInformation(shell, "AMALTHEA HW Visualization", message)
);
}
private void showErrorDialog(Shell shell, UISynchronize sync, String message) {
sync.asyncExec(() ->
MessageDialog.openError(shell, "AMALTHEA HW Visualization", message)
);
}
}