Commit 40ce7e24 authored by Eyrak Paen-Rochlitz's avatar Eyrak Paen-Rochlitz
Browse files

Initial attempt at integrating diagram export into doc generator

parent 50b6c714
......@@ -17,6 +17,7 @@ package org.eclipse.etrice.generator.base.io;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
......@@ -112,6 +113,28 @@ public class GeneratorFileIO implements IGeneratorFileIO {
generatedFiles.add(path);
}
@Override
public void generateFile(String file, byte[] content) {
generateFile("generating file", file, content);
}
@Override
public void generateFile(String desc, String filePath, byte[] content) {
Path path = getPath(filePath);
long checksum = getContentChecksum(content);
if (!isUnchanged(path, checksum)) {
logger.logInfo(desc + " " + filePath);
writeBinaryFile(path, content);
setChecksumAttribute(path, checksum);
}
else {
logger.logInfo(desc + " (unchanged) " + filePath);
}
generatedFiles.add(path);
}
/**
* Removes all files in the output directory that haven't been written by this instance.
*/
......@@ -143,6 +166,21 @@ public class GeneratorFileIO implements IGeneratorFileIO {
}
}
private void writeBinaryFile(Path path, byte[] bytes) {
try {
Path parent = path.getParent();
if(parent != null) {
Files.createDirectories(parent);
}
OutputStream out = Files.newOutputStream(path);
out.write(bytes);
out.close();
}
catch(IOException e) {
throw new RuntimeIOException(e);
}
}
private boolean isUnchanged(Path path, long checksum) {
if(Files.exists(path)) {
Long oldChecksum = getChecksumAttribute(path);
......@@ -162,6 +200,12 @@ public class GeneratorFileIO implements IGeneratorFileIO {
crc.update(content.getBytes(StandardCharsets.UTF_8));
return crc.getValue();
}
private long getContentChecksum(byte[] rawContent) {
CRC32 crc = new CRC32();
crc.update(rawContent);
return crc.getValue();
}
private Long getChecksumAttribute(Path path) {
Long checksum = null;
......
......@@ -53,4 +53,22 @@ public interface IGeneratorFileIO {
generateFile(filePath, content);
}
/**
* This method saves binary content to a file in the given path.
*
* @param filePath the file path name of the file that should be marked as generated
* @param content the contents of the generated file
*/
void generateFile(String filePath, byte[] content);
/**
* This method saves binary content to a file in the given path.
*
* @param description a description which may be logged
* @param filePath the file path name of the generated file
* @param content the contents of the generated file
*/
default void generateFile(String description, String filePath, byte[] content) {
generateFile(filePath, content);
}
}
......@@ -5,12 +5,12 @@ Bundle-Vendor: Eclipse eTrice
Bundle-Version: 0.0.0.qualifier
Bundle-SymbolicName: org.eclipse.etrice.generator.doc;singleton:=true
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.etrice.core.genmodel,
org.eclipse.etrice.core.genmodel.fsm,
org.eclipse.etrice.generator.fsm,
org.eclipse.etrice.generator,
org.eclipse.etrice.core.etmap,
org.eclipse.etrice.core.etphys,
Require-Bundle: org.eclipse.etrice.core.genmodel,
org.eclipse.etrice.core.genmodel.fsm,
org.eclipse.etrice.generator.fsm,
org.eclipse.etrice.generator,
org.eclipse.etrice.core.etmap,
org.eclipse.etrice.core.etphys,
org.eclipse.core.resources,
org.eclipse.core.runtime,
org.eclipse.ui,
......@@ -18,8 +18,12 @@ Require-Bundle: org.eclipse.etrice.core.genmodel,
org.eclipse.xtend.lib,
org.eclipse.xtext.util,
org.eclipse.xtext.generator,
org.eclipse.etrice.generator.base,
org.eclipse.etrice.abstractexec.behavior
org.eclipse.etrice.generator.base,
org.eclipse.etrice.abstractexec.behavior,
org.eclipse.etrice.ui.behavior;resolution:=optional,
org.eclipse.etrice.ui.structure;resolution:=optional,
org.eclipse.etrice.ui.commands;resolution:=optional,
org.eclipse.etrice.ui.common.base;resolution:=optional
Import-Package: org.apache.log4j
Bundle-RequiredExecutionEnvironment: JavaSE-11
Export-Package: org.eclipse.etrice.generator.doc;uses:="org.eclipse.etrice.generator.base",
......
......@@ -55,7 +55,7 @@ public class Main extends AbstractGenerator {
ETMapUtil.processModels(genModel, getResourceSet(), diagnostician);
mainGenerator.doGenerate(genModel, arguments, fileIO);
mainGenerator.doGenerate(genModel, arguments, fileIO, logger);
return GENERATOR_OK;
}
......
/*******************************************************************************
* Copyright (c) 2022 protos software gmbh (http://www.protos.de).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* CONTRIBUTORS:
* epaen (initial contribution)
*
*******************************************************************************/
package org.eclipse.etrice.generator.doc.gen;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.etrice.core.fsm.fSM.ModelComponent;
import org.eclipse.etrice.core.room.StructureClass;
import org.eclipse.etrice.generator.base.io.IGeneratorFileIO;
import org.eclipse.etrice.generator.doc.gen.URIBasedDiagramResolver.DiagramType;
import org.eclipse.etrice.ui.commands.RoomOpeningHelper;
import org.eclipse.etrice.ui.common.base.UIBaseActivator;
import org.eclipse.etrice.ui.common.base.export.IBulkDiagramExporter;
import org.eclipse.etrice.ui.common.base.preferences.UIBasePreferenceConstants;
import org.eclipse.etrice.ui.common.base.support.DiagramAccessBase;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;
import com.google.inject.Inject;
public class DiagramExportService {
@Inject URIBasedDiagramResolver resolver;
public boolean exportStructure(StructureClass root, IGeneratorFileIO genIO, Path destFolder) {
return export(Collections.singletonList(root), DiagramType.STRUCTURE, genIO, destFolder);
}
public boolean exportBehavior(ModelComponent root, IGeneratorFileIO genIO, Path destFolder) {
return export(Collections.singletonList(root), DiagramType.BEHAVIOR, genIO, destFolder);
}
public boolean exportStructure(Collection<? extends StructureClass> rootList, IGeneratorFileIO genIO, Path destFolder) {
return export(rootList, DiagramType.STRUCTURE, genIO, destFolder);
}
public boolean exportBehavior(Collection<? extends ModelComponent> rootList, IGeneratorFileIO genIO, Path destFolder) {
return export(rootList, DiagramType.BEHAVIOR, genIO, destFolder);
}
private boolean export(Collection<? extends EObject> rootObjects, DiagramType diagramType, IGeneratorFileIO genIO, Path destFolder) {
String origFormat = setFormatPreference();
try (TempDirectoryAutoCleanup tmpDir = new TempDirectoryAutoCleanup()) {
ExportJob exportJob = new ExportJob(rootObjects, diagramType, tmpDir.getPath(), resolver);
exportJob.schedule();
exportJob.join();
copyToOutputDir(tmpDir.getPath(), genIO, destFolder);
return exportJob.getResult().isOK();
} catch (InterruptedException e) {
return false;
} catch (IOException e) {
return false;
} finally {
resetFormatPreference(origFormat);
}
}
private static class ExportJob extends UIJob {
final Collection<? extends EObject> rootObjects;
final DiagramType diagramType;
final Path exportDir;
final URIBasedDiagramResolver resolver;
public ExportJob(Collection<? extends EObject> rootObjects, DiagramType diagramType, Path exportDir, URIBasedDiagramResolver resolver) {
super("Export eTrice diagrams");
this.rootObjects = rootObjects;
this.diagramType = diagramType;
this.exportDir = exportDir;
this.resolver = resolver;
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (exportDir == null) {
return Status.error("Cannot determine export folder");
}
if (!canExport()) {
return Status.error("Export operation is not supported");
}
try {
List<IStatus> results = rootObjects.stream()
.map(obj -> export(obj, exportDir))
.collect(Collectors.toList());
return results.stream().allMatch(status -> status.isOK())
? Status.OK_STATUS
: new MultiStatus(
ExportJob.class, Status.OK,
results.toArray(new IStatus[results.size()]),
"Failed to export all diagrams", null
);
} catch (Exception ex) {
return Status.error("Diagram export failed with an exception", ex);
}
}
private IStatus export(EObject root, Path exportDir) {
if (!resolver.hasDiagram(root, diagramType)) {
return Status.info("Diagram not found, skipping");
}
DiagramAccessBase da = (diagramType == DiagramType.BEHAVIOR) ? RoomOpeningHelper.getBehaviorDiagramAccess()
: RoomOpeningHelper.getStructureDiagramAccess();
if (da == null) {
return Status.error("Unable to access diagram plugin");
}
IBulkDiagramExporter exporter = da.getDiagramExporter();
if (exporter == null) {
return Status.error("No exporter available for diagram");
}
exporter.export(root, exportDir.toString());
return Status.OK_STATUS;
}
private boolean canExport() {
return PlatformUI.isWorkbenchRunning();
}
};
private static class TempDirectoryAutoCleanup implements AutoCloseable {
Path path;
public TempDirectoryAutoCleanup() throws IOException {
this(UUID.randomUUID().toString());
}
public TempDirectoryAutoCleanup(String prefix) throws IOException {
this.path = Files.createTempDirectory(prefix);
}
public Path getPath() {
return path;
}
@Override
public void close() throws IOException {
deleteAll(path);
}
private void deleteAll(Path dir) throws IOException {
try (Stream<Path> paths = Files.walk(dir)) {
paths
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
}
}
private String setFormatPreference() {
IPreferenceStore store = UIBaseActivator.getDefault().getPreferenceStore();
String format = store.getString(UIBasePreferenceConstants.EXPORT_DIAGRAM_FORMAT);
store.setValue(UIBasePreferenceConstants.EXPORT_DIAGRAM_FORMAT, UIBasePreferenceConstants.FORMAT_JPG);
return format;
}
private void resetFormatPreference(String format) {
IPreferenceStore store = UIBaseActivator.getDefault().getPreferenceStore();
store.setValue(UIBasePreferenceConstants.EXPORT_DIAGRAM_FORMAT, UIBasePreferenceConstants.FORMAT_JPG);
}
private void copyToOutputDir(Path srcDir, IGeneratorFileIO genIO, Path destFolder) throws IOException {
try (Stream<Path> paths = Files.walk(srcDir)) {
List<IOException> exceptions = paths
.map(path -> {
if (!Files.isRegularFile(path)) {
return null;
}
try (InputStream istream = new FileInputStream(path.toFile())) {
byte[] imageData = istream.readAllBytes();
Path relativePath = destFolder.resolve(path.getFileName()).normalize();
genIO.generateFile(relativePath.toString(), imageData);
} catch (IOException e) {
return e;
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!exceptions.isEmpty()) {
IOException ex = new IOException("One or more files could not be copied to the output directory.");
for (IOException childEx : exceptions) {
ex.addSuppressed(childEx);
}
throw ex;
}
}
}
}
......@@ -16,22 +16,39 @@ package org.eclipse.etrice.generator.doc.gen
import com.google.inject.Inject
import com.google.inject.Singleton
import java.nio.file.Path
import java.util.Optional
import org.eclipse.etrice.core.genmodel.etricegen.Root
import org.eclipse.etrice.generator.base.io.IGeneratorFileIO
import org.eclipse.etrice.generator.base.args.Arguments
import org.eclipse.etrice.generator.base.io.IGeneratorFileIO
import static org.eclipse.etrice.generator.doc.setup.DocGeneratorOptions.INCLUDE_IMAGES
import org.eclipse.etrice.generator.base.logging.ILogger
@Singleton
class MainGen {
@Inject InstanceDiagramGen instanceDiagramGen
@Inject AsciiDocGen docGen
@Inject Optional<DiagramExportService> diagramExportService
def void doGenerate(Root root, Arguments args, IGeneratorFileIO fileIO) {
def void doGenerate(Root root, Arguments args, IGeneratorFileIO fileIO, ILogger logger) {
val includeImages = args.get(INCLUDE_IMAGES)
fileIO.logIntro();
diagramExportService.ifPresent[service |
if (includeImages) {
val Path imagesDir = Path.of("images")
logger.logInfo("DiagramExportService: generating SubSystemClass structure diagrams...")
service.exportStructure(root.subSystemClasses, fileIO, imagesDir)
logger.logInfo("DiagramExportService: generating ActorClass structure diagrams...")
service.exportStructure(root.actorClasses, fileIO, imagesDir)
logger.logInfo("DiagramExportService: generating ActorClass behavior diagrams...")
service.exportBehavior(root.actorClasses, fileIO, imagesDir)
}
]
instanceDiagramGen.doGenerate(root, fileIO);
docGen.doGenerate(root, fileIO, includeImages);
}
......
......@@ -14,6 +14,7 @@
package org.eclipse.etrice.generator.doc.setup;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
......@@ -30,12 +31,17 @@ import org.eclipse.etrice.generator.base.setup.GeneratorName;
import org.eclipse.etrice.generator.base.setup.GeneratorOptions;
import org.eclipse.etrice.generator.base.validation.IGeneratorResourceValidator;
import org.eclipse.etrice.generator.doc.Main;
import org.eclipse.etrice.generator.doc.gen.DiagramExportService;
import org.eclipse.etrice.generator.doc.gen.DocTranslationProvider;
import org.eclipse.etrice.generator.fsm.base.Diagnostician;
import org.osgi.framework.Bundle;
import com.google.inject.Binder;
import com.google.inject.MembersInjector;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.multibindings.OptionalBinder;
public class GeneratorModule implements Module {
......@@ -57,6 +63,27 @@ public class GeneratorModule implements Module {
binder.bind(EValidator.Registry.class).toInstance(EValidator.Registry.INSTANCE);
binder.bind(org.eclipse.emf.ecore.util.Diagnostician.class).to(GenerationEMFDiagnostician.class).asEagerSingleton();
OptionalBinder.newOptionalBinder(binder, DiagramExportService.class);
}
@Provides
public DiagramExportService provideDiagramExportService(MembersInjector<DiagramExportService> memberInjector) {
// check core platform
if (!Platform.isRunning()) {
return null;
}
// check plugins
Bundle bundleBehavior = Platform.getBundle("org.eclipse.etrice.ui.behavior");
Bundle bundleStructure= Platform.getBundle("org.eclipse.etrice.ui.structure");
if ((bundleBehavior == null) || (bundleStructure == null)) {
return null;
}
DiagramExportService service = new DiagramExportService();
memberInjector.injectMembers(service);
return service;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment