org.springframework.boot
spring-boot-starter-actuator
diff --git a/src/main/java/tools/vitruv/methodologist/setup/config/CustomClassLoader.java b/src/main/java/tools/vitruv/methodologist/setup/config/CustomClassLoader.java
new file mode 100644
index 0000000..5bd3253
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/config/CustomClassLoader.java
@@ -0,0 +1,26 @@
+package tools.vitruv.methodologist.setup.config;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/** The CustomClassLoader class is used to load classes from a custom classpath. */
+public class CustomClassLoader extends URLClassLoader {
+ /**
+ * The constructor of the CustomClassLoader class.
+ *
+ * @param urls The URLs of the classpath.
+ * @param parent The parent class loader.
+ */
+ public CustomClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ }
+
+ /**
+ * Adds a JAR file to the classpath.
+ *
+ * @param url The URL of the JAR file.
+ */
+ public void addJar(URL url) {
+ this.addURL(url);
+ }
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/config/MetamodelLocation.java b/src/main/java/tools/vitruv/methodologist/setup/config/MetamodelLocation.java
new file mode 100644
index 0000000..67793d3
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/config/MetamodelLocation.java
@@ -0,0 +1,11 @@
+package tools.vitruv.methodologist.setup.config;
+
+import java.io.File;
+
+/**
+ * The MetamodelLocation class is used to store the location of a metamodel and
+ * its corresponding
+ * genmodel.
+ */
+public record MetamodelLocation(File metamodel, File genmodel, String genmodelUri, String modelDirectory) {
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/config/VitruvConfiguration.java b/src/main/java/tools/vitruv/methodologist/setup/config/VitruvConfiguration.java
new file mode 100644
index 0000000..c75a410
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/config/VitruvConfiguration.java
@@ -0,0 +1,150 @@
+package tools.vitruv.methodologist.setup.config;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
+import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
+
+/** The VitruvConfiguration class is used to store the configuration of the Vitruv CLI. */
+@Slf4j
+public class VitruvConfiguration {
+
+ /**
+ * -- SETTER -- Sets the local path of the configuration.
+ *
+ * -- GETTER -- Returns the local path of the configuration.
+ */
+ @Getter @Setter private Path localPath;
+
+ /** -- GETTER -- Returns the package name. */
+ @Getter private String packageName;
+
+ /**
+ * -- SETTER -- Sets the workflow of the configuration.
+ *
+ *
-- GETTER -- Returns the workflow of the configuration.
+ */
+ @Getter @Setter private File workflow;
+
+ /** -- GETTER -- Returns the model names. */
+ @Getter private final List modelNames = new ArrayList<>();
+
+ private final List metamodelLocations = new ArrayList<>();
+
+ /** -- GETTER -- Returns the reaction file locations used by the CLI. */
+ @Getter private final List reactionLocations = new ArrayList<>();
+
+ /**
+ * Adds a metamodel location to the configuration.
+ *
+ * @param metamodelLocation The metamodel location to add.
+ */
+ public void addMetamodelLocations(MetamodelLocation metamodelLocation) {
+ this.metamodelLocations.add(metamodelLocation);
+ }
+
+ /**
+ * Sets the metamodel locations using a semicolon-separated list of {@code ecore,genmodel} pairs.
+ *
+ * @param paths The metamodel argument string.
+ */
+ public void setMetaModelLocations(String paths) {
+ // Register the GenModel resource factory
+ String nsUri = "";
+ Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
+ reg.getExtensionToFactoryMap().put("ecore", new XMIResourceFactoryImpl());
+ reg.getExtensionToFactoryMap().put("genmodel", new XMIResourceFactoryImpl());
+
+ // Register the GenModel package
+ GenModelPackage.eINSTANCE.eClass();
+
+ for (String modelPaths : paths.split(";")) {
+ String metamodelPath = modelPaths.split(",")[0];
+ String genmodelPath = modelPaths.split(",")[1];
+
+ File metamodel = new File(metamodelPath);
+ File genmodel = new File(genmodelPath);
+
+ String localModelDirectory = "";
+
+ // getting the URI from the genmodels
+ ResourceSet resourceSet = new ResourceSetImpl();
+ URI uri = URI.createFileURI(metamodel.getAbsolutePath().trim());
+ Resource resource = resourceSet.getResource(uri, true);
+ if (!resource.getContents().isEmpty()
+ && resource.getContents().get(0) instanceof EPackage ePackage) {
+ // Load the GenModel to get the modelPluginID
+ URI genmodelURI = URI.createFileURI(genmodel.getAbsolutePath());
+ nsUri = genmodelURI.toString();
+ Resource genmodelResource = resourceSet.getResource(genmodelURI, true);
+ modelNames.add(ePackage.getName());
+ if (!genmodelResource.getContents().isEmpty()
+ && genmodelResource.getContents().get(0) instanceof GenModel genModel) {
+ String packageString = removeLastSegment(genModel.getModelPluginID());
+ log.info("--------------------->>>> " + packageString);
+ this.setPackageName(packageString);
+ localModelDirectory = genModel.getModelDirectory();
+ }
+ }
+
+ this.addMetamodelLocations(
+ new MetamodelLocation(metamodel, genmodel, nsUri, localModelDirectory));
+ }
+ }
+
+ /**
+ * Sets the reaction file locations used by the CLI.
+ *
+ * @param reactionLocations list of paths to reaction files.
+ */
+ public void setReactionLocations(List reactionLocations) {
+ this.reactionLocations.clear();
+ if (reactionLocations != null) {
+ this.reactionLocations.addAll(reactionLocations);
+ }
+ }
+
+ /**
+ * Removes the last segment of a string.
+ *
+ * @param input The input string.
+ * @return The input string without the last segment.
+ */
+ public static String removeLastSegment(String input) {
+ int lastDotIndex = input.lastIndexOf('.');
+ if (lastDotIndex == -1) {
+ // No dot found, return the original string
+ return input;
+ }
+ return input.substring(0, lastDotIndex);
+ }
+
+ /**
+ * Returns the metamodel locations.
+ *
+ * @return The metamodel locations.
+ */
+ public List getMetaModelLocations() {
+ return this.metamodelLocations;
+ }
+
+ /**
+ * Sets the package name.
+ *
+ * @param packageName The package name.
+ */
+ public void setPackageName(String packageName) {
+ this.packageName = packageName.replace("\\s", "");
+ }
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/emf/EMFModelInitializer.java b/src/main/java/tools/vitruv/methodologist/setup/emf/EMFModelInitializer.java
new file mode 100644
index 0000000..dbb7da8
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/emf/EMFModelInitializer.java
@@ -0,0 +1,178 @@
+package tools.vitruv.methodologist.setup.emf;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.emf.ecore.EPackage;
+
+/**
+ * Utility class for initializing EMF packages before Xtext validation.
+ *
+ * This class loads metamodel classes from a generated model JAR and registers them with EMF's
+ * package registry to ensure Xtext can resolve nsURIs during validation.
+ */
+public class EMFModelInitializer {
+ private static final Logger logger = LogManager.getLogger(EMFModelInitializer.class);
+
+ /**
+ * Initializes EMF packages from a model JAR file.
+ *
+ * @param modelJarPath path to the generated model JAR
+ * @return map of nsURI to EPackage for all registered packages
+ */
+ public static Map initializeFromJar(String modelJarPath) {
+ Map packages = new HashMap<>();
+
+ File jarFile = new File(modelJarPath);
+ if (!jarFile.exists()) {
+ logger.warn("Model JAR not found: {}", modelJarPath);
+ return packages;
+ }
+
+ try (JarFile jar = new JarFile(jarFile)) {
+ Collection entries = jar.stream().toList();
+
+ // Look for all ecore files
+ entries.stream()
+ .filter(entry -> entry.getName().endsWith(".ecore"))
+ .forEach(
+ entry -> {
+ try {
+ loadEcorePackage(jar.getInputStream(entry), packages);
+ } catch (Exception e) {
+ logger.warn("Failed to load ecore from {}: {}", entry.getName(), e.getMessage());
+ }
+ });
+
+ // Look for FactoryImpl classes that indicate metamodel packages
+ entries.stream()
+ .filter(entry -> entry.getName().endsWith("FactoryImpl.class"))
+ .map(entry -> entry.getName().replace('/', '.').replace(".class", ""))
+ .forEach(
+ className -> {
+ try {
+ loadMetamodelPackage(className, packages);
+ } catch (Exception e) {
+ logger.debug("Could not load package from {}: {}", className, e.getMessage());
+ }
+ });
+
+ } catch (Exception e) {
+ logger.error("Error initializing EMF packages from JAR: {}", modelJarPath, e);
+ }
+
+ logger.info("Initialized {} EMF packages from model JAR", packages.size());
+ return packages;
+ }
+
+ /**
+ * Initializes EMF packages from a classes directory.
+ *
+ * @param classesPath path to the generated classes directory
+ * @param classLoader classloader to use for loading classes
+ * @return map of nsURI to EPackage for all registered packages
+ */
+ public static Map initializeFromClasses(
+ String classesPath, ClassLoader classLoader) {
+ Map packages = new HashMap<>();
+
+ try {
+ Path classesDir = Path.of(classesPath);
+ if (!Files.exists(classesDir)) {
+ logger.warn("Classes directory not found: {}", classesPath);
+ return packages;
+ }
+
+ // Find all FactoryImpl classes
+ Files.walk(classesDir)
+ .filter(p -> Files.isRegularFile(p))
+ .filter(p -> p.toString().endsWith("FactoryImpl.class"))
+ .forEach(
+ classPath -> {
+ try {
+ String className =
+ classesDir
+ .relativize(classPath)
+ .toString()
+ .replace(File.separator, ".")
+ .replace(".class", "");
+ loadMetamodelPackage(className, packages);
+ } catch (Exception e) {
+ logger.debug("Could not load package from {}: {}", classPath, e.getMessage());
+ }
+ });
+
+ } catch (Exception e) {
+ logger.error("Error initializing EMF packages from classes directory: {}", classesPath, e);
+ }
+
+ logger.info("Initialized {} EMF packages from classes directory", packages.size());
+ return packages;
+ }
+
+ /**
+ * Registers an EPackage with EMF's package registry.
+ *
+ * @param nsUri the nsURI for the package
+ * @param ePackage the EPackage instance
+ */
+ public static void registerPackage(String nsUri, EPackage ePackage) {
+ if (nsUri != null && ePackage != null) {
+ EPackage.Registry.INSTANCE.put(nsUri, ePackage);
+ logger.debug("Registered EPackage: {} -> {}", nsUri, ePackage.getName());
+ }
+ }
+
+ /**
+ * Loads an ecore model and registers its packages.
+ *
+ * @param ecoreStream input stream containing an EMF ecore file
+ * @param packages map to collect registered packages
+ */
+ private static void loadEcorePackage(
+ java.io.InputStream ecoreStream, Map packages) {
+ // This would load and parse the ecore file
+ // For now, this is a placeholder that could be extended with actual implementation
+ }
+
+ /**
+ * Loads a metamodel package class and registers it.
+ *
+ * @param className fully qualified name of a FactoryImpl class
+ * @param packages map to collect registered packages
+ */
+ private static void loadMetamodelPackage(String className, Map packages) {
+ try {
+ // Load the FactoryImpl class
+ Class> factoryClass = Class.forName(className);
+
+ // Get the eINSTANCE field
+ java.lang.reflect.Field instanceField = factoryClass.getField("eINSTANCE");
+ Object factoryInstance = instanceField.get(null);
+
+ // Get the eClass field from the package
+ String packageClassName =
+ className.substring(0, className.lastIndexOf("FactoryImpl")) + "Package";
+ Class> packageClass = Class.forName(packageClassName);
+ java.lang.reflect.Field eINSTANCE = packageClass.getField("eINSTANCE");
+ EPackage ePackage = (EPackage) eINSTANCE.get(null);
+
+ if (ePackage != null) {
+ String nsUri = ePackage.getNsURI();
+ packages.put(nsUri, ePackage);
+ registerPackage(nsUri, ePackage);
+ }
+ } catch (ClassNotFoundException e) {
+ logger.debug("Metamodel class not found on this classpath: {}", className);
+ } catch (Exception e) {
+ logger.trace("Error loading metamodel package {}: {}", className, e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/exception/MissingModelException.java b/src/main/java/tools/vitruv/methodologist/setup/exception/MissingModelException.java
new file mode 100644
index 0000000..e2be40d
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/exception/MissingModelException.java
@@ -0,0 +1,33 @@
+package tools.vitruv.methodologist.setup.exception;
+
+/** Exception thrown when a required model is missing. */
+public class MissingModelException extends Exception {
+
+ /**
+ * Exception thrown when a required model is missing.
+ *
+ * @param message
+ */
+ public MissingModelException(String message) {
+ super(message);
+ }
+
+ /**
+ * Exception thrown when a required model is missing.
+ *
+ * @param message
+ * @param cause
+ */
+ public MissingModelException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Exception thrown when a required model is missing.
+ *
+ * @param cause
+ */
+ public MissingModelException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/util/FileUtils.java b/src/main/java/tools/vitruv/methodologist/setup/util/FileUtils.java
new file mode 100644
index 0000000..18e40e0
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/util/FileUtils.java
@@ -0,0 +1,167 @@
+package tools.vitruv.methodologist.setup.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import lombok.extern.slf4j.Slf4j;
+import tools.vitruv.methodologist.setup.config.CustomClassLoader;
+
+/** The FileUtils class provides utility methods for file operations. */
+@Slf4j
+public final class FileUtils {
+
+ private FileUtils() {}
+
+ /**
+ * The CLASS_LOADER is used to load classes from JAR files at runtime. It is used to load the
+ * classes of the virtual model builder.
+ */
+ public static final CustomClassLoader CLASS_LOADER =
+ new CustomClassLoader(new URL[] {}, ClassLoader.getSystemClassLoader());
+
+ /**
+ * Copy a file to a new location.
+ *
+ * @param filePath The path of the file that should be copied.
+ * @param folderPath The path of the folder to which the file should be copied.
+ * @param relativeSubfolder The relative subfolder in which the file should be copied.
+ * @return The target file.
+ */
+ public static File copyFile(String filePath, Path folderPath, String relativeSubfolder) {
+ File source;
+ File target;
+ if (new File(filePath).isAbsolute()) {
+ source = Path.of(filePath).toFile();
+ } else {
+ source = Path.of(new File("").getAbsolutePath().trim() + "/" + filePath.trim()).toFile();
+ }
+ if (folderPath.isAbsolute()) {
+ target =
+ Path.of(folderPath.toString().trim() + "/" + relativeSubfolder + source.getName().trim())
+ .toFile();
+ } else {
+
+ target =
+ Path.of(
+ new File("").getAbsolutePath().trim()
+ + "/"
+ + folderPath.toString().trim()
+ + "/"
+ + relativeSubfolder
+ + source.getName().trim())
+ .toFile();
+ }
+ // Files.copy throws a misleading Exception if the target File and/or the
+ // folders of the target file are not existing.
+ log.info("Copying file " + source.getAbsolutePath() + " to " + target.getAbsolutePath());
+ target.getParentFile().mkdirs();
+ try {
+ target.createNewFile();
+ Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return target;
+ }
+
+ /**
+ * Create a new file in the given path.
+ *
+ * @param filePath The path of the file that should be created.
+ */
+ public static void createFile(String filePath) {
+ File file = new File(filePath);
+ try {
+ // Ensure the directory exists
+ File parentDir = file.getParentFile();
+ if (parentDir != null && !parentDir.exists()) {
+ parentDir.mkdirs();
+ }
+ // Create the file
+ if (file.createNewFile()) {
+ log.info("File created: " + file.getAbsolutePath());
+ } else {
+ log.info("File already exists: " + file.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ log.error("An error occurred while creating the file: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Create a new folder in the given path.
+ *
+ * @param path The path of the folder that should be created.
+ * @param folder The name of the folder that should be created.
+ * @return The created folder.
+ */
+ public static Path createNewFolder(Path path, String folder) {
+ Path folderPath = path.resolve(folder);
+ File file = folderPath.toFile();
+ if (file.mkdirs()) {
+ log.info("Directory created: " + file.getAbsolutePath());
+ } else {
+ log.info("Directory already exists: " + file.getAbsolutePath());
+ }
+ return folderPath;
+ }
+
+ public static String findOption(File file, String option) {
+ try {
+ for (String line : Files.readAllLines(file.toPath())) {
+ if (line.startsWith(option)) {
+ return line.substring(option.length()).trim();
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ throw new IllegalArgumentException("Option: " + option + "not found in given file!");
+ }
+
+ /**
+ * Adding Jar to a class path.
+ *
+ * @param jarPath The path of the JAR file that should be added to the class path.
+ */
+ public static void addJarToClassPath(String jarPath) {
+ try {
+ URL jarUrl = new URL("file:///" + jarPath);
+ CLASS_LOADER.addJar(jarUrl);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ try {
+ // Open the JAR file
+ JarFile jarFile = new JarFile(new File(jarPath));
+
+ // Get the entries in the JAR file
+ Enumeration entries = jarFile.entries();
+
+ // Iterate through the entries
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+
+ // Check if the entry is a class file
+ if (entry.getName().endsWith(".class")) {
+ // Print the class name
+ String className = entry.getName().replace("/", ".").replace(".class", "");
+ log.info(className);
+ }
+ }
+
+ // Close the JAR file
+ jarFile.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/tools/vitruv/methodologist/setup/vsum/controller/VsumController.java b/src/main/java/tools/vitruv/methodologist/setup/vsum/controller/VsumController.java
new file mode 100644
index 0000000..a52125e
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/vsum/controller/VsumController.java
@@ -0,0 +1,89 @@
+package tools.vitruv.methodologist.setup.vsum.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ContentDisposition;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import tools.vitruv.methodologist.setup.vsum.service.VsumProjectBuildService;
+
+/** REST API for VSUM project generation and packaging. */
+@RestController
+@RequestMapping("/api/vsum")
+@RequiredArgsConstructor
+@Tag(name = "VSUM", description = "VSUM project generation endpoints")
+public class VsumController {
+
+ private final VsumProjectBuildService vsumProjectBuildService;
+
+ /**
+ * Accepts model and reaction files, builds a VSUM project, and returns the built project zip.
+ *
+ * @param metamodelFiles metamodel files
+ * @param genmodelFiles genmodel files paired by index with metamodel files
+ * @param reactionFiles optional reaction files
+ * @return zip file response containing generated project
+ */
+ @GetMapping(value = "/build", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ @Operation(
+ summary = "Build VSUM project",
+ description =
+ "Uploads metamodel/genmodel files, builds the project from templates, and returns a zip archive",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Project built successfully",
+ content = @Content(mediaType = "application/zip")),
+ @ApiResponse(responseCode = "400", description = "Invalid input files"),
+ @ApiResponse(responseCode = "500", description = "Build failed")
+ })
+ public ResponseEntity buildProject(
+ @Parameter(description = "Metamodel files", required = true)
+ @RequestPart("metamodelFiles")
+ List metamodelFiles,
+ @Parameter(description = "Genmodel files paired by index with metamodel files", required = true)
+ @RequestPart("genmodelFiles")
+ List genmodelFiles,
+ @Parameter(description = "Optional reaction files")
+ @RequestPart(value = "reactionFiles", required = false)
+ List reactionFiles) {
+
+ byte[] archive =
+ vsumProjectBuildService.buildProjectArchive(metamodelFiles, genmodelFiles, reactionFiles);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.parseMediaType("application/zip"));
+ headers.setContentDisposition(
+ ContentDisposition.attachment()
+ .filename(generateArchiveFilename(), StandardCharsets.UTF_8)
+ .build());
+ headers.setContentLength(archive.length);
+
+ return ResponseEntity.ok().headers(headers).body(archive);
+ }
+
+ /**
+ * Creates a deterministic archive filename prefix for build downloads.
+ *
+ * @return zip filename
+ */
+ private String generateArchiveFilename() {
+ String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+ return "vsum-project-" + timestamp + ".zip";
+ }
+}
+
diff --git a/src/main/java/tools/vitruv/methodologist/setup/vsum/service/GenerateFromTemplate.java b/src/main/java/tools/vitruv/methodologist/setup/vsum/service/GenerateFromTemplate.java
new file mode 100644
index 0000000..c65cb56
--- /dev/null
+++ b/src/main/java/tools/vitruv/methodologist/setup/vsum/service/GenerateFromTemplate.java
@@ -0,0 +1,327 @@
+package tools.vitruv.methodologist.setup.vsum.service;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import tools.vitruv.methodologist.setup.config.MetamodelLocation;
+import tools.vitruv.methodologist.setup.config.VitruvConfiguration;
+import tools.vitruv.methodologist.setup.exception.MissingModelException;
+import tools.vitruv.methodologist.setup.util.FileUtils;
+
+/** This class is responsible for generating files from templates. */
+@Slf4j
+@Service
+public class GenerateFromTemplate {
+ /** Constructor. */
+ public GenerateFromTemplate() {}
+
+ private static final String PACKAGE_NAME = "packageName";
+
+ private Configuration getConfiguration() {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
+ cfg.setDefaultEncoding("UTF-8");
+ cfg.setClassForTemplateLoading(this.getClass(), "/templates");
+ cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ cfg.setLogTemplateExceptions(false);
+ cfg.setWrapUncheckedExceptions(true);
+ return cfg;
+ }
+
+ private void writeTemplate(Template template, File filePath, Map data)
+ throws IOException {
+ FileUtils.createFile(filePath.getAbsolutePath());
+ try (Writer fileWriter = new FileWriter(filePath.getAbsolutePath(), false)) {
+ template.process(data, fileWriter);
+ fileWriter.flush();
+ log.info("writing to " + filePath.getAbsolutePath());
+ } catch (TemplateException e) {
+ throw new IOException("Failed to process template for file: " + filePath.getAbsolutePath(), e);
+ }
+ }
+
+ private Template loadTemplate(Configuration cfg, String templateName) throws IOException {
+ try {
+ return cfg.getTemplate(templateName);
+ } catch (IOException e) {
+ throw new IOException("Could not load Freemarker template: " + templateName, e);
+ }
+ }
+
+ /**
+ * Generates the root pom file.
+ *
+ * @param filePath The file path to write the root pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ * @throws MissingModelException If the package name is missing.
+ */
+ public void generateRootPom(File filePath, String packageName)
+ throws IOException, MissingModelException {
+
+ if (packageName == null || packageName.isEmpty()) {
+ throw new MissingModelException("-m ModelOption is missing the PackageName");
+ }
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "rootPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the vsum pom file.
+ *
+ * @param filePath The file path to write the vsum pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateVsumPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "vsumPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the vsum example file.
+ *
+ * @param filePath The file path to write the vsum example file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateVsumExample(File filePath, String packageName, List models)
+ throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+ data.put("models", models);
+
+ Template template = loadTemplate(cfg, "vsumExample.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the p2wrappers pom file.
+ *
+ * @param filePath The file path to write the p2wrappers pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateP2WrappersPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "p2wrappersPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the javautils pom file.
+ *
+ * @param filePath The file path to write the javautils pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateJavaUtilsPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "javautilsPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the xannotations pom file.
+ *
+ * @param filePath The file path to write the xannotations pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateXAnnotationsPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "xannotationsPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the emfutils pom file.
+ *
+ * @param filePath The file path to write the emfutils pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateEMFUtilsPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "emfutilsPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the vsum test file.
+ *
+ * @param filePath The file path to write the vsum test file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateVsumTest(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "vsumTest.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the project file.
+ *
+ * @param filePath The file path to write the project file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateProjectFile(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put("packageName", packageName.trim());
+
+ Template template = loadTemplate(cfg, "project.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the model pom file.
+ *
+ * @param filePath The file path to write the model pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateModelPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put(PACKAGE_NAME, packageName);
+
+ Template template = loadTemplate(cfg, "modelPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ /**
+ * Generates the consistency pom file.
+ *
+ * @param filePath The file path to write the consistency pom file to.
+ * @param packageName The package name from the genmodel.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateConsistencyPom(File filePath, String packageName) throws IOException {
+ Configuration cfg = getConfiguration();
+
+ Map data = new HashMap<>();
+ data.put(PACKAGE_NAME, packageName);
+
+ Template template = loadTemplate(cfg, "consistencyPom.ftl");
+ writeTemplate(template, filePath, data);
+ }
+
+ private String getNormalizedDirectoryString(String targetDir) {
+ return targetDir.replace("\\", "/").replaceAll("//+", "/");
+ }
+
+ /**
+ * Generates the mwe2 file.
+ *
+ * @param filePath the file path to write the mwe2 file to.
+ * @param models the list of metamodel locations.
+ * @param config the vitruv cli configuration.
+ * @throws java.io.IOException If the file cannot be written.
+ */
+ public void generateMwe2(
+ File filePath, List models, VitruvConfiguration config)
+ throws IOException {
+
+ Configuration cfg = getConfiguration();
+ List