diff --git a/.checkstyle b/.checkstyle
new file mode 100644
index 0000000..435a63d
--- /dev/null
+++ b/.checkstyle
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 78f9a62..ae43790 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
target/
bin/
+**/.polyglot.build.properties
xtend-gen/
-
*.xtendbin
-*._trace
+*._trace
\ No newline at end of file
diff --git a/README.md b/README.md
index 7bc3663..9e025d3 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-# Ecore-Workflow
\ No newline at end of file
+# Ecore-Workflow
+
+* see also [Switch Documentation](docu/switches.MD)
diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF
index fb8d8e7..3a568bf 100644
--- a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF
+++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF
@@ -6,10 +6,13 @@ Bundle-Version: 0.1.0.qualifier
Automatic-Module-Name: tools.mdsd.ecoreworkflow.mwe2lib
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: tools.mdsd.ecoreworkflow.mwe2lib.bean,
- tools.mdsd.ecoreworkflow.mwe2lib.component
+ tools.mdsd.ecoreworkflow.mwe2lib.component,
+ tools.mdsd.ecoreworkflow.mwe2lib.util
Require-Bundle: org.eclipse.emf.mwe.utils,
org.eclipse.emf.ecore,
org.eclipse.emf.mwe.core,
org.apache.commons.logging,
- org.eclipse.core.resources
+ org.eclipse.core.resources,
+ org.eclipse.emf.codegen.ecore;bundle-version="2.18.0"
Import-Package: org.eclipse.core.runtime
+DynamicImport-Package: *
diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/AdditionalTemplateGenerator.java b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/AdditionalTemplateGenerator.java
new file mode 100644
index 0000000..c27a313
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/AdditionalTemplateGenerator.java
@@ -0,0 +1,129 @@
+package tools.mdsd.ecoreworkflow.mwe2lib.component;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
+import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
+import org.eclipse.emf.codegen.ecore.genmodel.impl.GenModelImpl;
+import org.eclipse.emf.common.util.URI;
+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.mwe.core.WorkflowContext;
+import org.eclipse.emf.mwe.core.issues.Issues;
+import org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent2;
+import org.eclipse.emf.mwe.core.monitor.ProgressMonitor;
+import org.eclipse.emf.mwe2.runtime.Mandatory;
+
+import tools.mdsd.ecoreworkflow.mwe2lib.util.URIToPath;
+
+/**
+ * MWE2 component used to generate an additional extra file for an ecore package
+ * based on a template.
+ *
+ */
+public class AdditionalTemplateGenerator extends AbstractWorkflowComponent2 {
+
+ private String destPath;
+ private String genModel;
+ private List packageLevelGenerators;
+
+
+ public AdditionalTemplateGenerator() {
+ packageLevelGenerators = new ArrayList<>();
+ }
+
+ /**
+ * Set the path to the base folder.
+ * (This is usually a platform:-URL pointing at the target project's src-gen folder).
+ * The template's relative path will be resolved against this URL.
+ *
+ * @param destPath path to the base folder
+ */
+ @Mandatory
+ public void setDestPath(String destPath) {
+ this.destPath = destPath;
+ }
+
+ /**
+ * Set the path to the .genmodel-file.
+ * The template will have acces to the genmodel.
+ *
+ * @param genModel path to the .genmodel file
+ */
+ @Mandatory
+ public void setGenModel(String genModel) {
+ this.genModel = genModel;
+ }
+
+ /**
+ * Add a template (=generator) to be executed.
+ * @param gen classname of the generator to be added. Must be on the classpath and must be a subclass of tools.mdsd.ecoreworkflow.mwe2lib.component.PackageLevelCodeFileGenerator.
+ */
+ public void addPackageLevelGenerator(String gen) {
+ try {
+ packageLevelGenerators.add((PackageLevelCodeFileGenerator) Class.forName(gen).getConstructor().newInstance());
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // This method is the workflow component's entry point.
+ @Override
+ protected void invokeInternal(WorkflowContext workflowContext, ProgressMonitor progressMonitor, Issues issues) {
+ // ProgressMonitor is a (useless) NullProgressMonitor in the mwe2 context :(
+ progressMonitor.beginTask("Creating additional templates", 20);
+
+ GenModel loadedGenModel = loadGenModel(this.genModel);
+ runGenerator(loadedGenModel);
+
+ progressMonitor.done();
+
+ }
+
+ /**
+ * Load the genmodel as an ecore resource
+ * @param pathToGenmodelFile TODO
+ * @return
+ */
+ private GenModel loadGenModel(String pathToGenmodelFile) {
+ ResourceSet resSet = new ResourceSetImpl();
+ Resource resource = resSet.getResource(URI.createURI(pathToGenmodelFile), true);
+ GenModel genModel = (GenModelImpl) resource.getContents().get(0);
+ return genModel;
+ }
+
+ private void runGenerator(GenModel genModel) {
+ genModel.getGenPackages().forEach(this::generatePackageLevelCode);
+ }
+
+ private void generatePackageLevelCode(GenPackage genPackage) {
+ Path path = Paths.get(new URIToPath().convertUri(URI.createURI(destPath)));
+ for (PackageLevelCodeFileGenerator gen : packageLevelGenerators) {
+ gen.setGenPackage(genPackage);
+ Path outfilePath = path.resolve(gen.getRelativePath());
+
+ try {
+ Files.createDirectories(outfilePath.getParent());
+ } catch (IOException e) {
+ throw new RuntimeException("Parent directory could not be created", e);
+ }
+
+ try (FileOutputStream out = new FileOutputStream(outfilePath.toFile())) {
+ gen.generateCode(out);
+ } catch (IOException e) {
+ throw new RuntimeException("Error writing the generated code", e);
+ }
+ }
+ }
+
+
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/GapPatternPostProcessor.java b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/GapPatternPostProcessor.java
index 3b2ca30..21986a3 100644
--- a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/GapPatternPostProcessor.java
+++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/GapPatternPostProcessor.java
@@ -17,10 +17,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
import org.eclipse.emf.mwe.core.WorkflowContext;
@@ -28,6 +25,8 @@
import org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent2;
import org.eclipse.emf.mwe.core.monitor.ProgressMonitor;
+import tools.mdsd.ecoreworkflow.mwe2lib.util.URIToPath;
+
public class GapPatternPostProcessor extends AbstractWorkflowComponent2 {
private static final String CLASSNAME_MATCHER_PATTERN =
"(?<=[^a-zA-Z\\d_$])(%s)(?=[^a-zA-Z\\d_$])";
@@ -61,11 +60,11 @@ protected void invokeInternal(WorkflowContext arg0, ProgressMonitor arg1, Issues
try {
List manualFolders = set.getManualSourceFolders().stream()
.map(URI::createURI)
- .map(this::convertUri)
+ .map(new URIToPath()::convertUri)
.map(Paths::get).collect(Collectors.toList());
List generatedFolders = set.getGeneratedSourceFolders().stream()
.map(URI::createURI)
- .map(this::convertUri)
+ .map(new URIToPath()::convertUri)
.map(Paths::get)
.collect(Collectors.toList());
@@ -117,19 +116,4 @@ public FileVisitResult visitFile(Path arg0, BasicFileAttributes arg1) throws IOE
}
arg1.done();
}
-
- protected String convertUri(URI uri) {
- if (uri.isPlatform()) {
- if (Platform.isRunning()) {
- return ResourcesPlugin.getWorkspace().getRoot()
- .getFile(new org.eclipse.core.runtime.Path(uri.toPlatformString(true))).getLocation()
- .toString();
- } else {
- return EcorePlugin.resolvePlatformResourcePath(uri.toPlatformString(true)).toFileString();
- }
- } else {
- return uriConverter.normalize(uri).toFileString();
- }
- }
-
}
diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/PackageLevelCodeFileGenerator.java b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/PackageLevelCodeFileGenerator.java
new file mode 100644
index 0000000..5471412
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/PackageLevelCodeFileGenerator.java
@@ -0,0 +1,32 @@
+package tools.mdsd.ecoreworkflow.mwe2lib.component;
+
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
+
+/**
+ * Generator that can generate an additional single code file given a GenPackage.
+ * The kind and contents of the generated is completely up to the implementation.
+ */
+public interface PackageLevelCodeFileGenerator {
+ /**
+ * Generate the code now.
+ * Must only be invoked after setGenPackage
+ * @param out the output stream to write the code to.
+ */
+ public void generateCode(OutputStream out);
+
+ /**
+ * Specify where the generated code is to be written to,
+ * relative to the src-gen folder of the ecore code generation
+ * @return a relative Path
+ */
+ public Path getRelativePath();
+
+ /**
+ * Set the GenPackage that the code is to be based upon / created for.
+ * @param genPackage
+ */
+ public void setGenPackage(GenPackage genPackage);
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/util/URIToPath.java b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/util/URIToPath.java
new file mode 100644
index 0000000..5619eb7
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/util/URIToPath.java
@@ -0,0 +1,32 @@
+package tools.mdsd.ecoreworkflow.mwe2lib.util;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.plugin.EcorePlugin;
+import org.eclipse.emf.ecore.resource.URIConverter;
+import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
+
+/**
+ * utility class for converting platform: and other URIs to absolute file paths
+ * (method copied from {@link tools.mdsd.ecoreworkflow.mwe2lib.component.GapPatternPostProcessor})
+ *
+ *
+ */
+
+public class URIToPath {
+ public String convertUri(URI uri) {
+ if (uri.isPlatform()) {
+ if (Platform.isRunning()) {
+ return ResourcesPlugin.getWorkspace().getRoot()
+ .getFile(new org.eclipse.core.runtime.Path(uri.toPlatformString(true))).getLocation()
+ .toString();
+ } else {
+ return EcorePlugin.resolvePlatformResourcePath(uri.toPlatformString(true)).toFileString();
+ }
+ } else {
+ URIConverter uriConverter = new ExtensibleURIConverterImpl();
+ return uriConverter.normalize(uri).toFileString();
+ }
+ }
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/.checkstyle b/bundles/tools.mdsd.ecoreworkflow.switches/.checkstyle
new file mode 100644
index 0000000..435a63d
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/.checkstyle
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/.classpath b/bundles/tools.mdsd.ecoreworkflow.switches/.classpath
new file mode 100644
index 0000000..428337e
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/.gitignore b/bundles/tools.mdsd.ecoreworkflow.switches/.gitignore
new file mode 100644
index 0000000..b9d07cf
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/.gitignore
@@ -0,0 +1 @@
+xtend-gen
\ No newline at end of file
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/.project b/bundles/tools.mdsd.ecoreworkflow.switches/.project
new file mode 100644
index 0000000..11ca6cb
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/.project
@@ -0,0 +1,40 @@
+
+
+ tools.mdsd.ecoreworkflow.switches
+
+
+
+
+
+ org.eclipse.xtext.ui.shared.xtextBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ net.sf.eclipsecs.core.CheckstyleBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.xtext.ui.shared.xtextNature
+ net.sf.eclipsecs.core.CheckstyleNature
+
+
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/.settings/org.eclipse.jdt.core.prefs b/bundles/tools.mdsd.ecoreworkflow.switches/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..a3743d7
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF
@@ -0,0 +1,19 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: MDSD Tools Ecore Workflow Switches
+Bundle-SymbolicName: tools.mdsd.ecoreworkflow.switches
+Bundle-Version: 0.1.0.qualifier
+Automatic-Module-Name: tools.mdsd.ecoreworkflow.switches
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: tools.mdsd.ecoreworkflow.switches
+Require-Bundle: org.eclipse.emf.mwe.utils,
+ org.eclipse.emf.ecore,
+ org.eclipse.emf.mwe.core,
+ org.apache.commons.logging,
+ org.eclipse.core.resources,
+ org.eclipse.emf.codegen.ecore;bundle-version="2.18.0",
+ org.eclipse.xtext.xbase.lib;bundle-version="2.18.0",
+ tools.mdsd.ecoreworkflow.mwe2lib;bundle-version="0.1.0",
+ net.bytebuddy.byte-buddy;bundle-version="1.9.0"
+Import-Package: org.eclipse.core.runtime
+DynamicImport-Package: *
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/build.properties b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties
new file mode 100644
index 0000000..d8e2f0e
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties
@@ -0,0 +1,5 @@
+source.. = src/,\
+ xtend-gen/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java
new file mode 100644
index 0000000..f10aaff
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java
@@ -0,0 +1,55 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EcorePackage;
+
+/**
+ * an implementation of DynamicSwitch and InspectableSwitch that serves as a base class for dynamic switch implementations
+ * to which it leaves the actual implementation of the doSwitch method
+ *
+ * @param the result type of the case methods
+ */
+public abstract class AbstractInspectableDynamicSwitch implements DynamicSwitch, InspectableSwitch {
+
+ protected Map> caseDefinitions = new LinkedHashMap<>();
+ protected Function defaultCase;
+ private static final EClass E_OBJECT_CLASS = EcorePackage.Literals.EOBJECT;
+
+ @Override
+ public DynamicSwitch dynamicCase(EClass clazz, Function then) {
+ if (!canDafineCases()) {
+ throw new IllegalStateException("The switch was modified after already being used");
+ }
+ if (E_OBJECT_CLASS.equals(clazz)) {
+ // special treatment necessary, because EObject might not be caught otherwise (EObject is not always returned by eGetSupertypes()).
+ defaultCase(then);
+ } else {
+ caseDefinitions.put(clazz, then);
+ }
+ return this;
+ }
+
+ @Override
+ public DynamicSwitch defaultCase(Function then) {
+ this.defaultCase = then;
+ return this;
+ }
+
+ @Override
+ public Map> getCases() {
+ return Collections.unmodifiableMap(caseDefinitions);
+ }
+
+ @Override
+ public Function getDefaultCase() {
+ return defaultCase;
+ }
+
+ protected abstract boolean canDafineCases();
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java
new file mode 100644
index 0000000..72c47e9
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java
@@ -0,0 +1,18 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * can take an EObject and perform switching.
+ *
+ * @param the return type of the switch's clauses
+ */
+public interface ApplyableSwitch {
+
+ /**
+ * takes an eObject and applies the best-matching case.
+ * @param object the input for the switch
+ * @return the switches output
+ */
+ T doSwitch(EObject object);
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BreadthFirstSearch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BreadthFirstSearch.java
new file mode 100644
index 0000000..51bddc1
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BreadthFirstSearch.java
@@ -0,0 +1,65 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Queue;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+/***
+ * Utility class for performing breadth-first search on a graph that is only implicitly
+ * given by its root node and a function that returns each node's neighbours.
+ * @author Christian
+ *
+ * @param
+ */
+public class BreadthFirstSearch {
+
+ /**
+ * perform a breadth-first search in an acyclic graph represented by a root node and a
+ * given exploration relationship and return the first matching node.
+ *
+ * @param rootNode the starting node
+ * @param criterion used to check if a node matches. Is also given the set of currently unexplored nodes.
+ * @param getParents exploration relationship
+ * @return the first matching node, null when no node is found
+ */
+ public T find(T rootNode, BiPredicate> criterion,
+ Function> getParents) {
+ Queue queue = new ArrayDeque<>();
+ queue.add(rootNode);
+ while (!queue.isEmpty()) {
+ T current = queue.remove();
+ if (criterion.test(current, Collections.unmodifiableCollection(queue))) {
+ return current;
+ } else {
+ queue.addAll(getParents.apply(current));
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * perform a breadth-first search in an acyclic graph represented by a root node and a
+ * given exploration relationship and feed all nodes into the given consumer in the order
+ * that the breadth-first search finds them.
+ * The consumer also has access to a list of unexplored nodes in the queue.
+ * @param rootNode
+ * @param consumer
+ * @param getParents
+ */
+ public void scan(T rootNode, BiConsumer> consumer,
+ Function> getParents) {
+ Queue queue = new ArrayDeque<>();
+ queue.add(rootNode);
+ while (!queue.isEmpty()) {
+ T current = queue.remove();
+ consumer.accept(current, Collections.unmodifiableCollection(queue));
+ queue.addAll(getParents.apply(current));
+ }
+ }
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java
new file mode 100644
index 0000000..27350e9
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java
@@ -0,0 +1,62 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import tools.mdsd.ecoreworkflow.switches.bytecodegen.ByteCodeSwitchCompiler;
+
+/**
+ * A dynamic switch that is accelerated using bytecode generation.
+ * Note that for this to work, knowledge of the complete package hierarchy that might
+ * be fed into the doSwitch method is required. Use the addPackage-method to add the
+ * possible packages.
+ *
+ * @param the return type of the case methods
+ */
+public class BytecodeDynamicSwitch extends AbstractInspectableDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch {
+
+ /**
+ * a pre-compiled switch to which we delegate all doSwitch calls
+ */
+ private ApplyableSwitch compiledSwitch;
+ private Set explicitPackages = new HashSet<>();
+
+ public BytecodeDynamicSwitch precompile() {
+ if (compiledSwitch == null) {
+ compiledSwitch = new ByteCodeSwitchCompiler(caseDefinitions, defaultCase, explicitPackages).compileSwitch();
+ }
+ return this;
+ }
+
+ /**
+ * Tell the switch that objects fed into the switch might have
+ * a dynamic type of a class in the given package.
+ *
+ * If a package's class is part of a case definition, that package is
+ * already implicitly added and it is not necessary to call addPackage
+ *
+ * @param ePackage
+ * @return itself (builder pattern)
+ */
+ public BytecodeDynamicSwitch addPackage(EPackage ePackage) {
+ explicitPackages.add(ePackage);
+ return this;
+ }
+
+ @Override
+ protected boolean canDafineCases() {
+ /** don't allow to define more cases once the switch has been compiled.
+ Clients who need this can merge the switch into a new one and add their
+ cases there. **/
+ return compiledSwitch == null;
+ }
+
+ @Override
+ public T doSwitch(EObject object) {
+ precompile();
+ return compiledSwitch.doSwitch(object);
+ }
+}
+
+
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java
new file mode 100644
index 0000000..c75616e
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java
@@ -0,0 +1,52 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * a switch upon which cases may be defined dynamically.
+ *
+ * @param the return type of the switch's clauses
+ */
+/* inheriting from ApplyableSwitch, InspectableSwitch is against single-concern principles,
+ but is necessary for the builder pattern */
+public interface DynamicSwitch extends ApplyableSwitch, InspectableSwitch,
+ MergeableSwitch, DynamicSwitch> {
+ /**
+ * add a dynamically specified case clause to the switch.
+ *
+ * @param clazz the class for which to define the case
+ * @param then the functional body of the case clause
+ * @return {@code this}
+ */
+ DynamicSwitch dynamicCase(EClass clazz, Function then);
+
+ /**
+ * define the default case clause for the switch.
+ *
+ * @param then the functional body of the case clause
+ * @return {@code this}
+ */
+ DynamicSwitch defaultCase(Function then);
+
+ /**
+ * merge another switch into this switch. equivalent to defining all of the other switches cases
+ * and default case on this switch
+ *
+ * @param other the other switch
+ * @return {@code this}
+ */
+ default DynamicSwitch merge(InspectableSwitch extends T> other) {
+ other.getCases().forEach((clazz, then) -> {
+ this.dynamicCase(clazz, then::apply); // up-casting if necessary
+ });
+ Function defaultCase = other.getDefaultCase();
+ if (defaultCase != null) {
+ defaultCase(defaultCase::apply); // up-casting if necessary
+ }
+ return this;
+ }
+
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java
new file mode 100644
index 0000000..5419238
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java
@@ -0,0 +1,86 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * A dynamic switch implementation that is optimized for being quick when the objects the graph is
+ * applied to are hierarchically close to the types the cases are defined on.
+ *
+ *
+ * Switching works by exploring an objects type hierarchy upwards until a match is found in the
+ * table of registered cases.
+ *
+ *
+ *
+ * Note that matching an object to the default cases therefore involves scanning its whole, possibly
+ * complex, type hierarchy. On the other hand the lookup complexity is sublinear in the number of
+ * registered cases.
+ *
+ *
+ * @author Christian van Rensen
+ *
+ * @param the return type of the switch's clauses
+ */
+public class HashDynamicSwitch extends AbstractInspectableDynamicSwitch
+ implements DynamicSwitch, ApplyableSwitch, InspectableSwitch {
+
+ private Map[]> cachedInvokationSequences =
+ new HashMap<>();
+
+ @Override
+ public T doSwitch(EObject object) {
+ EClass eClass = object.eClass();
+
+ // determine in which order to call which cases
+ Function[] targets = cachedInvokationSequences
+ .computeIfAbsent(eClass, this::calculateInvocationSequence);
+
+ // and then call those cases until one does not delegate.
+ for (Function target : targets) {
+ T evaluation = target.apply(object);
+ if (evaluation != null) {
+ return evaluation;
+ }
+
+ }
+
+ if (defaultCase != null) {
+ return defaultCase.apply(object); // the default case will not fall through!
+ }
+
+ throw new SwitchingException("no default case defined");
+ }
+
+ @Override
+ protected boolean canDafineCases() {
+ return cachedInvokationSequences.isEmpty(); // adding cases to a used switch might cause synchronization issues
+ }
+
+ @SuppressWarnings("unchecked") /*
+ * not problematic as the function we return comes from our
+ * correctly typed map.
+ */
+ private Function[] calculateInvocationSequence(EClass eClass) {
+
+ // In a tree that only contains the longest possibly path to each ancestor, do a
+ // breadth-first-search and add all results to a list of fall-through-targets.
+ // This algorithm is compatible to EMF's semantics which were reverse-engineered.
+
+ List> invocations = new ArrayList<>();
+ new BreadthFirstSearch().scan(eClass, (c, r) -> {
+ // the second part of the condition ensures that we are exploring a longest path.
+ if (caseDefinitions.containsKey(c) && r.stream().noneMatch(c::isSuperTypeOf)) {
+ invocations.add(caseDefinitions.get(c));
+ }
+ }, EClass::getESuperTypes);
+
+ return invocations.toArray(new Function[0]);
+ }
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/InspectableSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/InspectableSwitch.java
new file mode 100644
index 0000000..7f76094
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/InspectableSwitch.java
@@ -0,0 +1,16 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.Map;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * a switch that can list the cases upon which it is defined.
+ * @param the return type of the switch's clauses
+ */
+public interface InspectableSwitch {
+ Map> getCases();
+
+ Function getDefaultCase();
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitch.java
new file mode 100644
index 0000000..8c5bae1
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitch.java
@@ -0,0 +1,54 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.util.List;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+
+/**
+ * Base class for static (=package specific) switches generated with the MSwitchClassGenerator.
+ *
+ * @param return type of the case methods
+ */
+public abstract class MSwitch implements ApplyableSwitch, InspectableSwitch {
+ protected Function defaultCase;
+
+ public MSwitch() {
+ super();
+ }
+
+ public T doSwitch(EObject s) {
+ return doSwitch(s.eClass(), s);
+ }
+
+ protected T doSwitch(EClass eClass, EObject eObject) {
+ if (isSwitchFor(eClass.getEPackage())) {
+ return doSwitch(eClass.getClassifierID(), eObject);
+ } else { // logic as in Ecore: when a type comes from an unknown child package, climb up to its first supertype.
+ // This is NOT 100% compliant to the semantics of DynamicSwitch in a cross package setting.
+ // However, use cases where this matters should be very rare, because static switches are used for intra-package switching.
+ // It would be even uglier if the behaviour of dynamic switching depended on package boundaries.
+ List eSuperTypes = eClass.getESuperTypes();
+ return eSuperTypes.isEmpty() ? applyDefaultCase(eObject) : doSwitch(eSuperTypes.get(0), eObject);
+ }
+ }
+
+ protected abstract T doSwitch(int classifierID, EObject eObject) throws SwitchingException;
+
+ @Override
+ public Function getDefaultCase() {
+ return defaultCase;
+ }
+
+ protected T applyDefaultCase(EObject eObject) {
+ if (defaultCase != null) {
+ return defaultCase.apply(eObject); // the default case will not fall through
+ } else {
+ throw new SwitchingException("no default case defined");
+ }
+ }
+
+ public abstract boolean isSwitchFor(EPackage ePackage);
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend
new file mode 100644
index 0000000..1f3cb5e
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend
@@ -0,0 +1,151 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+import java.io.OutputStream
+import java.io.PrintWriter
+import java.nio.file.Paths
+import org.eclipse.emf.codegen.ecore.genmodel.GenClass
+import org.eclipse.emf.codegen.ecore.genmodel.GenPackage
+import tools.mdsd.ecoreworkflow.mwe2lib.component.PackageLevelCodeFileGenerator
+
+/**
+ * Generates a ...MSwitch. The M stands for mergeable:
+ * MSwitches are similar to the package switches generated by Ecore,
+ * but they provide mergeability and a compact lambda-based syntax.
+ */
+class MSwitchClassGenerator implements PackageLevelCodeFileGenerator {
+
+ GenPackage genPackage
+
+ override setGenPackage(GenPackage genPackage) {
+ this.genPackage = genPackage;
+ }
+
+ override getRelativePath() {
+ if (genPackage === null) throw new IllegalStateException("genPackage was not initialized");
+ Paths
+ .get("", packageName.split("\\."))
+ .resolve(className + ".java")
+ }
+
+ override generateCode(OutputStream out) {
+ if (genPackage === null) throw new IllegalStateException("genPackage was not initialized");
+ val printWriter = new PrintWriter(out)
+ printWriter.print(makeContent())
+ printWriter.flush()
+ }
+
+ private def String getClassName() {
+ genPackage.switchClassName.replaceFirst("Switch$", "MSwitch")
+ }
+
+ private def String getPackageName() {
+ val basePrefix =
+ if (genPackage.basePackage !== null && genPackage.basePackage.length > 0) {
+ genPackage.basePackage + "."
+ } else {
+ ""
+ }
+ basePrefix + genPackage.packageName + ".xutil"
+ }
+
+ private def String makeContent() {
+ '''
+ package «packageName»;
+
+ import java.util.function.Function;
+ import java.util.HashMap;
+ import java.util.Map;
+
+ import org.eclipse.emf.ecore.EClass;
+ import org.eclipse.emf.ecore.EPackage;
+ import org.eclipse.emf.ecore.EObject;
+
+ import tools.mdsd.ecoreworkflow.switches.MSwitch;
+ import tools.mdsd.ecoreworkflow.switches.SwitchingException;
+ import tools.mdsd.ecoreworkflow.switches.MergeableSwitch;
+
+ // auto-generated class, do not edit
+
+ public class «className» extends MSwitch implements MergeableSwitch<«className», «className»> {
+ private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE;
+ «FOR c:genPackage.allSwitchGenClasses»
+ private Function<«c.importedInterfaceName»,T> «getCaseName(c)»;
+ «ENDFOR»
+
+ public boolean isSwitchFor(EPackage ePackage) {
+ return ePackage == MODEL_PACKAGE;
+ }
+
+ protected T doSwitch(int classifierID, EObject eObject) throws SwitchingException {
+ T result;
+ switch(classifierID) {
+ «FOR c : genPackage.allSwitchGenClasses»
+ case «c.genPackage.importedPackageInterfaceName».«genPackage.getClassifierID(c)»: {
+ «c.importedInterfaceName» casted = («c.importedInterfaceName») eObject;
+ if («getCaseName(c)» != null) {
+ result = «getCaseName(c)».apply(casted);
+ if (result != null) return result;
+ }
+ «FOR alternative:c.switchGenClasses»
+ if («getCaseName(alternative)» != null) {
+ result = «getCaseName(alternative)».apply(casted);
+ if (result != null) return result;
+ }
+ «ENDFOR»
+ break;
+ }
+ «ENDFOR»
+ default:
+ throw new Error("type " + eObject.eClass() + " was not considered by the mswitch code generator");
+ }
+ return applyDefaultCase(eObject);
+ }
+
+ public «className» merge(«className» other) {
+ «FOR field: genPackage.allSwitchGenClasses.map[caseName]»
+ if (other.«field» != null) this.«field» = other.«field»;
+ «ENDFOR»
+ if (other.defaultCase != null) this.defaultCase = other.defaultCase;
+ return this;
+ }
+
+ «FOR c : genPackage.allSwitchGenClasses»
+ public interface «getInterfaceName(c)» extends Function<«c.importedInterfaceName»,T> {}
+ «ENDFOR»
+
+ «FOR c : genPackage.allSwitchGenClasses»
+ public «className» when(«getInterfaceName(c)» then) {
+ this.«getCaseName(c)» = then;
+ return this;
+ }
+ «ENDFOR»
+ public «className» orElse(Function defaultCase) {
+ this.defaultCase = defaultCase;
+ return this;
+ }
+
+ @Override
+ public Map> getCases() {
+ Map> definedCases = new HashMap<>();
+
+ «FOR c:genPackage.allSwitchGenClasses»
+ if (this.«getCaseName(c)» != null) {
+ definedCases.put(«c.genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o));
+ }
+ «ENDFOR»
+
+ return definedCases;
+ }
+ }
+ '''
+ }
+
+ private def getCaseName(GenClass c) {
+ "case" + genPackage.getClassUniqueName(c)
+ }
+
+ private def getInterfaceName(GenClass c) {
+ "When" + genPackage.getClassUniqueName(c)
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MergeableSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MergeableSwitch.java
new file mode 100644
index 0000000..35b043c
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MergeableSwitch.java
@@ -0,0 +1,14 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+/**
+ * a switch that supports merging other switches behaviour into itself.
+ *
+ * @author Christian van Rensen
+ *
+ * @param the type of switches that can be merged
+ * @param the type of switch that is result of the merge,
+ * should be equal to the implementing class
+ */
+public interface MergeableSwitch> {
+ I merge(F other);
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/SwitchingException.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/SwitchingException.java
new file mode 100644
index 0000000..26f81eb
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/SwitchingException.java
@@ -0,0 +1,10 @@
+package tools.mdsd.ecoreworkflow.switches;
+
+public class SwitchingException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public SwitchingException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/ByteCodeSwitchCompiler.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/ByteCodeSwitchCompiler.java
new file mode 100644
index 0000000..cb0ae91
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/ByteCodeSwitchCompiler.java
@@ -0,0 +1,134 @@
+package tools.mdsd.ecoreworkflow.switches.bytecodegen;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType.Builder;
+import net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition;
+import net.bytebuddy.dynamic.DynamicType.Unloaded;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.BreadthFirstSearch;
+
+public class ByteCodeSwitchCompiler {
+ private Map> caseDefinitions;
+ private Function defaultCase;
+ private Set explicitPackages;
+ private Map> invocationOrders;
+ private FieldNamingRule namingRule;
+
+ public ByteCodeSwitchCompiler(Map> caseDefinitions,
+ Function defaultCase, Set explicitPackages) {
+ this.caseDefinitions = caseDefinitions;
+ this.defaultCase = defaultCase;
+ this.explicitPackages = explicitPackages;
+ this.invocationOrders = computeInvocationOrders();
+ this.namingRule = new FieldNamingRule();
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"}) // the instantiated type will conform because the functions we feed it are type checked
+ public ApplyableSwitch compileSwitch() {
+ Builder typeUnderConstruction = new ByteBuddy().subclass(ApplyableSwitch.class);
+
+ typeUnderConstruction = withAddedCaseFields(typeUnderConstruction); // fields for storing function pointers
+ typeUnderConstruction = withDoSwitchMethodImplemented(typeUnderConstruction); // hardcoded doSwitch-method stub
+
+ Unloaded unloadedType = typeUnderConstruction.make();
+
+ Class extends ApplyableSwitch> loadedClass = unloadedType.load(Thread.currentThread().getContextClassLoader()).getLoaded();
+ ApplyableSwitch instance = null;
+
+ try {
+ instance = loadedClass.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException("error invoking the constructor of the byte-assembled class", e);
+ }
+
+ assignFieldValues(instance); // assign concrete function pointers to the fields
+ return instance;
+ }
+
+ private void assignFieldValues(ApplyableSwitch instance) {
+ caseDefinitions.forEach((eClass, lambda) -> {
+ try {
+ Field field = instance.getClass().getField(namingRule.getFieldNameForCase(eClass));
+ field.set(instance, lambda);
+ } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException("could not set the case function correspondingly", e);
+ }
+
+ });
+ try {
+ instance.getClass().getField("defaultCase").set(instance, defaultCase);
+ } catch (IllegalArgumentException | NoSuchFieldException | SecurityException | IllegalAccessException e) {
+ throw new RuntimeException("could not set the default function correspondingly", e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private ReceiverTypeDefinition withDoSwitchMethodImplemented(Builder typeUnderConstruction) {
+ return typeUnderConstruction
+ .method(named("doSwitch"))
+ .intercept(new DoSwitchImplementation(invocationOrders, namingRule));
+ }
+
+ private Builder withAddedCaseFields(Builder to) {
+ for (EClass caseClass: invocationOrders.keySet()) { // stores a function pointer for each case
+ to = to.defineField(namingRule.getFieldNameForCase(caseClass), Function.class, Visibility.PUBLIC);
+ }
+ to = to.defineField("defaultCase", Function.class, Visibility.PUBLIC);
+ return to;
+ }
+
+ /**
+ * defines the intended behaviour of the switch
+ * @return a map that maps dynamic types to an ordered list of which static cases to call
+ */
+ private Map> computeInvocationOrders() {
+ Set allPackages = new HashSet(explicitPackages);
+ Set implicitClasses = caseDefinitions.keySet();
+ Set implicitSupertypes = caseDefinitions.keySet()
+ .stream()
+ .flatMap(c -> c.getEAllSuperTypes().stream())
+ .collect(Collectors.toSet());
+
+ Stream.concat(implicitSupertypes.stream(), implicitClasses.stream())
+ .map(EClass::getEPackage)
+ .forEach(allPackages::add);
+
+ Map> invocationOrders = new HashMap<>();
+
+ for (EPackage ePackage : allPackages) {
+ ePackage
+ .getEClassifiers()
+ .stream()
+ .filter(clf -> clf instanceof EClass)
+ .forEach((EClassifier classifier) -> {
+ EClass eClass = (EClass) classifier;
+ List invocations = new ArrayList<>();
+ new BreadthFirstSearch().scan(eClass, (c, r) -> {
+ if (caseDefinitions.containsKey(c) && r.stream().noneMatch(c::isSuperTypeOf)) {
+ invocations.add(c);
+ }
+ }, EClass::getESuperTypes);
+
+ invocationOrders.put(eClass, invocations);
+ });
+ }
+ return invocationOrders;
+ }
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchBodyAppender.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchBodyAppender.java
new file mode 100644
index 0000000..79aaff1
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchBodyAppender.java
@@ -0,0 +1,213 @@
+package tools.mdsd.ecoreworkflow.switches.bytecodegen;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.jar.asm.Label;
+import net.bytebuddy.jar.asm.MethodVisitor;
+import net.bytebuddy.jar.asm.Opcodes;
+import net.bytebuddy.jar.asm.Type;
+import tools.mdsd.ecoreworkflow.switches.SwitchingException;
+
+/**
+ * appends the body of a DynamicBytecodesSwitch's doSwitch method
+ * @author Christian van Rensen
+ *
+ */
+class DoSwitchBodyAppender implements ByteCodeAppender {
+
+ private static final String OBJECT_N = Type.getInternalName(Object.class);
+ private static final String FUNCTION_N = Type.getInternalName(Function.class);
+ private static final String E_OBJECT_N = Type.getInternalName(EObject.class);
+ private static final String ECLASS_N = Type.getInternalName(EClass.class);
+ private static final String E_CLASSIFIER_N = Type.getInternalName(EClassifier.class);
+ private static final String EPACKAGE_N = Type.getInternalName(EPackage.class);
+
+ private static final Type OBJECT_TYPE = Type.getType(Object.class);
+
+ private static final String FUNCTION_TDESCRIPTOR = Type.getDescriptor(Function.class);
+
+ private static final String TAKES_OBJECT_RETURNS_OBJECT_MDESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE);
+ private static final String RETURNS_INT_MDESCRIPTOR = Type.getMethodDescriptor(Type.INT_TYPE);
+ private static final String RETURNS_ECLASS_MDESCRIPTOR = Type.getMethodDescriptor(Type.getType(EClass.class));
+ private static final String RETURNS_EPACKAGE_MDESCRIPTOR = Type.getMethodDescriptor(Type.getType(EPackage.class));
+
+ private Map> invocationsPerClass;
+ private Map> classesPerPackage;
+ private FieldNamingRule namingRule;
+
+ public DoSwitchBodyAppender(Map> invocationOrders, FieldNamingRule namingRule) {
+ this.namingRule = namingRule;
+ this.invocationsPerClass = invocationOrders;
+ this.classesPerPackage = groupByEPackage(invocationOrders);
+ }
+
+ private Map> groupByEPackage(Map> invocationOrders) {
+ return invocationOrders.keySet().stream().collect(Collectors.groupingBy(EClass::getEPackage));
+ }
+
+ @Override
+ public Size apply(MethodVisitor mv, Context ctx, MethodDescription md) {
+ // Stack: |
+ mv.visitVarInsn(Opcodes.ALOAD, 1); // [eObj]
+ // Stack: | eObj |
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | eObj
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, E_OBJECT_N, "eClass", RETURNS_ECLASS_MDESCRIPTOR, true);
+ // Stack: | eObj | eClass
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | eClass | eClass
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, E_CLASSIFIER_N, "getEPackage", RETURNS_EPACKAGE_MDESCRIPTOR, true);
+ // Stack: | eObj | eClass | ePackage
+
+ Label packageJumpLabel;
+ Label returnResult = new Label();
+ Label notFound = new Label();
+ String thisTypeName = ctx.getInstrumentedType().getInternalName();
+ for (Entry> packageEntry: classesPerPackage.entrySet()) {
+ assert 1 == packageEntry.getKey().getClass().getInterfaces().length; // EClass implementations should only implement the interface that defines their type
+ Class> candidatePackageInterface = packageEntry.getKey().getClass().getInterfaces()[0];
+
+ // Stack: | eObj | eClass | ePackage
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | eClass | ePackage | ePackage
+ mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(candidatePackageInterface), "eINSTANCE", Type.getDescriptor(candidatePackageInterface));
+
+ // Stack: | eObj | eClass | ePackage | ePackage | candidatePackage
+ packageJumpLabel = new Label();
+ mv.visitJumpInsn(Opcodes.IF_ACMPNE, packageJumpLabel);
+ // Stack: | eObj | eClass | ePackage
+ // INSIDE THE CORRECT PACKAGE
+ mv.visitInsn(Opcodes.POP);
+ // Stack: | eObj | eClass
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, E_CLASSIFIER_N, "getClassifierID", RETURNS_INT_MDESCRIPTOR, true);
+ // Stack: | eObj | classifierID
+ List possibleDynamicTypes = packageEntry.getValue();
+ appendTableSwitch(mv, returnResult, notFound, thisTypeName, possibleDynamicTypes);
+
+ //mv.visitJumpInsn(Opcodes.GOTO, notFound);
+ // END INSIDE THE CORRECT PACKAGE
+
+ mv.visitLabel(packageJumpLabel); // <-- incoming jump
+ mv.visitFrame(Opcodes.F_NEW, 2, new String[]{thisTypeName, E_OBJECT_N}, 3, new String[]{E_OBJECT_N, ECLASS_N, EPACKAGE_N});
+ // Stack: | eObj | eClass | ePackage
+ }
+ // Stack: | eObj | eClass | ePackage
+ mv.visitInsn(Opcodes.POP2);
+
+ mv.visitLabel(notFound);
+ // Stack: | eObj
+ mv.visitFrame(Opcodes.F_NEW, 2, new String[]{thisTypeName, E_OBJECT_N}, 1, new String[]{E_OBJECT_N});
+
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ // Stack: | eObj | this
+ mv.visitFieldInsn(Opcodes.GETFIELD, thisTypeName, "defaultCase", FUNCTION_TDESCRIPTOR);
+ // Stack: | eObj | defaultCase
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | defaultCase | defaultCase
+ Label noDefaultCase = new Label();
+ mv.visitJumpInsn(Opcodes.IFNULL, noDefaultCase);
+ // Stack: | eObj | defaultCase
+ mv.visitInsn(Opcodes.SWAP);
+ // Stack: | defaultCase | eObj
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, FUNCTION_N, "apply", TAKES_OBJECT_RETURNS_OBJECT_MDESCRIPTOR, true);
+ // Stack: | result
+ mv.visitInsn(Opcodes.ARETURN);
+
+ mv.visitLabel(noDefaultCase);
+ mv.visitFrame(Opcodes.F_NEW, 2, new String[]{thisTypeName, E_OBJECT_N}, 2, new String[]{E_OBJECT_N, FUNCTION_N});
+ // Stack: | eObject | null
+ mv.visitInsn(Opcodes.POP2);
+ // Stack: |
+ mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(SwitchingException.class));
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn("no default case is defined and you have fallen through all cases");
+ String exceptionConstructor;
+ try {
+ exceptionConstructor = Type.getConstructorDescriptor(SwitchingException.class.getConstructor(String.class));
+ } catch (NoSuchMethodException | SecurityException e) {
+ throw new RuntimeException("could not find SwitchingException's constructor", e);
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(SwitchingException.class), "", exceptionConstructor, false);
+ mv.visitInsn(Opcodes.ATHROW);
+
+ mv.visitLabel(returnResult);
+ mv.visitFrame(Opcodes.F_NEW, 2, new String[]{thisTypeName, E_OBJECT_N}, 2, new String[]{E_OBJECT_N, OBJECT_N});
+ mv.visitInsn(Opcodes.ARETURN);
+ return new Size(5, md.getStackSize());
+ }
+
+ private void appendTableSwitch(MethodVisitor mv, Label successLabel, Label notFoundLabel,
+ String thisTypeName, List possibleDynamicTypes) {
+
+ // each class has a classifierID unique for the package
+ // therefore we will do a tableswitch instruction with the classifierID and have to provide a label for every possible int value
+
+ //first: determine the ranges:
+ int minId = 1, maxId = 0;
+ for (EClass eClass : possibleDynamicTypes) {
+ int classifierID = eClass.getClassifierID();
+ if (minId > classifierID) minId = classifierID;
+ if (maxId < classifierID) maxId = classifierID;
+ }
+
+ // create a label for every classifierID in the range
+ Label[] labels = new Label[maxId - minId + 1];
+ for (int j = 0; j <= maxId - minId; j++) {
+ labels[j] = new Label();
+ }
+
+ mv.visitTableSwitchInsn(minId, maxId, notFoundLabel, labels);
+
+ for (int j = 0; j <= maxId - minId; j++) {
+ int classifierId = minId + j;
+ mv.visitLabel(labels[j]);
+ mv.visitFrame(Opcodes.F_NEW, 2, new String[]{thisTypeName, E_OBJECT_N}, 1, new String[]{E_OBJECT_N});
+ // Stack: | eObj |
+ Optional dynamicType = possibleDynamicTypes.stream().filter(c -> c.getClassifierID() == classifierId).findAny();
+ if (dynamicType.isPresent()) {
+ // the classifier is associated with a class we know.
+ // now try to get each of the functions on the stack and call them one by one
+ // Stack: | eObj
+ appendCaseInvocationsChain(mv, thisTypeName, successLabel, dynamicType.get());
+ // Stack: | eObj
+ }
+ mv.visitJumpInsn(Opcodes.GOTO, notFoundLabel);
+ // Stack: | eObj
+ }
+ }
+
+ private void appendCaseInvocationsChain(MethodVisitor mv, String thisTypeName, Label successLabel, EClass dynamicType) {
+ for (EClass caseDefinedOn : invocationsPerClass.getOrDefault(dynamicType, new LinkedList<>())) {
+ // Stack: | eObj
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | eObj
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ // Stack: | eObj | eObj | this
+ mv.visitFieldInsn(Opcodes.GETFIELD, thisTypeName, namingRule.getFieldNameForCase(caseDefinedOn), FUNCTION_TDESCRIPTOR);
+ // Stack: | eObj | eObj | fptr
+ mv.visitInsn(Opcodes.SWAP);
+ // Stack: | eObj | fptr | eObj
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, FUNCTION_N, "apply", TAKES_OBJECT_RETURNS_OBJECT_MDESCRIPTOR, true);
+ // Stack: | eObj | result
+ mv.visitInsn(Opcodes.DUP);
+ // Stack: | eObj | result | result
+ mv.visitJumpInsn(Opcodes.IFNONNULL, successLabel);
+ // Stack: | eObj | result
+ mv.visitInsn(Opcodes.POP);
+ // Stack: | eObj
+ }
+ }
+
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchImplementation.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchImplementation.java
new file mode 100644
index 0000000..e208242
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchImplementation.java
@@ -0,0 +1,30 @@
+package tools.mdsd.ecoreworkflow.switches.bytecodegen;
+
+import java.util.List;
+import java.util.Map;
+import org.eclipse.emf.ecore.EClass;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+
+class DoSwitchImplementation implements Implementation {
+
+ private Map> invocationOrders;
+ private FieldNamingRule namingRule;
+
+ public DoSwitchImplementation(Map> invocationOrders, FieldNamingRule namingRule) {
+ this.invocationOrders = invocationOrders;
+ this.namingRule = namingRule;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrType) {
+ return instrType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target target) {
+ return new DoSwitchBodyAppender(invocationOrders, namingRule);
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/FieldNamingRule.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/FieldNamingRule.java
new file mode 100644
index 0000000..84e80e6
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/FieldNamingRule.java
@@ -0,0 +1,9 @@
+package tools.mdsd.ecoreworkflow.switches.bytecodegen;
+
+import org.eclipse.emf.ecore.EClass;
+
+class FieldNamingRule {
+ public String getFieldNameForCase(EClass eClass) {
+ return "case_" + Math.abs(eClass.getEPackage().getNsURI().hashCode()) + eClass.getEPackage().getName() + "_" + eClass.getClassifierID();
+ }
+}
diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/package-info.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/package-info.java
new file mode 100644
index 0000000..e5e1f64
--- /dev/null
+++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/package-info.java
@@ -0,0 +1 @@
+package tools.mdsd.ecoreworkflow.switches.bytecodegen;
diff --git a/docu/switches.MD b/docu/switches.MD
new file mode 100644
index 0000000..d9e23c2
--- /dev/null
+++ b/docu/switches.MD
@@ -0,0 +1,418 @@
+# Advanced Ecore Switching
+*The Ecore advanced switching project is about bringing composability, dynamic case definitions and cross-package-capabilities to EMF switches. Benchmarking was used to verify that this is possible with only a reasonable loss in throughput.*
+## Introduction and concepts
+Switches are an important feature for performing calculations upon EMF model instances. Let us start with a brief introduction on what EMF code generation means and how switches can be used.
+### EMF
+[EMF](https://www.eclipse.org/modeling/emf/) is a modeling framework. It allows code generation from Ecore models.
+Assume the following simple ecore model:
+
+
+
+EMF allows us to create a GenModel based upon it (wich also includes details about the generation process, such as included classes, package names, and the destination folder). Based upon the genmodel, java code can automatically be generated. The generated code has the following structure:
+
+````
+ interface StrassenFahrzeug {}
+ interface PKW extends StrassenFahrzeug {}
+ interface LKW extends StrassenFahrzeug {}
+
+ class StrassenFahrzeugImpl implements StrassenFahrzeug {…}
+ class PKWImpl implements PKW {…}
+ class LKWImpl implements LKW {…}
+
+ class TransportmittelPackage {…}
+ class TransportmittelSwitch {…}
+````
+For each class in the model, a corresponding interface is created. Apart from that, the generated classes include implementation classes for the model, a package class for reflectively inspecting the packages class hierarchy, a factory and a switch class.
+
+
+### Switching
+Switches in EMF allow to call different methods based on an object's dynamic type, enabling polymorphic behaviour. They are called switches, because - similar to a switch-case-control structure - they transfer control flow to one of several case branches, depending on an input value. In contrast to a usual switch-case control structure, with EMF switches, the branch that is taken does not depend on the entire object value, but on only on its dynamic type.
+A case definition and switch invocation in EMF looks like this:
+
+```
+float maxSpeed = new TransportationSwitch() {
+ public Float caseLKW(LKW lkw) {
+ return 80.0f;
+ }
+ public Float casePKW(PKW pkw) {
+ return 120.0f;
+ }
+ public Float defaultCase(EObject object) {
+ return FLOAT.POSITIVE_INFINITY;
+ }
+ }.doSwitch(anEObject);
+```
+
+#### Purpose
+This switching functionality is similar to pattern matching and serves a similar purpose as multimethods: It enables the creation of type-specific behaviour, but without incorporating this behaviour into the types' classes themselves. The model and its types do not need to know about the different switches that might exist.
+For example, in the code example above, a type-specific speed limit was implemented without changes to the model.
+
+#### Semantics
+Sometimes it is not obvious, which case should be called when many cases match. EMFs switching semantics therefore were reverse-engineered from the original switch class's [template](https://github.com/eclipse/emf/blob/d45610f/plugins/org.eclipse.emf.codegen.ecore/templates/model/SwitchClass.javajet#L179) and from the [implementation](https://github.com/eclipse/emf/blob/d45610fdd4c22493ce69705b3c569d279deb5617/plugins/org.eclipse.emf.codegen.ecore/src/org/eclipse/emf/codegen/ecore/genmodel/impl/GenClassImpl.java#L487) of `GenClass#getSwitchGenClasses()` used in the template.
+It boils down to the following rules:
+* An object matches a case, if it's type is a subtype of the type used in the case definition.
+* The default case matches all objects.
+* If an object's type matches several of the cases, the most specific one is invoked first. If a case returns `null`, less specific cases are invoked, until one returns a non-null value (which is then returned as result of the switch invocation) or all matching cases have been tried (in which case `null` is returned as result of the switch invocation).
+* A case defined on type A is more specific than a case defined on type B for an object of dynamic type X if the longest inheritance path from X to A is shorter than the longest inheritance path from X to B OR if these longest paths are of the same lenght and A is found before B in a breadth-first-search starting at X that only considers paths of that specific length. (This implies that in the multi-inheritance setting, `extends A, B` can have a different effect than `extends B,A`.)
+
+Basically, to calculate the order in which the different cases are tried when supplying an object of dynamic type `dynamicType`, EMF uses an algorithm that is equivalent to the following pseudocode:
+```
+function calculateInvocationSequence(caseDefinitions, dynamicType) {
+ invocations = [];
+ queue = [eClass];
+ while (!queue.empty()) {
+ next = queue.pop();
+ if (caseDefinitions[next] && queue.none(e -> next.isSupertypeOf(e)) ) {
+ invocations.append(caseDefinitions[next]);
+ }
+ queue.appendAll(next.getSupertypes());
+ }
+ return invocations;
+}
+```
+
+As an example, take the following complex class hierarchy:
+
+
+
+There are two base classes, A and B, from which several subclasses inherit in different ways.
+Furthermore, a switch is assumed that defines cases for the classes A and B, but not for any of their subclasses.
+The result is that the two cases are invoked in different orders for different subclasses:
+
+
+| Dynamic Type | Order of case Invocation | Reason |
+|:------------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------- |
+| C | caseA, caseB | A occurs before B in the list of C's supertypes. |
+| D | caseB, caseA | B occurs before A in the list of D's supertypes. |
+| E | caseB, caseA | The longest Inheritance path from E to B is shorter (length 1) than from E to A (length 2). |
+| F | caseA, caseB | The longest Inheritance pathes from F to A and from F to B are of the same length (2) and Y inherits from A before B. |
+
+
+## Aim of this project
+While EMF's switches can be very useful for implementing model-type-depending behaviour outside of the model, which for example is used excessively in the [Palladio Software](https://www.palladio-simulator.com/home/), some additional functionality is wished for in some use cases.
+
+### Composability for switches
+The way that one defines cases for EMF's built-in switches is by sublclassing them: One writes a class that inherits from the generated switch class and overrides the caseXYZ-methods of the cases one wishes to define. This approach contradicts the *Composition over Subclassing* design pattern and imposes limitations to the reusability of such defined switching rules: While a defined switch can also be subclassed and extended with more special cases or modified with partially overridden behaviour, it is not possible in a non-verbose way to reuse the behaviour of two or more switches at once. Extending two switch classes would require multi-inheritance in Java. We want an option to merge an existing switch, effectively copying all case definitions from one switch to another, possibly overriding already existent definitions.
+
+### New API
+This also means that our new switching mechanisms will not use subclassing for defining cases anymore, but an easy-to-read builder API:
+```
+float maxSpeed = new TransportationMSwitch()
+ .when((LKW lkw) -> 80.0f)
+ .when((PKW pkw) -> 120.0f)
+ .orElse((EObject object) -> Float.POSITIVE_INFINITY)
+ .merge(someOtherSwitch)
+ .doSwitch(anEObject);
+```
+
+### Consistent behaviour with EMF switches
+As many already existend EMF switches will likely be converted to new switches, translating EMF case definitions into new switches' definitions must yield semantically equivalent results, event in presence of complex multi-inheritance hierarchies.
+With one exception: For the sake of better error tracement, when an object is not handled by any of the defined cases and no default case is defined, new switches throw an exception instead of returning `null`, so make sure to always define a default case when porting existing switches.
+
+### Cross-package switching
+Another downside of the EMF approach is to be mitigated: Because the EMF approach relies on per-package code generation at compile time, case definition is always limited to the types of one package at a time.
+While EMF does offer its `ComposedSwitch` to circumvent this issue, that switch is slow at runtime and only works with packages that have a disjunct inheritance hierarchy.
+We want an approach that permits case definitions across package boundaries.
+
+### Case definition at runtime
+During the requirement engineering phase of the lab, it turned out that users might also want the ability to define switch behaviour with case types only known at runtime, allowing API calls such as:
+```
+new ExampleSwitch()
+ .dynamicCase(someEObj.eClass(), someAction)
+```
+
+### Maintain high throughput
+While the beforementioned requirements for additional functionality naturally come with a performance penalty, this performance loss must not be unbearable in comparison to the classic approach. The measure that matters, is switching throughput, the speed with which objects can be dispatched with a readily configured and loaded switch. It can be measured with benchmarks.
+
+## Switching aproaches
+Within the scope of this lab, a total of three approaches was implemented and evaluated:
+EMFs code generation approach was extended to allow mergeable switches (which we call MSwitches) in the package-specific setting.
+To also cover cross-package switching functionality and dynamic case definitions at runtime, we implemented two further approaches, a naive reflection-based one (the HashDynamicSwitch) and an optimized one that is functionally equal, but optimized for a higher throughput (the BytecodeDynamicSwitch).
+These three approaches then were compared to EMFs classic approach and also to each other.
+
+### Classic EMF aproach
+
+The classic EMF approach works as follows:
+1. At code generation time of a package, the package's class hierarchy is analyzed. A package-specific switch class is generated that contains a method stub for each type that can be overridden in order to define a case. It also includes a concrete `doSwitch(EObject)`-method that calls these methods in the appropriate order according to its dynamic type. Conceptually, the generated switch class for the above example looks like this:
+ ```
+ class TransportmittelSwitch {
+
+ T casePKW(PKW obj) {return null;}
+ T caseLKW(LKW obj) {return null;}
+ T caseStrassenFahrzeug(StrassenFahrzeug obj) {return null;}
+
+ T defaultCase(EObject obj) {return null;}
+
+ public T doSwitch(EObject obj) {
+ if (obj.getClass().equals(PKW.class)) {
+ return casePKW((PKW) obj) || caseStrassenFahrzeug((StrassenFahrzeug) obj) || defaultCase(obj);
+ } else if (obj.getClass().equals(LKW.class)) {
+ return caseLKW() || caseStrassenFahrzeug() || defaultCase();
+ } else if (...){
+ ...
+ }
+ ...
+ }
+ }
+ ```
+2. A switch user inherits from the generated class overriding some of the case methods.
+3. The child class is instantiated at runtime and the doSwitch method is called for each object to be switched and delegates to the caseXYZ()-methods.
+### MSwitch aproach
+Preserving the idea of the classic approach, but enhancing it with mergeability, MSwitches also use code generation, but swap the case method stubs for pointers to functional interfaces.
+This means that, as subclassing is no longer required, case definition can happen at runtime, which makes ist easy to implement trait-like merging capabilities. A generated MSwitch class conceptually looks like this:
+```
+ class TransportmittelMSwitch {
+
+ Function casePKW = null;
+ Function caseLKW = null;
+ Function caseStrassenFahrzeug = null;
+
+ Function defaultCase = null;
+
+ public T doSwitch(EObject obj) {
+ if (obj.getClass().equals(PKW.class)) {
+ T result = null;
+ if (casePKW != null) {
+ result = casePKW.apply((PKW) obj);
+ }
+ if (result == null && caseStrassenFahrzeug != null) {
+ result = caseStrassenFahrzeug.apply((StrassenFahrzeug) obj);
+ }
+ ...
+ if (result == null && defaultCase != null ){...}
+ return result;
+ } else if (...){
+ ...
+ }
+ ...
+ }
+
+ public void merge(TransmportmittelMSwitch other) {
+ if (other.casePKW != null) {
+ this.casePKW = other.casePKW;
+ }
+ if (other.caseLKW != null) {
+ this.caseLKW = other.caseLKW;
+ }
+ ...
+ }
+ }
+```
+The overall process only changes slightly:
+1. Package specific switch code is generated at compile time.
+2. The class is instantiated and cases are configured by assigning the function pointers at runtime.
+3. At switching time, doSwitch is called and delegates to the function pointers.
+### HashDynamicSwitch aproach
+In order to meet with the reqirement of cross package switching, it is unpractical to use code generation at compile time, because the packages across which to switch are be compiled individually, but affect the combined switching behaviour in a closely linked manner (if one package contains subclasses of the other).
+Therefore, evaluation of the defined cases relative specifity is performed at runtime when the dynamic type of the object to be switched is present.
+As this calculation is costly, the invocation sequence for each dynamic type is cached in a hashmap.
+Switching logic is roughly as follows:
+```
+T doSwitch(EObject obj) {
+ EClass dynamicType = obj.eClass();
+ if (!cache.containsKey(dynamicType)) {
+ cache.put(dynamicType, calculateInvocationSequence(caseDefinitions, dynamicType))
+ }
+ Iterable> invocationSequence = cache.get(dynamicType);
+ T result = null;
+ for (Function invokedCase : invokationSequence) {
+ result ||= invokedCase.apply(obj);
+ }
+ return result;
+}
+```
+The overall process now is fundamentally different:
+1. The switch class is always the same and no model specific code must be generated at compile time.
+2. The switch class is instantiated at runtime and cases are configured.
+3. At switching time the class hierarchy is analyzed as necessary and delegation to the correct function pointer is done.
+### BytecodeDynamicSwitch aproach
+A problem with above approach is that the high cost of the `calculateInvocationSequence()`-method (which has to traverse the dynamic type's inheritance hierarchy), is costly and, being executed at switching time, bottlenecks the throughput.
+
+One wishes for the efficient `doSwitch` implementation from the package specific approaches. This wish can indeed be granted by generating the `doSwitch` method's body at runtime - using bytecode generation:
+1. At runtime, case definitions are configured. By now, the involved class hierarchy is known completely.
+2. Bytecode generation is used to dynamically create a switch subclass with an efficiend `doSwitch`-method, which is immediately loaded and instantiated.
+3. Objects that are fed into the switch can be directly processed by the fast `doSwitch`-method that delegates to the adequate function pointers.
+
+## Performance Evaluation
+[JMH](https://openjdk.java.net/projects/code-tools/jmh/) was used to measure the most important performance indicator, the throughput, ie the speed at which objects can be processed by a readily instantiated and configured switch.
+
+### Single Package Szenario
+In the single package szenario, the classic EMF switch remains fastest. However, the MSwitch is only slightly slower. The HashDynamicSwitch achieves only about half the throughput. Therefore, if a switch is used extensively, it makes sense to use the funtionally equivalent ByteCodeSwitch instead.
+
+
+
+### Multi Package Szenario
+In the multi package szenario, the ComposableSwitch that EMF offers only works when the package hierarchies are distinct. But even then, it is slower than the HashDynamicSwitch and the BytecodeSwitch that perform as fast as in the single package szenario (That both achieve a slightly smaller throughput than in the single package benchmark ist probably due to the slightly larger test sample).
+
+
+### Limitations of the performance analysis method
+This measuring approach does not take into account the time it takes to configure the switch and also neglegts memory footprint. In case of creating numerous different switch instances, DynamicSwitch might take up significant amounts of memory because of its internal cache. BytecodeDynamicSwitch will take time to initialize, because everytime a new class has to be assembled and loaded into the VM. From a theoretical point of view, MSwitch should not take long to initialize, but stores one field per ecore class in the model while the classic EMF switch instances don't have any fields.
+
+### Conclusion: Usage Guideline
+If package-specific switching is sufficient, use the MSwitch in order to enable reuse of your switches later on, at no significant performance cost.
+If cross-package switching is needed, it makesuse a HashDynamicSwitch for a small number of objects to be switched with a particular switch and us
+
+## Build
+As described above, our advanced switching approaches are a promising substitute to Ecore's builtin switches in practice.
+In order to reproduce the benchmarking results or use the new switches for your own project, these are the technical steps required to build the project:
+1. Clone the repository:
+ ````
+ git clone https://github.com/.../Ecore-Workflow
+ cd Ecore-Workflow
+ ````
+2. Run maven:
+ ```
+ mvn clean verify
+ ```
+ (Pitfall: On windows the `JAVAHOME` variable must be set). Required dependencies will be downloaded automatically and an eclipse update site will be created in the subfolder `releng/tools.mdsd.ecoreworkflow.updatesite/target/repository`.
+3. If you want to temporarily host an update site for testing, you can start a local http server:
+ ```
+ cd releng/tools.mdsd.ecoreworkflow.updatesite/target/repository
+ python -m http.server 8080
+ ```
+
+## Usage
+
+The code generation is accessible as a [MWE2 component](https://www.eclipse.org/Xtext/documentation/306_mwe2.html) and can be combined with standard Ecore code generation using an adecuate MWE2 workflow that combines these two steps.
+There are several possibilities to invoke such a workflow:
+(a) as part of a maven tycho build,
+(b) directly from an Eclipse instance or
+\(c\) using the experimental Eclipse builder that is included in Ecore-Workflow.
+
+### (a) as part of a maven tycho build
+Following steps conceptually describe how to incorporate advanced switching capability into an ecore model build with maven tycho. (Instead of following the steps one by one, you can clone the [example repo](https://github.com/christianthechristian/example_mswitch_maven_project).)
+1. **Create a maven project** with the usual structure used in MDSD projects:
+ ````
+ |- .mvn
+ | |- extensions.xml
+ |- bundles
+ |- releng
+ | |- pom.xml
+ | |- .targetplatform
+ | | |- .project
+ | | |- tp.target
+ |- pom.xml
+ ````
+ Especially note:
+ 1. In `extensions.xml` add `org.eclipse.tycho.extras.tycho-pomless` and `org.palladiosimulator.tycho-tp-refresh-maven-plugin` as extensions.
+ 2. In the `tp.target` target definition the repositories that will be used to satisfy elipse plugins' dependencies are specified. You have to add the repository that resulted from the build. You can use [`https://updatesite.mdsd.tools/ecore-workflow/releases/0.1.0/`](https://updatesite.mdsd.tools/ecore-workflow/releases/0.1.0/) for the currently deployed oficial version of the ecore workflow or you can use your locally hosted update site by adding a section such as follows:
+ ```
+
+
+
+
+
+ ```
+ Note that tycho does not seem to support local `file://` URLs as repository locations.
+ 3. Use the following parent in the POM:
+ ```
+
+ tools.mdsd
+ eclipse-parent-updatesite
+ 0.4.2
+
+ ```
+2. **Create your eclipse modeling project** in the bundles subdirectory. You can use eclipse or create/copy the corresponding files manually. The `bundles` folder should typically look like this:
+ ````
+ |- ...
+ |- bundles
+ | |- .bundle
+ | | |- META-INF
+ | | | |- MANIFEST.MF
+ | | |- model
+ | | | |- .genmodel
+ | | | |- .ecore
+ | | |- .classpath
+ | | |- .project
+ | | |- build.properties
+ | | |- plugin.xml
+ ````
+ Add some classes to `.ecore`.
+3. In order to enable switch generation some **additional configuration** has to be done:
+ 1. Add `org.eclipse.emf.mwe2.launch`, `org.eclipse.emf.mwe2.lib`, `org.eclipse.emf.mwe2.launch`, `tools.mdsd.ecoreworkflow.mwe2lib` and `tools.mdsd.ecoreworkflow.switches` to the **dependencies** in the `Require-Bundle` section in the manifest file. They are required to launch mwe2 workflows and provide the additional code generation functionalities.
+ 2. In order to **configure the build workflow**, create a folder `workflow` and add files `generate.mwe2` and `clean.mwe2`. The maven parent we configured before executes them as part of the build process. Beside the usual `EcoreGenerator` component, add an `AdditionalTemplateGenerator` step to `generate.mwe2` that will create the advenced switch classes:
+ ```
+ component = AdditionalTemplateGenerator {
+ genModel = "platform:/resoure/.bundle/model/.genmodel"
+ destPath = "platform:/resource/.bundle/src/"
+ packageLevelGenerator = "tools.mdsd.ecoreworkflow.switches.MSwitchClassGenerator"
+ }
+ ```
+3. Now the tycho project can be built with maven:
+ ```
+ mvn clean verify
+ ```
+ The ecore model will be translated into Java, including the `MSwitch` class, compiled, and packaged into OSGI-compatible jar-files.
+### (b) directly from an Eclipse instance with XText
+1. Install a fresh copy of Eclipse (Eclipse Modeling Tools 2019-09).
+2. Install the EMF-Eclipse Modeling SDK, Xtext Complete SDK, and the ByteBuddy plugin (from eclipse orbit)
+3. Install both the MWE Lib and the Switches feature from the Ecore-Workflow update site (or a locally hosted variant).
+4. Create an Ecore project (with the usual structure):
+ ````
+ - src
+ - src-gen
+ - META-INF
+ - |- MANIFEST.MF
+ - model
+ |- somemodel.mwe2
+ ````
+4. Add a workflow in `model/workflow.mwe2` with `EcoreGenerator` and `AdditionalTemplateGenerator`.
+5. Make sure that the current target platform that eclipse uses includes the features from the Ecore-Workflow update site.
+6. Add the plugins `tools.mdsd.ecoreworkflow.switches` and `tools.mdsd.ecoreworkflow.mwe2lib` to the requirements in `MANIFEST.MF`.
+7. Right click the workflow -> Run as MWE Workflow.
+ That will build the model into the `src-gen` folder.
+### \(c\) using the experimental Eclipse builder that is included in Ecore-Workflow
+1. Open this project in Eclipse (= Java IDE 2019-09, with the modeling tools and ByteBuddy also installed).
+2. Right click some eclipse Project -> Run as -> Eclipse Application
+ * The target platform: must include ByteBuddy, Ecore, etc.
+ * As plugins, use all workspace and application plugins.
+3. In the now running eclipse application, create a project as in case (b).
+4. Open `.project` with an xml-based editor and add the following element as ``'s first child:
+ ````
+
+ tools.mdsd.ecoreworkflow.builder.Builder
+
+
+ tools.mdsd.ecoreworkflow.builder.workflowdefinition
+ /model/workflow.mwe2
+
+
+
+ ````
+ Now, whenever you save changes to the model, the workflow in `model/workflow.mwe2` will be run automatically, resulting in a smooth IDE-adecuate user experience. As the MWE builder is only considered experimental, this variant is not guaranteed to work stably.
+
+
+## Development
+
+Switch generation is part of the Ecore-Workflow project.
+As an eclipse tycho project, it consists of several components that each are also valid eclipse plugins.
+
+### Project Components
+
+These components play a part in switching:
+
+
+| Plugin | affected by switching | Purpose / Changes made |
+| -------- | -------- | -------- |
+| tools.mdsd.ecoreworkflow.switches | added | contains classes required for switching at runtime as well as the template needed for code generation at compile time |
+| tools.mdsd.ecoreworkflow.mwe2lib | changed | added a mwe2 component for invoking template generation |
+| tools.mdsd.ecoreworkflow.switches.feature | added | a feature allowing to install the tools.mdsd.ecoreworkflow.switches plugin via an update-site
+| tools.mdsd.ecoreworkflow.targetplatform | changed | added byte buddy to the required plugins
+| tools.mdsd.ecoreworkflow.switches.tests | added | unit tests for the switching, relies on below testmodels
+| tools.mdsd.ecoreworkflow.switches.testmodel | added | ecore testmodel that must pass the build and is base for the unit tests
+| tools.mdsd.ecoreworkflow.switches.testmodel2 | added | just another test model that inherits from testmodel
+| tools.mdsd.ecoreworkflow.switches.testmodel3 | added | just another test model, that does not inherit from testmodel (so that its switch can be combined with the testmodel for benchmarking EMF's ComposedSwitch)
+| tools.mdsd.ecoreworkflow.switches.tests.perf | added | contains benchmarks, uses JMH, warning: not a tycho project, and therefore does not use referenced tycho projects' transitive dependencies.
+
+Details on the implementation can be found in the code which should be self-explanatory.
+
+### Running Tests
+
+Tests are run with `mvn clean verify`.
+
+### Running Benchmarks
+
+Benchmarking takes precious build time and therefore benchmarks are only enabled in a specific maven profile. You can enable this profile with this additional command line parameter:
+```
+ mvn clean verify -Dbenchmarking=true
+```
diff --git a/features/tools.mdsd.ecoreworkflow.switches.feature/build.properties b/features/tools.mdsd.ecoreworkflow.switches.feature/build.properties
new file mode 100644
index 0000000..64f93a9
--- /dev/null
+++ b/features/tools.mdsd.ecoreworkflow.switches.feature/build.properties
@@ -0,0 +1 @@
+bin.includes = feature.xml
diff --git a/features/tools.mdsd.ecoreworkflow.switches.feature/feature.xml b/features/tools.mdsd.ecoreworkflow.switches.feature/feature.xml
new file mode 100644
index 0000000..be3725d
--- /dev/null
+++ b/features/tools.mdsd.ecoreworkflow.switches.feature/feature.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Enables seamless build and use of advanced switches for ecore.
+
+
+
+ [Enter Copyright Description here.]
+
+
+
+ [Enter License Description here.]
+
+
+
+
+
+
+
+
+
+
+
diff --git a/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target b/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target
index cf86e29..a948bd8 100644
--- a/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target
+++ b/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target
@@ -1,9 +1,14 @@
-
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml b/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml
index c67fdf0..7db1f5d 100644
--- a/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml
+++ b/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml
@@ -3,6 +3,9 @@
+
+
+
diff --git a/tests/pom.xml b/tests/pom.xml
index 7904660..9a7c1d6 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -11,7 +11,11 @@
pom
-
+ tools.mdsd.ecoreworkflow.switches.tests
+ tools.mdsd.ecoreworkflow.switches.testmodel
+ tools.mdsd.ecoreworkflow.switches.testmodel2
+ tools.mdsd.ecoreworkflow.switches.testmodel3
+ tools.mdsd.ecoreworkflow.switches.tests.perf
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.classpath
new file mode 100644
index 0000000..01836c4
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.gitignore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.gitignore
new file mode 100644
index 0000000..8014316
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.gitignore
@@ -0,0 +1 @@
+src/**/*.java
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project
new file mode 100644
index 0000000..cbe4778
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project
@@ -0,0 +1,34 @@
+
+
+ tools.mdsd.ecoreworkflow.switches.testmodel
+
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.settings/org.eclipse.jdt.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..74b136b
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: tools.mdsd.ecoreworkflow.switches.testmodel;singleton:=true
+Automatic-Module-Name: tools.mdsd.ecoreworkflow.switches.testmodel
+Bundle-Version: 0.1.0.qualifier
+Bundle-ClassPath: .
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: tools.mdsd.ecoreworkflow.switches.testmodel.testscenario,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.impl,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.util,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.18.0";visibility:=reexport,
+ org.eclipse.emf.mwe2.launch;bundle-version="2.10.0",
+ org.eclipse.emf.mwe2.lib;bundle-version="2.10.0",
+ org.eclipse.emf.codegen.ecore;bundle-version="2.18.0",
+ org.eclipse.core.resources;bundle-version="3.13.400",
+ tools.mdsd.ecoreworkflow.mwe2lib;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches;bundle-version="0.1.0"
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/build.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/build.properties
new file mode 100644
index 0000000..4465407
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/build.properties
@@ -0,0 +1,10 @@
+#
+
+bin.includes = .,\
+ model/,\
+ META-INF/,\
+ plugin.xml,\
+ plugin.properties
+jars.compile.order = .
+source.. = src/
+output.. = bin/
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.aird b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.aird
new file mode 100644
index 0000000..6afd5aa
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.aird
@@ -0,0 +1,704 @@
+
+
+
+ testscenario.ecore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+ KEEP_LOCATION
+ KEEP_SIZE
+ KEEP_RATIO
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+ italic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.ecore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.ecore
new file mode 100644
index 0000000..d51de31
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.ecore
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel
new file mode 100644
index 0000000..8b8bcce
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel
@@ -0,0 +1,23 @@
+
+
+ testscenario.ecore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.properties
new file mode 100644
index 0000000..4ede700
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.properties
@@ -0,0 +1,4 @@
+#
+
+pluginName = Testscenario Model
+providerName = mdsd.tools
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.xml b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.xml
new file mode 100644
index 0000000..6f915bf
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/clean.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/clean.mwe2
new file mode 100644
index 0000000..2068646
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/clean.mwe2
@@ -0,0 +1,17 @@
+module clean
+
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.URISupportingDirectoryCleaner
+
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = true
+ platformUri = workspaceRoot
+ }
+
+ component = URISupportingDirectoryCleaner {
+ directory = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/src/"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2
new file mode 100644
index 0000000..7ac4e01
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2
@@ -0,0 +1,33 @@
+module generate
+
+import org.eclipse.emf.mwe2.ecore.EcoreGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.AdditionalTemplateGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.component.ContextDependentMapping
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = false
+ platformUri = workspaceRoot
+
+ uriMap = ContextDependentMapping {
+ onRunningPlatform = "platform:/plugin/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel"
+ onStandalone = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel"
+ }
+ }
+
+ component = EcoreGenerator {
+ generateCustomClasses = false
+ generateEdit = false
+ generateEditor = false
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel"
+ srcPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/src/"
+ }
+
+ component = AdditionalTemplateGenerator {
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel"
+ destPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel/src/"
+ packageLevelGenerator = "tools.mdsd.ecoreworkflow.switches.MSwitchClassGenerator"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.classpath
new file mode 100644
index 0000000..01836c4
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.gitignore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.gitignore
new file mode 100644
index 0000000..8014316
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.gitignore
@@ -0,0 +1 @@
+src/**/*.java
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project
new file mode 100644
index 0000000..de068bc
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project
@@ -0,0 +1,34 @@
+
+
+ tools.mdsd.ecoreworkflow.switches.testmodel2
+
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.settings/org.eclipse.jdt.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2aaa1ea
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/META-INF/MANIFEST.MF
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: tools.mdsd.ecoreworkflow.switches.testmodel2;singleton:=true
+Automatic-Module-Name: tools.mdsd.ecoreworkflow.switches.testmodel2
+Bundle-Version: 0.1.0.qualifier
+Bundle-ClassPath: .
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.impl,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.util,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.xutil
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.18.0";visibility:=reexport,
+ org.eclipse.emf.mwe2.launch;bundle-version="2.10.0",
+ org.eclipse.emf.mwe2.lib;bundle-version="2.10.0",
+ org.eclipse.emf.codegen.ecore;bundle-version="2.18.0",
+ org.eclipse.core.resources;bundle-version="3.13.400",
+ tools.mdsd.ecoreworkflow.mwe2lib;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches;bundle-version="0.1.0"
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/build.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/build.properties
new file mode 100644
index 0000000..4465407
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/build.properties
@@ -0,0 +1,10 @@
+#
+
+bin.includes = .,\
+ model/,\
+ META-INF/,\
+ plugin.xml,\
+ plugin.properties
+jars.compile.order = .
+source.. = src/
+output.. = bin/
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.ecore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.ecore
new file mode 100644
index 0000000..69f4a0e
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.ecore
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel
new file mode 100644
index 0000000..a9e0b46
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel
@@ -0,0 +1,13 @@
+
+
+ testscenario2.ecore
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.properties
new file mode 100644
index 0000000..4ede700
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.properties
@@ -0,0 +1,4 @@
+#
+
+pluginName = Testscenario Model
+providerName = mdsd.tools
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.xml b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.xml
new file mode 100644
index 0000000..408e46a
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/clean.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/clean.mwe2
new file mode 100644
index 0000000..4ebac42
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/clean.mwe2
@@ -0,0 +1,17 @@
+module clean
+
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.URISupportingDirectoryCleaner
+
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = true
+ platformUri = workspaceRoot
+ }
+
+ component = URISupportingDirectoryCleaner {
+ directory = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/src/"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/generate.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/generate.mwe2
new file mode 100644
index 0000000..15b6f04
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/generate.mwe2
@@ -0,0 +1,32 @@
+module generate
+
+import org.eclipse.emf.mwe2.ecore.EcoreGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.AdditionalTemplateGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.component.ContextDependentMapping
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = true
+ platformUri = workspaceRoot
+ uriMap = ContextDependentMapping {
+ onRunningPlatform = "platform:/plugin/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel"
+ onStandalone = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel"
+ }
+ }
+
+ component = EcoreGenerator {
+ generateCustomClasses = false
+ generateEdit = false
+ generateEditor = false
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel"
+ srcPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/src/"
+ }
+
+ component = AdditionalTemplateGenerator {
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel"
+ destPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel2/src/"
+ packageLevelGenerator = "tools.mdsd.ecoreworkflow.switches.MSwitchClassGenerator"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.classpath
new file mode 100644
index 0000000..01836c4
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.gitignore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.gitignore
new file mode 100644
index 0000000..8014316
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.gitignore
@@ -0,0 +1 @@
+src/**/*.java
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project
new file mode 100644
index 0000000..6da3317
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project
@@ -0,0 +1,34 @@
+
+
+ tools.mdsd.ecoreworkflow.switches.testmodel3
+
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.settings/org.eclipse.jdt.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..7203884
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/META-INF/MANIFEST.MF
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: tools.mdsd.ecoreworkflow.switches.testmodel3;singleton:=true
+Automatic-Module-Name: tools.mdsd.ecoreworkflow.switches.testmodel3
+Bundle-Version: 0.1.0.qualifier
+Bundle-ClassPath: .
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.impl,
+ tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.util
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.18.0";visibility:=reexport,
+ org.eclipse.emf.mwe2.launch;bundle-version="2.10.0",
+ org.eclipse.emf.mwe2.lib;bundle-version="2.10.0",
+ org.eclipse.emf.codegen.ecore;bundle-version="2.18.0",
+ org.eclipse.core.resources;bundle-version="3.13.400",
+ tools.mdsd.ecoreworkflow.mwe2lib;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches.testmodel;bundle-version="0.1.0"
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/build.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/build.properties
new file mode 100644
index 0000000..4465407
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/build.properties
@@ -0,0 +1,10 @@
+#
+
+bin.includes = .,\
+ model/,\
+ META-INF/,\
+ plugin.xml,\
+ plugin.properties
+jars.compile.order = .
+source.. = src/
+output.. = bin/
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.ecore b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.ecore
new file mode 100644
index 0000000..2e06921
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.ecore
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel
new file mode 100644
index 0000000..ad2698f
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel
@@ -0,0 +1,13 @@
+
+
+ testscenario3.ecore
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.properties b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.properties
new file mode 100644
index 0000000..68e559d
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.properties
@@ -0,0 +1,4 @@
+#
+
+pluginName = Testscenario 3 Model
+providerName = mdsd.tools
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.xml b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.xml
new file mode 100644
index 0000000..610208e
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/clean.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/clean.mwe2
new file mode 100644
index 0000000..f5faee7
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/clean.mwe2
@@ -0,0 +1,17 @@
+module clean
+
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.URISupportingDirectoryCleaner
+
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = true
+ platformUri = workspaceRoot
+ }
+
+ component = URISupportingDirectoryCleaner {
+ directory = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/src/"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/generate.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/generate.mwe2
new file mode 100644
index 0000000..eca3cf5
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/generate.mwe2
@@ -0,0 +1,32 @@
+module generate
+
+import org.eclipse.emf.mwe2.ecore.EcoreGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup
+import tools.mdsd.ecoreworkflow.mwe2lib.component.AdditionalTemplateGenerator
+import tools.mdsd.ecoreworkflow.mwe2lib.component.ContextDependentMapping
+var workspaceRoot = "../../"
+
+Workflow {
+ bean = EclipseRCPSupportingStandaloneSetup {
+ scanClassPath = true
+ platformUri = workspaceRoot
+ uriMap = ContextDependentMapping {
+ onRunningPlatform = "platform:/plugin/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel"
+ onStandalone = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel"
+ }
+ }
+
+ component = EcoreGenerator {
+ generateCustomClasses = false
+ generateEdit = false
+ generateEditor = false
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel"
+ srcPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/src/"
+ }
+
+ component = AdditionalTemplateGenerator {
+ genModel = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel"
+ destPath = "platform:/resource/tools.mdsd.ecoreworkflow.switches.testmodel3/src/"
+ packageLevelGenerator = "tools.mdsd.ecoreworkflow.switches.MSwitchClassGenerator"
+ }
+}
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.classpath
new file mode 100644
index 0000000..1424b8b
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.classpath
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.gitignore b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.gitignore
new file mode 100644
index 0000000..90f0431
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.gitignore
@@ -0,0 +1 @@
+target/**
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.mvn/extensions.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.mvn/extensions.xml
new file mode 100644
index 0000000..d8c83ef
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.mvn/extensions.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.core.resources.prefs b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..e9441bb
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding/=UTF-8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.jdt.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..2f5cc74
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.m2e.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml
new file mode 100644
index 0000000..1c9dd45
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml
@@ -0,0 +1,223 @@
+
+
+ 4.0.0
+
+
+ tools.mdsd.ecoreworkflow
+ tools.mdsd.ecoreworkflow.switches.tests.perf
+ 1.0
+ jar
+ JMH benchmark for Ecore Switches, including our custom MSwitch and DynamicSwitch
+
+
+
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+ provided
+
+
+
+ tools.mdsd.ecoreworkflow
+ tools.mdsd.ecoreworkflow.switches.testmodel
+ 0.1.0-SNAPSHOT
+
+
+
+ tools.mdsd.ecoreworkflow
+ tools.mdsd.ecoreworkflow.switches.testmodel2
+ 0.1.0-SNAPSHOT
+
+
+
+ tools.mdsd.ecoreworkflow
+ tools.mdsd.ecoreworkflow.switches
+ 0.1.0-SNAPSHOT
+
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.9.0
+
+
+
+ org.eclipse.emf
+ org.eclipse.emf.ecore
+ 2.20.0
+
+
+
+ org.eclipse.emf
+ org.eclipse.emf.common
+ 2.17.0
+
+
+
+
+ UTF-8
+ 1.22
+ 1.8
+ benchmarks
+ json
+
+
+
+
+
+ benchmarking
+
+
+ benchmarking
+ true
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+
+ verify
+
+ exec
+
+
+
+
+ java
+
+ -jar
+ target/${uberjar.name}.jar
+
+ -foe
+ true
+
+ -r
+ 1 s
+
+ -w
+ 1 s
+
+ -rf
+ ${jmh.format}
+
+ -rff
+ target/jmh-result.${jmh.format}
+
+ -f
+ 5
+
+ -i
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+ ${javac.target}
+ ${javac.target}
+ ${javac.target}
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+
+ package
+
+ shade
+
+
+ ${uberjar.name}
+
+
+ org.openjdk.jmh.Main
+
+
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 2.5
+
+
+ maven-deploy-plugin
+ 2.8.1
+
+
+ maven-install-plugin
+ 2.5.1
+
+
+ maven-jar-plugin
+ 2.4
+
+
+ maven-javadoc-plugin
+ 2.9.1
+
+
+ maven-resources-plugin
+ 2.6
+
+
+ maven-site-plugin
+ 3.3
+
+
+ maven-source-plugin
+ 2.2.1
+
+
+ maven-surefire-plugin
+ 2.17
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/MultiPackageSwitchingSpeedBenchmark.java b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/MultiPackageSwitchingSpeedBenchmark.java
new file mode 100644
index 0000000..ee1133c
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/MultiPackageSwitchingSpeedBenchmark.java
@@ -0,0 +1,68 @@
+package tools.mdsd.ecoreworkflow;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.ComposedSwitch;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.BytecodeDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Factory;
+
+
+public class MultiPackageSwitchingSpeedBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+ SwitchConfigurator conf = new SwitchConfigurator();
+ ComposedSwitch composedSwitch = conf.buildComposedSwitch();
+ DynamicSwitch dynamicSwitch = conf.buildComposedDynamicSwitch();
+ DynamicSwitch bytecodeSwitch = conf.buildComposedDynamicBytecodeSwitch();
+ }
+
+ @State(Scope.Thread)
+ public static class ThreadState {
+ TestscenarioFactory factory = TestscenarioFactory.eINSTANCE;
+ Testscenario2Factory factory2 = Testscenario2Factory.eINSTANCE;
+ EObject[] testObjects = new EObject[] {
+ factory.createA(),
+ factory.createB(),
+ factory.createC(),
+ factory.createD(),
+ factory.createE(),
+ factory.createF(),
+ factory.createG(),
+ factory.createH(),
+ factory.createI(),
+ factory.createK(),
+ factory.createL(),
+ factory.createM(),
+ factory2.createY(),
+ factory2.createZ(),
+ };
+ }
+
+ @Benchmark
+ public void composedSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.composedSwitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void dynamicSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.dynamicSwitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void bytecodeSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.bytecodeSwitch.doSwitch(obj));
+ }
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java
new file mode 100644
index 0000000..a4f6f5d
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java
@@ -0,0 +1,109 @@
+package tools.mdsd.ecoreworkflow;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.ComposedSwitch;
+import org.eclipse.emf.ecore.util.Switch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Y;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Z;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.util.Testscenario2Switch;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.BytecodeDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Package.Literals.*;
+import java.util.Arrays;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.util.TestscenarioSwitch;
+
+public class SwitchConfigurator {
+ TestscenarioMSwitch buildMSwitch() {
+ return new TestscenarioMSwitch()
+ .when((L l)-> "l")
+ .when((B b)-> "b")
+ .when((C c)-> "c")
+ .when((H h)-> "h")
+ .orElse((EObject object)-> "*");
+ }
+
+ TestscenarioSwitch buildClassicSwitch() {
+ return new TestscenarioSwitch() {
+ public String caseL(tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.L object) {
+ return "l";
+ };
+ public String caseB(tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.B object) {
+ return "b";
+ };
+ public String caseC(tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.C object) {
+ return "c";
+ };
+ public String caseH(tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.H object) {
+ return "h";
+ };
+ public String defaultCase(org.eclipse.emf.ecore.EObject object) {
+ return "*";
+ };
+ };
+ }
+
+ DynamicSwitch buildDynamicSwitch() {
+ return new HashDynamicSwitch()
+ .dynamicCase(L, (EObject l)-> "l")
+ .dynamicCase(B, (EObject b)-> "b")
+ .dynamicCase(C, (EObject c)-> "c")
+ .dynamicCase(H, (EObject h)-> "h")
+ .defaultCase((EObject object)-> "*");
+ }
+
+ ComposedSwitch buildComposedSwitch() {
+ Switch switch1 = buildClassicSwitch();
+ Switch switch2 = buildClassicSwitch2();
+ return new ComposedSwitch(Arrays.asList(switch1, switch2));
+ }
+
+ DynamicSwitch buildComposedDynamicSwitch() {
+ return buildDynamicSwitch()
+ .dynamicCase(Y, (EObject y)-> "y")
+ .dynamicCase(Z, (EObject z)-> "z");
+ }
+
+ private Testscenario2Switch buildClassicSwitch2() {
+ return new Testscenario2Switch() {
+ @Override
+ public String caseY(Y object) {
+ return "y";
+ }
+
+ @Override
+ public String caseZ(Z object) {
+ return "z";
+ }
+ };
+ }
+
+ public DynamicSwitch buildDynamicBytecodeSwitch() {
+ BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch();
+ sw
+ .dynamicCase(L, (EObject l)-> "l")
+ .dynamicCase(B, (EObject b)-> "b")
+ .dynamicCase(C, (EObject c)-> "c")
+ .dynamicCase(H, (EObject h)-> "h")
+ .defaultCase((EObject object)-> "*")
+ .dynamicCase(Y, (EObject y)-> "y")
+ .dynamicCase(Z, (EObject z)-> "z");
+ return sw.precompile();
+ }
+
+ public DynamicSwitch buildComposedDynamicBytecodeSwitch() {
+ BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch();
+ sw
+ .dynamicCase(L, (EObject l)-> "l")
+ .dynamicCase(B, (EObject b)-> "b")
+ .dynamicCase(C, (EObject c)-> "c")
+ .dynamicCase(H, (EObject h)-> "h")
+ .defaultCase((EObject object)-> "*")
+ .dynamicCase(Y, (EObject y)-> "y")
+ .dynamicCase(Z, (EObject z)-> "z");
+ return sw.precompile();
+ }
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java
new file mode 100644
index 0000000..fd167d6
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java
@@ -0,0 +1,85 @@
+package tools.mdsd.ecoreworkflow;
+
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch;
+import org.eclipse.emf.ecore.EObject;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.util.TestscenarioSwitch;
+
+
+public class SwitchingSpeedBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+ SwitchConfigurator conf = new SwitchConfigurator();
+
+ TestscenarioSwitch classicSwitch = conf.buildClassicSwitch();
+ TestscenarioMSwitch mswitch = conf.buildMSwitch();
+ DynamicSwitch dynamicSwitch = conf.buildDynamicSwitch();
+ DynamicSwitch dynamicBytecodeSwitch = conf.buildDynamicBytecodeSwitch();
+ }
+
+ @State(Scope.Thread)
+ public static class ThreadState {
+ TestscenarioFactory factory = TestscenarioFactory.eINSTANCE;
+ EObject[] testObjects = new EObject[] {
+ factory.createA(),
+ factory.createB(),
+ factory.createC(),
+ factory.createD(),
+ factory.createE(),
+ factory.createF(),
+ factory.createG(),
+ factory.createH(),
+ factory.createI(),
+ factory.createK(),
+ factory.createL(),
+ factory.createM(),
+ };
+ }
+
+ @Benchmark
+ public void classicSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.classicSwitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void mSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.mswitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void dynamicSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.dynamicSwitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void dynamicBytecodeSwitch(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(benchmarkState.dynamicBytecodeSwitch.doSwitch(obj));
+ }
+ }
+
+ @Benchmark
+ public void justBlackhole(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) {
+ for (EObject obj: threadState.testObjects) {
+ blackHole.consume(obj);
+ }
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath
new file mode 100644
index 0000000..f7285c2
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/.settings/org.eclipse.jdt.core.prefs b/tests/tools.mdsd.ecoreworkflow.switches.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e50443c
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,15 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..663507b
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,19 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: MDSD.tools Switch Generator Tests Fragment
+Bundle-SymbolicName: tools.mdsd.ecoreworkflow.switches.tests
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: MDSD.tools
+Automatic-Module-Name: tools.mdsd.ecoreworkflow.switches.tests
+Import-Package: org.junit;version="4.12.0",
+ org.junit.jupiter.api;version="5.4.0",
+ org.junit.jupiter.api.function;version="5.4.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: tools.mdsd.ecoreworkflow.switches;bundle-version="0.1.0",
+ org.eclipse.emf.ecore;bundle-version="2.18.0",
+ tools.mdsd.ecoreworkflow.switches.testmodel;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches.testmodel2;bundle-version="0.1.0",
+ tools.mdsd.ecoreworkflow.switches.testmodel3;bundle-version="0.1.0"
+Export-Package: tools.mdsd.ecoreworkflow.switches.tests,
+ tools.mdsd.ecoreworkflow.switches.tests.builders,
+ tools.mdsd.ecoreworkflow.switches.tests.templates
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/build.properties b/tests/tools.mdsd.ecoreworkflow.switches.tests/build.properties
new file mode 100644
index 0000000..e0715e4
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/build.properties
@@ -0,0 +1,4 @@
+source.. = test/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/pom.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests/pom.xml
new file mode 100644
index 0000000..52324f9
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/pom.xml
@@ -0,0 +1,17 @@
+
+ 4.0.0
+
+
+ tools.mdsd.ecoreworkflow
+ tests
+ 0.1.0-SNAPSHOT
+
+
+ tools.mdsd.ecoreworkflow.switches.tests
+ 1.0.0-SNAPSHOT
+ eclipse-test-plugin
+
+
+ 8
+
+
\ No newline at end of file
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java
new file mode 100644
index 0000000..3753b00
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java
@@ -0,0 +1,127 @@
+package tools.mdsd.ecoreworkflow.switches.tests;
+
+import org.eclipse.emf.ecore.EObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Nested;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.BytecodeDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.InspectableSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Package;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.Testscenario3Factory;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.Testscenario3Package;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.BytecodeDynamicSwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.CrossPackageSwitchingRulesBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.InspectableBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.MergeableSwitchBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.SwitchingRulesBehaviourTest;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Package.Literals.*;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.Testscenario3Package.Literals.*;
+
+
+class BytecodeDynamicSwitchTest {
+ @Nested
+ class ConformsToSwitchingRules extends SwitchingRulesBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new BytecodeDynamicSwitchBuilder();
+ }
+
+ }
+
+ @Nested
+ class ConformsToMergeableSwitchBehaviour extends MergeableSwitchBehaviourTest> {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new BytecodeDynamicSwitchBuilder();
+ }
+ }
+
+ @Nested
+ class ConformsToInspectableSwitchBehaviour extends InspectableBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new BytecodeDynamicSwitchBuilder();
+ }
+
+ }
+
+ @Nested
+ class ConformsToCrossPackageSwitchingRulesBehaviour extends CrossPackageSwitchingRulesBehaviourTest {
+ @Override
+ protected DynamicSwitch getSubject() {
+ return new BytecodeDynamicSwitch()
+ .addPackage(TestscenarioPackage.eINSTANCE)
+ .addPackage(Testscenario2Package.eINSTANCE)
+ .addPackage(Testscenario3Package.eINSTANCE);
+ }
+ }
+
+ @Test
+ public void testChainedSyntax() {
+ String result = new BytecodeDynamicSwitch()
+ .dynamicCase(G, (EObject g) -> "G")
+ .dynamicCase(H, (EObject h) -> "H")
+ .dynamicCase(Z, (EObject z) -> "Z")
+ .doSwitch(TestscenarioFactory.eINSTANCE.create(E));
+
+ assertEquals("G", result);
+ }
+
+ @Test
+ public void registeredSubpackageEnablesMatching() {
+ BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch();
+
+ sw.addPackage(Testscenario3Package.eINSTANCE);
+
+ String result = sw
+ .dynamicCase(A, (EObject z) -> "an A") // Q is a child of A, the switch considers that fact because Testscenario2Package has been registered.
+ .defaultCase(o -> "")
+ .doSwitch(Testscenario3Factory.eINSTANCE.create(Q));
+
+ assertEquals("an A", result);
+ }
+
+ @Test
+ public void unregisteredSubpackageDoesntMatch() {
+ BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch();
+
+ // NOTE THAT Q IS NOT RECOGNIZED AS A CHILD OF A BECAUSE WE FAIL TO REGISTER Testscenario3Package
+ // NEGLECTED: sw.addPackage(Testscenario3Package.eINSTANCE);
+
+ String result = sw
+ .dynamicCase(A, a -> "an A") // Q is a child of A, the switch considers that fact because Testscenario2Package has been registered.
+ .defaultCase(o -> "")
+ .doSwitch(Testscenario3Factory.eINSTANCE.create(Q));
+
+ assertEquals("", result);
+ }
+
+ @Test
+ public void crossPackageDelegationWorks() {
+ BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch();
+
+ // NOTE THAT REGISTERING THE PACKAGE IS NOT NECESSARY BECAUSE WE DEFINE A CASE in Tescscenario3
+ // NOT NECARRARY: sw.addPackage(Testscenario3Package.eINSTANCE);
+
+ boolean[] qWasCalled = {false};
+
+ String result = sw
+ .dynamicCase(Q, q -> {qWasCalled[0] = true; return null;}) // Q is more specific then A, but delegates by returning null
+ .dynamicCase(A, a -> "an A") // Q is a child of A, the switch considers that fact because Testscenario2Package has been registered.
+ .defaultCase(o -> "")
+ .doSwitch(Testscenario3Factory.eINSTANCE.create(Q));
+
+ assertEquals("an A", result);
+ assertTrue(qWasCalled[0], "case Q must be called before");
+ }
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/HashDynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/HashDynamicSwitchTest.java
new file mode 100644
index 0000000..79c1dde
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/HashDynamicSwitchTest.java
@@ -0,0 +1,53 @@
+package tools.mdsd.ecoreworkflow.switches.tests;
+
+import org.junit.jupiter.api.Nested;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.InspectableSwitch;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.HashDynamicSwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.CrossPackageSwitchingRulesBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.InspectableBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.MergeableSwitchBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.SwitchingRulesBehaviourTest;
+
+class HashDynamicSwitchTest {
+ @Nested
+ class ConformsToSwitchingRules extends SwitchingRulesBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new HashDynamicSwitchBuilder();
+ }
+
+ }
+
+ @Nested
+ class ConformsToMergeableSwitchBehaviour extends MergeableSwitchBehaviourTest> {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new HashDynamicSwitchBuilder();
+ }
+ }
+
+ @Nested
+ class ConformsToInspectableSwitchBehaviour extends InspectableBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new HashDynamicSwitchBuilder();
+ }
+
+ }
+
+ @Nested
+ class ConformsToCrossPackageSwitchingRulesBehaviour extends CrossPackageSwitchingRulesBehaviourTest {
+ @Override
+ protected DynamicSwitch getSubject() {
+ return new HashDynamicSwitch();
+ }
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioMSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioMSwitchTest.java
new file mode 100644
index 0000000..49a5305
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioMSwitchTest.java
@@ -0,0 +1,58 @@
+package tools.mdsd.ecoreworkflow.switches.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.E;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.InspectableSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.G;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.TestscenarioMSwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.InspectableBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.MergeableSwitchBehaviourTest;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.SwitchingRulesBehaviourTest;
+
+// Note: Redundancy with StaticSwitchTest was deliberately chosen in order to have explicit and stable test cases.
+class TestscenarioMSwitchTest {
+ @Nested
+ class ConformsToSwitchingRules extends SwitchingRulesBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new TestscenarioMSwitchBuilder();
+ }
+
+ }
+
+ @Nested
+ class ConformsToInspectableMergeSwitch extends InspectableBehaviourTest {
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new TestscenarioMSwitchBuilder();
+ }
+ }
+
+ @Nested
+ class ConformsToMergeableSwitch extends MergeableSwitchBehaviourTest> {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new TestscenarioMSwitchBuilder();
+ }
+
+ }
+
+ @Test()
+ void testChainedSyntax() {
+ String result = new TestscenarioMSwitch()
+ .when((F f) -> "F")
+ .when((G g) -> "G")
+ .doSwitch(TestscenarioFactory.eINSTANCE.create(E));
+
+ assertEquals("F", result);
+ }
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioSwitchTest.java
new file mode 100644
index 0000000..91b054c
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioSwitchTest.java
@@ -0,0 +1,24 @@
+package tools.mdsd.ecoreworkflow.switches.tests;
+
+import org.junit.jupiter.api.Nested;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.builders.TestscenarioSwitchBuilder;
+import tools.mdsd.ecoreworkflow.switches.tests.templates.SwitchingRulesBehaviourTest;
+
+class TestscenarioSwitchTest {
+ @Nested
+ class ConformsToSwitchingRules extends SwitchingRulesBehaviourTest {
+
+ @Override
+ protected SwitchBuilder> createSwitchBuilder() {
+ return new TestscenarioSwitchBuilder();
+ }
+
+ @Override
+ protected boolean failsOnNoDefaultCase() {
+ return false; // the old switches do not support throwing exceptions when no default case is defined.
+ // This is a behavioural change. Therefore, by overriding this method, we disable the respective test.
+ }
+ }
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/BytecodeDynamicSwitchBuilder.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/BytecodeDynamicSwitchBuilder.java
new file mode 100644
index 0000000..68438a1
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/BytecodeDynamicSwitchBuilder.java
@@ -0,0 +1,27 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import tools.mdsd.ecoreworkflow.switches.BytecodeDynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+
+public class BytecodeDynamicSwitchBuilder implements SwitchBuilder> {
+ private BytecodeDynamicSwitch delegateTo = new BytecodeDynamicSwitch<>();
+
+ @Override
+ public void addCase(EClass clazz, Function then) {
+ delegateTo.dynamicCase(clazz, then);
+ }
+
+ @Override
+ public void setDefaultCase(Function then) {
+ delegateTo.defaultCase(then);
+ }
+
+ @Override
+ public BytecodeDynamicSwitch build() {
+ return delegateTo;
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/HashDynamicSwitchBuilder.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/HashDynamicSwitchBuilder.java
new file mode 100644
index 0000000..29715c1
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/HashDynamicSwitchBuilder.java
@@ -0,0 +1,27 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import tools.mdsd.ecoreworkflow.switches.DynamicSwitch;
+import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch;
+
+public class HashDynamicSwitchBuilder implements SwitchBuilder> {
+ private HashDynamicSwitch delegateTo = new HashDynamicSwitch<>();
+
+ @Override
+ public void addCase(EClass clazz, Function then) {
+ delegateTo.dynamicCase(clazz, then);
+ }
+
+ @Override
+ public void setDefaultCase(Function then) {
+ delegateTo.defaultCase(then);
+ }
+
+ @Override
+ public HashDynamicSwitch build() {
+ return delegateTo;
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/SwitchBuilder.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/SwitchBuilder.java
new file mode 100644
index 0000000..c320a05
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/SwitchBuilder.java
@@ -0,0 +1,11 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+public interface SwitchBuilder {
+ void addCase(EClass clazz, Function then);
+ void setDefaultCase(Function then);
+ S build();
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioMSwitchBuilder.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioMSwitchBuilder.java
new file mode 100644
index 0000000..6a90047
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioMSwitchBuilder.java
@@ -0,0 +1,57 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch;
+
+public class TestscenarioMSwitchBuilder implements SwitchBuilder> {
+
+ private TestscenarioMSwitch delegateTo = new TestscenarioMSwitch<>();
+
+ @Override
+ public void addCase(EClass clazz, Function then) {
+ Class> functionalInterfaceClass; // the WhenX interface that is used to identify the overloaded when method
+
+ try {
+ functionalInterfaceClass = Class.forName("tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch$When" + clazz.getName());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("The corresponding When...-interface could not be found in the mswitch class", e);
+ }
+
+ Method overloadedWhenMethod;
+
+ try {
+ overloadedWhenMethod = TestscenarioMSwitch.class.getMethod("when", functionalInterfaceClass);
+ } catch (NoSuchMethodException | SecurityException e) {
+ throw new RuntimeException("The corresponding when(...) method could not be found on the mswitch class", e);
+ }
+
+ // we have a Function-object but need a When...-object
+ InvocationHandler relayToThen = (proxy, method, args) -> method.invoke(then, args); // relay all method calls to the then-object
+ Object castedThen = Proxy.newProxyInstance(functionalInterfaceClass.getClassLoader(), new Class[]{functionalInterfaceClass}, relayToThen); // returns a When...-object
+
+ try {
+ overloadedWhenMethod.invoke(delegateTo, castedThen);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new RuntimeException(
+ "The corresponding when(...) method could not be invoked on the switch", e);
+ }
+
+ }
+
+ @Override
+ public void setDefaultCase(Function then) {
+ delegateTo.orElse(then);
+ }
+
+ @Override
+ public TestscenarioMSwitch build() {
+ return delegateTo;
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioSwitchBuilder.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioSwitchBuilder.java
new file mode 100644
index 0000000..1fe2d63
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioSwitchBuilder.java
@@ -0,0 +1,26 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+
+public class TestscenarioSwitchBuilder implements SwitchBuilder> {
+
+ private TestscenarioSwitchAdapter adapter = new TestscenarioSwitchAdapter<>();
+
+ @Override
+ public void addCase(EClass clazz, Function then) {
+ adapter.setCase(clazz, then);
+ }
+
+ @Override
+ public void setDefaultCase(Function then) {
+ adapter.setDefaultCase(then);
+ }
+
+ @Override
+ public TestscenarioSwitchAdapter build() {
+ return adapter;
+ }
+
+}
diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestszenarioSwitchAdapter.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestszenarioSwitchAdapter.java
new file mode 100644
index 0000000..5d2f42e
--- /dev/null
+++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestszenarioSwitchAdapter.java
@@ -0,0 +1,114 @@
+package tools.mdsd.ecoreworkflow.switches.tests.builders;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.A;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.B;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.C;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.D;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.E;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.G;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.H;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.I;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.K;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.L;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.M;
+import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.util.TestscenarioSwitch;
+
+/**
+ * adapter implementation of TestScenarioSwitch that implements all methods and allows for filling them at runtime
+ * @author Christian van Rensen
+ *
+ * @param return type of each case
+ */
+class TestscenarioSwitchAdapter extends TestscenarioSwitch implements ApplyableSwitch {
+ private final Map> functionMap;
+ private Function defaultValue = x -> null;
+
+ /**
+ * keep in mind that this method ONLY works for the classes defined in the switch.
+ * @param clazz
+ * @param then
+ */
+ public void setCase(EClass clazz, Function then) {
+ functionMap.put(clazz.getInstanceClassName(), then);
+ }
+
+ public void setDefaultCase(Function then) {
+ functionMap.put("", then);
+ }
+
+ TestscenarioSwitchAdapter() {
+ functionMap = new HashMap<>();
+ }
+
+ @Override
+ public T caseA(A object) {
+ return functionMap.getOrDefault(A.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseB(B object) {
+ return functionMap.getOrDefault(B.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseC(C object) {
+ return functionMap.getOrDefault(C.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseD(D object) {
+ return functionMap.getOrDefault(D.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseE(E object) {
+ return functionMap.getOrDefault(E.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseF(F object) {
+ return functionMap.getOrDefault(F.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseG(G object) {
+ return functionMap.getOrDefault(G.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseH(H object) {
+ return functionMap.getOrDefault(H.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseI(I object) {
+ return functionMap.getOrDefault(I.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseK(K object) {
+ return functionMap.getOrDefault(K.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseL(L object) {
+ return functionMap.getOrDefault(L.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T caseM(M object) {
+ return functionMap.getOrDefault(M.class.getCanonicalName(), defaultValue).apply(object);
+ }
+
+ @Override
+ public T defaultCase(EObject object) {
+ return functionMap.getOrDefault("