| /** |
| ******************************************************************************** |
| * 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) |
| ); |
| } |
| |
| } |