From ac3ffb704901ce2848831f11b614dcd16778f616 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 19 Dec 2019 23:55:45 +0100 Subject: [PATCH 01/52] add AdditionalTemplateGenerator MWE2 component --- .../META-INF/MANIFEST.MF | 6 +- .../AdditionalTemplateGenerator.java | 97 +++++++++++++++++++ .../component/GapPatternPostProcessor.java | 24 +---- .../PackageLevelCodeFileGenerator.java | 12 +++ .../ecoreworkflow/mwe2lib/util/URIToPath.java | 32 ++++++ 5 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/AdditionalTemplateGenerator.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/PackageLevelCodeFileGenerator.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/util/URIToPath.java diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF index fb8d8e7..266eaff 100644 --- a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF +++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF @@ -6,10 +6,12 @@ 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 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..9fb3acc --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/AdditionalTemplateGenerator.java @@ -0,0 +1,97 @@ +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; + +public class AdditionalTemplateGenerator extends AbstractWorkflowComponent2 { + + private String destPath; + private String genModel; + private List packageLevelGenerators; + + + public AdditionalTemplateGenerator() { + packageLevelGenerators = new ArrayList<>(); + } + + @Mandatory + public void setDestPath(String destPath) { + this.destPath = destPath; + } + + @Mandatory + public void setGenModel(String genModel) { + this.genModel = genModel; + } + + 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); + } + } + + @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); + + ResourceSet resSet = new ResourceSetImpl(); + Resource resource = resSet.getResource(URI.createURI(genModel), true); + GenModel genModel = (GenModelImpl) resource.getContents().get(0); + + runGenerator(genModel); + + progressMonitor.done(); + + } + + 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..29beaff --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/src/tools/mdsd/ecoreworkflow/mwe2lib/component/PackageLevelCodeFileGenerator.java @@ -0,0 +1,12 @@ +package tools.mdsd.ecoreworkflow.mwe2lib.component; + +import java.io.OutputStream; +import java.nio.file.Path; + +import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; + +public interface PackageLevelCodeFileGenerator { + public void generateCode(OutputStream out); + public Path getRelativePath(); + 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(); + } + } +} From cad2c40165ffe22dcd2b8ccd0f88b3817c4bd30e Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 20 Dec 2019 01:14:51 +0100 Subject: [PATCH 02/52] switch generator --- bundles/pom.xml | 1 + .../.classpath | 8 ++ .../.gitignore | 1 + .../.project | 34 +++++ .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 17 +++ .../build.properties | 4 + .../mdsd/ecoreworkflow/switches/MSwitch.java | 49 +++++++ .../switches/MSwitchClassGenerator.xtend | 125 ++++++++++++++++++ 9 files changed, 246 insertions(+) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/.classpath create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/.gitignore create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/.project create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/build.properties create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitch.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend diff --git a/bundles/pom.xml b/bundles/pom.xml index 1b72823..3c86447 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -13,6 +13,7 @@ tools.mdsd.ecoreworkflow.builder tools.mdsd.ecoreworkflow.mwe2lib + tools.mdsd.ecoreworkflow.switches 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..f1a4a27 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/.project @@ -0,0 +1,34 @@ + + + tools.mdsd.ecoreworkflow.switches + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.xtext.ui.shared.xtextNature + + 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..862c2ef --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +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" +Import-Package: org.eclipse.core.runtime diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/build.properties b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties new file mode 100644 index 0000000..34d2e4d --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . 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..8e5ca56 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitch.java @@ -0,0 +1,49 @@ +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; + +public abstract class MSwitch { + protected Function defaultCase; + + public static class SwitchingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public SwitchingException(String message) { + super(message); + } + } + + 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 { + List eSuperTypes = eClass.getESuperTypes(); + return eSuperTypes.isEmpty() ? applyDefaultCase(eObject) : doSwitch(eSuperTypes.get(0), eObject); + } + } + + 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"); + } + } + + protected abstract T doSwitch(int classifierID, EObject eObject) throws SwitchingException; + 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..e3329e4 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend @@ -0,0 +1,125 @@ +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 + +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() { + genPackage.packageName + ".xutil" + } + + private def String makeContent() { + ''' + package «packageName»; + + import java.util.function.Function; + + import org.eclipse.emf.ecore.EPackage; + import org.eclipse.emf.ecore.EObject; + + import tools.mdsd.ecoreworkflow.switches.MSwitch; + + public class «className» extends MSwitch { + private static «genPackage.importedPackageInterfaceName» modelPackage; + «FOR c:genPackage.genClasses» + private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; + «ENDFOR» + + public «className»() { + if (modelPackage == null) { + modelPackage = «genPackage.importedPackageInterfaceName».eINSTANCE; + } + } + + public boolean isSwitchFor(EPackage ePackage) { + return ePackage == modelPackage; + } + + protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException { + T result; + switch(classifierID) { + «FOR c : genPackage.genClasses» + case «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.genClasses.map[caseName]» + if (other.«field» != null) this.«field» = other.«field»; + «ENDFOR» + return this; + } + + «FOR c : genPackage.genClasses» + public interface «getInterfaceName(c)» extends Function<«c.importedInterfaceName»,T> {} + «ENDFOR» + + «FOR c : genPackage.genClasses» + public «className» when(«getInterfaceName(c)» then) { + this.«getCaseName(c)» = then; + return this; + } + «ENDFOR» + public «className» orElse(Function defaultCase) { + this.defaultCase = defaultCase; + return this; + } + } + ''' + } + + private def getCaseName(GenClass c) { + "case" + genPackage.getClassUniqueName(c) + } + + private def getInterfaceName(GenClass c) { + "When" + genPackage.getClassUniqueName(c) + } + +} \ No newline at end of file From 9522955e545c21c2fa7cd2bfebdc743a6ffb7547 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 20 Dec 2019 01:18:05 +0100 Subject: [PATCH 03/52] dynamic switching --- .../switches/BreadthFirstSearch.java | 46 +++++++ .../ecoreworkflow/switches/DynamicSwitch.java | 26 ++++ .../switches/HashDynamicSwitch.java | 115 ++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BreadthFirstSearch.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java 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..b0acdb2 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BreadthFirstSearch.java @@ -0,0 +1,46 @@ +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; + +class BreadthFirstSearch { + + /** + * perform a BreathFirstSearch in an acyclic graph represented by a root node and an exploration relationship + * and return the first matching node + * + * @param rootNode the starting node + * @param criterion for a node and a set of unexplored nodes determines if the node matches + * @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; + } + + 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)); + } + } +} \ No newline at end of file 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..184dbc2 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java @@ -0,0 +1,26 @@ +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; + +public interface DynamicSwitch { + public DynamicSwitch dynamicCase(EClass clazz, Function then); + public DynamicSwitch defaultCase(Function then); + public T doSwitch(EObject object); + + public default DynamicSwitch merge(DynamicSwitch dyn) { + dyn.getCases().forEach((clazz, then) -> { + this.dynamicCase(clazz, then::apply); // up-casting if necessary + }); + Function defaultCase = dyn.getDefaultCase(); + if (defaultCase != null) { + defaultCase(defaultCase::apply); // up-casting if necessary + } + return this; + } + public Map> getCases(); + public Function getDefaultCase(); +} 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..974de56 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java @@ -0,0 +1,115 @@ +package tools.mdsd.ecoreworkflow.switches; + +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EcorePackage; + +/** + * 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 + */ +public class HashDynamicSwitch implements DynamicSwitch { + + private Map> caseDefinitions = new LinkedHashMap<>(); + private ConcurrentMap[]> cachedInvokationSequences = new ConcurrentHashMap<>(); + private Function defaultCase; + private static EClass eObjectClass; + + public HashDynamicSwitch() { + if (eObjectClass == null) { + eObjectClass = EcorePackage.eINSTANCE.getEObject(); + } + } + + @Override + public DynamicSwitch dynamicCase(EClass clazz, Function then) { + if (!cachedInvokationSequences.isEmpty()) { // adding cases might cause synchronization issues + throw new IllegalStateException("The switch was modified after already being used"); + } + caseDefinitions.put(clazz, then); + return this; + } + + @Override + public DynamicSwitch defaultCase(Function then) { + this.defaultCase = then; + return this; + } + + @Override + public T doSwitch(EObject object) { + EClass eClass = object.eClass(); + + Function[] targets = cachedInvokationSequences.computeIfAbsent(eClass, this::calculateInvocationSequence); // atomically + + 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"); + } + + @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. + List> invocations = new ArrayList<>(); + new BreadthFirstSearch().scan( + eClass, + (c,r) -> { + if (caseDefinitions.containsKey(c) && r.stream().noneMatch(c::isSuperTypeOf)) { + invocations.add(caseDefinitions.get(c));} + }, + EClass::getESuperTypes + ); + + // EObject::isSuperTypeOf never returns 'EObject', but the user might have supplied it as a type + if (caseDefinitions.containsKey(eObjectClass)) { + invocations.add(caseDefinitions.get(eObjectClass)); + } + + return invocations.toArray(new Function[0]); + } + + @Override + public Map> getCases() { + return Collections.unmodifiableMap(caseDefinitions); + } + + @Override + public Function getDefaultCase() { + return defaultCase; + } + +} From bd6a5b8425c8a2c6670f3c998b5d2b5ffe9997f0 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 30 Dec 2019 12:39:39 +0100 Subject: [PATCH 04/52] add auto-generated hint to switch template --- .../mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend | 2 ++ 1 file changed, 2 insertions(+) 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 index e3329e4..ba4879c 100644 --- 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 @@ -48,6 +48,8 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { import tools.mdsd.ecoreworkflow.switches.MSwitch; + // auto-generated class, do not edit + public class «className» extends MSwitch { private static «genPackage.importedPackageInterfaceName» modelPackage; «FOR c:genPackage.genClasses» From 0d9a0bbc30416e9ae3016ed5cc3cb7df5cb11cbd Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 30 Dec 2019 12:42:50 +0100 Subject: [PATCH 05/52] use constant for E_OBJECT_CLASS --- .../ecoreworkflow/switches/HashDynamicSwitch.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) 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 index 974de56..7f843b1 100644 --- 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 @@ -36,13 +36,8 @@ public class HashDynamicSwitch implements DynamicSwitch { private Map> caseDefinitions = new LinkedHashMap<>(); private ConcurrentMap[]> cachedInvokationSequences = new ConcurrentHashMap<>(); private Function defaultCase; - private static EClass eObjectClass; + private static final EClass E_OBJECT_CLASS = EcorePackage.Literals.EOBJECT; - public HashDynamicSwitch() { - if (eObjectClass == null) { - eObjectClass = EcorePackage.eINSTANCE.getEObject(); - } - } @Override public DynamicSwitch dynamicCase(EClass clazz, Function then) { @@ -95,8 +90,8 @@ private Function[] calculateInvocationSequence(EClass eClass) { ); // EObject::isSuperTypeOf never returns 'EObject', but the user might have supplied it as a type - if (caseDefinitions.containsKey(eObjectClass)) { - invocations.add(caseDefinitions.get(eObjectClass)); + if (caseDefinitions.containsKey(E_OBJECT_CLASS)) { + invocations.add(caseDefinitions.get(E_OBJECT_CLASS)); } return invocations.toArray(new Function[0]); From 5ae02b80f84d76a3c9456457e4320b746357af3b Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 18:27:31 +0100 Subject: [PATCH 06/52] separate interfaces ApplyableSwitch, DynamicSwitch, InspectableSwitch --- .../switches/ApplyableSwitch.java | 16 ++++++ .../ecoreworkflow/switches/DynamicSwitch.java | 57 +++++++++++++------ .../switches/HashDynamicSwitch.java | 2 +- .../switches/InspectableSwitch.java | 16 ++++++ 4 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/InspectableSwitch.java 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..affb7b4 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java @@ -0,0 +1,16 @@ +package tools.mdsd.ecoreworkflow.switches; + +import org.eclipse.emf.ecore.EObject; + +/** + * can take an EObject and perform switching + * @param + */ +public interface ApplyableSwitch { + /** + * + * @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/DynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/DynamicSwitch.java index 184dbc2..38ac388 100644 --- 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 @@ -1,26 +1,47 @@ 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 upon which cases may be defined dynamically. + * + * @param the return type of the switch's clauses + */ public interface DynamicSwitch { - public DynamicSwitch dynamicCase(EClass clazz, Function then); - public DynamicSwitch defaultCase(Function then); - public T doSwitch(EObject object); - - public default DynamicSwitch merge(DynamicSwitch dyn) { - dyn.getCases().forEach((clazz, then) -> { - this.dynamicCase(clazz, then::apply); // up-casting if necessary - }); - Function defaultCase = dyn.getDefaultCase(); - if (defaultCase != null) { - defaultCase(defaultCase::apply); // up-casting if necessary - } - return this; - } - public Map> getCases(); - public Function getDefaultCase(); + /** + * 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 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 index 7f843b1..99f630c 100644 --- 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 @@ -31,7 +31,7 @@ * * @param */ -public class HashDynamicSwitch implements DynamicSwitch { +public class HashDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { private Map> caseDefinitions = new LinkedHashMap<>(); private ConcurrentMap[]> cachedInvokationSequences = new ConcurrentHashMap<>(); 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..354e65a --- /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(); +} From 57cfbc3a5cfbcf01729b0fa3d7383cc8ea545b21 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 18:45:14 +0100 Subject: [PATCH 07/52] checkstyle --- .checkstyle | 8 + .../.checkstyle | 8 + .../.project | 6 + .../switches/ApplyableSwitch.java | 7 +- .../switches/BreadthFirstSearch.java | 72 +++---- .../switches/HashDynamicSwitch.java | 179 +++++++++--------- .../switches/InspectableSwitch.java | 2 +- .../mdsd/ecoreworkflow/switches/MSwitch.java | 79 ++++---- 8 files changed, 196 insertions(+), 165 deletions(-) create mode 100644 .checkstyle create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/.checkstyle diff --git a/.checkstyle b/.checkstyle new file mode 100644 index 0000000..435a63d --- /dev/null +++ b/.checkstyle @@ -0,0 +1,8 @@ + + + + + + + + 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/.project b/bundles/tools.mdsd.ecoreworkflow.switches/.project index f1a4a27..11ca6cb 100644 --- a/bundles/tools.mdsd.ecoreworkflow.switches/.project +++ b/bundles/tools.mdsd.ecoreworkflow.switches/.project @@ -25,10 +25,16 @@ + + 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/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/ApplyableSwitch.java index affb7b4..b19649f 100644 --- 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 @@ -3,12 +3,13 @@ import org.eclipse.emf.ecore.EObject; /** - * can take an EObject and perform switching - * @param + * can take an EObject and perform switching. + * @param the return type of the switch's clauses */ public interface ApplyableSwitch { + // TODO: describe matching semantics /** - * + * takes an eObject and applies the best-matching case. * @param object the input for the switch * @return the switches output */ 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 index b0acdb2..2798078 100644 --- 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 @@ -9,38 +9,40 @@ import java.util.function.Function; class BreadthFirstSearch { - - /** - * perform a BreathFirstSearch in an acyclic graph represented by a root node and an exploration relationship - * and return the first matching node - * - * @param rootNode the starting node - * @param criterion for a node and a set of unexplored nodes determines if the node matches - * @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; - } - - 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)); - } - } -} \ No newline at end of file + + /** + * perform a BreathFirstSearch in an acyclic graph represented by a root node and an exploration. + * relationship and return the first matching node + * + * @param rootNode the starting node + * @param criterion for a node and a set of unexplored nodes determines if the node matches + * @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; + } + + 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/HashDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java index 99f630c..3e5d897 100644 --- 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 @@ -1,7 +1,5 @@ package tools.mdsd.ecoreworkflow.switches; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; - import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -10,101 +8,108 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; - import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EcorePackage; +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; /** - * 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. + * 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. + *

+ * 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. + *

+ * 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 + * @param the return type of the switch's clauses */ -public class HashDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { - - private Map> caseDefinitions = new LinkedHashMap<>(); - private ConcurrentMap[]> cachedInvokationSequences = new ConcurrentHashMap<>(); - private Function defaultCase; - private static final EClass E_OBJECT_CLASS = EcorePackage.Literals.EOBJECT; - - - @Override - public DynamicSwitch dynamicCase(EClass clazz, Function then) { - if (!cachedInvokationSequences.isEmpty()) { // adding cases might cause synchronization issues - throw new IllegalStateException("The switch was modified after already being used"); - } - caseDefinitions.put(clazz, then); - return this; - } - - @Override - public DynamicSwitch defaultCase(Function then) { - this.defaultCase = then; - return this; - } - - @Override - public T doSwitch(EObject object) { - EClass eClass = object.eClass(); - - Function[] targets = cachedInvokationSequences.computeIfAbsent(eClass, this::calculateInvocationSequence); // atomically - - 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"); - } - - @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. - List> invocations = new ArrayList<>(); - new BreadthFirstSearch().scan( - eClass, - (c,r) -> { - if (caseDefinitions.containsKey(c) && r.stream().noneMatch(c::isSuperTypeOf)) { - invocations.add(caseDefinitions.get(c));} - }, - EClass::getESuperTypes - ); - - // EObject::isSuperTypeOf never returns 'EObject', but the user might have supplied it as a type - if (caseDefinitions.containsKey(E_OBJECT_CLASS)) { - invocations.add(caseDefinitions.get(E_OBJECT_CLASS)); - } - - return invocations.toArray(new Function[0]); - } - - @Override - public Map> getCases() { - return Collections.unmodifiableMap(caseDefinitions); - } - - @Override - public Function getDefaultCase() { - return defaultCase; - } +public class HashDynamicSwitch + implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { + + private Map> caseDefinitions = new LinkedHashMap<>(); + private ConcurrentMap[]> cachedInvokationSequences = + new ConcurrentHashMap<>(); + private Function defaultCase; + private static final EClass E_OBJECT_CLASS = EcorePackage.Literals.EOBJECT; + + + @Override + public DynamicSwitch dynamicCase(EClass clazz, Function then) { + if (!cachedInvokationSequences.isEmpty()) { // adding cases might cause synchronization issues + throw new IllegalStateException("The switch was modified after already being used"); + } + caseDefinitions.put(clazz, then); + return this; + } + + @Override + public DynamicSwitch defaultCase(Function then) { + this.defaultCase = then; + return this; + } + + @Override + public T doSwitch(EObject object) { + EClass eClass = object.eClass(); + + Function[] targets = cachedInvokationSequences + .computeIfAbsent(eClass, this::calculateInvocationSequence); + + 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"); + } + + @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. + List> invocations = new ArrayList<>(); + new BreadthFirstSearch().scan(eClass, (c, r) -> { + if (caseDefinitions.containsKey(c) && r.stream().noneMatch(c::isSuperTypeOf)) { + invocations.add(caseDefinitions.get(c)); + } + }, EClass::getESuperTypes); + + // EObject::isSuperTypeOf never returns 'EObject', but the user might have supplied it as a type + if (caseDefinitions.containsKey(E_OBJECT_CLASS)) { + invocations.add(caseDefinitions.get(E_OBJECT_CLASS)); + } + + return invocations.toArray(new Function[0]); + } + + @Override + public Map> getCases() { + return Collections.unmodifiableMap(caseDefinitions); + } + + @Override + public Function getDefaultCase() { + return defaultCase; + } } 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 index 354e65a..7f76094 100644 --- 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 @@ -6,7 +6,7 @@ import org.eclipse.emf.ecore.EObject; /** - * a switch that can list the cases upon which it is defined + * a switch that can list the cases upon which it is defined. * @param the return type of the switch's clauses */ public interface InspectableSwitch { 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 index 8e5ca56..bdfbd6a 100644 --- 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 @@ -2,48 +2,49 @@ 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; public abstract class MSwitch { - protected Function defaultCase; - - public static class SwitchingException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public SwitchingException(String message) { - super(message); - } - } - - 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 { - List eSuperTypes = eClass.getESuperTypes(); - return eSuperTypes.isEmpty() ? applyDefaultCase(eObject) : doSwitch(eSuperTypes.get(0), eObject); - } - } - - 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"); - } - } - - protected abstract T doSwitch(int classifierID, EObject eObject) throws SwitchingException; - public abstract boolean isSwitchFor(EPackage ePackage); + protected Function defaultCase; + + public static class SwitchingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public SwitchingException(String message) { + super(message); + } + } + + 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 { + List eSuperTypes = eClass.getESuperTypes(); + return eSuperTypes.isEmpty() ? applyDefaultCase(eObject) + : doSwitch(eSuperTypes.get(0), eObject); + } + } + + protected abstract T doSwitch(int classifierID, EObject eObject) throws SwitchingException; + + 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); } From 0106250dfafc064fb383ac968ce3035600202a99 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 20:59:00 +0100 Subject: [PATCH 08/52] for usability, make dynamic switch iherit ApplyableSwitch, InspectableSwitch --- .../mdsd/ecoreworkflow/switches/DynamicSwitch.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 index 38ac388..679554b 100644 --- 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 @@ -9,9 +9,12 @@ * * @param the return type of the switch's clauses */ -public interface DynamicSwitch { +/* inheriting from ApplyableSwitch, InspectableSwitch is against single-concern principles, + but is necessary for the builder pattern */ +public interface DynamicSwitch extends ApplyableSwitch, InspectableSwitch { /** * 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} @@ -27,8 +30,9 @@ public interface DynamicSwitch { 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 + * 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} */ @@ -43,5 +47,5 @@ default DynamicSwitch merge(InspectableSwitch other) { return this; } - + } From c361339d9fc8fbf3601388b378ea8e21694f6297 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 21:00:35 +0100 Subject: [PATCH 09/52] allow to merge static switches with dynamic switches --- .../mdsd/ecoreworkflow/switches/MSwitch.java | 12 ++++++-- .../switches/MSwitchClassGenerator.xtend | 28 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) 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 index bdfbd6a..5e1ee06 100644 --- 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 @@ -6,7 +6,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; -public abstract class MSwitch { +public abstract class MSwitch implements ApplyableSwitch, InspectableSwitch { protected Function defaultCase; public static class SwitchingException extends RuntimeException { @@ -25,7 +25,7 @@ public MSwitch() { 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); @@ -37,7 +37,12 @@ protected T doSwitch(EClass eClass, EObject 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 @@ -47,4 +52,5 @@ protected T applyDefaultCase(EObject eObject) { } 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 index ba4879c..fcb8299 100644 --- 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 @@ -42,7 +42,10 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { 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; @@ -51,19 +54,13 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { // auto-generated class, do not edit public class «className» extends MSwitch { - private static «genPackage.importedPackageInterfaceName» modelPackage; + private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; «FOR c:genPackage.genClasses» private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; «ENDFOR» - - public «className»() { - if (modelPackage == null) { - modelPackage = «genPackage.importedPackageInterfaceName».eINSTANCE; - } - } - + public boolean isSwitchFor(EPackage ePackage) { - return ePackage == modelPackage; + return ePackage == MODEL_PACKAGE; } protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException { @@ -112,6 +109,19 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { this.defaultCase = defaultCase; return this; } + + @Override + public Map> getCases() { + Map> definedCases = new HashMap<>(); + + «FOR c:genPackage.genClasses» + if (this.«getCaseName(c)» != null) { + definedCases.put(«genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o)); + } + «ENDFOR» + + return definedCases; + } } ''' } From 50e5001315815d4ba8e9e7909ed1f0be1aa5979c Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 21:05:33 +0100 Subject: [PATCH 10/52] move EObject-class treatment to setup-time method --- .../ecoreworkflow/switches/HashDynamicSwitch.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 index 3e5d897..40afe62 100644 --- 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 @@ -47,7 +47,12 @@ public DynamicSwitch dynamicCase(EClass clazz, Function then) { if (!cachedInvokationSequences.isEmpty()) { // adding cases might cause synchronization issues throw new IllegalStateException("The switch was modified after already being used"); } - caseDefinitions.put(clazz, then); + if (E_OBJECT_CLASS.equals(clazz)) { + // special treatment necessary, because EObject might not be caught otherwise. + defaultCase(then); + } else { + caseDefinitions.put(clazz, then); + } return this; } @@ -94,11 +99,6 @@ private Function[] calculateInvocationSequence(EClass eClass) { } }, EClass::getESuperTypes); - // EObject::isSuperTypeOf never returns 'EObject', but the user might have supplied it as a type - if (caseDefinitions.containsKey(E_OBJECT_CLASS)) { - invocations.add(caseDefinitions.get(E_OBJECT_CLASS)); - } - return invocations.toArray(new Function[0]); } From 8ff072f883460d33ab619f5d7654c519bbec5d96 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 2 Jan 2020 21:08:11 +0100 Subject: [PATCH 11/52] fix dynamic class loading wildcard dynamic import for mwe2lib in order to allow dynamic class loading --- bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF index 266eaff..3a568bf 100644 --- a/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF +++ b/bundles/tools.mdsd.ecoreworkflow.mwe2lib/META-INF/MANIFEST.MF @@ -15,3 +15,4 @@ Require-Bundle: org.eclipse.emf.mwe.utils, org.eclipse.core.resources, org.eclipse.emf.codegen.ecore;bundle-version="2.18.0" Import-Package: org.eclipse.core.runtime +DynamicImport-Package: * From 094db1b6d6fbf4757164c8d6cb18fa17ebcac9eb Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 30 Dec 2019 18:18:17 +0100 Subject: [PATCH 12/52] configure a new test project for switches --- tests/pom.xml | 2 +- .../.classpath | 15 ++++++++++++++ .../.settings/org.eclipse.jdt.core.prefs | 15 ++++++++++++++ .../META-INF/MANIFEST.MF | 12 +++++++++++ .../build.properties | 4 ++++ .../pom.xml | 17 ++++++++++++++++ .../switches/tests/DynamicSwitchTest.java | 20 +++++++++++++++++++ 7 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/.settings/org.eclipse.jdt.core.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/build.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/pom.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java diff --git a/tests/pom.xml b/tests/pom.xml index 7904660..539f6f6 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -11,7 +11,7 @@ pom - + tools.mdsd.ecoreworkflow.switches.tests diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath new file mode 100644 index 0000000..2c47dab --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + 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..5876f9c --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +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" +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" 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/DynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java new file mode 100644 index 0000000..faced5d --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java @@ -0,0 +1,20 @@ +package tools.mdsd.ecoreworkflow.switches.tests; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; +import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch; + +class DynamicSwitchTest { + + @Test + void testGetCases() { + DynamicSwitch s = new HashDynamicSwitch<>(); + s.defaultCase(o -> "default"); + assertEquals(0, s.getCases().size(), "Only the default case must be defined"); + assertNotNull(s.getDefaultCase(), "The default case must be defined"); + } + +} From 0d6eb581f8d89597d2b066dded5011b747c246a5 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Thu, 9 Jan 2020 16:25:49 +0100 Subject: [PATCH 13/52] fix: for allowing to run junit tests from inside eclipse --- tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath index 2c47dab..f7285c2 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/.classpath @@ -1,6 +1,7 @@ + From c9f957cb2523d9daefb062874cd177c55cb0aa72 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 10 Jan 2020 02:30:02 +0100 Subject: [PATCH 14/52] TEMPORARY: MSwitchGenerator as Java instead of xtend --- .../switches/MSwitchClassGenerator.java | 392 ++++++++++++++++++ .../switches/MSwitchClassGenerator.xtend | 5 +- 2 files changed, 395 insertions(+), 2 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java new file mode 100644 index 0000000..0a396b0 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java @@ -0,0 +1,392 @@ +package tools.mdsd.ecoreworkflow.switches; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.eclipse.emf.codegen.ecore.genmodel.GenClass; +import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; +import org.eclipse.emf.common.util.EList; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import tools.mdsd.ecoreworkflow.mwe2lib.component.PackageLevelCodeFileGenerator; + +@SuppressWarnings("all") +public class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { + private GenPackage genPackage; + + @Override + public void setGenPackage(final GenPackage genPackage) { + this.genPackage = genPackage; + } + + @Override + public Path getRelativePath() { + Path _xblockexpression = null; + { + if ((this.genPackage == null)) { + throw new IllegalStateException("genPackage was not initialized"); + } + Path _get = Paths.get("", this.getPackageName().split("\\.")); + String _className = this.getClassName(); + String _plus = (_className + ".java"); + _xblockexpression = _get.resolve(_plus); + } + return _xblockexpression; + } + + @Override + public void generateCode(final OutputStream out) { + if ((this.genPackage == null)) { + throw new IllegalStateException("genPackage was not initialized"); + } + final PrintWriter printWriter = new PrintWriter(out); + printWriter.print(this.makeContent()); + printWriter.flush(); + } + + private String getClassName() { + return this.genPackage.getSwitchClassName().replaceFirst("Switch$", "MSwitch"); + } + + private String getPackageName() { + String _packageName = this.genPackage.getPackageName(); + return (_packageName + ".xutil"); + } + + private String makeContent() { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("package "); + String _packageName = this.getPackageName(); + _builder.append(_packageName); + _builder.append(";"); + _builder.newLineIfNotEmpty(); + _builder.newLine(); + _builder.append("import java.util.function.Function;"); + _builder.newLine(); + _builder.append("import java.util.HashMap;"); + _builder.newLine(); + _builder.append("import java.util.Map;"); + _builder.newLine(); + _builder.newLine(); + _builder.append("import org.eclipse.emf.ecore.EClass;"); + _builder.newLine(); + _builder.append("import org.eclipse.emf.ecore.EPackage;"); + _builder.newLine(); + _builder.append("import org.eclipse.emf.ecore.EObject;"); + _builder.newLine(); + _builder.newLine(); + _builder.append("import tools.mdsd.ecoreworkflow.switches.MSwitch;"); + _builder.newLine(); + _builder.newLine(); + _builder.append("// auto-generated class, do not edit"); + _builder.newLine(); + _builder.newLine(); + _builder.append("public class "); + String _className = this.getClassName(); + _builder.append(_className); + _builder.append(" extends MSwitch {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t"); + _builder.append("private static "); + String _importedPackageInterfaceName = this.genPackage.getImportedPackageInterfaceName(); + _builder.append(_importedPackageInterfaceName, "\t"); + _builder.append(" MODEL_PACKAGE = "); + String _importedPackageInterfaceName_1 = this.genPackage.getImportedPackageInterfaceName(); + _builder.append(_importedPackageInterfaceName_1, "\t"); + _builder.append(".eINSTANCE;"); + _builder.newLineIfNotEmpty(); + { + EList _genClasses = this.genPackage.getGenClasses(); + for(final GenClass c : _genClasses) { + _builder.append("\t"); + _builder.append("private Function<"); + String _importedInterfaceName = c.getImportedInterfaceName(); + _builder.append(_importedInterfaceName, "\t"); + _builder.append(",T> "); + String _caseName = this.getCaseName(c); + _builder.append(_caseName, "\t"); + _builder.append(";"); + _builder.newLineIfNotEmpty(); + } + } + _builder.newLine(); + _builder.append("\t"); + _builder.append("public boolean isSwitchFor(EPackage ePackage) {"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("return ePackage == MODEL_PACKAGE;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException {"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("T result;"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("switch(classifierID) {"); + _builder.newLine(); + { + EList _genClasses_1 = this.genPackage.getGenClasses(); + for(final GenClass c_1 : _genClasses_1) { + _builder.append("\t\t\t"); + _builder.append("case "); + String _importedPackageInterfaceName_2 = this.genPackage.getImportedPackageInterfaceName(); + _builder.append(_importedPackageInterfaceName_2, "\t\t\t"); + _builder.append("."); + String _classifierID = this.genPackage.getClassifierID(c_1); + _builder.append(_classifierID, "\t\t\t"); + _builder.append(": {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t"); + String _importedInterfaceName_1 = c_1.getImportedInterfaceName(); + _builder.append(_importedInterfaceName_1, "\t\t\t\t"); + _builder.append(" casted = ("); + String _importedInterfaceName_2 = c_1.getImportedInterfaceName(); + _builder.append(_importedInterfaceName_2, "\t\t\t\t"); + _builder.append(") eObject;"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("if ("); + String _caseName_1 = this.getCaseName(c_1); + _builder.append(_caseName_1, "\t\t\t\t"); + _builder.append(" != null) {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t\t"); + _builder.append("result = "); + String _caseName_2 = this.getCaseName(c_1); + _builder.append(_caseName_2, "\t\t\t\t\t"); + _builder.append(".apply(casted);"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t\t"); + _builder.append("if (result != null) return result;"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + { + List _switchGenClasses = c_1.getSwitchGenClasses(); + for(final GenClass alternative : _switchGenClasses) { + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("if ("); + String _caseName_3 = this.getCaseName(alternative); + _builder.append(_caseName_3, "\t\t\t\t"); + _builder.append(" != null) {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("\t"); + _builder.append("result = "); + String _caseName_4 = this.getCaseName(alternative); + _builder.append(_caseName_4, "\t\t\t\t\t"); + _builder.append(".apply(casted);"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("\t"); + _builder.append("if (result != null) return result;"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + } + } + _builder.append("\t\t\t"); + _builder.append("\t"); + _builder.append("break;"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("}"); + _builder.newLine(); + } + } + _builder.append("\t\t\t"); + _builder.append("default:"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("throw new Error(\"type \" + eObject.eClass() + \" was not considered by the mswitch code generator\");"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("return applyDefaultCase(eObject);"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("public "); + String _className_1 = this.getClassName(); + _builder.append(_className_1, "\t"); + _builder.append(" merge("); + String _className_2 = this.getClassName(); + _builder.append(_className_2, "\t"); + _builder.append(" other) {"); + _builder.newLineIfNotEmpty(); + { + final Function1 _function = (GenClass it) -> { + return this.getCaseName(it); + }; + List _map = ListExtensions.map(this.genPackage.getGenClasses(), _function); + for(final String field : _map) { + _builder.append("\t\t"); + _builder.append("if (other."); + _builder.append(field, "\t\t"); + _builder.append(" != null) this."); + _builder.append(field, "\t\t"); + _builder.append(" = other."); + _builder.append(field, "\t\t"); + _builder.append(";"); + _builder.newLineIfNotEmpty(); + } + } + _builder.append("\t\t"); + _builder.append("return this;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("} "); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + { + EList _genClasses_2 = this.genPackage.getGenClasses(); + for(final GenClass c_2 : _genClasses_2) { + _builder.append("\t"); + _builder.append("public interface "); + String _interfaceName = this.getInterfaceName(c_2); + _builder.append(_interfaceName, "\t"); + _builder.append(" extends Function<"); + String _importedInterfaceName_3 = c_2.getImportedInterfaceName(); + _builder.append(_importedInterfaceName_3, "\t"); + _builder.append(",T> {}"); + _builder.newLineIfNotEmpty(); + } + } + _builder.append("\t"); + _builder.newLine(); + { + EList _genClasses_3 = this.genPackage.getGenClasses(); + for(final GenClass c_3 : _genClasses_3) { + _builder.append("\t"); + _builder.append("public "); + String _className_3 = this.getClassName(); + _builder.append(_className_3, "\t"); + _builder.append(" when("); + String _interfaceName_1 = this.getInterfaceName(c_3); + _builder.append(_interfaceName_1, "\t"); + _builder.append(" then) {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t"); + _builder.append("\t"); + _builder.append("this."); + String _caseName_5 = this.getCaseName(c_3); + _builder.append(_caseName_5, "\t\t"); + _builder.append(" = then;"); + _builder.newLineIfNotEmpty(); + _builder.append("\t"); + _builder.append("\t"); + _builder.append("return this;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + } + } + _builder.append("\t"); + _builder.append("public "); + String _className_4 = this.getClassName(); + _builder.append(_className_4, "\t"); + _builder.append(" orElse(Function defaultCase) {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t\t"); + _builder.append("this.defaultCase = defaultCase;"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("return this;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("@Override"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("public Map> getCases() {"); + _builder.newLine(); + _builder.append("\t "); + _builder.append("Map> definedCases = new HashMap<>();"); + _builder.newLine(); + _builder.append("\t "); + _builder.newLine(); + { + EList _genClasses_4 = this.genPackage.getGenClasses(); + for(final GenClass c_4 : _genClasses_4) { + _builder.append("\t "); + _builder.append("if (this."); + String _caseName_6 = this.getCaseName(c_4); + _builder.append(_caseName_6, "\t "); + _builder.append(" != null) {"); + _builder.newLineIfNotEmpty(); + _builder.append("\t "); + _builder.append("\t"); + _builder.append("definedCases.put("); + String _importedPackageInterfaceName_3 = this.genPackage.getImportedPackageInterfaceName(); + _builder.append(_importedPackageInterfaceName_3, "\t \t"); + _builder.append(".Literals."); + String _classifierID_1 = this.genPackage.getClassifierID(c_4); + _builder.append(_classifierID_1, "\t \t"); + _builder.append(", this."); + String _caseName_7 = this.getCaseName(c_4); + _builder.append(_caseName_7, "\t \t"); + _builder.append(".compose(o -> ("); + String _importedInterfaceName_4 = c_4.getImportedInterfaceName(); + _builder.append(_importedInterfaceName_4, "\t \t"); + _builder.append(") o));"); + _builder.newLineIfNotEmpty(); + _builder.append("\t "); + _builder.append("}"); + _builder.newLine(); + } + } + _builder.append("\t "); + _builder.newLine(); + _builder.append("\t "); + _builder.append("return definedCases;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("}"); + _builder.newLine(); + return _builder.toString(); + } + + private String getCaseName(final GenClass c) { + String _classUniqueName = this.genPackage.getClassUniqueName(c); + return ("case" + _classUniqueName); + } + + private String getInterfaceName(final GenClass c) { + String _classUniqueName = this.genPackage.getClassUniqueName(c); + return ("When" + _classUniqueName); + } +} 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 index fcb8299..70359b7 100644 --- 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 @@ -1,5 +1,6 @@ package tools.mdsd.ecoreworkflow.switches; - +// TODO uncomment the following. It is only commented out, because xtend classes don't make it into the maven build up to now +/* import java.io.OutputStream import java.io.PrintWriter import java.nio.file.Paths @@ -134,4 +135,4 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { "When" + genPackage.getClassUniqueName(c) } -} \ No newline at end of file +}*/ \ No newline at end of file From 47180dbeae89cc380dcdff788e1e1cf5d56fbadc Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 10 Jan 2020 02:37:16 +0100 Subject: [PATCH 15/52] add model as test scenario --- tests/pom.xml | 1 + .../.classpath | 7 + .../.gitignore | 1 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 21 + .../build.properties | 10 + .../model/testscenario.aird | 704 ++++++++++++++++++ .../model/testscenario.ecore | 17 + .../model/testscenario.genmodel | 23 + .../plugin.properties | 4 + .../plugin.xml | 17 + .../src/.gitkeep | 0 .../workflow/generate.mwe2 | 27 + .../META-INF/MANIFEST.MF | 4 +- 14 files changed, 842 insertions(+), 1 deletion(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/.classpath create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/.gitignore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/.settings/org.eclipse.jdt.core.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/build.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.aird create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.ecore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/model/testscenario.genmodel create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/plugin.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/src/.gitkeep create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 diff --git a/tests/pom.xml b/tests/pom.xml index 539f6f6..c3bab37 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -12,6 +12,7 @@ tools.mdsd.ecoreworkflow.switches.tests + tools.mdsd.ecoreworkflow.switches.testmodel 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/.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..d702593 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF @@ -0,0 +1,21 @@ +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 +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/src/.gitkeep b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/src/.gitkeep new file mode 100644 index 0000000..e69de29 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..ce0e2b7 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 @@ -0,0 +1,27 @@ +module generate + +import org.eclipse.emf.mwe2.ecore.EcoreGenerator +import tools.mdsd.ecoreworkflow.mwe2lib.bean.EclipseRCPSupportingStandaloneSetup +import tools.mdsd.ecoreworkflow.mwe2lib.component.AdditionalTemplateGenerator +var workspaceRoot = "../../" + +Workflow { + bean = EclipseRCPSupportingStandaloneSetup { + scanClassPath = true + platformUri = workspaceRoot + } + + 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.tests/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF index 5876f9c..00cfcf8 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -9,4 +9,6 @@ Import-Package: org.junit;version="4.12.0", org.junit.jupiter.api;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" + org.eclipse.emf.ecore;bundle-version="2.18.0", + tools.mdsd.ecoreworkflow.switches.testmodel;bundle-version="0.1.0" +Export-Package: tools.mdsd.ecoreworkflow.switches.tests From 1cc79cbec7b7c385e4b83ffe64848028764e6c7e Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 10 Jan 2020 03:11:14 +0100 Subject: [PATCH 16/52] test case priorities of static mswitches --- .../META-INF/MANIFEST.MF | 3 +- .../switches/tests/StaticSwitchTest.java | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF index d702593..63cd0a5 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF @@ -8,7 +8,8 @@ Bundle-ClassPath: . Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Export-Package: tools.mdsd.ecoreworkflow.switches.testmodel.testscenario, +Export-Package: testscenario.xutil, + tools.mdsd.ecoreworkflow.switches.testmodel.testscenario, tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.impl, tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.util Bundle-ActivationPolicy: lazy diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java new file mode 100644 index 0000000..0dae799 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java @@ -0,0 +1,49 @@ +package tools.mdsd.ecoreworkflow.switches.tests; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import testscenario.xutil.TestscenarioMSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*; + +class StaticSwitchTest { + + @Test + void childCaseWins() { + String result = + new TestscenarioMSwitch() + .when((G g) -> "G") + .when((H h) -> "H") + .doSwitch(getE()); + + assertEquals("G", result, "child cases are more specific"); + } + + @Test + void firstParentWins() { + String result = new TestscenarioMSwitch() + .when((F f) -> "F") + .when((G g) -> "G") + .doSwitch(getE()); + + assertEquals("F", result, "first listed parent is treated more specific"); + } + + @Test + void shorterInheritancePathsAreMoreSpecific() { + StringBuffer callOrder = new StringBuffer(); + new TestscenarioMSwitch() + .when((G g) -> {callOrder.append("G"); return null;}) + .when((I i) -> {callOrder.append("I"); return null;}) + .when((H h) -> {callOrder.append("H"); return "H";}) + .orElse(x -> "X") + .doSwitch(getE()); + + assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); + } + + + private E getE() { + return TestscenarioFactory.eINSTANCE.createE(); + } + +} From 600c13468b7823a6a55d019c45b9013d4b2c912b Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 10 Jan 2020 07:23:52 +0100 Subject: [PATCH 17/52] further test cases for static MSwitches concerning default cases, merging --- .../switches/MSwitchClassGenerator.java | 2 + .../switches/MSwitchClassGenerator.xtend | 1 + .../META-INF/MANIFEST.MF | 3 +- .../switches/tests/StaticSwitchTest.java | 103 +++++++++++++++++- 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java index 0a396b0..2af7ead 100644 --- a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java @@ -258,6 +258,8 @@ private String makeContent() { } } _builder.append("\t\t"); + _builder.append("if (other.defaultCase != null) this.defaultCase = other.defaultCase;"); + _builder.append("\t\t"); _builder.append("return this;"); _builder.newLine(); _builder.append("\t"); 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 index 70359b7..8035ecd 100644 --- 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 @@ -93,6 +93,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { «FOR field: genPackage.genClasses.map[caseName]» if (other.«field» != null) this.«field» = other.«field»; «ENDFOR» + if (other.defaultCase != null) this.defaultCase = other.defaultCase; return this; } diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF index 00cfcf8..690742f 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ 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;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", diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java index 0dae799..1de3701 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import testscenario.xutil.TestscenarioMSwitch; +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*; class StaticSwitchTest { @@ -41,7 +42,107 @@ void shorterInheritancePathsAreMoreSpecific() { assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); } - + @Test + void exceptionOnNoMatch() { + TestscenarioMSwitch sw = new TestscenarioMSwitch(); + sw.when((G g) -> null); + F nonMatchingObject = TestscenarioFactory.eINSTANCE.createF(); + + assertThrows(SwitchingException.class, ()->{sw.doSwitch(nonMatchingObject);}, "a SwitchingException must be thrown when none of the defined branches match and no default case is defined."); + } + + @Test + void defaultCaseIsCalled() { + TestscenarioMSwitch sw = new TestscenarioMSwitch(); + F nonMathingObject = TestscenarioFactory.eINSTANCE.createF(); + + sw.when((G g) -> null); + sw.orElse((param) -> { + assertTrue(nonMathingObject == param, "default case must be passed the EObject that was put into the switch"); + return "default"; + }); + + assertEquals("default", sw.doSwitch(nonMathingObject), "default case must be used when the defined cases don't match"); + } + + @Test + void subsequentCasesNotCalled() { + TestscenarioMSwitch sw = new TestscenarioMSwitch<>(); + sw.when((A a) -> {fail("The A case must not be called"); return null;}); + sw.when((D d) -> new Object()); + sw.doSwitch(getE()); + } + + @Test + void subsequentCasesCalledInCorrectOrder() { + TestscenarioMSwitch sw = new TestscenarioMSwitch<>(); + StringBuffer callOrder = new StringBuffer(); + + sw.when((A x)->{callOrder.append("A"); return null;}); + sw.when((B x)->{callOrder.append("B"); return null;}); + sw.when((C x)->{callOrder.append("C"); return null;}); + sw.when((D x)->{callOrder.append("D"); return null;}); + sw.when((E x)->{callOrder.append("E"); return null;}); + sw.when((F x)->{callOrder.append("F"); return null;}); + sw.when((G x)->{callOrder.append("G"); return null;}); + sw.when((H x)->{callOrder.append("H"); return null;}); + sw.when((I x)->{callOrder.append("I"); return null;}); + sw.when((K x)->{callOrder.append("K"); return null;}); + sw.when((L x)->{callOrder.append("L"); return null;}); + sw.when((M x)->{callOrder.append("M"); return null;}); + sw.orElse((x) ->{callOrder.append("X"); return null;}); + + sw.doSwitch(getE()); + + assertEquals("EKDCFGILBHMAX", callOrder.toString()); + } + + @Test + void mergingLessSpecificBranchWithoutEffect() { + TestscenarioMSwitch original = new TestscenarioMSwitch<>(); + TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); + + original.when((D d)->"D"); + merged.when((B b) -> "B"); + + assertEquals("D", original.merge(merged).doSwitch(getE())); + } + + @Test + void mergingSameBranchOverrides() { + TestscenarioMSwitch original = new TestscenarioMSwitch<>(); + TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); + + original.when((D d)->"1"); + merged.when((D d) -> "2"); + + assertEquals("2", original.merge(merged).doSwitch(getE())); + } + + @Test + void mergingDefaultBranchOverrides() { + TestscenarioMSwitch original = new TestscenarioMSwitch<>(); + TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); + + original.when((D d) -> null); + original.orElse(x -> "1"); + merged.orElse(x -> "2"); + + assertEquals("2", original.merge(merged).doSwitch(getE())); + } + + @Test + void mergingAffectsGetCases() { + TestscenarioMSwitch original = new TestscenarioMSwitch<>(); + TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); + + merged.when((A a) -> "a"); + + original.merge(merged); + + assertEquals("a", original.getCases().get(TestscenarioPackage.Literals.A).apply(getE())); + } + private E getE() { return TestscenarioFactory.eINSTANCE.createE(); } From 0eba493bbafff89252de9f4ac284869d796d5464 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 10 Jan 2020 07:40:22 +0100 Subject: [PATCH 18/52] add equivalent cases for dynamic switches --- .../switches/tests/DynamicSwitchTest.java | 219 +++++++++++++++++- 1 file changed, 209 insertions(+), 10 deletions(-) diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java index faced5d..7e323d2 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java @@ -1,20 +1,219 @@ package tools.mdsd.ecoreworkflow.switches.tests; -import static org.junit.jupiter.api.Assertions.*; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.eclipse.emf.ecore.EObject; import org.junit.jupiter.api.Test; - import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch; +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.E; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals; class DynamicSwitchTest { - @Test - void testGetCases() { - DynamicSwitch s = new HashDynamicSwitch<>(); - s.defaultCase(o -> "default"); - assertEquals(0, s.getCases().size(), "Only the default case must be defined"); - assertNotNull(s.getDefaultCase(), "The default case must be defined"); - } + @Test + void testGetCases() { + DynamicSwitch s = new HashDynamicSwitch<>(); + s.defaultCase(o -> "default"); + assertEquals(0, s.getCases().size(), "Only the default case must be defined"); + assertNotNull(s.getDefaultCase(), "The default case must be defined"); + } + + @Test + void childCaseWins() { + String result = getSwitch().dynamicCase(Literals.G, (EObject g) -> "G") + .dynamicCase(Literals.H, (EObject h) -> "H").doSwitch(getE()); + + assertEquals("G", result, "child cases are more specific"); + } + + @Test + void firstParentWins() { + String result = getSwitch().dynamicCase(Literals.F, (EObject f) -> "F") + .dynamicCase(Literals.G, (EObject g) -> "G").doSwitch(getE()); + + assertEquals("F", result, "first listed parent is treated more specific"); + } + + @Test + void shorterInheritancePathsAreMoreSpecific() { + StringBuffer callOrder = new StringBuffer(); + getSwitch().dynamicCase(Literals.G, (EObject g) -> { + callOrder.append("G"); + return null; + }).dynamicCase(Literals.I, (EObject i) -> { + callOrder.append("I"); + return null; + }).dynamicCase(Literals.H, (EObject h) -> { + callOrder.append("H"); + return "H"; + }).defaultCase(x -> "X").doSwitch(getE()); + + assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); + } + + @Test + void exceptionOnNoMatch() { + DynamicSwitch sw = getSwitch(); + sw.dynamicCase(Literals.G, (EObject g) -> null); + F nonMatchingObject = TestscenarioFactory.eINSTANCE.createF(); + + assertThrows(SwitchingException.class, () -> { + sw.doSwitch(nonMatchingObject); + }, "a SwitchingException must be thrown when none of the defined branches match and no default case is defined."); + } + + @Test + void defaultCaseIsCalled() { + DynamicSwitch sw = getSwitch(); + F nonMathingObject = TestscenarioFactory.eINSTANCE.createF(); + + sw.dynamicCase(Literals.G, (EObject g) -> null); + sw.defaultCase((param) -> { + assertTrue(nonMathingObject == param, + "default case must be passed the EObject that was put into the switch"); + return "default"; + }); + + assertEquals("default", sw.doSwitch(nonMathingObject), + "default case must be used when the defined cases don't match"); + } + + @Test + void subsequentCasesNotCalled() { + DynamicSwitch sw = getSwitch(); + sw.dynamicCase(Literals.A, (EObject a) -> { + fail("The A case must not be called"); + return null; + }); + sw.dynamicCase(Literals.D, (EObject d) -> "d"); + sw.doSwitch(getE()); + } + + @Test + void subsequentCasesCalledInCorrectOrder() { + DynamicSwitch sw = getSwitch(); + StringBuffer callOrder = new StringBuffer(); + + sw.dynamicCase(Literals.A, (EObject x) -> { + callOrder.append("A"); + return null; + }); + sw.dynamicCase(Literals.B, (EObject x) -> { + callOrder.append("B"); + return null; + }); + sw.dynamicCase(Literals.C, (EObject x) -> { + callOrder.append("C"); + return null; + }); + sw.dynamicCase(Literals.D, (EObject x) -> { + callOrder.append("D"); + return null; + }); + sw.dynamicCase(Literals.E, (EObject x) -> { + callOrder.append("E"); + return null; + }); + sw.dynamicCase(Literals.F, (EObject x) -> { + callOrder.append("F"); + return null; + }); + sw.dynamicCase(Literals.G, (EObject x) -> { + callOrder.append("G"); + return null; + }); + sw.dynamicCase(Literals.H, (EObject x) -> { + callOrder.append("H"); + return null; + }); + sw.dynamicCase(Literals.I, (EObject x) -> { + callOrder.append("I"); + return null; + }); + sw.dynamicCase(Literals.K, (EObject x) -> { + callOrder.append("K"); + return null; + }); + sw.dynamicCase(Literals.L, (EObject x) -> { + callOrder.append("L"); + return null; + }); + sw.dynamicCase(Literals.M, (EObject x) -> { + callOrder.append("M"); + return null; + }); + sw.defaultCase((x) -> { + callOrder.append("X"); + return null; + }); + + sw.doSwitch(getE()); + + assertEquals("EKDCFGILBHMAX", callOrder.toString()); + } + + @Test + void mergingLessSpecificBranchWithoutEffect() { + DynamicSwitch original = getSwitch(); + DynamicSwitch merged = getSwitch(); + + original.dynamicCase(Literals.D, (EObject d) -> "D"); + merged.dynamicCase(Literals.B, (EObject b) -> "B"); + + assertEquals("D", original.merge(merged).doSwitch(getE())); + } + + + + @Test + void mergingSameBranchOverrides() { + DynamicSwitch original = getSwitch(); + DynamicSwitch merged = getSwitch(); + + original.dynamicCase(Literals.D, (EObject d) -> "1"); + merged.dynamicCase(Literals.D, (EObject d) -> "2"); + + assertEquals("2", original.merge(merged).doSwitch(getE())); + } + + @Test + void mergingDefaultBranchOverrides() { + DynamicSwitch original = getSwitch(); + DynamicSwitch merged = getSwitch(); + + original.dynamicCase(Literals.D, (EObject d) -> null); + original.defaultCase(x -> "1"); + merged.defaultCase(x -> "2"); + + assertEquals("2", original.merge(merged).doSwitch(getE())); + } + + @Test + void mergingAffectsGetCases() { + DynamicSwitch original = getSwitch(); + DynamicSwitch merged = getSwitch(); + + merged.dynamicCase(Literals.A, (EObject a) -> "a"); + + original.merge(merged); + + assertEquals("a", original.getCases().get(TestscenarioPackage.Literals.A).apply(getE())); + } + + private E getE() { + return TestscenarioFactory.eINSTANCE.createE(); + } + + private DynamicSwitch getSwitch() { + return new HashDynamicSwitch<>(); + } } From e782a165964efa8d5d08fdb63e330b3d725bd2ef Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 15 Jan 2020 16:58:35 +0100 Subject: [PATCH 19/52] fix: add xtend-src to the build source --- bundles/tools.mdsd.ecoreworkflow.switches/build.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/build.properties b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties index 34d2e4d..d8e2f0e 100644 --- a/bundles/tools.mdsd.ecoreworkflow.switches/build.properties +++ b/bundles/tools.mdsd.ecoreworkflow.switches/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . From e67e300f25f5f3ca8296b42b98e25e2ae70bfa61 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 15 Jan 2020 16:59:32 +0100 Subject: [PATCH 20/52] restore xtend version of MSwitchClassGenerator and change its encoding to UTF8 --- .../switches/MSwitchClassGenerator.java | 394 ------------------ .../switches/MSwitchClassGenerator.xtend | 68 +-- 2 files changed, 34 insertions(+), 428 deletions(-) delete mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java deleted file mode 100644 index 2af7ead..0000000 --- a/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MSwitchClassGenerator.java +++ /dev/null @@ -1,394 +0,0 @@ -package tools.mdsd.ecoreworkflow.switches; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import org.eclipse.emf.codegen.ecore.genmodel.GenClass; -import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; -import org.eclipse.emf.common.util.EList; -import org.eclipse.xtend2.lib.StringConcatenation; -import org.eclipse.xtext.xbase.lib.Functions.Function1; -import org.eclipse.xtext.xbase.lib.ListExtensions; -import tools.mdsd.ecoreworkflow.mwe2lib.component.PackageLevelCodeFileGenerator; - -@SuppressWarnings("all") -public class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { - private GenPackage genPackage; - - @Override - public void setGenPackage(final GenPackage genPackage) { - this.genPackage = genPackage; - } - - @Override - public Path getRelativePath() { - Path _xblockexpression = null; - { - if ((this.genPackage == null)) { - throw new IllegalStateException("genPackage was not initialized"); - } - Path _get = Paths.get("", this.getPackageName().split("\\.")); - String _className = this.getClassName(); - String _plus = (_className + ".java"); - _xblockexpression = _get.resolve(_plus); - } - return _xblockexpression; - } - - @Override - public void generateCode(final OutputStream out) { - if ((this.genPackage == null)) { - throw new IllegalStateException("genPackage was not initialized"); - } - final PrintWriter printWriter = new PrintWriter(out); - printWriter.print(this.makeContent()); - printWriter.flush(); - } - - private String getClassName() { - return this.genPackage.getSwitchClassName().replaceFirst("Switch$", "MSwitch"); - } - - private String getPackageName() { - String _packageName = this.genPackage.getPackageName(); - return (_packageName + ".xutil"); - } - - private String makeContent() { - StringConcatenation _builder = new StringConcatenation(); - _builder.append("package "); - String _packageName = this.getPackageName(); - _builder.append(_packageName); - _builder.append(";"); - _builder.newLineIfNotEmpty(); - _builder.newLine(); - _builder.append("import java.util.function.Function;"); - _builder.newLine(); - _builder.append("import java.util.HashMap;"); - _builder.newLine(); - _builder.append("import java.util.Map;"); - _builder.newLine(); - _builder.newLine(); - _builder.append("import org.eclipse.emf.ecore.EClass;"); - _builder.newLine(); - _builder.append("import org.eclipse.emf.ecore.EPackage;"); - _builder.newLine(); - _builder.append("import org.eclipse.emf.ecore.EObject;"); - _builder.newLine(); - _builder.newLine(); - _builder.append("import tools.mdsd.ecoreworkflow.switches.MSwitch;"); - _builder.newLine(); - _builder.newLine(); - _builder.append("// auto-generated class, do not edit"); - _builder.newLine(); - _builder.newLine(); - _builder.append("public class "); - String _className = this.getClassName(); - _builder.append(_className); - _builder.append(" extends MSwitch {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t"); - _builder.append("private static "); - String _importedPackageInterfaceName = this.genPackage.getImportedPackageInterfaceName(); - _builder.append(_importedPackageInterfaceName, "\t"); - _builder.append(" MODEL_PACKAGE = "); - String _importedPackageInterfaceName_1 = this.genPackage.getImportedPackageInterfaceName(); - _builder.append(_importedPackageInterfaceName_1, "\t"); - _builder.append(".eINSTANCE;"); - _builder.newLineIfNotEmpty(); - { - EList _genClasses = this.genPackage.getGenClasses(); - for(final GenClass c : _genClasses) { - _builder.append("\t"); - _builder.append("private Function<"); - String _importedInterfaceName = c.getImportedInterfaceName(); - _builder.append(_importedInterfaceName, "\t"); - _builder.append(",T> "); - String _caseName = this.getCaseName(c); - _builder.append(_caseName, "\t"); - _builder.append(";"); - _builder.newLineIfNotEmpty(); - } - } - _builder.newLine(); - _builder.append("\t"); - _builder.append("public boolean isSwitchFor(EPackage ePackage) {"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("return ePackage == MODEL_PACKAGE;"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - _builder.append("\t"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException {"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("T result;"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("switch(classifierID) {"); - _builder.newLine(); - { - EList _genClasses_1 = this.genPackage.getGenClasses(); - for(final GenClass c_1 : _genClasses_1) { - _builder.append("\t\t\t"); - _builder.append("case "); - String _importedPackageInterfaceName_2 = this.genPackage.getImportedPackageInterfaceName(); - _builder.append(_importedPackageInterfaceName_2, "\t\t\t"); - _builder.append("."); - String _classifierID = this.genPackage.getClassifierID(c_1); - _builder.append(_classifierID, "\t\t\t"); - _builder.append(": {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t"); - String _importedInterfaceName_1 = c_1.getImportedInterfaceName(); - _builder.append(_importedInterfaceName_1, "\t\t\t\t"); - _builder.append(" casted = ("); - String _importedInterfaceName_2 = c_1.getImportedInterfaceName(); - _builder.append(_importedInterfaceName_2, "\t\t\t\t"); - _builder.append(") eObject;"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("if ("); - String _caseName_1 = this.getCaseName(c_1); - _builder.append(_caseName_1, "\t\t\t\t"); - _builder.append(" != null) {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t\t"); - _builder.append("result = "); - String _caseName_2 = this.getCaseName(c_1); - _builder.append(_caseName_2, "\t\t\t\t\t"); - _builder.append(".apply(casted);"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t\t"); - _builder.append("if (result != null) return result;"); - _builder.newLine(); - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - { - List _switchGenClasses = c_1.getSwitchGenClasses(); - for(final GenClass alternative : _switchGenClasses) { - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("if ("); - String _caseName_3 = this.getCaseName(alternative); - _builder.append(_caseName_3, "\t\t\t\t"); - _builder.append(" != null) {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("\t"); - _builder.append("result = "); - String _caseName_4 = this.getCaseName(alternative); - _builder.append(_caseName_4, "\t\t\t\t\t"); - _builder.append(".apply(casted);"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("\t"); - _builder.append("if (result != null) return result;"); - _builder.newLine(); - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - } - } - _builder.append("\t\t\t"); - _builder.append("\t"); - _builder.append("break;"); - _builder.newLine(); - _builder.append("\t\t\t"); - _builder.append("}"); - _builder.newLine(); - } - } - _builder.append("\t\t\t"); - _builder.append("default:"); - _builder.newLine(); - _builder.append("\t\t\t\t"); - _builder.append("throw new Error(\"type \" + eObject.eClass() + \" was not considered by the mswitch code generator\");"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("}"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("return applyDefaultCase(eObject);"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - _builder.append("\t"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("public "); - String _className_1 = this.getClassName(); - _builder.append(_className_1, "\t"); - _builder.append(" merge("); - String _className_2 = this.getClassName(); - _builder.append(_className_2, "\t"); - _builder.append(" other) {"); - _builder.newLineIfNotEmpty(); - { - final Function1 _function = (GenClass it) -> { - return this.getCaseName(it); - }; - List _map = ListExtensions.map(this.genPackage.getGenClasses(), _function); - for(final String field : _map) { - _builder.append("\t\t"); - _builder.append("if (other."); - _builder.append(field, "\t\t"); - _builder.append(" != null) this."); - _builder.append(field, "\t\t"); - _builder.append(" = other."); - _builder.append(field, "\t\t"); - _builder.append(";"); - _builder.newLineIfNotEmpty(); - } - } - _builder.append("\t\t"); - _builder.append("if (other.defaultCase != null) this.defaultCase = other.defaultCase;"); - _builder.append("\t\t"); - _builder.append("return this;"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("} "); - _builder.newLine(); - _builder.append("\t"); - _builder.newLine(); - { - EList _genClasses_2 = this.genPackage.getGenClasses(); - for(final GenClass c_2 : _genClasses_2) { - _builder.append("\t"); - _builder.append("public interface "); - String _interfaceName = this.getInterfaceName(c_2); - _builder.append(_interfaceName, "\t"); - _builder.append(" extends Function<"); - String _importedInterfaceName_3 = c_2.getImportedInterfaceName(); - _builder.append(_importedInterfaceName_3, "\t"); - _builder.append(",T> {}"); - _builder.newLineIfNotEmpty(); - } - } - _builder.append("\t"); - _builder.newLine(); - { - EList _genClasses_3 = this.genPackage.getGenClasses(); - for(final GenClass c_3 : _genClasses_3) { - _builder.append("\t"); - _builder.append("public "); - String _className_3 = this.getClassName(); - _builder.append(_className_3, "\t"); - _builder.append(" when("); - String _interfaceName_1 = this.getInterfaceName(c_3); - _builder.append(_interfaceName_1, "\t"); - _builder.append(" then) {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t"); - _builder.append("\t"); - _builder.append("this."); - String _caseName_5 = this.getCaseName(c_3); - _builder.append(_caseName_5, "\t\t"); - _builder.append(" = then;"); - _builder.newLineIfNotEmpty(); - _builder.append("\t"); - _builder.append("\t"); - _builder.append("return this;"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - } - } - _builder.append("\t"); - _builder.append("public "); - String _className_4 = this.getClassName(); - _builder.append(_className_4, "\t"); - _builder.append(" orElse(Function defaultCase) {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t\t"); - _builder.append("this.defaultCase = defaultCase;"); - _builder.newLine(); - _builder.append("\t\t"); - _builder.append("return this;"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - _builder.append("\t"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("@Override"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("public Map> getCases() {"); - _builder.newLine(); - _builder.append("\t "); - _builder.append("Map> definedCases = new HashMap<>();"); - _builder.newLine(); - _builder.append("\t "); - _builder.newLine(); - { - EList _genClasses_4 = this.genPackage.getGenClasses(); - for(final GenClass c_4 : _genClasses_4) { - _builder.append("\t "); - _builder.append("if (this."); - String _caseName_6 = this.getCaseName(c_4); - _builder.append(_caseName_6, "\t "); - _builder.append(" != null) {"); - _builder.newLineIfNotEmpty(); - _builder.append("\t "); - _builder.append("\t"); - _builder.append("definedCases.put("); - String _importedPackageInterfaceName_3 = this.genPackage.getImportedPackageInterfaceName(); - _builder.append(_importedPackageInterfaceName_3, "\t \t"); - _builder.append(".Literals."); - String _classifierID_1 = this.genPackage.getClassifierID(c_4); - _builder.append(_classifierID_1, "\t \t"); - _builder.append(", this."); - String _caseName_7 = this.getCaseName(c_4); - _builder.append(_caseName_7, "\t \t"); - _builder.append(".compose(o -> ("); - String _importedInterfaceName_4 = c_4.getImportedInterfaceName(); - _builder.append(_importedInterfaceName_4, "\t \t"); - _builder.append(") o));"); - _builder.newLineIfNotEmpty(); - _builder.append("\t "); - _builder.append("}"); - _builder.newLine(); - } - } - _builder.append("\t "); - _builder.newLine(); - _builder.append("\t "); - _builder.append("return definedCases;"); - _builder.newLine(); - _builder.append("\t"); - _builder.append("}"); - _builder.newLine(); - _builder.append("}"); - _builder.newLine(); - return _builder.toString(); - } - - private String getCaseName(final GenClass c) { - String _classUniqueName = this.genPackage.getClassUniqueName(c); - return ("case" + _classUniqueName); - } - - private String getInterfaceName(final GenClass c) { - String _classUniqueName = this.genPackage.getClassUniqueName(c); - return ("When" + _classUniqueName); - } -} 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 index 8035ecd..6837fca 100644 --- 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 @@ -1,6 +1,6 @@ package tools.mdsd.ecoreworkflow.switches; // TODO uncomment the following. It is only commented out, because xtend classes don't make it into the maven build up to now -/* + import java.io.OutputStream import java.io.PrintWriter import java.nio.file.Paths @@ -40,7 +40,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { private def String makeContent() { ''' - package «packageName»; + package «packageName»; import java.util.function.Function; import java.util.HashMap; @@ -54,11 +54,11 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { // auto-generated class, do not edit - public class «className» extends MSwitch { - private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; - «FOR c:genPackage.genClasses» - private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; - «ENDFOR» + public class «className» extends MSwitch { + private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; + «FOR c:genPackage.genClasses» + private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; + «ENDFOR» public boolean isSwitchFor(EPackage ePackage) { return ePackage == MODEL_PACKAGE; @@ -67,47 +67,47 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException { T result; switch(classifierID) { - «FOR c : genPackage.genClasses» - case «genPackage.importedPackageInterfaceName».«genPackage.getClassifierID(c)»: { - «c.importedInterfaceName» casted = («c.importedInterfaceName») eObject; - if («getCaseName(c)» != null) { - result = «getCaseName(c)».apply(casted); + «FOR c : genPackage.genClasses» + case «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); + «FOR alternative:c.switchGenClasses» + if («getCaseName(alternative)» != null) { + result = «getCaseName(alternative)».apply(casted); if (result != null) return result; } - «ENDFOR» + «ENDFOR» break; } - «ENDFOR» + «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.genClasses.map[caseName]» - if (other.«field» != null) this.«field» = other.«field»; - «ENDFOR» + public «className» merge(«className» other) { + «FOR field: genPackage.genClasses.map[caseName]» + if (other.«field» != null) this.«field» = other.«field»; + «ENDFOR» if (other.defaultCase != null) this.defaultCase = other.defaultCase; return this; } - «FOR c : genPackage.genClasses» - public interface «getInterfaceName(c)» extends Function<«c.importedInterfaceName»,T> {} - «ENDFOR» + «FOR c : genPackage.genClasses» + public interface «getInterfaceName(c)» extends Function<«c.importedInterfaceName»,T> {} + «ENDFOR» - «FOR c : genPackage.genClasses» - public «className» when(«getInterfaceName(c)» then) { - this.«getCaseName(c)» = then; + «FOR c : genPackage.genClasses» + public «className» when(«getInterfaceName(c)» then) { + this.«getCaseName(c)» = then; return this; } - «ENDFOR» - public «className» orElse(Function defaultCase) { + «ENDFOR» + public «className» orElse(Function defaultCase) { this.defaultCase = defaultCase; return this; } @@ -116,11 +116,11 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { public Map> getCases() { Map> definedCases = new HashMap<>(); - «FOR c:genPackage.genClasses» - if (this.«getCaseName(c)» != null) { - definedCases.put(«genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o)); + «FOR c:genPackage.genClasses» + if (this.«getCaseName(c)» != null) { + definedCases.put(«genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o)); } - «ENDFOR» + «ENDFOR» return definedCases; } @@ -136,4 +136,4 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { "When" + genPackage.getClassUniqueName(c) } -}*/ \ No newline at end of file +} \ No newline at end of file From 85ac44f15a7c50f64540a4b5d8359fcab77243e2 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sun, 19 Jan 2020 19:58:20 +0100 Subject: [PATCH 21/52] refactor tests * applying the same tests to the three different switch implementations shows that their behaviour is identical at the tested points --- .../ecoreworkflow/switches/DynamicSwitch.java | 3 +- .../switches/MSwitchClassGenerator.xtend | 3 +- .../switches/MergeableSwitch.java | 14 ++ .../META-INF/MANIFEST.MF | 4 +- .../switches/tests/DynamicSwitchTest.java | 219 ------------------ .../switches/tests/HashDynamicSwitchTest.java | 58 +++++ .../switches/tests/StaticSwitchTest.java | 150 ------------ .../tests/TestscenarioMSwitchTest.java | 58 +++++ .../tests/TestscenarioSwitchTest.java | 24 ++ .../builders/HashDynamicSwitchBuilder.java | 27 +++ .../tests/builders/SwitchBuilder.java | 11 + .../builders/TestscenarioMSwitchBuilder.java | 57 +++++ .../builders/TestscenarioSwitchBuilder.java | 26 +++ .../builders/TestszenarioSwitchAdapter.java | 114 +++++++++ .../switches/tests/builders/package-info.java | 4 + .../templates/InspectableBehaviourTest.java | 20 ++ .../MergeableSwitchBehaviourTest.java | 65 ++++++ .../SwitchingRulesBehaviourTest.java | 164 +++++++++++++ .../switches/tests/templates/Utils.java | 12 + .../tests/templates/package-info.java | 4 + 20 files changed, 665 insertions(+), 372 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/MergeableSwitch.java delete mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/HashDynamicSwitchTest.java delete mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioMSwitchTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/TestscenarioSwitchTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/HashDynamicSwitchBuilder.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/SwitchBuilder.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioMSwitchBuilder.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestscenarioSwitchBuilder.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/TestszenarioSwitchAdapter.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/package-info.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/InspectableBehaviourTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/MergeableSwitchBehaviourTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/Utils.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/package-info.java 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 index 679554b..c75616e 100644 --- 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 @@ -11,7 +11,8 @@ */ /* inheriting from ApplyableSwitch, InspectableSwitch is against single-concern principles, but is necessary for the builder pattern */ -public interface DynamicSwitch extends ApplyableSwitch, InspectableSwitch { +public interface DynamicSwitch extends ApplyableSwitch, InspectableSwitch, + MergeableSwitch, DynamicSwitch> { /** * add a dynamically specified case clause to the switch. * 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 index 6837fca..8dbfc58 100644 --- 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 @@ -51,10 +51,11 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { import org.eclipse.emf.ecore.EObject; import tools.mdsd.ecoreworkflow.switches.MSwitch; + import tools.mdsd.ecoreworkflow.switches.MergeableSwitch; // auto-generated class, do not edit - public class «className» extends MSwitch { + public class «className» extends MSwitch implements MergeableSwitch, TestscenarioMSwitch> { private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; «FOR c:genPackage.genClasses» private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; 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/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF index 690742f..bbbd8a1 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -12,4 +12,6 @@ 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" -Export-Package: tools.mdsd.ecoreworkflow.switches.tests +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/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java deleted file mode 100644 index 7e323d2..0000000 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/DynamicSwitchTest.java +++ /dev/null @@ -1,219 +0,0 @@ -package tools.mdsd.ecoreworkflow.switches.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import org.eclipse.emf.ecore.EObject; -import org.junit.jupiter.api.Test; -import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; -import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.E; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals; - -class DynamicSwitchTest { - - @Test - void testGetCases() { - DynamicSwitch s = new HashDynamicSwitch<>(); - s.defaultCase(o -> "default"); - assertEquals(0, s.getCases().size(), "Only the default case must be defined"); - assertNotNull(s.getDefaultCase(), "The default case must be defined"); - } - - @Test - void childCaseWins() { - String result = getSwitch().dynamicCase(Literals.G, (EObject g) -> "G") - .dynamicCase(Literals.H, (EObject h) -> "H").doSwitch(getE()); - - assertEquals("G", result, "child cases are more specific"); - } - - @Test - void firstParentWins() { - String result = getSwitch().dynamicCase(Literals.F, (EObject f) -> "F") - .dynamicCase(Literals.G, (EObject g) -> "G").doSwitch(getE()); - - assertEquals("F", result, "first listed parent is treated more specific"); - } - - @Test - void shorterInheritancePathsAreMoreSpecific() { - StringBuffer callOrder = new StringBuffer(); - getSwitch().dynamicCase(Literals.G, (EObject g) -> { - callOrder.append("G"); - return null; - }).dynamicCase(Literals.I, (EObject i) -> { - callOrder.append("I"); - return null; - }).dynamicCase(Literals.H, (EObject h) -> { - callOrder.append("H"); - return "H"; - }).defaultCase(x -> "X").doSwitch(getE()); - - assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); - } - - @Test - void exceptionOnNoMatch() { - DynamicSwitch sw = getSwitch(); - sw.dynamicCase(Literals.G, (EObject g) -> null); - F nonMatchingObject = TestscenarioFactory.eINSTANCE.createF(); - - assertThrows(SwitchingException.class, () -> { - sw.doSwitch(nonMatchingObject); - }, "a SwitchingException must be thrown when none of the defined branches match and no default case is defined."); - } - - @Test - void defaultCaseIsCalled() { - DynamicSwitch sw = getSwitch(); - F nonMathingObject = TestscenarioFactory.eINSTANCE.createF(); - - sw.dynamicCase(Literals.G, (EObject g) -> null); - sw.defaultCase((param) -> { - assertTrue(nonMathingObject == param, - "default case must be passed the EObject that was put into the switch"); - return "default"; - }); - - assertEquals("default", sw.doSwitch(nonMathingObject), - "default case must be used when the defined cases don't match"); - } - - @Test - void subsequentCasesNotCalled() { - DynamicSwitch sw = getSwitch(); - sw.dynamicCase(Literals.A, (EObject a) -> { - fail("The A case must not be called"); - return null; - }); - sw.dynamicCase(Literals.D, (EObject d) -> "d"); - sw.doSwitch(getE()); - } - - @Test - void subsequentCasesCalledInCorrectOrder() { - DynamicSwitch sw = getSwitch(); - StringBuffer callOrder = new StringBuffer(); - - sw.dynamicCase(Literals.A, (EObject x) -> { - callOrder.append("A"); - return null; - }); - sw.dynamicCase(Literals.B, (EObject x) -> { - callOrder.append("B"); - return null; - }); - sw.dynamicCase(Literals.C, (EObject x) -> { - callOrder.append("C"); - return null; - }); - sw.dynamicCase(Literals.D, (EObject x) -> { - callOrder.append("D"); - return null; - }); - sw.dynamicCase(Literals.E, (EObject x) -> { - callOrder.append("E"); - return null; - }); - sw.dynamicCase(Literals.F, (EObject x) -> { - callOrder.append("F"); - return null; - }); - sw.dynamicCase(Literals.G, (EObject x) -> { - callOrder.append("G"); - return null; - }); - sw.dynamicCase(Literals.H, (EObject x) -> { - callOrder.append("H"); - return null; - }); - sw.dynamicCase(Literals.I, (EObject x) -> { - callOrder.append("I"); - return null; - }); - sw.dynamicCase(Literals.K, (EObject x) -> { - callOrder.append("K"); - return null; - }); - sw.dynamicCase(Literals.L, (EObject x) -> { - callOrder.append("L"); - return null; - }); - sw.dynamicCase(Literals.M, (EObject x) -> { - callOrder.append("M"); - return null; - }); - sw.defaultCase((x) -> { - callOrder.append("X"); - return null; - }); - - sw.doSwitch(getE()); - - assertEquals("EKDCFGILBHMAX", callOrder.toString()); - } - - @Test - void mergingLessSpecificBranchWithoutEffect() { - DynamicSwitch original = getSwitch(); - DynamicSwitch merged = getSwitch(); - - original.dynamicCase(Literals.D, (EObject d) -> "D"); - merged.dynamicCase(Literals.B, (EObject b) -> "B"); - - assertEquals("D", original.merge(merged).doSwitch(getE())); - } - - - - @Test - void mergingSameBranchOverrides() { - DynamicSwitch original = getSwitch(); - DynamicSwitch merged = getSwitch(); - - original.dynamicCase(Literals.D, (EObject d) -> "1"); - merged.dynamicCase(Literals.D, (EObject d) -> "2"); - - assertEquals("2", original.merge(merged).doSwitch(getE())); - } - - @Test - void mergingDefaultBranchOverrides() { - DynamicSwitch original = getSwitch(); - DynamicSwitch merged = getSwitch(); - - original.dynamicCase(Literals.D, (EObject d) -> null); - original.defaultCase(x -> "1"); - merged.defaultCase(x -> "2"); - - assertEquals("2", original.merge(merged).doSwitch(getE())); - } - - @Test - void mergingAffectsGetCases() { - DynamicSwitch original = getSwitch(); - DynamicSwitch merged = getSwitch(); - - merged.dynamicCase(Literals.A, (EObject a) -> "a"); - - original.merge(merged); - - assertEquals("a", original.getCases().get(TestscenarioPackage.Literals.A).apply(getE())); - } - - private E getE() { - return TestscenarioFactory.eINSTANCE.createE(); - } - - private DynamicSwitch getSwitch() { - return new HashDynamicSwitch<>(); - } - -} 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..c0bcb54 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/HashDynamicSwitchTest.java @@ -0,0 +1,58 @@ +package tools.mdsd.ecoreworkflow.switches.tests; + +import org.eclipse.emf.ecore.EObject; +import org.junit.Test; +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.testmodel.testscenario.TestscenarioFactory; +import tools.mdsd.ecoreworkflow.switches.tests.builders.HashDynamicSwitchBuilder; +import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder; +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 tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*; + +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(); + } + + } + + @Test + void testChainedSyntax() { + String result = new HashDynamicSwitch() + .dynamicCase(G, (EObject g) -> "G") + .dynamicCase(H, (EObject h) -> "H") + .doSwitch(TestscenarioFactory.eINSTANCE.create(E)); + + assertEquals("G", result); + } +} diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java deleted file mode 100644 index 1de3701..0000000 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/StaticSwitchTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package tools.mdsd.ecoreworkflow.switches.tests; - -import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.Test; -import testscenario.xutil.TestscenarioMSwitch; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; -import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*; - -class StaticSwitchTest { - - @Test - void childCaseWins() { - String result = - new TestscenarioMSwitch() - .when((G g) -> "G") - .when((H h) -> "H") - .doSwitch(getE()); - - assertEquals("G", result, "child cases are more specific"); - } - - @Test - void firstParentWins() { - String result = new TestscenarioMSwitch() - .when((F f) -> "F") - .when((G g) -> "G") - .doSwitch(getE()); - - assertEquals("F", result, "first listed parent is treated more specific"); - } - - @Test - void shorterInheritancePathsAreMoreSpecific() { - StringBuffer callOrder = new StringBuffer(); - new TestscenarioMSwitch() - .when((G g) -> {callOrder.append("G"); return null;}) - .when((I i) -> {callOrder.append("I"); return null;}) - .when((H h) -> {callOrder.append("H"); return "H";}) - .orElse(x -> "X") - .doSwitch(getE()); - - assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); - } - - @Test - void exceptionOnNoMatch() { - TestscenarioMSwitch sw = new TestscenarioMSwitch(); - sw.when((G g) -> null); - F nonMatchingObject = TestscenarioFactory.eINSTANCE.createF(); - - assertThrows(SwitchingException.class, ()->{sw.doSwitch(nonMatchingObject);}, "a SwitchingException must be thrown when none of the defined branches match and no default case is defined."); - } - - @Test - void defaultCaseIsCalled() { - TestscenarioMSwitch sw = new TestscenarioMSwitch(); - F nonMathingObject = TestscenarioFactory.eINSTANCE.createF(); - - sw.when((G g) -> null); - sw.orElse((param) -> { - assertTrue(nonMathingObject == param, "default case must be passed the EObject that was put into the switch"); - return "default"; - }); - - assertEquals("default", sw.doSwitch(nonMathingObject), "default case must be used when the defined cases don't match"); - } - - @Test - void subsequentCasesNotCalled() { - TestscenarioMSwitch sw = new TestscenarioMSwitch<>(); - sw.when((A a) -> {fail("The A case must not be called"); return null;}); - sw.when((D d) -> new Object()); - sw.doSwitch(getE()); - } - - @Test - void subsequentCasesCalledInCorrectOrder() { - TestscenarioMSwitch sw = new TestscenarioMSwitch<>(); - StringBuffer callOrder = new StringBuffer(); - - sw.when((A x)->{callOrder.append("A"); return null;}); - sw.when((B x)->{callOrder.append("B"); return null;}); - sw.when((C x)->{callOrder.append("C"); return null;}); - sw.when((D x)->{callOrder.append("D"); return null;}); - sw.when((E x)->{callOrder.append("E"); return null;}); - sw.when((F x)->{callOrder.append("F"); return null;}); - sw.when((G x)->{callOrder.append("G"); return null;}); - sw.when((H x)->{callOrder.append("H"); return null;}); - sw.when((I x)->{callOrder.append("I"); return null;}); - sw.when((K x)->{callOrder.append("K"); return null;}); - sw.when((L x)->{callOrder.append("L"); return null;}); - sw.when((M x)->{callOrder.append("M"); return null;}); - sw.orElse((x) ->{callOrder.append("X"); return null;}); - - sw.doSwitch(getE()); - - assertEquals("EKDCFGILBHMAX", callOrder.toString()); - } - - @Test - void mergingLessSpecificBranchWithoutEffect() { - TestscenarioMSwitch original = new TestscenarioMSwitch<>(); - TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); - - original.when((D d)->"D"); - merged.when((B b) -> "B"); - - assertEquals("D", original.merge(merged).doSwitch(getE())); - } - - @Test - void mergingSameBranchOverrides() { - TestscenarioMSwitch original = new TestscenarioMSwitch<>(); - TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); - - original.when((D d)->"1"); - merged.when((D d) -> "2"); - - assertEquals("2", original.merge(merged).doSwitch(getE())); - } - - @Test - void mergingDefaultBranchOverrides() { - TestscenarioMSwitch original = new TestscenarioMSwitch<>(); - TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); - - original.when((D d) -> null); - original.orElse(x -> "1"); - merged.orElse(x -> "2"); - - assertEquals("2", original.merge(merged).doSwitch(getE())); - } - - @Test - void mergingAffectsGetCases() { - TestscenarioMSwitch original = new TestscenarioMSwitch<>(); - TestscenarioMSwitch merged = new TestscenarioMSwitch<>(); - - merged.when((A a) -> "a"); - - original.merge(merged); - - assertEquals("a", original.getCases().get(TestscenarioPackage.Literals.A).apply(getE())); - } - - private E getE() { - return TestscenarioFactory.eINSTANCE.createE(); - } - -} 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..5ee9669 --- /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 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/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..5691641 --- /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 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("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("", defaultValue).apply(object); + } +} \ No newline at end of file diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/package-info.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/package-info.java new file mode 100644 index 0000000..626819a --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/package-info.java @@ -0,0 +1,4 @@ +/** + * builders for providing a homogenous interface for testing the different switches functionality using the same tests + */ +package tools.mdsd.ecoreworkflow.switches.tests.builders; diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/InspectableBehaviourTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/InspectableBehaviourTest.java new file mode 100644 index 0000000..deb8b38 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/InspectableBehaviourTest.java @@ -0,0 +1,20 @@ +package tools.mdsd.ecoreworkflow.switches.tests.templates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.Test; +import tools.mdsd.ecoreworkflow.switches.InspectableSwitch; +import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder; + +public abstract class InspectableBehaviourTest { + protected abstract SwitchBuilder> createSwitchBuilder(); + + @Test + void testGetCases() { + SwitchBuilder> s = createSwitchBuilder(); + s.setDefaultCase(o -> "default"); + InspectableSwitch builtSwitch = s.build(); + assertEquals(0, builtSwitch.getCases().size(), "Only the default case must be defined"); + assertNotNull(builtSwitch.getDefaultCase(), "The default case must be defined"); + } +} diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/MergeableSwitchBehaviourTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/MergeableSwitchBehaviourTest.java new file mode 100644 index 0000000..2497d80 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/MergeableSwitchBehaviourTest.java @@ -0,0 +1,65 @@ +package tools.mdsd.ecoreworkflow.switches.tests.templates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Test; +import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch; +import tools.mdsd.ecoreworkflow.switches.InspectableSwitch; +import tools.mdsd.ecoreworkflow.switches.MergeableSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals; +import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder; + +public abstract class MergeableSwitchBehaviourTest & ApplyableSwitch & InspectableSwitch> { + + protected abstract SwitchBuilder createSwitchBuilder(); + + @Test + void mergingLessSpecificBranchWithoutEffect() { + SwitchBuilder original = createSwitchBuilder(); + SwitchBuilder merged = createSwitchBuilder(); + + original.addCase(Literals.D, (EObject d) -> "D"); + merged.addCase(Literals.B, (EObject b) -> "B"); + + assertEquals("D", original.build().merge(merged.build()).doSwitch(Utils.createE())); + } + + + + @Test + void mergingSameBranchOverrides() { + SwitchBuilder original = createSwitchBuilder(); + SwitchBuilder merged = createSwitchBuilder(); + + original.addCase(Literals.D, (EObject d) -> "1"); + merged.addCase(Literals.D, (EObject d) -> "2"); + + assertEquals("2", original.build().merge(merged.build()).doSwitch(Utils.createE())); + } + + @Test + void mergingDefaultBranchOverrides() { + SwitchBuilder original = createSwitchBuilder(); + SwitchBuilder merged = createSwitchBuilder(); + + original.addCase(Literals.D, (EObject d) -> null); + original.setDefaultCase(x -> "1"); + merged.setDefaultCase(x -> "2"); + + assertEquals("2", original.build().merge(merged.build()).doSwitch(Utils.createE())); + } + + @Test + void mergingAffectsGetCases() { + SwitchBuilder original = createSwitchBuilder(); + SwitchBuilder merged = createSwitchBuilder(); + + merged.addCase(Literals.A, (EObject a) -> "a"); + + S builtOriginal = original.build(); + builtOriginal.merge(merged.build()); + // TODO + assertEquals("a", original.build().getCases().get(TestscenarioPackage.Literals.A).apply(Utils.createE())); + } +} diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java new file mode 100644 index 0000000..c8c981d --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java @@ -0,0 +1,164 @@ +package tools.mdsd.ecoreworkflow.switches.tests.templates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Test; +import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch; +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals; +import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder; + +public abstract class SwitchingRulesBehaviourTest { + protected abstract SwitchBuilder> createSwitchBuilder(); + protected boolean failsOnNoDefaultCase() {return true;} + + @Test + void childCaseWins() { + SwitchBuilder> sw = this.createSwitchBuilder(); + sw.addCase(Literals.G, (EObject g) -> "G"); + sw.addCase(Literals.H, (EObject h) -> "H"); + String result = sw.build().doSwitch(Utils.createE()); + + assertEquals("G", result, "child cases are more specific"); + } + + @Test + void firstParentWins() { + SwitchBuilder> sw = this.createSwitchBuilder(); + sw.addCase(Literals.F, (EObject f) -> "F"); + sw.addCase(Literals.G, (EObject g) -> "G"); + String result = sw.build().doSwitch(Utils.createE()); + + assertEquals("F", result, "first listed parent is treated more specific"); + } + + @Test + void shorterInheritancePathsAreMoreSpecific() { + StringBuffer callOrder = new StringBuffer(); + SwitchBuilder> sw = createSwitchBuilder(); + sw.addCase(Literals.G, (EObject g) -> { + callOrder.append("G"); + return null; + }); + sw.addCase(Literals.I, (EObject i) -> { + callOrder.append("I"); + return null; + }); + sw.addCase(Literals.H, (EObject h) -> { + callOrder.append("H"); + return "H"; + }); + sw.setDefaultCase(x -> "X"); + sw.build().doSwitch(Utils.createE()); + + assertEquals("GIH", callOrder.toString(), "shorter pathes are more specific"); + } + + @Test + public void exceptionOnNoMatch() { + if (failsOnNoDefaultCase()) { + SwitchBuilder> sw = createSwitchBuilder(); + sw.addCase(Literals.G, (EObject g) -> null); + F nonMatchingObject = TestscenarioFactory.eINSTANCE.createF(); + + assertThrows(SwitchingException.class, () -> { + sw.build().doSwitch(nonMatchingObject); + }, "a SwitchingException must be thrown when none of the defined branches match and no default case is defined."); + } + } + + @Test + void defaultCaseIsCalled() { + SwitchBuilder> sw = createSwitchBuilder(); + F nonMathingObject = TestscenarioFactory.eINSTANCE.createF(); + + sw.addCase(Literals.G, (EObject g) -> null); + sw.setDefaultCase((param) -> { + assertTrue(nonMathingObject == param, + "default case must be passed the EObject that was put into the switch"); + return "default"; + }); + + assertEquals("default", sw.build().doSwitch(nonMathingObject), + "default case must be used when the defined cases don't match"); + } + + @Test + void subsequentCasesNotCalled() { + SwitchBuilder> sw = createSwitchBuilder(); + sw.addCase(Literals.A, (EObject a) -> { + fail("The A case must not be called"); + return null; + }); + sw.addCase(Literals.D, (EObject d) -> "d"); + sw.build().doSwitch(Utils.createE()); + } + + @Test + void subsequentCasesCalledInCorrectOrder() { + SwitchBuilder> sw = createSwitchBuilder(); + StringBuffer callOrder = new StringBuffer(); + + sw.addCase(Literals.A, (EObject x) -> { + callOrder.append("A"); + return null; + }); + sw.addCase(Literals.B, (EObject x) -> { + callOrder.append("B"); + return null; + }); + sw.addCase(Literals.C, (EObject x) -> { + callOrder.append("C"); + return null; + }); + sw.addCase(Literals.D, (EObject x) -> { + callOrder.append("D"); + return null; + }); + sw.addCase(Literals.E, (EObject x) -> { + callOrder.append("E"); + return null; + }); + sw.addCase(Literals.F, (EObject x) -> { + callOrder.append("F"); + return null; + }); + sw.addCase(Literals.G, (EObject x) -> { + callOrder.append("G"); + return null; + }); + sw.addCase(Literals.H, (EObject x) -> { + callOrder.append("H"); + return null; + }); + sw.addCase(Literals.I, (EObject x) -> { + callOrder.append("I"); + return null; + }); + sw.addCase(Literals.K, (EObject x) -> { + callOrder.append("K"); + return null; + }); + sw.addCase(Literals.L, (EObject x) -> { + callOrder.append("L"); + return null; + }); + sw.addCase(Literals.M, (EObject x) -> { + callOrder.append("M"); + return null; + }); + sw.setDefaultCase((x) -> { + callOrder.append("X"); + return null; + }); + + sw.build().doSwitch(Utils.createE()); + + assertEquals("EKDCFGILBHMAX", callOrder.toString()); + } +} diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/Utils.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/Utils.java new file mode 100644 index 0000000..a87bb47 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/Utils.java @@ -0,0 +1,12 @@ +package tools.mdsd.ecoreworkflow.switches.tests.templates; + +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.E; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; + +public class Utils { + + public static E createE() { + return TestscenarioFactory.eINSTANCE.createE(); + } + +} diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/package-info.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/package-info.java new file mode 100644 index 0000000..81d85c0 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/package-info.java @@ -0,0 +1,4 @@ +/** + * contains reusable test templates for various facets of switch behaviour + */ +package tools.mdsd.ecoreworkflow.switches.tests.templates; From ad3738e261f3358381e6d0070cef9ef417f58b22 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 22 Jan 2020 12:00:10 +0100 Subject: [PATCH 22/52] benchmark to compare switches --- tests/pom.xml | 1 + .../.gitignore | 1 + .../.mvn/extensions.xml | 4 + .../pom.xml | 210 ++++++++++++++++++ .../ecoreworkflow/SwitchConfigurator.java | 49 ++++ .../SwitchingSpeedBenchmark.java | 76 +++++++ 6 files changed, 341 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.gitignore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.mvn/extensions.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java diff --git a/tests/pom.xml b/tests/pom.xml index c3bab37..53355a4 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -13,6 +13,7 @@ tools.mdsd.ecoreworkflow.switches.tests tools.mdsd.ecoreworkflow.switches.testmodel + tools.mdsd.ecoreworkflow.switches.tests.perf 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/pom.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml new file mode 100644 index 0000000..f1b2718 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml @@ -0,0 +1,210 @@ + + + 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 + 0.1.0-SNAPSHOT + + + + 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/SwitchConfigurator.java b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java new file mode 100644 index 0000000..3f9c68f --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java @@ -0,0 +1,49 @@ +package tools.mdsd.ecoreworkflow; + +import org.eclipse.emf.ecore.EObject; +import testscenario.xutil.TestscenarioMSwitch; +import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; +import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*; +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)-> "*"); + } +} 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..7b641af --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java @@ -0,0 +1,76 @@ +package tools.mdsd.ecoreworkflow; + +import org.openjdk.jmh.runner.options.OptionsBuilder; +import 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.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(); + } + + @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 justBlackhole(BenchmarkState benchmarkState, ThreadState threadState, Blackhole blackHole) { + for (EObject obj: threadState.testObjects) { + blackHole.consume(obj); + } + } + +} From 6ca566111966090b5d5ced08032861f8fe9ff729 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sat, 1 Feb 2020 09:17:40 +0100 Subject: [PATCH 23/52] bugfix: fix missing parametrization --- .../mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8dbfc58..0afc5c1 100644 --- 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 @@ -55,7 +55,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { // auto-generated class, do not edit - public class «className» extends MSwitch implements MergeableSwitch, TestscenarioMSwitch> { + public class «className» extends MSwitch implements MergeableSwitch<«className», «className»> { private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; «FOR c:genPackage.genClasses» private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; From 46b8f32656867a5615bf012677d29249100cff3b Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sat, 1 Feb 2020 10:50:57 +0100 Subject: [PATCH 24/52] bugfix: consider base package in generated switch classes --- .../ecoreworkflow/switches/MSwitchClassGenerator.xtend | 8 +++++++- .../META-INF/MANIFEST.MF | 6 +++--- .../java/tools/mdsd/ecoreworkflow/SwitchConfigurator.java | 2 +- .../tools/mdsd/ecoreworkflow/SwitchingSpeedBenchmark.java | 2 +- .../switches/tests/TestscenarioMSwitchTest.java | 2 +- .../tests/builders/TestscenarioMSwitchBuilder.java | 4 ++-- 6 files changed, 15 insertions(+), 9 deletions(-) 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 index 0afc5c1..6ee43aa 100644 --- 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 @@ -35,7 +35,13 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { } private def String getPackageName() { - genPackage.packageName + ".xutil" + val basePrefix = + if (genPackage.basePackage !== null && genPackage.basePackage.length > 0) { + genPackage.basePackage + "." + } else { + "" + } + basePrefix + genPackage.packageName + ".xutil" } private def String makeContent() { diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF index 63cd0a5..74b136b 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/META-INF/MANIFEST.MF @@ -8,10 +8,10 @@ Bundle-ClassPath: . Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Export-Package: testscenario.xutil, - tools.mdsd.ecoreworkflow.switches.testmodel.testscenario, +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.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", 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 index 3f9c68f..1a9b3d2 100644 --- 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 @@ -1,7 +1,7 @@ package tools.mdsd.ecoreworkflow; import org.eclipse.emf.ecore.EObject; -import testscenario.xutil.TestscenarioMSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch; import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; import tools.mdsd.ecoreworkflow.switches.HashDynamicSwitch; import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.*; 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 index 7b641af..7dca14a 100644 --- 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 @@ -1,7 +1,7 @@ package tools.mdsd.ecoreworkflow; import org.openjdk.jmh.runner.options.OptionsBuilder; -import testscenario.xutil.TestscenarioMSwitch; +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; 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 index 5ee9669..49a5305 100644 --- 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 @@ -4,7 +4,7 @@ import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.E; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import testscenario.xutil.TestscenarioMSwitch; +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; 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 index 5691641..6a90047 100644 --- 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 @@ -7,7 +7,7 @@ import java.util.function.Function; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; -import testscenario.xutil.TestscenarioMSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.xutil.TestscenarioMSwitch; public class TestscenarioMSwitchBuilder implements SwitchBuilder> { @@ -18,7 +18,7 @@ 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("testscenario.xutil.TestscenarioMSwitch$When" + clazz.getName()); + 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); } From 4b22a5249448b4a05f2f75507df059d0cbac1c39 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sat, 1 Feb 2020 10:51:24 +0100 Subject: [PATCH 25/52] build process: clean orderly --- .../workflow/clean.mwe2 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/clean.mwe2 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 From 98a884a7d6d4687a332f5414905f27d92a9f6fa5 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sun, 2 Feb 2020 00:24:08 +0100 Subject: [PATCH 26/52] add a second test model package (for testing multi-package switching) --- tests/pom.xml | 1 + .../.classpath | 7 ++++ .../.gitignore | 1 + .../.settings/org.eclipse.jdt.core.prefs | 7 ++++ .../META-INF/MANIFEST.MF | 22 +++++++++++++ .../build.properties | 10 ++++++ .../model/testscenario2.ecore | 7 ++++ .../model/testscenario2.genmodel | 13 ++++++++ .../plugin.properties | 4 +++ .../plugin.xml | 17 ++++++++++ .../workflow/clean.mwe2 | 17 ++++++++++ .../workflow/generate.mwe2 | 32 +++++++++++++++++++ .../META-INF/MANIFEST.MF | 3 +- 13 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.classpath create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.gitignore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.settings/org.eclipse.jdt.core.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/META-INF/MANIFEST.MF create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/build.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.ecore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/model/testscenario2.genmodel create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/plugin.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/clean.mwe2 create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/workflow/generate.mwe2 diff --git a/tests/pom.xml b/tests/pom.xml index 53355a4..118bd98 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -13,6 +13,7 @@ tools.mdsd.ecoreworkflow.switches.tests tools.mdsd.ecoreworkflow.switches.testmodel + tools.mdsd.ecoreworkflow.switches.testmodel2 tools.mdsd.ecoreworkflow.switches.tests.perf 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/.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.tests/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF index bbbd8a1..1abe64b 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -11,7 +11,8 @@ Import-Package: org.junit;version="4.12.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.testmodel;bundle-version="0.1.0", + tools.mdsd.ecoreworkflow.switches.testmodel2;bundle-version="0.1.0" Export-Package: tools.mdsd.ecoreworkflow.switches.tests, tools.mdsd.ecoreworkflow.switches.tests.builders, tools.mdsd.ecoreworkflow.switches.tests.templates From 6413ab73707fd73764b02a98b83d7dcad175e016 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 06:37:24 +0100 Subject: [PATCH 27/52] benchmark the difference between ComposedSwitch and DynamicSwitch --- .../pom.xml | 6 ++ .../MultiPackageSwitchingSpeedBenchmark.java | 59 +++++++++++++++++++ .../ecoreworkflow/SwitchConfigurator.java | 33 +++++++++++ 3 files changed, 98 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/MultiPackageSwitchingSpeedBenchmark.java diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml index f1b2718..bd001ff 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml @@ -29,6 +29,12 @@ 0.1.0-SNAPSHOT + + tools.mdsd.ecoreworkflow + tools.mdsd.ecoreworkflow.switches.testmodel2 + 0.1.0-SNAPSHOT + + tools.mdsd.ecoreworkflow tools.mdsd.ecoreworkflow.switches 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..4cedf15 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/src/main/java/tools/mdsd/ecoreworkflow/MultiPackageSwitchingSpeedBenchmark.java @@ -0,0 +1,59 @@ +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.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(); + } + + @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)); + } + } + +} 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 index 1a9b3d2..d660a0e 100644 --- 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 @@ -1,11 +1,18 @@ 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.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 { @@ -46,4 +53,30 @@ DynamicSwitch buildDynamicSwitch() { .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"; + } + }; + } } From 7166e5fdfc20797ca72618244b7466505e108e52 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 06:39:05 +0100 Subject: [PATCH 28/52] import tools.mdsd.ecoreworkflow.switches.tests.perf as an eclipse project --- .../.classpath | 29 +++++++++++++++++++ .../org.eclipse.core.resources.prefs | 3 ++ .../.settings/org.eclipse.jdt.core.prefs | 8 +++++ .../.settings/org.eclipse.m2e.core.prefs | 4 +++ 4 files changed, 44 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.classpath create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.core.resources.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.jdt.core.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests.perf/.settings/org.eclipse.m2e.core.prefs 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/.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 From 6093dd2839503ff70d95da150065481c4a0e0631 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 06:40:50 +0100 Subject: [PATCH 29/52] add context dependent resource mapping to testmodel's generate.mwe2 --- .../workflow/generate.mwe2 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 index ce0e2b7..7ac4e01 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/workflow/generate.mwe2 @@ -3,12 +3,18 @@ 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 + 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 { From 2a94475ce3a111e6b3b510a37e1ea3e325980091 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 13:48:39 +0100 Subject: [PATCH 30/52] refactor DynamicSwitch * and use a non synchronized variant --- .../AbstractInspectableDynamicSwitch.java | 53 +++++++++++++++++++ .../switches/HashDynamicSwitch.java | 51 ++++-------------- 2 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java 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..53b3c33 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java @@ -0,0 +1,53 @@ +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; + +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; + + public AbstractInspectableDynamicSwitch() { + super(); + } + + @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. + 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/HashDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/HashDynamicSwitch.java index 40afe62..72d6e2e 100644 --- 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 @@ -1,8 +1,7 @@ package tools.mdsd.ecoreworkflow.switches; import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -10,7 +9,6 @@ import java.util.function.Function; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.EcorePackage; import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; /** @@ -32,36 +30,12 @@ * * @param the return type of the switch's clauses */ -public class HashDynamicSwitch +public class HashDynamicSwitch extends AbstractInspectableDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { - private Map> caseDefinitions = new LinkedHashMap<>(); - private ConcurrentMap[]> cachedInvokationSequences = - new ConcurrentHashMap<>(); - private Function defaultCase; - private static final EClass E_OBJECT_CLASS = EcorePackage.Literals.EOBJECT; - - - @Override - public DynamicSwitch dynamicCase(EClass clazz, Function then) { - if (!cachedInvokationSequences.isEmpty()) { // adding cases might cause synchronization issues - 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. - defaultCase(then); - } else { - caseDefinitions.put(clazz, then); - } - return this; - } - - @Override - public DynamicSwitch defaultCase(Function then) { - this.defaultCase = then; - return this; - } - + private Map[]> cachedInvokationSequences = + new HashMap<>(); + @Override public T doSwitch(EObject object) { EClass eClass = object.eClass(); @@ -83,6 +57,11 @@ public T doSwitch(EObject object) { 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 @@ -102,14 +81,4 @@ private Function[] calculateInvocationSequence(EClass eClass) { return invocations.toArray(new Function[0]); } - @Override - public Map> getCases() { - return Collections.unmodifiableMap(caseDefinitions); - } - - @Override - public Function getDefaultCase() { - return defaultCase; - } - } From f8af2e0f5a37c74ac1420f14b788698ffa6a836a Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 13:50:38 +0100 Subject: [PATCH 31/52] test for byte code switch --- .../tests/BytecodeDynamicSwitchTest.java | 62 +++++++++++++++++++ .../BytecodeDynamicSwitchBuilder.java | 27 ++++++++ 2 files changed, 89 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/builders/BytecodeDynamicSwitchBuilder.java 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..d274ca2 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java @@ -0,0 +1,62 @@ +package tools.mdsd.ecoreworkflow.switches.tests; + +import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Disabled; +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.tests.builders.BytecodeDynamicSwitchBuilder; +import tools.mdsd.ecoreworkflow.switches.tests.builders.SwitchBuilder; +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 tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Package.Literals.*; + +@Disabled +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(); + } + + } + + @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); + } +} 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; + } + +} From 97078c6d8123938c67697dee56295dd7e40e4a71 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Mon, 3 Feb 2020 13:51:02 +0100 Subject: [PATCH 32/52] require ByteBuddy --- bundles/pom.xml | 8 ++++++++ .../META-INF/MANIFEST.MF | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bundles/pom.xml b/bundles/pom.xml index 3c86447..f74c9e5 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -16,4 +16,12 @@ tools.mdsd.ecoreworkflow.switches + + + orbit + p2 + http://download.eclipse.org/tools/orbit/downloads/drops/R20190827152740/repository/ + + + diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF index 862c2ef..187f243 100644 --- a/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF +++ b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF @@ -13,5 +13,6 @@ Require-Bundle: org.eclipse.emf.mwe.utils, 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" + tools.mdsd.ecoreworkflow.mwe2lib;bundle-version="0.1.0", + net.bytebuddy.byte-buddy;bundle-version="1.9.0";resolution:=optional Import-Package: org.eclipse.core.runtime From 43371fb102f2ca5d50c7671a65f0eac015e4a7bd Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Tue, 4 Feb 2020 17:42:13 +0100 Subject: [PATCH 33/52] first working (but still ugly) version of ByteCodeDynamicSwitch --- .../switches/BytecodeDynamicSwitch.java | 332 ++++++++++++++++++ .../tests/BytecodeDynamicSwitchTest.java | 3 +- 2 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java 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..8461a12 --- /dev/null +++ b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java @@ -0,0 +1,332 @@ +package tools.mdsd.ecoreworkflow.switches; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.DynamicType.Builder; +import net.bytebuddy.dynamic.DynamicType.Unloaded; +import net.bytebuddy.dynamic.scaffold.InstrumentedType; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.FixedValue; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.Implementation.Context; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.constant.IntegerConstant; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; +import static net.bytebuddy.matcher.ElementMatchers.*; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcorePackage; +import com.google.common.collect.Streams; +import org.eclipse.emf.ecore.EClassifier; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; +import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; + +public class BytecodeDynamicSwitch extends AbstractInspectableDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { + + private ApplyableSwitch compiledSwitch; + private Set explicitPackages = new HashSet<>(); + + public BytecodeDynamicSwitch precompile() { + if (compiledSwitch == null) { + compiledSwitch = compileSwitch(); + } + return this; + } + + class LargeSwitchLogicAppender implements ByteCodeAppender { + + private Map> invocationsPerClass; + private Map> classesPerPackage; + + public LargeSwitchLogicAppender(Map> invocationOrders) { + invocationsPerClass = invocationOrders; + classesPerPackage = new HashMap<>(); + invocationOrders.entrySet().stream().forEach(entry -> { + EClass eClass = entry.getKey(); + EPackage ePackage = eClass.getEPackage(); + if (!classesPerPackage.containsKey(ePackage)) { + classesPerPackage.put(ePackage, new ArrayList<>()); + } + classesPerPackage.get(ePackage).add(eClass); + }); + } + + @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, Type.getInternalName(EObject.class), "eClass", Type.getMethodDescriptor(Type.getType(EClass.class)), true); + // Stack: | eObj | eClass + mv.visitInsn(Opcodes.DUP); + // Stack: | eObj | eClass | eClass + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(EClassifier.class), "getEPackage", Type.getMethodDescriptor(Type.getType(EPackage.class)), true); + // Stack: | eObj | eClass | ePackage + + Label packageJumpLabel; + Label returnResult = new Label(); + Label notFound = new Label(); + for (Entry> packageEntry: classesPerPackage.entrySet()) { + 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, Type.getInternalName(EClassifier.class), "getClassifierID", Type.getMethodDescriptor(Type.INT_TYPE), true); + // Stack: | eObj | classifierID + List classEntries = packageEntry.getValue(); + int minId = 1, maxId = 0; + for (EClass eClass : classEntries) { + int clf = eClass.getClassifierID(); + if (minId > clf) minId = clf; + if (maxId < clf) maxId = clf; + } + Label[] labels = new Label[maxId - minId + 1]; + for (int j = 0; j <= maxId - minId; j++) { + labels[j] = new Label(); + } + + mv.visitTableSwitchInsn(minId, maxId, notFound, 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[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 1, new String[]{Type.getInternalName(EObject.class)}); + // Stack: | eObj | + Optional dynamicType = classEntries.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 + for (EClass caseDefinedOn : invocationsPerClass.getOrDefault(dynamicType.get(), new LinkedList<>())) { + // Stack: | eObj + mv.visitInsn(Opcodes.DUP); + // Stack: | eObj | eObj + mv.visitVarInsn(Opcodes.ALOAD, 0); + // Stack: | eObj | eObj | this + mv.visitFieldInsn(Opcodes.GETFIELD, ctx.getInstrumentedType().getInternalName(), getCaseName(caseDefinedOn), Type.getDescriptor(Function.class)); + // Stack: | eObj | eObj | fptr + mv.visitInsn(Opcodes.SWAP); + // Stack: | eObj | fptr | eObj + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Function.class), "apply", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), true); + // Stack: | eObj | result + mv.visitInsn(Opcodes.DUP); + // Stack: | eObj | result | result + mv.visitJumpInsn(Opcodes.IFNONNULL, returnResult); + // Stack: | eObj | result + mv.visitInsn(Opcodes.POP); + // Stack: | eObj + } + } + mv.visitJumpInsn(Opcodes.GOTO, notFound); + + } + + //mv.visitJumpInsn(Opcodes.GOTO, notFound); + // END INSIDE THE CORRECT PACKAGE + + mv.visitLabel(packageJumpLabel); // <-- incoming jump + mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 3, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(EClass.class), Type.getInternalName(EPackage.class)}); + // 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[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 1, new String[]{Type.getInternalName(EObject.class)}); + + mv.visitVarInsn(Opcodes.ALOAD, 0); + // Stack: | eObj | this + mv.visitFieldInsn(Opcodes.GETFIELD, ctx.getInstrumentedType().getInternalName(), "defaultCase", Type.getDescriptor(Function.class)); + // 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, Type.getInternalName(Function.class), "apply", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), true); + // Stack: | result + mv.visitInsn(Opcodes.ARETURN); + + mv.visitLabel(noDefaultCase); + mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 2, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(Function.class)}); + // 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"); + try { + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(SwitchingException.class), "", Type.getConstructorDescriptor(SwitchingException.class.getConstructor(String.class)), false); + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException("could not find SwitchingException's constructor", e); + } + mv.visitInsn(Opcodes.ATHROW); + + mv.visitLabel(returnResult); + mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 2, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(Object.class)}); + mv.visitInsn(Opcodes.ARETURN); + return new Size(5, md.getStackSize()); + } + + } + + class LargeSwitchLogicImplementation implements Implementation { + + private Map> invocationOrders; + + public LargeSwitchLogicImplementation(Map> invocationOrders) { + this.invocationOrders = invocationOrders; + } + + @Override + public InstrumentedType prepare(InstrumentedType instrType) { + return instrType; + } + + @Override + public ByteCodeAppender appender(Target target) { + return new LargeSwitchLogicAppender(invocationOrders); + } + + } + + @SuppressWarnings("unchecked") + private ApplyableSwitch compileSwitch() { + Map> invocationOrders = computeInvocationOrders(); + + //invocationOrders.forEach((cl, invs)->System.out.println(cl.getName() + ":" + invs.stream().map(EClass::getName).collect(Collectors.joining(",")))); + + Builder typeUnderConstruction = new ByteBuddy() + .subclass(ApplyableSwitch.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR); + + for (EClass caseClass: invocationOrders.keySet()) { // stores a function pointer for each case + typeUnderConstruction = typeUnderConstruction.defineField(getCaseName(caseClass), Function.class, Visibility.PUBLIC); + } + typeUnderConstruction = typeUnderConstruction.defineField("defaultCase", Function.class, Visibility.PUBLIC); + + Unloaded made = typeUnderConstruction + .method(named("doSwitch")) + .intercept(new LargeSwitchLogicImplementation(invocationOrders)) + .make(); + +// try { +// made.saveIn(new File("c:/temp")); +// } catch (IOException e1) { +// // TODO Auto-generated catch block +// e1.printStackTrace(); +// } + + Class dynamicType = made + .load(Thread.currentThread().getContextClassLoader()) + .getLoaded(); + try { + ApplyableSwitch instance = dynamicType.newInstance(); + caseDefinitions.forEach((eClass, lambda) -> { + try { + Field field = instance.getClass().getField(getCaseName(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 e) { + throw new RuntimeException("could not set the default function correspondingly", e); + } + + return instance; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException("error invoking the constructor of the byte-assembled class", e); + } + } + + private String getCaseName(EClass eClass) { + return "case_" + Math.abs(eClass.getEPackage().getNsURI().hashCode()) + eClass.getEPackage().getName() + "_" + eClass.getClassifierID(); + } + + protected Map> computeInvocationOrders() { + Set allPackages = new HashSet(explicitPackages); + Set implicitClasses = caseDefinitions.keySet(); + Set implicitSupertypes = caseDefinitions.keySet() + .stream() + .flatMap(c -> c.getEAllSuperTypes().stream()) + .collect(Collectors.toSet()); + + Streams.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; + } + + @Override + protected boolean canDafineCases() { + return compiledSwitch == null; + } + + @Override + public T doSwitch(EObject object) { + precompile(); + return compiledSwitch.doSwitch(object); + } +} + + 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 index d274ca2..47f7764 100644 --- 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 @@ -2,7 +2,6 @@ import org.eclipse.emf.ecore.EObject; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch; import tools.mdsd.ecoreworkflow.switches.BytecodeDynamicSwitch; @@ -18,7 +17,7 @@ import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*; import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario2.Testscenario2Package.Literals.*; -@Disabled + class BytecodeDynamicSwitchTest { @Nested class ConformsToSwitchingRules extends SwitchingRulesBehaviourTest { From add138d6c5ae7ec5848cd1327fbb1ff18f54b542 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 16:19:00 +0100 Subject: [PATCH 34/52] fixup require ByteBuddy * manifest: bytebuddy must (of course) not be optional * adding the repo in pom does not work * use releng instead --- bundles/pom.xml | 7 ------- .../META-INF/MANIFEST.MF | 3 ++- releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target | 9 +++++++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/bundles/pom.xml b/bundles/pom.xml index f74c9e5..0b2e7ae 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -16,12 +16,5 @@ tools.mdsd.ecoreworkflow.switches - - - orbit - p2 - http://download.eclipse.org/tools/orbit/downloads/drops/R20190827152740/repository/ - - diff --git a/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF index 187f243..a3743d7 100644 --- a/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF +++ b/bundles/tools.mdsd.ecoreworkflow.switches/META-INF/MANIFEST.MF @@ -14,5 +14,6 @@ Require-Bundle: org.eclipse.emf.mwe.utils, 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";resolution:=optional + net.bytebuddy.byte-buddy;bundle-version="1.9.0" Import-Package: org.eclipse.core.runtime +DynamicImport-Package: * diff --git a/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target b/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target index 866e65e..92c3ed3 100644 --- a/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target +++ b/releng/tools.mdsd.ecoreworkflow.targetplatform/tp.target @@ -1,5 +1,10 @@ - + + + + + + - + \ No newline at end of file From 277e339cafcda75b4b48493bc621f816264e021e Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 16:22:09 +0100 Subject: [PATCH 35/52] add a benchmark for the bytecode switch --- .../MultiPackageSwitchingSpeedBenchmark.java | 9 +++++++++ .../mdsd/ecoreworkflow/SwitchConfigurator.java | 14 ++++++++++++++ 2 files changed, 23 insertions(+) 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 index 4cedf15..7432177 100644 --- 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 @@ -7,6 +7,7 @@ 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; @@ -18,6 +19,7 @@ public static class BenchmarkState { SwitchConfigurator conf = new SwitchConfigurator(); ComposedSwitch composedSwitch = conf.buildComposedSwitch(); DynamicSwitch dynamicSwitch = conf.buildComposedDynamicSwitch(); + DynamicSwitch bytecodeSwitch = conf.buildDynamicBytecodeSwitch(); } @State(Scope.Thread) @@ -56,4 +58,11 @@ public void dynamicSwitch(BenchmarkState benchmarkState, ThreadState threadState } } + @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 index d660a0e..94230fd 100644 --- 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 @@ -9,6 +9,7 @@ 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.*; @@ -79,4 +80,17 @@ public String caseZ(Z object) { } }; } + + 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(); + } } From eb2eefaa49b3915fec916b28027885d7f7229cc6 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 16:34:32 +0100 Subject: [PATCH 36/52] fixup require ByteBuddy --- tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml index bd001ff..1c9dd45 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests.perf/pom.xml @@ -10,6 +10,7 @@ JMH benchmark for Ecore Switches, including our custom MSwitch and DynamicSwitch + org.openjdk.jmh jmh-core @@ -41,6 +42,12 @@ 0.1.0-SNAPSHOT + + net.bytebuddy + byte-buddy + 1.9.0 + + org.eclipse.emf org.eclipse.emf.ecore From bb7d868d889d39441277ff2836c585cd72a21a5d Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 16:40:39 +0100 Subject: [PATCH 37/52] remove accidental dependency on google commons --- .../mdsd/ecoreworkflow/switches/BytecodeDynamicSwitch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 8461a12..50cfb87 100644 --- 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 @@ -32,11 +32,11 @@ 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.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EcorePackage; -import com.google.common.collect.Streams; import org.eclipse.emf.ecore.EClassifier; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; @@ -291,7 +291,7 @@ protected Map> computeInvocationOrders() { .flatMap(c -> c.getEAllSuperTypes().stream()) .collect(Collectors.toSet()); - Streams.concat(implicitSupertypes.stream(), implicitClasses.stream()) + Stream.concat(implicitSupertypes.stream(), implicitClasses.stream()) .map(EClass::getEPackage) .forEach(allPackages::add); From 39624b27535ee499d8cbe217e495af64ed229e4f Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 18:28:17 +0100 Subject: [PATCH 38/52] refactor ByteCodeDynamicSwitch --- .../switches/BreadthFirstSearch.java | 2 +- .../switches/BytecodeDynamicSwitch.java | 304 +----------------- .../bytecodegen/ByteCodeSwitchCompiler.java | 134 ++++++++ .../bytecodegen/DoSwitchBodyAppender.java | 213 ++++++++++++ .../bytecodegen/DoSwitchImplementation.java | 30 ++ .../switches/bytecodegen/FieldNamingRule.java | 9 + .../switches/bytecodegen/package-info.java | 1 + 7 files changed, 393 insertions(+), 300 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/ByteCodeSwitchCompiler.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchBodyAppender.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchImplementation.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/FieldNamingRule.java create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/package-info.java 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 index 2798078..3cbe777 100644 --- 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 @@ -8,7 +8,7 @@ import java.util.function.BiPredicate; import java.util.function.Function; -class BreadthFirstSearch { +public class BreadthFirstSearch { /** * perform a BreathFirstSearch in an acyclic graph represented by a root node and an exploration. 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 index 50cfb87..ff87779 100644 --- 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 @@ -1,46 +1,10 @@ package tools.mdsd.ecoreworkflow.switches; -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.DynamicType.Builder; -import net.bytebuddy.dynamic.DynamicType.Unloaded; -import net.bytebuddy.dynamic.scaffold.InstrumentedType; -import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FixedValue; -import net.bytebuddy.implementation.Implementation; -import net.bytebuddy.implementation.Implementation.Context; -import net.bytebuddy.implementation.SuperMethodCall; -import net.bytebuddy.implementation.bytecode.ByteCodeAppender; -import net.bytebuddy.implementation.bytecode.StackManipulation; -import net.bytebuddy.implementation.bytecode.constant.IntegerConstant; -import net.bytebuddy.jar.asm.Label; -import net.bytebuddy.jar.asm.MethodVisitor; -import static net.bytebuddy.matcher.ElementMatchers.*; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; 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.EObject; import org.eclipse.emf.ecore.EPackage; -import org.eclipse.emf.ecore.EcorePackage; -import org.eclipse.emf.ecore.EClassifier; -import net.bytebuddy.jar.asm.Opcodes; -import net.bytebuddy.jar.asm.Type; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; +import tools.mdsd.ecoreworkflow.switches.bytecodegen.ByteCodeSwitchCompiler; public class BytecodeDynamicSwitch extends AbstractInspectableDynamicSwitch implements DynamicSwitch, ApplyableSwitch, InspectableSwitch { @@ -49,274 +13,16 @@ public class BytecodeDynamicSwitch extends AbstractInspectableDynamicSwitch precompile() { if (compiledSwitch == null) { - compiledSwitch = compileSwitch(); + compiledSwitch = new ByteCodeSwitchCompiler(caseDefinitions, defaultCase, explicitPackages).compileSwitch(); } return this; } - class LargeSwitchLogicAppender implements ByteCodeAppender { - - private Map> invocationsPerClass; - private Map> classesPerPackage; - - public LargeSwitchLogicAppender(Map> invocationOrders) { - invocationsPerClass = invocationOrders; - classesPerPackage = new HashMap<>(); - invocationOrders.entrySet().stream().forEach(entry -> { - EClass eClass = entry.getKey(); - EPackage ePackage = eClass.getEPackage(); - if (!classesPerPackage.containsKey(ePackage)) { - classesPerPackage.put(ePackage, new ArrayList<>()); - } - classesPerPackage.get(ePackage).add(eClass); - }); - } - - @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, Type.getInternalName(EObject.class), "eClass", Type.getMethodDescriptor(Type.getType(EClass.class)), true); - // Stack: | eObj | eClass - mv.visitInsn(Opcodes.DUP); - // Stack: | eObj | eClass | eClass - mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(EClassifier.class), "getEPackage", Type.getMethodDescriptor(Type.getType(EPackage.class)), true); - // Stack: | eObj | eClass | ePackage - - Label packageJumpLabel; - Label returnResult = new Label(); - Label notFound = new Label(); - for (Entry> packageEntry: classesPerPackage.entrySet()) { - 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, Type.getInternalName(EClassifier.class), "getClassifierID", Type.getMethodDescriptor(Type.INT_TYPE), true); - // Stack: | eObj | classifierID - List classEntries = packageEntry.getValue(); - int minId = 1, maxId = 0; - for (EClass eClass : classEntries) { - int clf = eClass.getClassifierID(); - if (minId > clf) minId = clf; - if (maxId < clf) maxId = clf; - } - Label[] labels = new Label[maxId - minId + 1]; - for (int j = 0; j <= maxId - minId; j++) { - labels[j] = new Label(); - } - - mv.visitTableSwitchInsn(minId, maxId, notFound, 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[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 1, new String[]{Type.getInternalName(EObject.class)}); - // Stack: | eObj | - Optional dynamicType = classEntries.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 - for (EClass caseDefinedOn : invocationsPerClass.getOrDefault(dynamicType.get(), new LinkedList<>())) { - // Stack: | eObj - mv.visitInsn(Opcodes.DUP); - // Stack: | eObj | eObj - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack: | eObj | eObj | this - mv.visitFieldInsn(Opcodes.GETFIELD, ctx.getInstrumentedType().getInternalName(), getCaseName(caseDefinedOn), Type.getDescriptor(Function.class)); - // Stack: | eObj | eObj | fptr - mv.visitInsn(Opcodes.SWAP); - // Stack: | eObj | fptr | eObj - mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Function.class), "apply", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), true); - // Stack: | eObj | result - mv.visitInsn(Opcodes.DUP); - // Stack: | eObj | result | result - mv.visitJumpInsn(Opcodes.IFNONNULL, returnResult); - // Stack: | eObj | result - mv.visitInsn(Opcodes.POP); - // Stack: | eObj - } - } - mv.visitJumpInsn(Opcodes.GOTO, notFound); - - } - - //mv.visitJumpInsn(Opcodes.GOTO, notFound); - // END INSIDE THE CORRECT PACKAGE - - mv.visitLabel(packageJumpLabel); // <-- incoming jump - mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 3, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(EClass.class), Type.getInternalName(EPackage.class)}); - // 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[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 1, new String[]{Type.getInternalName(EObject.class)}); - - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack: | eObj | this - mv.visitFieldInsn(Opcodes.GETFIELD, ctx.getInstrumentedType().getInternalName(), "defaultCase", Type.getDescriptor(Function.class)); - // 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, Type.getInternalName(Function.class), "apply", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), true); - // Stack: | result - mv.visitInsn(Opcodes.ARETURN); - - mv.visitLabel(noDefaultCase); - mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 2, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(Function.class)}); - // 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"); - try { - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(SwitchingException.class), "", Type.getConstructorDescriptor(SwitchingException.class.getConstructor(String.class)), false); - } catch (NoSuchMethodException | SecurityException e) { - throw new RuntimeException("could not find SwitchingException's constructor", e); - } - mv.visitInsn(Opcodes.ATHROW); - - mv.visitLabel(returnResult); - mv.visitFrame(Opcodes.F_NEW, 2, new String[]{ctx.getInstrumentedType().getInternalName(), Type.getInternalName(EObject.class)}, 2, new String[]{Type.getInternalName(EObject.class), Type.getInternalName(Object.class)}); - mv.visitInsn(Opcodes.ARETURN); - return new Size(5, md.getStackSize()); - } - - } - - class LargeSwitchLogicImplementation implements Implementation { - - private Map> invocationOrders; - - public LargeSwitchLogicImplementation(Map> invocationOrders) { - this.invocationOrders = invocationOrders; - } - - @Override - public InstrumentedType prepare(InstrumentedType instrType) { - return instrType; - } - - @Override - public ByteCodeAppender appender(Target target) { - return new LargeSwitchLogicAppender(invocationOrders); - } - - } - - @SuppressWarnings("unchecked") - private ApplyableSwitch compileSwitch() { - Map> invocationOrders = computeInvocationOrders(); - - //invocationOrders.forEach((cl, invs)->System.out.println(cl.getName() + ":" + invs.stream().map(EClass::getName).collect(Collectors.joining(",")))); - - Builder typeUnderConstruction = new ByteBuddy() - .subclass(ApplyableSwitch.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR); - - for (EClass caseClass: invocationOrders.keySet()) { // stores a function pointer for each case - typeUnderConstruction = typeUnderConstruction.defineField(getCaseName(caseClass), Function.class, Visibility.PUBLIC); - } - typeUnderConstruction = typeUnderConstruction.defineField("defaultCase", Function.class, Visibility.PUBLIC); - - Unloaded made = typeUnderConstruction - .method(named("doSwitch")) - .intercept(new LargeSwitchLogicImplementation(invocationOrders)) - .make(); - -// try { -// made.saveIn(new File("c:/temp")); -// } catch (IOException e1) { -// // TODO Auto-generated catch block -// e1.printStackTrace(); -// } - - Class dynamicType = made - .load(Thread.currentThread().getContextClassLoader()) - .getLoaded(); - try { - ApplyableSwitch instance = dynamicType.newInstance(); - caseDefinitions.forEach((eClass, lambda) -> { - try { - Field field = instance.getClass().getField(getCaseName(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 e) { - throw new RuntimeException("could not set the default function correspondingly", e); - } - - return instance; - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException("error invoking the constructor of the byte-assembled class", e); - } + public BytecodeDynamicSwitch addPackage(EPackage ePackage) { + explicitPackages.add(ePackage); + return this; } - private String getCaseName(EClass eClass) { - return "case_" + Math.abs(eClass.getEPackage().getNsURI().hashCode()) + eClass.getEPackage().getName() + "_" + eClass.getClassifierID(); - } - - protected 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; - } - @Override protected boolean canDafineCases() { return compiledSwitch == null; 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 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..2e58189 --- /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.MSwitch.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; From ca9bf57e172a283a9a0177a5bfcde519d310028e Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 20:25:22 +0100 Subject: [PATCH 39/52] bugfix: make MSwitches compatible with interpackage inheritance * consider gen classes from other packages (use allSwitchGenClasses instead of genClasses) * use classe's gen package instead of own gen package --- .../switches/MSwitchClassGenerator.xtend | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 index 6ee43aa..2e32b77 100644 --- 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 @@ -63,7 +63,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { public class «className» extends MSwitch implements MergeableSwitch<«className», «className»> { private static «genPackage.importedPackageInterfaceName» MODEL_PACKAGE = «genPackage.importedPackageInterfaceName».eINSTANCE; - «FOR c:genPackage.genClasses» + «FOR c:genPackage.allSwitchGenClasses» private Function<«c.importedInterfaceName»,T> «getCaseName(c)»; «ENDFOR» @@ -74,8 +74,8 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException { T result; switch(classifierID) { - «FOR c : genPackage.genClasses» - case «genPackage.importedPackageInterfaceName».«genPackage.getClassifierID(c)»: { + «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); @@ -97,18 +97,18 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { } public «className» merge(«className» other) { - «FOR field: genPackage.genClasses.map[caseName]» + «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.genClasses» + «FOR c : genPackage.allSwitchGenClasses» public interface «getInterfaceName(c)» extends Function<«c.importedInterfaceName»,T> {} «ENDFOR» - «FOR c : genPackage.genClasses» + «FOR c : genPackage.allSwitchGenClasses» public «className» when(«getInterfaceName(c)» then) { this.«getCaseName(c)» = then; return this; @@ -123,9 +123,9 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { public Map> getCases() { Map> definedCases = new HashMap<>(); - «FOR c:genPackage.genClasses» + «FOR c:genPackage.allSwitchGenClasses» if (this.«getCaseName(c)» != null) { - definedCases.put(«genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o)); + definedCases.put(«c.genPackage.importedPackageInterfaceName».Literals.«genPackage.getClassifierID(c)», this.«getCaseName(c)».compose(o -> («c.importedInterfaceName») o)); } «ENDFOR» From e0d78eba13e4d2ad2c271391b47df81c6cfda27c Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 21:06:19 +0100 Subject: [PATCH 40/52] add test for BytecodeDynamicSwitchTest with cross-package inheritance --- tests/pom.xml | 1 + .../.classpath | 7 +++ .../.gitignore | 1 + .../.settings/org.eclipse.jdt.core.prefs | 7 +++ .../META-INF/MANIFEST.MF | 22 ++++++++ .../build.properties | 10 ++++ .../model/testscenario3.ecore | 6 ++ .../model/testscenario3.genmodel | 13 +++++ .../plugin.properties | 4 ++ .../plugin.xml | 17 ++++++ .../workflow/clean.mwe2 | 17 ++++++ .../workflow/generate.mwe2 | 32 +++++++++++ .../META-INF/MANIFEST.MF | 3 +- .../tests/BytecodeDynamicSwitchTest.java | 55 ++++++++++++++++++- 14 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.classpath create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.gitignore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.settings/org.eclipse.jdt.core.prefs create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/META-INF/MANIFEST.MF create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/build.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.ecore create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/model/testscenario3.genmodel create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.properties create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/plugin.xml create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/clean.mwe2 create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/workflow/generate.mwe2 diff --git a/tests/pom.xml b/tests/pom.xml index 118bd98..9a7c1d6 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -14,6 +14,7 @@ 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.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/.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/META-INF/MANIFEST.MF b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF index 1abe64b..663507b 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ 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.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/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/BytecodeDynamicSwitchTest.java index 47f7764..17056b8 100644 --- 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 @@ -8,14 +8,19 @@ 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.testscenario2.Testscenario2Factory; +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.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 { @@ -55,7 +60,55 @@ public void testChainedSyntax() { .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"); + } } From a870b1e88952f58b51e7565f785ced3d0d174d56 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 21:22:35 +0100 Subject: [PATCH 41/52] also test HashDynamicSwitch for cross package inheritance behaviour * extract CrossPackageSwitchingRulesBehaviourTest --- .../tests/BytecodeDynamicSwitchTest.java | 15 ++++++- .../switches/tests/HashDynamicSwitchTest.java | 21 ++++----- ...ossPackageSwitchingRulesBehaviourTest.java | 44 +++++++++++++++++++ 3 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/CrossPackageSwitchingRulesBehaviourTest.java 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 index 17056b8..3753b00 100644 --- 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 @@ -8,11 +8,13 @@ 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.testscenario2.Testscenario2Factory; +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; @@ -53,6 +55,17 @@ class ConformsToInspectableSwitchBehaviour extends InspectableBehaviourTest { } + @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() 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 index c0bcb54..79c1dde 100644 --- 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 @@ -1,20 +1,16 @@ package tools.mdsd.ecoreworkflow.switches.tests; -import org.eclipse.emf.ecore.EObject; -import org.junit.Test; 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.testmodel.testscenario.TestscenarioFactory; 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; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.*; class HashDynamicSwitchTest { @Nested @@ -46,13 +42,12 @@ class ConformsToInspectableSwitchBehaviour extends InspectableBehaviourTest { } - @Test - void testChainedSyntax() { - String result = new HashDynamicSwitch() - .dynamicCase(G, (EObject g) -> "G") - .dynamicCase(H, (EObject h) -> "H") - .doSwitch(TestscenarioFactory.eINSTANCE.create(E)); - - assertEquals("G", result); + @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/templates/CrossPackageSwitchingRulesBehaviourTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/CrossPackageSwitchingRulesBehaviourTest.java new file mode 100644 index 0000000..71e7c5e --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/CrossPackageSwitchingRulesBehaviourTest.java @@ -0,0 +1,44 @@ +package tools.mdsd.ecoreworkflow.switches.tests.templates; + +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.A; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.E; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.G; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals.H; +import static tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.Testscenario3Package.Literals.Q; +import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Test; +import tools.mdsd.ecoreworkflow.switches.DynamicSwitch; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; +import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario3.Testscenario3Factory; + +public abstract class CrossPackageSwitchingRulesBehaviourTest { + protected abstract DynamicSwitch getSubject(); + + @Test + public void crossPackageDelegationWorks() { + DynamicSwitch sw = getSubject(); + + 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"); + } + + @Test + void testChainedSyntax() { + String result = getSubject() + .dynamicCase(G, (EObject g) -> "G") + .dynamicCase(H, (EObject h) -> "H") + .doSwitch(TestscenarioFactory.eINSTANCE.create(E)); + + assertEquals("G", result); + } +} From 71b2993da0beb2441810a6aebc96736b2b3e505c Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 5 Feb 2020 21:32:21 +0100 Subject: [PATCH 42/52] ignore .polyglot.properties files * exist while build is running and are cleaned up afterwards * which is annoying when looking at diffs --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2461756..e3db090 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ -bin/ \ No newline at end of file +bin/ +**/.polyglot.build.properties From c1ccbeebd474e0883e2f1ce85b1ebda49b50353a Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 20 Mar 2020 09:25:52 +0100 Subject: [PATCH 43/52] remove unnecessary comment --- .../mdsd/ecoreworkflow/switches/MSwitchClassGenerator.xtend | 1 - 1 file changed, 1 deletion(-) 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 index 2e32b77..70225f0 100644 --- 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 @@ -1,5 +1,4 @@ package tools.mdsd.ecoreworkflow.switches; -// TODO uncomment the following. It is only commented out, because xtend classes don't make it into the maven build up to now import java.io.OutputStream import java.io.PrintWriter From 65679321923c2707d4f378fdc6e0a061aea1e104 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Fri, 20 Mar 2020 09:29:00 +0100 Subject: [PATCH 44/52] let bytecode switch participate in single package benchmark --- .../MultiPackageSwitchingSpeedBenchmark.java | 2 +- .../mdsd/ecoreworkflow/SwitchConfigurator.java | 15 ++++++++++++++- .../ecoreworkflow/SwitchingSpeedBenchmark.java | 9 +++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) 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 index 7432177..ee1133c 100644 --- 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 @@ -19,7 +19,7 @@ public static class BenchmarkState { SwitchConfigurator conf = new SwitchConfigurator(); ComposedSwitch composedSwitch = conf.buildComposedSwitch(); DynamicSwitch dynamicSwitch = conf.buildComposedDynamicSwitch(); - DynamicSwitch bytecodeSwitch = conf.buildDynamicBytecodeSwitch(); + DynamicSwitch bytecodeSwitch = conf.buildComposedDynamicBytecodeSwitch(); } @State(Scope.Thread) 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 index 94230fd..a4f6f5d 100644 --- 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 @@ -80,7 +80,7 @@ public String caseZ(Z object) { } }; } - + public DynamicSwitch buildDynamicBytecodeSwitch() { BytecodeDynamicSwitch sw = new BytecodeDynamicSwitch(); sw @@ -93,4 +93,17 @@ public DynamicSwitch buildDynamicBytecodeSwitch() { .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 index 7dca14a..fd167d6 100644 --- 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 @@ -11,6 +11,7 @@ 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; @@ -24,6 +25,7 @@ public static class BenchmarkState { TestscenarioSwitch classicSwitch = conf.buildClassicSwitch(); TestscenarioMSwitch mswitch = conf.buildMSwitch(); DynamicSwitch dynamicSwitch = conf.buildDynamicSwitch(); + DynamicSwitch dynamicBytecodeSwitch = conf.buildDynamicBytecodeSwitch(); } @State(Scope.Thread) @@ -66,6 +68,13 @@ public void dynamicSwitch(BenchmarkState benchmarkState, ThreadState threadState } } + @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) { From 0a88cb8a6acb559f3546b9e372510f8f36dad43b Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sat, 21 Mar 2020 18:01:35 +0100 Subject: [PATCH 45/52] add switches eclipse feature --- features/pom.xml | 1 + .../build.properties | 1 + .../feature.xml | 33 +++++++++++++++++++ .../category.xml | 3 ++ 4 files changed, 38 insertions(+) create mode 100644 features/tools.mdsd.ecoreworkflow.switches.feature/build.properties create mode 100644 features/tools.mdsd.ecoreworkflow.switches.feature/feature.xml diff --git a/features/pom.xml b/features/pom.xml index f6ced9b..eef80f7 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -13,6 +13,7 @@ tools.mdsd.ecoreworkflow.builder.feature tools.mdsd.ecoreworkflow.mwe2lib.feature + tools.mdsd.ecoreworkflow.switches.feature 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.updatesite/category.xml b/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml index ea47dd8..a6f35ae 100644 --- a/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml +++ b/releng/tools.mdsd.ecoreworkflow.updatesite/category.xml @@ -3,5 +3,8 @@ + + + From 9d072a2968721c683b3f55b7f71e90c0aa25d6e3 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sun, 22 Mar 2020 01:00:44 +0100 Subject: [PATCH 46/52] add .project files * otherwise the build can break --- .../.project | 17 +++++++++++++++++ .../.project | 17 +++++++++++++++++ .../.project | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project create mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project new file mode 100644 index 0000000..827f4bf --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project @@ -0,0 +1,17 @@ + + + tools.mdsd.ecoreworkflow.switches.testmodel + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project new file mode 100644 index 0000000..fc2d14d --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project @@ -0,0 +1,17 @@ + + + tools.mdsd.ecoreworkflow.switches.testmodel2 + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project new file mode 100644 index 0000000..4475644 --- /dev/null +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project @@ -0,0 +1,17 @@ + + + tools.mdsd.ecoreworkflow.switches.testmodel3 + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + From 75c861ce1c5c2704fbd1300708cf6604b639cee3 Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Sun, 22 Mar 2020 01:36:46 +0100 Subject: [PATCH 47/52] fixup add .project files --- .../.project | 19 ++++++++++++++++++- .../.project | 19 ++++++++++++++++++- .../.project | 19 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project index 827f4bf..cbe4778 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/.project @@ -5,13 +5,30 @@ - + + + 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/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project index fc2d14d..de068bc 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel2/.project @@ -5,13 +5,30 @@ - + + + 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/.project b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project index 4475644..6da3317 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project +++ b/tests/tools.mdsd.ecoreworkflow.switches.testmodel3/.project @@ -5,13 +5,30 @@ - + + + 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 From b854f934ddcded3a264b70953096e2dc55e7e222 Mon Sep 17 00:00:00 2001 From: christianthechristian Date: Tue, 24 Mar 2020 23:20:16 +0100 Subject: [PATCH 48/52] documentation: switch usage and benchmark results --- docu/switches.MD | 177 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 docu/switches.MD diff --git a/docu/switches.MD b/docu/switches.MD new file mode 100644 index 0000000..de135de --- /dev/null +++ b/docu/switches.MD @@ -0,0 +1,177 @@ +# Advanced Ecore Switching + +## Introduction and concepts +### EMF +[EMF](https://www.eclipse.org/modeling/emf/) is a modeling framework. It allows code generation from Ecore models. +What does switching mean? :pencil: + +How do the different approaches work? :pencil: + +## 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. +![](https://i.imgur.com/vkP17Wa.png) + + +### Multi Package Szenario +In the multi package szenario, the ComposableSwitch that EMF offers only works when the package hierarchies are disjunct. 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). +![](https://i.imgur.com/KyTMpnc.png) + +### Limitations of the performance analysis +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. + + +## Build +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`. 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, 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/` for the currently deployed oficial version of the ecore workflow or you can use your locally hosted update site by adding a section 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 -> 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 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. + +### Running Tests + +Tests are run with `mvn clean verify`. + +### Running Benchmarks + +Benchmarking takes precious build time and are only run when explicitly called with: +``` + mvn clean verify -Dbenchmarking=true +``` From 5992e0f1045ccadb7ee61b6d20dbf00cd7c0c9a0 Mon Sep 17 00:00:00 2001 From: christianthechristian Date: Tue, 24 Mar 2020 23:21:38 +0100 Subject: [PATCH 49/52] add a reference to the switch documentation --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) From 8a8a320e192db3fb571f21e0c9b53f35c42641ca Mon Sep 17 00:00:00 2001 From: HackMD Date: Wed, 25 Mar 2020 17:45:56 +0000 Subject: [PATCH 50/52] add to the switch docu --- docu/switches.MD | 100 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/docu/switches.MD b/docu/switches.MD index de135de..1106c41 100644 --- a/docu/switches.MD +++ b/docu/switches.MD @@ -1,11 +1,73 @@ # Advanced Ecore Switching - +The Ecore advanced switching project is about bringing composability and cross-package-capabilities to EMF switches without harming performance too much. ## 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. -What does switching mean? :pencil: - -How do the different approaches work? :pencil: +Assume the following simple ecore model: + +![](https://i.imgur.com/doM4ay5.png) + +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 be automatically generated: + +```` + 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 our 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. 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 our api might look like this: +```` +float maxSpeed = new TransportationMSwitch() + .when((LKW lkw) -> 80.0f) + .when((PKW pkw) -> 120.0f) + .orElse((EObject object) -> Float.POSITIVE_INFINITY) + .doSwitch(anEObject); +```` + +#### Purpose +The switching functionality is similar to pattern matching and serves a similar purpose as multimethods: It enables the creation of type-specific behaviour (as with polimorphism), 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 +EMFs switching semantics 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) and from the [implementation](https://github.com/eclipse/emf/blob/d45610f/plugins/org.eclipse.emf.codegen.ecore/templates/model/SwitchClass.javajet) of `GenPackage#getAllSwitchGenClasses()` 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`.) + +### Aim of this project +It turns out that in order to define the switching rules on EMF's built-in switches, sublclassing is necessary. This 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. + +Another downside: Because EMFs 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. + +The aim of this project is to allow switches to be composed and to also provide a possibility of cross-package switching without incurring an unbearable performance loss in comparison to the classic approach in terms of switching throughput. + +### Switching aproaches + +#### Classic EMF aproach + +The classic EMF approach works as follows: +1. At code generation time of a package, its class hierarchy is analyzed. A package-specific switch class is generated that contains method stubs for all types + +#### MSwitch aproach +:pencil: +#### HashDynamisSwitch aproach +:pencil: +#### BytecodeDynamicSwitch aproach +:pencil: ## 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. @@ -25,11 +87,16 @@ This measuring approach does not take into account the time it takes to configur ## Build 1. Clone the repository: - ``git clone https://github.com/.../Ecore-Workflow`` - `cd Ecore-Workflow` + ```` + 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`. If you want to temporarily host an update site for testing, you can start a local http server: + ``` + 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 @@ -38,10 +105,13 @@ This measuring approach does not take into account the time it takes to configur ## 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. +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, you can clone the [example repo](https://github.com/christianthechristian/example_mswitch_maven_project). +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 @@ -56,7 +126,7 @@ Following steps conceptually describe how to incorporate advanced switching capa ```` 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/` for the currently deployed oficial version of the ecore workflow or you can use your locally hosted update site by adding a section as follows: + 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: ``` @@ -120,10 +190,10 @@ Following steps conceptually describe how to incorporate advanced switching capa 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 -> Run as MWE Workflow. +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 2019-09, with the modeling tools and ByteBuddy also installed). +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. @@ -165,13 +235,15 @@ These components play a part in switching: | 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 are only run when explicitly called with: +Benchmarking takes precious build time and therefore are only enabled in a specific maven profile. You can enable this profile by an additional command line parameter: ``` mvn clean verify -Dbenchmarking=true ``` From ddaa13f0ce0a27a296969a63273a326a25015f1b Mon Sep 17 00:00:00 2001 From: Christian van Rensen Date: Wed, 1 Apr 2020 00:08:50 +0200 Subject: [PATCH 51/52] code cleanup and comments --- .../AdditionalTemplateGenerator.java | 46 ++++++++++++++++--- .../PackageLevelCodeFileGenerator.java | 20 ++++++++ .../AbstractInspectableDynamicSwitch.java | 12 +++-- .../switches/ApplyableSwitch.java | 3 +- .../switches/BreadthFirstSearch.java | 27 +++++++++-- .../switches/BytecodeDynamicSwitch.java | 24 ++++++++++ .../switches/HashDynamicSwitch.java | 10 ++-- .../mdsd/ecoreworkflow/switches/MSwitch.java | 22 ++++----- .../switches/MSwitchClassGenerator.xtend | 8 +++- .../switches/SwitchingException.java | 10 ++++ .../bytecodegen/DoSwitchBodyAppender.java | 2 +- .../src/.gitkeep | 0 .../SwitchingRulesBehaviourTest.java | 2 +- 13 files changed, 149 insertions(+), 37 deletions(-) create mode 100644 bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/SwitchingException.java delete mode 100644 tests/tools.mdsd.ecoreworkflow.switches.testmodel/src/.gitkeep 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 index 9fb3acc..c27a313 100644 --- 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 @@ -24,9 +24,14 @@ 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 destPath; private String genModel; private List packageLevelGenerators; @@ -35,16 +40,33 @@ 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()); @@ -53,21 +75,31 @@ public void addPackageLevelGenerator(String gen) { } } + // 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); - ResourceSet resSet = new ResourceSetImpl(); - Resource resource = resSet.getResource(URI.createURI(genModel), true); - GenModel genModel = (GenModelImpl) resource.getContents().get(0); - - runGenerator(genModel); + 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); } 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 index 29beaff..5471412 100644 --- 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 @@ -5,8 +5,28 @@ 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.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/AbstractInspectableDynamicSwitch.java index 53b3c33..f10aaff 100644 --- 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 @@ -8,23 +8,25 @@ 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; - public AbstractInspectableDynamicSwitch() { - super(); - } - @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. + // special treatment necessary, because EObject might not be caught otherwise (EObject is not always returned by eGetSupertypes()). defaultCase(then); } else { caseDefinitions.put(clazz, then); 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 index b19649f..72c47e9 100644 --- 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 @@ -4,10 +4,11 @@ /** * can take an EObject and perform switching. + * * @param the return type of the switch's clauses */ public interface ApplyableSwitch { - // TODO: describe matching semantics + /** * takes an eObject and applies the best-matching case. * @param object the input for the switch 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 index 3cbe777..51bddc1 100644 --- 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 @@ -7,15 +7,22 @@ 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 BreathFirstSearch in an acyclic graph represented by a root node and an exploration. - * relationship and return the first matching node + * 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 for a node and a set of unexplored nodes determines if the node matches + * @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 */ @@ -34,7 +41,17 @@ public T find(T rootNode, BiPredicate> criterion, 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<>(); 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 index ff87779..27350e9 100644 --- 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 @@ -6,8 +6,19 @@ 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<>(); @@ -18,6 +29,16 @@ public BytecodeDynamicSwitch precompile() { 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; @@ -25,6 +46,9 @@ public BytecodeDynamicSwitch addPackage(EPackage ePackage) { @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; } 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 index 72d6e2e..5419238 100644 --- 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 @@ -4,12 +4,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; /** * A dynamic switch implementation that is optimized for being quick when the objects the graph is @@ -40,9 +37,11 @@ public class HashDynamicSwitch extends AbstractInspectableDynamicSwitch 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) { @@ -71,8 +70,11 @@ 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)); } 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 index 5e1ee06..8c5bae1 100644 --- 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 @@ -6,18 +6,14 @@ 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 static class SwitchingException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public SwitchingException(String message) { - super(message); - } - } - public MSwitch() { super(); } @@ -29,10 +25,12 @@ public T doSwitch(EObject s) { protected T doSwitch(EClass eClass, EObject eObject) { if (isSwitchFor(eClass.getEPackage())) { return doSwitch(eClass.getClassifierID(), eObject); - } else { + } 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); + return eSuperTypes.isEmpty() ? applyDefaultCase(eObject) : doSwitch(eSuperTypes.get(0), eObject); } } 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 index 70225f0..1f3cb5e 100644 --- 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 @@ -7,6 +7,11 @@ 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 @@ -56,6 +61,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { 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 @@ -70,7 +76,7 @@ class MSwitchClassGenerator implements PackageLevelCodeFileGenerator { return ePackage == MODEL_PACKAGE; } - protected T doSwitch(int classifierID, EObject eObject) throws MSwitch.SwitchingException { + protected T doSwitch(int classifierID, EObject eObject) throws SwitchingException { T result; switch(classifierID) { «FOR c : genPackage.allSwitchGenClasses» 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/DoSwitchBodyAppender.java b/bundles/tools.mdsd.ecoreworkflow.switches/src/tools/mdsd/ecoreworkflow/switches/bytecodegen/DoSwitchBodyAppender.java index 2e58189..79aaff1 100644 --- 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 @@ -18,7 +18,7 @@ import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; +import tools.mdsd.ecoreworkflow.switches.SwitchingException; /** * appends the body of a DynamicBytecodesSwitch's doSwitch method diff --git a/tests/tools.mdsd.ecoreworkflow.switches.testmodel/src/.gitkeep b/tests/tools.mdsd.ecoreworkflow.switches.testmodel/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java index c8c981d..b5e219f 100644 --- a/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java +++ b/tests/tools.mdsd.ecoreworkflow.switches.tests/test/tools/mdsd/ecoreworkflow/switches/tests/templates/SwitchingRulesBehaviourTest.java @@ -7,7 +7,7 @@ import org.eclipse.emf.ecore.EObject; import org.junit.jupiter.api.Test; import tools.mdsd.ecoreworkflow.switches.ApplyableSwitch; -import tools.mdsd.ecoreworkflow.switches.MSwitch.SwitchingException; +import tools.mdsd.ecoreworkflow.switches.SwitchingException; import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.F; import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioFactory; import tools.mdsd.ecoreworkflow.switches.testmodel.testscenario.TestscenarioPackage.Literals; From 2508eb432976f7f7b8841173281b3feed4660145 Mon Sep 17 00:00:00 2001 From: HackMD Date: Wed, 8 Apr 2020 13:43:49 +0000 Subject: [PATCH 52/52] complete docu --- docu/switches.MD | 237 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 203 insertions(+), 34 deletions(-) diff --git a/docu/switches.MD b/docu/switches.MD index 1106c41..d9e23c2 100644 --- a/docu/switches.MD +++ b/docu/switches.MD @@ -1,14 +1,14 @@ # Advanced Ecore Switching -The Ecore advanced switching project is about bringing composability and cross-package-capabilities to EMF switches without harming performance too much. +*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: -![](https://i.imgur.com/doM4ay5.png) +![](https://i.imgur.com/doM4ay5m.png) -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 be automatically generated: +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 {} @@ -22,52 +22,216 @@ EMF allows us to create a GenModel based upon it (wich also includes details abo class TransportmittelPackage {…} class TransportmittelSwitch {…} ```` -For each class in our 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. +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. 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 our api might look like this: -```` -float maxSpeed = new TransportationMSwitch() - .when((LKW lkw) -> 80.0f) - .when((PKW pkw) -> 120.0f) - .orElse((EObject object) -> Float.POSITIVE_INFINITY) - .doSwitch(anEObject); -```` +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 -The switching functionality is similar to pattern matching and serves a similar purpose as multimethods: It enables the creation of type-specific behaviour (as with polimorphism), 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. +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 -EMFs switching semantics 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) and from the [implementation](https://github.com/eclipse/emf/blob/d45610f/plugins/org.eclipse.emf.codegen.ecore/templates/model/SwitchClass.javajet) of `GenPackage#getAllSwitchGenClasses()` used in the template. +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. +* 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`.) -### Aim of this project -It turns out that in order to define the switching rules on EMF's built-in switches, sublclassing is necessary. This 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. +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: + +![](https://i.imgur.com/XwbL5bT.png) + +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. -Another downside: Because EMFs approach relies on per-package code generation at compile time, case definition is always limited to the types of one package at a time. +### 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) +``` -The aim of this project is to allow switches to be composed and to also provide a possibility of cross-package switching without incurring an unbearable performance loss in comparison to the classic approach in terms of switching throughput. +### 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 +## 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 +### Classic EMF aproach The classic EMF approach works as follows: -1. At code generation time of a package, its class hierarchy is analyzed. A package-specific switch class is generated that contains method stubs for all types - -#### MSwitch aproach -:pencil: -#### HashDynamisSwitch aproach -:pencil: -#### BytecodeDynamicSwitch aproach -:pencil: +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. @@ -78,14 +242,19 @@ In the single package szenario, the classic EMF switch remains fastest. However, ### Multi Package Szenario -In the multi package szenario, the ComposableSwitch that EMF offers only works when the package hierarchies are disjunct. 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). +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). ![](https://i.imgur.com/KyTMpnc.png) -### Limitations of the performance analysis +### 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 @@ -111,7 +280,7 @@ There are several possibilities to invoke such a workflow: \(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). +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 @@ -243,7 +412,7 @@ Tests are run with `mvn clean verify`. ### Running Benchmarks -Benchmarking takes precious build time and therefore are only enabled in a specific maven profile. You can enable this profile by an additional command line parameter: +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 ```