diff --git a/.gitignore b/.gitignore index e872dd4040..346ebb1b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,8 @@ tmp/ .gradletasknamecache # polygot -*.polyglot.META-INF \ No newline at end of file +*.polyglot.META-INF + +# FluentAPI +/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.*/metamodel/ + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.classpath b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.classpath new file mode 100644 index 0000000000..bbfe5b0e61 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.project b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.project new file mode 100644 index 0000000000..d3979f624d --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.project @@ -0,0 +1,28 @@ + + + cipm.consistency.fluentapi.java + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.settings/org.eclipse.jdt.core.prefs b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..c9545f06a4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +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=11 diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/META-INF/MANIFEST.MF b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..95c088a2cd --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/META-INF/MANIFEST.MF @@ -0,0 +1,35 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: cipm.consistency.fluentapi.java;singleton:=true +Bundle-Version: 0.2.0.qualifier +Bundle-ClassPath: . +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Export-Package: cipm.consistency.fluentapi.java.api, + cipm.consistency.fluentapi.java.api.impl, + cipm.consistency.fluentapi.java.api.inits, + cipm.consistency.fluentapi.java.api.inits.impl, + cipm.consistency.fluentapi.java.api.inits.util, + cipm.consistency.fluentapi.java.api.placeholderTypes, + cipm.consistency.fluentapi.java.api.placeholderTypes.impl, + cipm.consistency.fluentapi.java.api.util, + cipm.consistency.fluentapi.java.builder, + cipm.consistency.fluentapi.java.metamodel, + cipm.consistency.fluentapi.java.postprocessor, + cipm.consistency.fluentapi.java.test, + cipm.consistency.fluentapi.java.test.metamodel +Automatic-Module-Name: cipm.consistency.fluentapi.java +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Require-Bundle: cipm.consistency.fluentapi, + junit-jupiter-api, + junit-jupiter-engine, + junit-jupiter-params, + org.apache.log4j, + org.eclipse.emf.codegen.ecore, + org.eclipse.core.runtime, + org.eclipse.emf.ecore;visibility:=reexport, + org.eclipse.emf.ecore.xmi;visibility:=reexport, + org.emftext.language.java;visibility:=reexport, + org.apache.commons.lang +Bundle-ActivationPolicy: lazy diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/README.md b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/README.md new file mode 100644 index 0000000000..ec97eac9c9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/README.md @@ -0,0 +1,9 @@ +# Fluent API for Java + +This plug-in contains the implementation of the fluent API generation for Java and considers the (EMF-based) [JaMoPP metamodel for Java](https://github.com/MDSD-Tools/TheExtendedJavaModelParserAndPrinter). The structure of this plug-in reflects the structure of the [base plug-in for fluent API generation](../cipm.consistency.fluentapi/README.md). + +The fluent API class within this plug-in is called [FluentJavaAPI](./src-gen/cipm.consistency.fluentapi.java.api/FluentJavaAPI.java). Note that this class is not present in this plug-in by default will be generated from the fluent API model. + +Note that the Layout package and the relevant JaMoPP features are excluded in this implementation, in order to keep the generated fluent API code to the metamodel elements that are directly related to Java. + +For exemplary usage, refer to the test cases within the [test package of this plug-in](./src/cipm.consistency.fluentapi.java.test). For further details, refer to the [base plug-in for fluent API generation](../cipm.consistency.fluentapi/README.md). diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/build.properties b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/build.properties new file mode 100644 index 0000000000..29969d1711 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/build.properties @@ -0,0 +1,11 @@ +# + +bin.includes = .,\ + metamodel/,\ + META-INF/,\ + plugin.xml,\ + plugin.properties +jars.compile.order = . +source.. = src-gen/,\ + src/ +output.. = bin/ diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.properties b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.properties new file mode 100644 index 0000000000..2f40076a23 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.properties @@ -0,0 +1,4 @@ +# + +pluginName = java-fluentapi Model +providerName = MCSE, KIT diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.xml b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.xml new file mode 100644 index 0000000000..0304644be4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/plugin.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/FluentJavaAPIBuilder.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/FluentJavaAPIBuilder.java new file mode 100644 index 0000000000..e8071c6b44 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/FluentJavaAPIBuilder.java @@ -0,0 +1,92 @@ +package cipm.consistency.fluentapi.java.builder; + +import java.util.ArrayList; + +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.codegen.ecore.genmodel.GenModelFactory; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; + +import cipm.consistency.fluentapi.builder.FluentAPIAbstractBuilder; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerator; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelFilter; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelPackageProvider; +import cipm.consistency.fluentapi.java.postprocessor.FluentAPIGenerationJavaMetamodelPostProcessor; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationBigNumberParameterPostProcessor; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationForEachOverloadPostProcessor; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor; + +/** + * An implementation of {@link FluentAPIAbstractBuilder} for the JaMoPP + * metamodel. + * + * @author Alp Torac Genc + */ +public class FluentJavaAPIBuilder extends FluentAPIAbstractBuilder { + private static final FluentAPITargetMetamodelPackageProvider provider = new FluentAPIJavaMetamodelPackageProvider(); + private static final FluentAPITargetMetamodelFilter filter = new FluentAPIJavaMetamodelFilter(); + + @Override + protected GenModel generateGenModel(Resource genModelRes, Resource ecoreRes, FluentAPIGenerationContext context) { + var genModel = GenModelFactory.eINSTANCE.createGenModel(); + genModelRes.getContents().add(genModel); + + genModel.setModelDirectory("/" + getGeneratedFluentAPIModelDirectoryPath().toString()); + genModel.setOperationReflection(true); + genModel.setImportOrganizing(true); + genModel.setComplianceLevel(getJDKVersion()); + genModel.setModelName(getModelName()); + genModel.setModelPluginID(getModelPluginID()); + genModel.getForeignModel().add(ecoreRes.getURI().lastSegment()); + + var targetMetamodelGenModel = provider.getTargetMetamodelGenModels().get(0); + genModel.getUsedGenPackages().addAll(targetMetamodelGenModel.getGenPackages()); + + var initEPacs = new ArrayList(); + var toGen = (EPackage) ecoreRes.getContents().get(0); + + initEPacs.add(toGen); + genModel.initialize(initEPacs); + + genModel.reconcile(); + + genModel.setCanGenerate(true); + + var apiGenPac = genModel.findGenPackage(toGen); + apiGenPac.setBasePackage(context.getBasePackageName()); + + return genModel; + } + + @Override + protected void generateEcoreModel(Resource ecoreRes, FluentAPIGenerationContext context) { + new FluentAPIGenerator().generateRootAPIPackages(context); + new FluentAPIGenerationJavaMetamodelPostProcessor(context.getAllInitEClss(), provider).apply(); + new FluentAPIGenerationBigNumberParameterPostProcessor(context.getAllInitEClss()).apply(); + + var allEClss = new ArrayList(); + allEClss.add(context.getFluentAPIECls()); + allEClss.add(context.getInitSuperECls()); + allEClss.addAll(context.getAllInitEClss()); + + new FluentAPIGenerationForEachOverloadPostProcessor(context, allEClss).apply(); + new FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor(context, allEClss).apply(); + + ecoreRes.getContents().add(context.getRootPackage()); + } + + @Override + protected FluentAPITargetMetamodelPackageProvider getTargetMetamodelPackageProvider() { + return provider; + } + + @Override + protected FluentAPITargetMetamodelFilter getTargetMetamodelFilter() { + return filter; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/package-info.java new file mode 100644 index 0000000000..63b5b2f1e4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/builder/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains the builder class, which is responsible for building the fluent api + * model (for JaMoPP) that consists of an ecore and a genmodel file. + */ +package cipm.consistency.fluentapi.java.builder; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelFilter.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelFilter.java new file mode 100644 index 0000000000..8b6c89a3b6 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelFilter.java @@ -0,0 +1,50 @@ +package cipm.consistency.fluentapi.java.metamodel; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.emftext.commons.layout.LayoutPackage; +import org.emftext.language.java.commons.CommonsPackage; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; + +/** + * An implementation of {@link FluentAPITargetMetamodelFilter} for + * JaMoPP. + *

+ *

+ * Excludes the features ( {@link EStructuralFeature} ) that are present in + * {@link Commentable} and its super-types. Also excludes EClasses found under + * {@link LayoutPackage}. All other EClasses and features under + * {@link JavaPackage} are included. + * + * @author Alp Torac Genc + */ +public class FluentAPIJavaMetamodelFilter extends FluentAPITargetMetamodelFilter { + /** + * {@inheritDoc} + *

+ *

+ * Excludes the features ( {@link EStructuralFeature} ) that are present in + * {@link Commentable} and its super-types. All other features under + * {@link JavaPackage} are included. + */ + @Override + public boolean isFeatureEligible(EClass holderOfFeat, EStructuralFeature feat) { + return isFeatureChangeable(feat) + && !feat.getEContainingClass().getName().equals(CommonsPackage.Literals.COMMENTABLE.getName()); +// !CommonsPackage.Literals.COMMENTABLE.isSuperTypeOf(feat.getEContainingClass()) +// !feat.getEContainingClass().getInstanceClass().isAssignableFrom(Commentable.class) + } + + /** + * {@inheritDoc} + *

+ *

+ * Excludes EClasses found under the {@link LayoutPackage}. All other EClasses + * under {@link JavaPackage} are included. + */ + @Override + public boolean isEClassEligible(EClass eCls) { + return eCls.getEPackage() == null || !eCls.getEPackage().getName().equals(LayoutPackage.eNAME); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelPackageProvider.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelPackageProvider.java new file mode 100644 index 0000000000..94ea6a9ab9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/FluentAPIJavaMetamodelPackageProvider.java @@ -0,0 +1,157 @@ +package cipm.consistency.fluentapi.java.metamodel; + +import java.util.ArrayList; +import java.util.List; + +//import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.emftext.commons.layout.LayoutPackage; +import org.emftext.language.java.JavaPackage; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.metamodel.MetamodelUtil; + +/** + * An implementation of {@link FluentAPITargetMetamodelPackageProvider} for + * JaMoPP. + *

+ *

+ * This class internally "fixes" the Ecore and GenModel of JaMoPP that it + * parses, in order to avoid having duplicated Resource instances during fluent + * api generation. This is due to Eclipse plug-in limitations. + *

+ *

+ * Note: The original JaMoPP metamodel considers both {@link JavaPackage} and + * {@link LayoutPackage}. In order to keep the parsed metamodels valid, this + * class parses both of them. For filtering out {@link LayoutPackage}, + * {@link FluentAPIJavaMetamodelFilter} can be used. + * + * @author Alp Torac Genc + */ +public class FluentAPIJavaMetamodelPackageProvider extends FluentAPITargetMetamodelPackageProvider { + /** + * The (plug-in based) URI to JaMoPP's genmodel file + */ + private static final URI jaMoPPGenModelURI = URI + .createURI("platform:/plugin/org.emftext.language.java/metamodel/java.genmodel"); + /** + * The (plug-in based) URI to JaMoPP's ecore file + */ + private static final URI jaMoPPEcoreModelURI = URI + .createURI("platform:/plugin/org.emftext.language.java/metamodel/java.ecore"); + + /** + * The ResourceSet, which will contain the Resources of the GenModel and the + * Ecore model parsed by this class ( {@link #ecoreRes} and {@link #genModelRes} + * ). Note that those Resources are not the original Resources of the JaMoPP + * model. + */ + private final ResourceSet metamodelResSet = new ResourceSetImpl(); + /** + * The Resource instance containing the parsed Ecore model of JaMoPP. This is + * NOT the Resource instance of {@code JavaPackage.eINSTANCE} nor + * {@code LayoutPackage.eINSTANCE}. + */ + private Resource ecoreRes; + /** + * The Resource instance containing the parsed GenModel of JaMoPP. This does NOT + * use the Resource instance of {@code JavaPackage.eINSTANCE} nor + * {@code LayoutPackage.eINSTANCE}, but {@link #ecoreRes}. + */ + private Resource genModelRes; + /** + * The list containing the original JaMoPP EClasses that are available under + * {@code JavaPackage.eINSTANCE} and {@code LayoutPackage.eINSTANCE}. These + * EClasses are NOT the same as those in {@link #ecoreRes}. + */ + private List originalEClss; + + @Override + public String getTargetMetamodelName() { + var topPac = getTargetMetamodelEcoreEPackages().get(0); + return topPac.getName(); + } + + @Override + public List getAllTargetMetamodelEClasses() { + var topPac = getTargetMetamodelEcoreEPackages().get(0); + return List.copyOf(MetamodelUtil.getAllEClasses(topPac)); + } + + /** + * Caches the (original) EClasses found under the JaMoPP metamodel ( under + * {@code JavaPackage.eINSTANCE} and {@code LayoutPackage.eINSTANCE}) in + * {@link #originalEClss}, in order to spare constantly retrieving them from the + * Resource instances. + */ + private void cacheOriginalEClasses() { + if (originalEClss == null) { + originalEClss = new ArrayList(MetamodelUtil.getAllEClasses(JavaPackage.eINSTANCE)); + originalEClss.addAll(MetamodelUtil.getAllEClasses(LayoutPackage.eINSTANCE)); + } + } + + /** + * Changes the instance classes within the EClasses under parsedJaMoPPEcoreModel + * to the original EClasses from JaMoPP ( {@link #originalEClss} ). + * + * @param parsedJaMoPPEcoreModel The Ecore model of JaMoPP, which has been + * parsed by this class. + */ + private void fixInstanceClasses(EPackage parsedJaMoPPEcoreModel) { + var jaMoPPParsedEClss = MetamodelUtil.getAllEClasses(parsedJaMoPPEcoreModel); + cacheOriginalEClasses(); + + if (jaMoPPParsedEClss.size() != originalEClss.size()) + throw new IllegalStateException( + "Parsed Java package and the actual Java package contain different amounts of EClasses"); + + for (var parsedECls : jaMoPPParsedEClss) { + var matchingActualECls = originalEClss.stream() + .filter((cls) -> cls.getEPackage().getName().equals(parsedECls.getEPackage().getName())) + .filter((cls) -> cls.getName().equals(parsedECls.getName())).toArray(EClass[]::new); + if (matchingActualECls.length != 1) + throw new IllegalStateException("Unknown EClass has been parsed"); + + var actualECls = matchingActualECls[0]; + parsedECls.setInstanceClassName(actualECls.getInstanceClassName()); + parsedECls.setInstanceTypeName(actualECls.getInstanceTypeName()); + parsedECls.setInstanceClass(actualECls.getInstanceClass()); + } + } + + @Override + public List getTargetMetamodelEcoreEPackages() { + if (ecoreRes == null) { + ecoreRes = metamodelResSet.getResource(jaMoPPEcoreModelURI, true); + var parsedJaMoPPEcoreModel = (EPackage) ecoreRes.getContents().get(0); + fixInstanceClasses(parsedJaMoPPEcoreModel); + } + + return List.of((EPackage) ecoreRes.getContents().get(0)); + } + + @Override + public List getTargetMetamodelGenModels() { + if (genModelRes == null) { + genModelRes = metamodelResSet.getResource(jaMoPPGenModelURI, true); + } + + var javaGenModel = (GenModel) genModelRes.getContents().get(0); + javaGenModel.setCanGenerate(false); + + return List.of(javaGenModel); + } + + @Override + public List getAllEClassesInOriginalMetamodel() { + cacheOriginalEClasses(); + return originalEClss; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/package-info.java new file mode 100644 index 0000000000..f3c6b02272 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/metamodel/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes that provide access to the JaMoPP metamodel during the + * fluent api generation. + */ +package cipm.consistency.fluentapi.java.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/FluentAPIGenerationJavaMetamodelPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/FluentAPIGenerationJavaMetamodelPostProcessor.java new file mode 100644 index 0000000000..c1c5b88428 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/FluentAPIGenerationJavaMetamodelPostProcessor.java @@ -0,0 +1,117 @@ +package cipm.consistency.fluentapi.java.postprocessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.emftext.language.java.classifiers.ClassifiersPackage; +import org.emftext.language.java.types.ClassifierReference; +import org.emftext.language.java.types.TypesPackage; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationPostProcessor; + +/** + * A post-processor that adds overloads for methods that use parameters of type + * {@link TypeReference} for convenience. Using the overloading methods spare + * having to manually construct {@link ClassifierReference}s for + * {@link Classifier}s, in order to create a TypeReference instance for them. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationJavaMetamodelPostProcessor implements FluentAPIGenerationPostProcessor { + private static final Pattern methodNamePatternToOverload = Pattern + .compile(String.join("|", ModelConstants.Initialiation.With.NAME.getFor(".*"), + ModelConstants.Initialiation.WithAdded.NAME.getFor(".*"))); + + private static final String typeReferenceParameterOverrideTemplate = ModelConstants.SuperInitialisation.ToAPI.NAME + .thisCall() + + ModelConstants.FluentAPI.New.NAME.callFor(new String[] { ClassifierReference.class.getSimpleName() }) + + ModelConstants.Initialiation.With.NAME.callFor(new String[] { "Target" }, "%s") + + ModelConstants.SuperInitialisation.CreateNow.NAME.call(); + private static final String typeReferenceParameterOverrideParameterDocumentation = "The classifier instance, which will be referenced"; + + /** + * {@link #getInitEClss()} + */ + private List initEClss; + private FluentAPITargetMetamodelPackageProvider provider; + + /** + * @param initEClss {@link #getInitEClss()} + */ + public FluentAPIGenerationJavaMetamodelPostProcessor(List initEClss, + FluentAPITargetMetamodelPackageProvider provider) { + this.initEClss = initEClss; + this.provider = provider; + } + + /** + * @return The list of {@link EClass}es that this post-processor should apply to + */ + public List getInitEClss() { + return this.initEClss; + } + + /** + * @return The object that grants access to the JaMoPP metamodel. + */ + public FluentAPITargetMetamodelPackageProvider getTargetMetamodelPackageProvider() { + return provider; + } + + private List createOverloadingMethodsFor(EOperation opToOverload) { + var copier = new EcoreUtil.Copier(); + var overloadingOp = (EOperation) copier.copy(opToOverload); + copier.copyReferences(); + + // Adjust parameters + + var paramExprs = new ArrayList(); + + for (int i = 0; i < overloadingOp.getEParameters().size(); i++) { + var currentParam = overloadingOp.getEParameters().get(i); + if (currentParam.getEType().equals(provider.getEClass(TypesPackage.Literals.TYPE_REFERENCE.getName()))) { + paramExprs.add(String.format(typeReferenceParameterOverrideTemplate, currentParam.getName())); + currentParam.setEType(provider.getEClass(ClassifiersPackage.Literals.CLASSIFIER.getName())); + + // Add parameter documentation to clarify intent + FluentAPIGenerationUtil.addDocumentation(currentParam, + typeReferenceParameterOverrideParameterDocumentation); + } else { + paramExprs.add(currentParam.getName()); + } + } + + // Adjust method body + var serialisedParameters = String.join(",", paramExprs.toArray(String[]::new)); + FluentAPIGenerationUtil.addBody(overloadingOp, FluentAPIMethodsUtil + .joinLOC("return this." + overloadingOp.getName() + "(" + serialisedParameters + ")")); + + return List.of(overloadingOp); + } + + @Override + public void apply() { + for (var initECls : this.getInitEClss()) { + var opsToOverload = initECls.getEOperations().stream() + .filter((op) -> methodNamePatternToOverload.matcher(op.getName()).matches()) + .collect(Collectors.toList()); + for (var op : opsToOverload) { + // Skip parameters of type EList, since overloading them results in type erasure + // related issues + if (op.getEParameters().stream().anyMatch((p) -> p.getEType() != null && !p.isMany() + && p.getEType().equals(provider.getEClass(TypesPackage.Literals.TYPE_REFERENCE.getName())))) { + initECls.getEOperations().addAll(createOverloadingMethodsFor(op)); + } + } + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/package-info.java new file mode 100644 index 0000000000..f1450ad05d --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/postprocessor/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains post-processors that can be used on the generated fluent api model + * for JaMoPP after its generation. + */ +package cipm.consistency.fluentapi.java.postprocessor; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIContainmentTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIContainmentTest.java new file mode 100644 index 0000000000..a75d693a3b --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIContainmentTest.java @@ -0,0 +1,109 @@ +package cipm.consistency.fluentapi.java.test; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * Tests the construction of models, where model elements of the same type are + * nested within one another. Also tests construction of model elements with + * multiple EReferences using values of the same type. + * + * @author Alp Torac Genc + */ +public class FluentAPIContainmentTest extends AbstractFluentAPITest { + /** + * Tests whether model elements of the same type can be successfully nested in + * one another. + *

+ *

+ * Ensures that the following construction is possible and works as intended: + * + * class outer { class inner {} } + */ + @Test + public void testNesting() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var innerClsName = "inner"; + var outerClsName = "outer"; + + var innerCls = api.newClass().withName(innerClsName).createNow(); + var outerCls = api.newClass().withName(outerClsName).withAddedMembers(innerCls).createNow(); + + Assertions.assertEquals(1, outerCls.getMembers().size()); + Assertions.assertEquals(innerCls, outerCls.getMembers().get(0)); + Assertions.assertEquals(0, outerCls.getDefaultMembers().size()); + Assertions.assertNull(outerCls.eContainer()); + + Assertions.assertEquals(0, innerCls.getMembers().size()); + Assertions.assertEquals(0, innerCls.getDefaultMembers().size()); + Assertions.assertEquals(outerCls, innerCls.eContainer()); + } + + /** + * Tests whether it is possible to set the features of model elements to other + * (nested) model elements of the same type. + *

+ *

+ * Ensures that the following construction is possible and works as intended: + * + * class cls1 { class cls2 extends cls1 {} } + */ + @Test + public void testNestingAndReferencing() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var outerClsName = "outer"; + var innerClsName = "inner"; + + var outerCls = api.newClass().withName(outerClsName).createNow(); + var innerCls = api.newClass().withName(innerClsName) + .withExtends(api.newClassifierReference().withTarget(outerCls).createNow()).createNow(); + api.modifyClass(outerCls).withAddedMembers(innerCls).dropInitialisation(); + + Assertions.assertEquals(1, outerCls.getMembers().size()); + Assertions.assertEquals(innerCls, outerCls.getMembers().get(0)); + Assertions.assertEquals(0, outerCls.getDefaultMembers().size()); + Assertions.assertNull(outerCls.eContainer()); + + Assertions.assertEquals(0, innerCls.getMembers().size()); + Assertions.assertEquals(0, innerCls.getDefaultMembers().size()); + Assertions.assertEquals(outerCls, innerCls.getExtends().getPureClassifierReference().getTarget()); + Assertions.assertEquals(outerCls, innerCls.eContainer()); + } + + /** + * Tests whether fluent api sets the value of the correct feature of the model + * element. + *

+ *

+ * Ensures setting values EReferences using the same value type works as + * intended, for instance: + * + *

+ * PrimitiveTypeReference has the EReferences ArrayDimensionsBefore (ADB) and + * ArrayDimensionsAfter (ADA), which consider ArrayDimension instances AD1 and + * AD2. Assuming AD1 and AD2 are separate ArrayDimension instances, setting ADB + * = AD1 and ADA = AD2 via Fluent API should not mix up ADB and ADA. + */ + @Test + public void testTwoContainmentFeaturesWithSameType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var arrDimBefore = api.newArrayDimension().createNow(); + var arrDimAfter = api.newArrayDimension().createNow(); + var ptr = api.newPrimitiveTypeReference().withAddedArrayDimensionsBefore(arrDimBefore) + .withAddedArrayDimensionsAfter(arrDimAfter).createNow(); + + Assertions.assertEquals(1, ptr.getArrayDimensionsBefore().size()); + Assertions.assertEquals(arrDimBefore, ptr.getArrayDimensionsBefore().get(0)); + Assertions.assertEquals(ptr, arrDimBefore.eContainer()); + + Assertions.assertEquals(1, ptr.getArrayDimensionsAfter().size()); + Assertions.assertEquals(arrDimAfter, ptr.getArrayDimensionsAfter().get(0)); + Assertions.assertEquals(ptr, arrDimAfter.eContainer()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitBigNumberOverloadsTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitBigNumberOverloadsTest.java new file mode 100644 index 0000000000..36d3f3c7b9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitBigNumberOverloadsTest.java @@ -0,0 +1,77 @@ +package cipm.consistency.fluentapi.java.test; + +import java.math.BigInteger; + +import org.emftext.language.java.literals.DecimalIntegerLiteral; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for checking whether fluent api model has overloading methods + * for operations that consider {@link BigInteger} parameters and that they work + * as intended. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitBigNumberOverloadsTest extends AbstractFluentAPITest { + /** + * Ensures that overloading methods for BigInteger are generated + */ + @Test + public void testInit_OverloadedBigIntegerMethodsTest_SingleValue() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + final var lit = new DecimalIntegerLiteral[3]; + + BigInteger bigIntVal = BigInteger.valueOf(1); + + // Ensure that the original method for BigInteger is generated + Assertions.assertDoesNotThrow( + () -> lit[0] = api.newDecimalIntegerLiteral().withDecimalValue(bigIntVal).createNow()); + // Ensure that the original method for BigInteger works as intended + Assertions.assertEquals(bigIntVal, lit[0].getDecimalValue()); + + int intVal = bigIntVal.intValue(); + Assertions.assertEquals(1, intVal); + // Ensure that the overloading method is generated (with int parameter) + Assertions + .assertDoesNotThrow(() -> lit[1] = api.newDecimalIntegerLiteral().withDecimalValue(intVal).createNow()); + // Ensure that the overloading method works as intended + Assertions.assertEquals(intVal, lit[1].getDecimalValue().intValue()); + + long longVal = bigIntVal.longValue(); + Assertions.assertEquals(1, longVal); + // Ensure that the overloading method is generated (with long parameter) + Assertions.assertDoesNotThrow( + () -> lit[2] = api.newDecimalIntegerLiteral().withDecimalValue(longVal).createNow()); + // Ensure that the overloading method works as intended + Assertions.assertEquals(longVal, lit[2].getDecimalValue().longValue()); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testInit_OverloadedBigIntegerMethodsTest_OnlyOneSingleValuedModifiableFeature() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + final var obj = new DecimalIntegerLiteral[2]; + + int intVal = 1; + // Ensure that the overloading method is generated (with int parameter) + Assertions.assertDoesNotThrow(() -> obj[0] = api.newDecimalIntegerLiteral(intVal)); + // Ensure that the overloading method works as intended + Assertions.assertInstanceOf(DecimalIntegerLiteral.class, obj[0]); + Assertions.assertEquals(intVal, obj[0].getDecimalValue().intValue()); + + long longVal = 1; + // Ensure that the overloading method is generated (with long parameter) + Assertions.assertDoesNotThrow(() -> obj[1] = api.newDecimalIntegerLiteral(longVal)); + // Ensure that the overloading method works as intended + Assertions.assertInstanceOf(DecimalIntegerLiteral.class, obj[1]); + Assertions.assertEquals(longVal, obj[1].getDecimalValue().longValue()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitJavaOverloadsTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitJavaOverloadsTest.java new file mode 100644 index 0000000000..9a18b1bef2 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitJavaOverloadsTest.java @@ -0,0 +1,55 @@ +package cipm.consistency.fluentapi.java.test; + +import org.emftext.language.java.classifiers.Classifier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for checking whether certain methods in initialisations within + * the fluent api class for Java are overloaded as expected and that the + * overloaded versions work as intended. + *

+ *

+ * Currently only methods that consider {@link TypeReference} as parameter. + * + * @author Alp Torac Genc + * @see {@link FluentAPIGenerationJavaMetamodelPostProcessor} for more details + * on overloaded methods + */ +public class FluentAPIInitJavaOverloadsTest extends AbstractFluentAPITest { + /** + * Checks whether initialisation methods in fluent api for Java have an + * overloading variant that takes (a singular) Classifier parameters instead of + * TypeReferences and converts them into ClassifierTypeReferences. + */ + @Test + public void testInit_WithTypeReferenceOverloadTest_SingleValued() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var cls = api.createNewClass(); + + var clsMet = api.newClassMethod().withTypeReference(cls).createNow(); + Assertions.assertSame(cls, clsMet.getTypeReference().getPureClassifierReference().getTarget()); + } + + /** + * Checks whether initialisation methods in fluent api for Java have an + * overloading variant that takes multiple Classifier parameters instead of + * TypeReferences and converts them into ClassifierTypeReferences. + */ + @Test + public void testInit_WithTypeReferenceOverloadTest_ManyValued() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsOne = api.createNewClass(); + var clsTwo = api.createNewClass(); + + var ai = api.newAnnotationInstance().withAddedActualTargets(new Classifier[] { clsOne, clsTwo }).createNow(); + + Assertions.assertSame(clsOne, ai.getActualTargets().get(0).getPureClassifierReference().getTarget()); + Assertions.assertSame(clsTwo, ai.getActualTargets().get(1).getPureClassifierReference().getTarget()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitTest.java new file mode 100644 index 0000000000..4d32a79638 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitTest.java @@ -0,0 +1,272 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; + +import org.emftext.language.java.classifiers.ClassifiersPackage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class containing tests for initialisation classes within the fluent + * api model. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitTest extends AbstractFluentAPITest { + /** + * Checks whether init.getInitialisedEClass() works as intended. + */ + @Test + public void testInit_getInitialisedEClass() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var eCls = ClassifiersPackage.Literals.CLASS; + Assertions.assertEquals(eCls, api.newX(eCls).getInitialisedEClass()); + } + + /** + * Checks whether init.toAPI() works as intended. + */ + @Test + public void testInit_ToAPI() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + Assertions.assertSame(api, api.newAdditionalField().toAPI()); + } + + /** + * Checks whether init.reset() works as intended. + */ + @Test + public void testInit_Reset() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsInit = api.newClass(); + Assertions.assertNotNull(clsInit.getCurrentElement()); + + clsInit.reset(); + Assertions.assertNull(clsInit.getCurrentElement()); + + // Ensure that reset() does not remove the Initialisation instance from API + Assertions.assertNotNull(api.continueClass()); + } + + /** + * Checks whether init.createNow() works as intended. + */ + @Test + public void testInit_CreateNow() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var mod = api.newModule().createNow(); + Assertions.assertInstanceOf(org.emftext.language.java.containers.Module.class, mod); + + // Ensure that createNow() removes the Initialisation instance from api + Assertions.assertNull(api.continueModule()); + } + + /** + * Checks whether init.createNow(class) works as intended, when class is the + * exact type of the created object. + */ + @Test + public void testInit_CreateNow_WithTypeCast_ExactType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var mod = api.newModule().createNow(org.emftext.language.java.containers.Module.class); + Assertions.assertInstanceOf(org.emftext.language.java.containers.Module.class, mod); + } + + /** + * Checks whether init.createNow(class) works as intended, where class is a + * super-type of the created object. + */ + @Test + public void testInit_CreateNow_WithTypeCast_Upcasting() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsToCastTo = org.emftext.language.java.commons.NamedElement.class; + var mod = api.newModule().createNow(clsToCastTo); + Assertions.assertInstanceOf(org.emftext.language.java.containers.Module.class, mod); + Assertions.assertInstanceOf(clsToCastTo, mod); + } + + /** + * Checks whether init.createNow(class) works as intended, where class is a + * sub-type of the created object. + */ + @Test + public void testInit_CreateNow_WithTypeCast_Downcasting() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsToCastTo = org.emftext.language.java.containers.impl.ModuleImpl.class; + var mod = api.newModule().createNow(clsToCastTo); + Assertions.assertInstanceOf(org.emftext.language.java.containers.Module.class, mod); + Assertions.assertInstanceOf(clsToCastTo, mod); + + // Make sure that the method basicGetOpen() in ModuleImpl is actually found + // during compilation (not present in + // org.emftext.language.java.containers.Module, which is the usual return type + // of api.newModule().createNow()) + Assertions.assertDoesNotThrow(() -> mod.basicGetOpen()); + } + + /** + * Checks whether init.dropInitialisation() works as intended. + */ + @Test + public void testInit_DropInitialisation() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsInit = api.newClass(); + + // Ensure that clsInit is added to FluentAPIInitialisationStorage + var inits = FluentAPIInitialisationStorage.getOngoingInitialisations(); + Assertions.assertEquals(1, inits.size()); + Assertions.assertSame(clsInit, inits.get(0)); + + clsInit.dropInitialisation(); + // Re-retrieve ongoing initialisations + inits = FluentAPIInitialisationStorage.getOngoingInitialisations(); + // Ensure that clsInit is removed from FluentAPIInitialisationStorage + Assertions.assertEquals(0, inits.size()); + } + + /** + * Checks whether init.markCurrentElement(key) works as intended. + */ + @Test + public void testInit_MarkCurrentElement() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var key = new Object(); + + var clsInit = api.newClass(); + var cls = clsInit.markCurrentElement(key).getCurrentElement(); + + Assertions.assertTrue(FluentAPIMarkExtension.hasMark(key)); + Assertions.assertSame(cls, FluentAPIMarkExtension.getMarked(key)); + } + + /** + * Checks whether init.unmarkCurrentElement(key) works as intended. + */ + @Test + public void testInit_UnmarkCurrentElement() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var key = new Object(); + + var clsInit = api.newClass(); + var cls = clsInit.markCurrentElement(key).getCurrentElement(); + + Assertions.assertTrue(FluentAPIMarkExtension.hasMark(key)); + Assertions.assertSame(cls, FluentAPIMarkExtension.getMarked(key)); + + clsInit.unmarkCurrentElement(key); + + Assertions.assertFalse(FluentAPIMarkExtension.hasMark(key)); + Assertions.assertNull(FluentAPIMarkExtension.getMarked(key)); + } + + /** + * Checks whether init.waitForMark(singleKey, task) works as intended. + */ + @Test + public void testInit_WaitForMark_SingleKey() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsOneName = "clsOne"; + var clsTwoName = "clsTwo"; + var clsOneKey = new Object(); + var clsTwoKey = new Object(); + + // Goal: + // class clsOne extends clsTwo {} + // class clsTwo {} + + var clsOne = + // Create clsOne (in isolation) + api.newClass().withName(clsOneName).markCurrentElement(clsOneKey) + // Have clsOne extend clsTwo, once clsTwo exists and is marked with clsTwoKey + .waitForMark(clsTwoKey, + () -> api.continueMarkedClass(clsOneKey).withExtends(api.getMarkedClass(clsTwoKey))) + // Create clsTwo and mark it + .toAPI().newClass().withName(clsTwoName).markCurrentElement(clsTwoKey) + // Swap to clsOne and return it + .toAPI().continueMarkedClass(clsOneKey).createNow(); + + Assertions.assertEquals(clsOneName, clsOne.getName()); + Assertions.assertEquals(clsTwoName, clsOne.getExtends().getPureClassifierReference().getTarget().getName()); + } + + /** + * Checks whether init.waitForMark(keyArray, task) works as intended. + */ + @Test + public void testInit_WaitForMark_KeyArray() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsOneName = "clsOne"; + var clsTwoName = "clsTwo"; + var clsThreeName = "clsThree"; + var clsOneKey = new Object(); + var clsTwoKey = new Object(); + var clsThreeKey = new Object(); + var clsMemberKeys = new Object[] { clsTwoKey, clsThreeKey }; + + // Goal: + // class clsOne {class clsTwo {} class clsThree {}} + + var clsOne = + // Create clsOne (in isolation) + api.newClass().withName(clsOneName).markCurrentElement(clsOneKey) + // Add clsTwo and clsThree to clsOne once both of them are present and marked + .waitForMark(clsMemberKeys, + () -> api.continueMarkedClass(clsOneKey).withAddedMembers( + List.of(api.getMarkedClass(clsTwoKey), api.getMarkedClass(clsThreeKey)))) + // Create clsTwo and mark it + .toAPI().newClass().withName(clsTwoName).markCurrentElement(clsTwoKey) + // Create clsThree and mark it + .toAPI().newClass().withName(clsThreeName).markCurrentElement(clsThreeKey) + // Swap to clsOne and return it + .toAPI().continueMarkedClass(clsOneKey).createNow(); + + Assertions.assertEquals(clsOneName, clsOne.getName()); + Assertions.assertEquals(2, clsOne.getMembers().size()); + Assertions.assertEquals(clsTwoName, clsOne.getMembers().get(0).getName()); + Assertions.assertEquals(clsThreeName, clsOne.getMembers().get(1).getName()); + } + + /** + * Checks whether init.waitForMark(keyCollection, task) works as intended. + */ + @Test + public void testInit_WaitForMark_KeyCollection() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsOneName = "clsOne"; + var clsTwoName = "clsTwo"; + var clsThreeName = "clsThree"; + var clsOneKey = new Object(); + var clsTwoKey = new Object(); + var clsThreeKey = new Object(); + var clsMemberKeys = List.of(clsTwoKey, clsThreeKey); + + // Goal: + // class clsOne {class clsTwo {} class clsThree {}} + + var clsOne = + // Create clsOne (in isolation) + api.newClass().withName(clsOneName).markCurrentElement(clsOneKey) + // Add clsTwo and clsThree to clsOne once both of them are present and marked + .waitForMark(clsMemberKeys, + () -> api.continueMarkedClass(clsOneKey).withAddedMembers( + List.of(api.getMarkedClass(clsTwoKey), api.getMarkedClass(clsThreeKey)))) + // Create clsTwo and mark it + .toAPI().newClass().withName(clsTwoName).markCurrentElement(clsTwoKey) + // Create clsThree and mark it + .toAPI().newClass().withName(clsThreeName).markCurrentElement(clsThreeKey) + // Swap to clsOne and return it + .toAPI().continueMarkedClass(clsOneKey).createNow(); + + Assertions.assertEquals(clsOneName, clsOne.getName()); + Assertions.assertEquals(2, clsOne.getMembers().size()); + Assertions.assertEquals(clsTwoName, clsOne.getMembers().get(0).getName()); + Assertions.assertEquals(clsThreeName, clsOne.getMembers().get(1).getName()); + } + +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitWithTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitWithTest.java new file mode 100644 index 0000000000..c783915edb --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIInitWithTest.java @@ -0,0 +1,329 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; +import cipm.consistency.fluentapi.test.FluentAPITestUtils; + +/** + * A test class containing tests for modification methods of the initialisation + * classes within the fluent api model. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitWithTest extends AbstractFluentAPITest { + /** + * Checks whether init.withX(...) works as intended. + */ + @Test + public void testInit_With() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var name = "cuName"; + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + Assertions.assertNull(cu.getName()); + + init.withName(name); + Assertions.assertEquals(name, cu.getName()); + } + + /** + * Checks whether init.withoutX(...) works as intended. + */ + @Test + public void testInit_Without() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var name = "cuName"; + var cu = api.createNewCompilationUnit(); + cu.setName(name); + var init = api.modifyCompilationUnit(cu); + Assertions.assertEquals(name, cu.getName()); + + init.withoutName(); + Assertions.assertNull(cu.getName()); + } + + /** + * Checks whether init.withRemovedX(val) works as intended, if val is not an + * eligible value. + */ + @Test + public void testInit_WithRemoved_RemoveNonExistentValue() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withRemovedNamespaces("ns4"); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether init.withRemovedX(val) works as intended. + */ + @Test + public void testInit_WithRemoved_SingularParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var expectedNss = List.of(ns2, ns3); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withRemovedNamespaces(ns1); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withRemovedX(valArray) works as intended. + */ + @Test + public void testInit_WithRemoved_ArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var toRemove = new String[] { ns1, ns3 }; + var expectedNss = List.of(ns2); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withRemovedNamespaces(toRemove); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withRemovedX(valArray) works as intended, if valArray + * were empty. + */ + @Test + public void testInit_WithRemoved_EmptyArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + init.withRemovedNamespaces(new String[] {}); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether init.withRemovedX(valCollection) works as intended. + */ + @Test + public void testInit_WithRemoved_CollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var toRemove = List.of(ns1, ns3); + var expectedNss = List.of(ns2); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withRemovedNamespaces(toRemove); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withRemovedX(valCollection) works as intended, if + * valCollection were empty. + */ + @Test + public void testInit_WithRemoved_EmptyCollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + init.withRemovedNamespaces(List.of()); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether init.clean() works as intended. + */ + @Test + public void testInit_Clean() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.cleanNamespaces(); + Assertions.assertEquals(0, cu.getNamespaces().size()); + } + + /** + * Checks whether init.withAddedX(val) works as intended, if there were no prior + * values. + */ + @Test + public void testInit_WithAdded_SingularParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var ns = "ns"; + + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.withAddedNamespaces(ns); + Assertions.assertEquals(1, cu.getNamespaces().size()); + Assertions.assertEquals(ns, cu.getNamespaces().get(0)); + } + + /** + * Checks whether init.withAddedX(val) works as intended, if there were prior + * values. + */ + @Test + public void testInit_WithAdded_SingularParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nsToAdd = "ns"; + var expectedNss = List.of("ns1", "ns2", nsToAdd); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withAddedNamespaces(nsToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withAddedX(valArray) works as intended, if there were no + * prior values. + */ + @Test + public void testInit_WithAdded_ArrayParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = new String[] { "ns1", "ns2" }; + + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.withAddedNamespaces(nss); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether init.withAddedX(valArray) works as intended, if there were + * prior values. + */ + @Test + public void testInit_WithAdded_ArrayParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nssToAdd = new String[] { "ns3", "ns4" }; + var expectedNss = List.of("ns1", "ns2", "ns3", "ns4"); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withAddedNamespaces(nssToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withAddedX(valArray) works as intended, if valArray were + * empty. + */ + @Test + public void testInit_WithAdded_EmptyArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + init.withAddedNamespaces(new String[] {}); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether init.withAddedX(valCollection) works as intended, if there + * were no prior values. + */ + @Test + public void testInit_WithAdded_CollectionParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.withAddedNamespaces(nss); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether init.withAddedX(valCollection) works as intended, if there + * were prior values. + */ + @Test + public void testInit_WithAdded_CollectionParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nssToAdd = List.of("ns3", "ns4"); + var expectedNss = List.of("ns1", "ns2", "ns3", "ns4"); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyCompilationUnit(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.withAddedNamespaces(nssToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether init.withAddedX(valCollection) works as intended, if + * valCollection were empty. + */ + @Test + public void testInit_WithAdded_EmptyCollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyCompilationUnit(cu); + init.withAddedNamespaces(List.of()); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIContinueTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIContinueTest.java new file mode 100644 index 0000000000..9dae4e96a5 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIContinueTest.java @@ -0,0 +1,132 @@ +package cipm.consistency.fluentapi.java.test; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for the following fluent api class methods: continue...(), + * continueX(), continueMarked...(), continueMarkedX(). + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPIContinueTest extends AbstractFluentAPITest { + /** + * Checks whether the api.continueX() method works as intended. + */ + @Test + public void testAPI_Continue_TopLevel() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var modInit = api.newModule(); + var pacInit = api.newPackage(); + + var continuedModInit = api.continueX(org.emftext.language.java.containers.Module.class); + Assertions.assertSame(modInit, continuedModInit); + var continuedPacInit = api.continueX(org.emftext.language.java.containers.Package.class); + Assertions.assertSame(pacInit, continuedPacInit); + } + + /** + * Checks whether the api.continueX() method works as intended, when the + * construction of the same element is continued. + */ + @Test + public void testAPI_Continue_SameElementInstance() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var modInit = api.newModule(); + var continuedModInit = api.continueModule(); + + Assertions.assertSame(modInit, continuedModInit); + } + + /** + * Checks whether the api.continueX() method works as intended, when the + * construction of a different element of the same type is continued. + */ + @Test + public void testAPI_Continue_SameElementType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var modInit1 = api.newModule(); + var modInit2 = api.newModule(); + Assertions.assertNotSame(modInit1, modInit2); + Assertions.assertSame(modInit2, api.continueModule()); + } + + /** + * Checks whether the api.continueX() method works as intended, when the + * construction of a different element is continued. + */ + @Test + public void testAPI_Continue_DifferentElementType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var modInit = api.newModule(); + var pacInit = api.newPackage(); + + Assertions.assertSame(modInit, api.continueModule()); + Assertions.assertSame(pacInit, api.continuePackage()); + } + + /** + * Checks whether api.continueX() returns null, if there is no XInitialisation + * instance to be found + */ + @Test + public void testAPI_Continue_NoInitialisation() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + Assertions.assertNull(api.continueModule()); + } + + /** + * Checks whether the api.continueMarked...() method works as intended, when + * there is only one Initialisation instance. + */ + @Test + public void testAPI_ContinueMarked_SingleInitialisation() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var modKey = new Object(); + + var modInit = api.newModule().markCurrentElement(modKey); + + Assertions.assertSame(modInit, api.continueMarkedModule(modKey)); + } + + /** + * Checks whether the api.continueMarked...() method works as intended, when + * there are multiple Initialisation instances. + */ + @Test + public void testAPI_ContinueMarked_MultipleInitialisation() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var keyOne = new Object(); + var keyTwo = new Object(); + + var modInitOne = api.newModule().markCurrentElement(keyOne); + var modInitTwo = api.newModule().markCurrentElement(keyTwo); + + Assertions.assertSame(modInitOne, api.continueMarkedModule(keyOne)); + Assertions.assertSame(modInitTwo, api.continueMarkedModule(keyTwo)); + } + + /** + * Checks whether the api.continueMarkedX() method works as intended. + */ + @Test + public void testAPI_ContinueMarked_TopLevel() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var keyOne = new Object(); + var keyTwo = new Object(); + + var modInit = api.newModule().markCurrentElement(keyOne); + var pacInit = api.newPackage().markCurrentElement(keyTwo); + + Assertions.assertSame(modInit, api.continueMarkedX(keyOne)); + Assertions.assertSame(pacInit, api.continueMarkedX(keyTwo)); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIGetOngoingInitTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIGetOngoingInitTest.java new file mode 100644 index 0000000000..cbc3cba6c1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIGetOngoingInitTest.java @@ -0,0 +1,74 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for the following fluent api class methods: getOngoingInits(), + * clearAllOngoingInits(). + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPIGetOngoingInitTest extends AbstractFluentAPITest { + /** + * Checks whether api.getOngoingInitialisations() returns an empty list, if + * there are no initialisations. + */ + @Test + public void testAPI_GetOngoingInitialisations_NoInitialisations() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + Assertions.assertEquals(0, api.getOngoingInitialisations().size()); + } + + /** + * Checks whether api.getOngoingInitialisations() works as intended. + */ + @Test + public void testAPI_GetOngoingInitialisations() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsInit = api.newClass(); + + Assertions.assertEquals(1, api.getOngoingInitialisations().size()); + Assertions.assertSame(clsInit, api.getOngoingInitialisations().get(0)); + } + + /** + * Ensures that different fluent api instances share the same initialisation + * instances. + */ + @Test + public void testAPI_GetOngoingInitialisations_DifferentAPIInstances() { + var apiOne = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var apiTwo = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var init1 = apiOne.newClass(); + var init2 = apiTwo.newClass(); + var init3 = apiOne.newClass(); + + for (var api : List.of(apiOne, apiTwo)) { + Assertions.assertEquals(3, api.getOngoingInitialisations().size()); + Assertions.assertSame(init1, api.getOngoingInitialisations().get(0)); + Assertions.assertSame(init2, api.getOngoingInitialisations().get(1)); + Assertions.assertSame(init3, api.getOngoingInitialisations().get(2)); + } + } + + /** + * Ensures that api.clearAllOngoingInitialisations() works as intended. + */ + @Test + public void testAPI_ClearAllOngoingInitialisations() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + api.newClass(); + api.newInterface(); + Assertions.assertEquals(2, api.getOngoingInitialisations().size()); + api.clearAllOngoingInitialisations(); + Assertions.assertEquals(0, api.getOngoingInitialisations().size()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPINewMethodTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPINewMethodTest.java new file mode 100644 index 0000000000..f4d263bf24 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPINewMethodTest.java @@ -0,0 +1,166 @@ +package cipm.consistency.fluentapi.java.test; + +import java.math.BigInteger; +import java.util.List; + +import org.emftext.language.java.containers.ContainersPackage; +import org.emftext.language.java.expressions.AndExpression; +import org.emftext.language.java.expressions.AndExpressionChild; +import org.emftext.language.java.literals.DecimalIntegerLiteral; +import org.emftext.language.java.modifiers.Abstract; +import org.emftext.language.java.statements.Break; +import org.emftext.language.java.variables.AdditionalLocalVariable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.java.api.FluentAPISuperInitialisation; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for the following methods of fluent api class: new...(), newX(), + * createNew...(), createNewX(). + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPINewMethodTest extends AbstractFluentAPITest { + /** + * Ensures that direct creation methods for types with no modifiable features is + * possible via the generated API + */ + @Test + public void testAPI_New_NoModifiableFeatures() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + final var obj = new Abstract[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newAbstract()); + Assertions.assertInstanceOf(Abstract.class, obj[0]); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testAPI_New_OnlyOneSingleValuedModifiableFeature_EAttribute() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var val = BigInteger.valueOf(1); + final var obj = new DecimalIntegerLiteral[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newDecimalIntegerLiteral(val)); + Assertions.assertInstanceOf(DecimalIntegerLiteral.class, obj[0]); + Assertions.assertEquals(val, obj[0].getDecimalValue()); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testAPI_New_OnlyOneSingleValuedModifiableFeature_EReference() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var val = api.createNewJumpLabel(); + final var obj = new Break[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newBreak(val)); + Assertions.assertInstanceOf(Break.class, obj[0]); + Assertions.assertSame(val, obj[0].getTarget()); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testAPI_New_OnlyOneManyValuedModifiableFeature_EReference_SingleValue() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var val = api.createNewEqualityExpression(); + final var obj = new AndExpression[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newAndExpression(val)); + Assertions.assertInstanceOf(AndExpression.class, obj[0]); + Assertions.assertSame(val, obj[0].getChildren().get(0)); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testAPI_New_OnlyOneManyValuedModifiableFeature_EReference_Array() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var val = new AndExpressionChild[] { api.createNewEqualityExpression(), api.createNewEqualityExpression() }; + final var obj = new AndExpression[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newAndExpression(val)); + Assertions.assertInstanceOf(AndExpression.class, obj[0]); + Assertions.assertSame(val[0], obj[0].getChildren().get(0)); + Assertions.assertSame(val[1], obj[0].getChildren().get(1)); + } + + /** + * Ensures that direct creation methods for types with only one modifiable + * features is possible via the generated API class + */ + @Test + public void testAPI_New_OnlyOneManyValuedModifiableFeature_EReference_Collection() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var val = List.of(api.createNewEqualityExpression(), api.createNewEqualityExpression()); + final var obj = new AndExpression[1]; + Assertions.assertDoesNotThrow(() -> obj[0] = api.newAndExpression(val)); + Assertions.assertInstanceOf(AndExpression.class, obj[0]); + Assertions.assertSame(val.get(0), obj[0].getChildren().get(0)); + Assertions.assertSame(val.get(1), obj[0].getChildren().get(1)); + } + + /** + * Ensures that instantiation of types with multiple modifiable features is + * possible via Initialisation classes. + */ + @Test + public void testAPI_New_MultipleModifiableFeatures() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var init = api.newAdditionalLocalVariable(); + Assertions.assertInstanceOf(FluentAPISuperInitialisation.class, init); + Assertions.assertInstanceOf(AdditionalLocalVariable.class, init.createNow()); + } + + /** + * Ensures that api.newX(EClass) works as intended. + */ + @Test + public void testAPI_NewX_WithEClass() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ContainersPackage.Literals.MODULE.getInstanceClass(); + var mod = api.newX(cls).createNow(); + Assertions.assertInstanceOf(cls, mod); + } + + /** + * Ensures that api.newX(class) works as intended. + */ + @Test + public void testAPI_NewX_WithClass() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ContainersPackage.Literals.MODULE.getInstanceClass(); + var mod = api.newX(cls).createNow(); + Assertions.assertInstanceOf(cls, mod); + } + + /** + * Ensures that api.createNewX(class) works as intended. + */ + @Test + public void testAPI_CreateNewX() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ContainersPackage.Literals.MODULE.getInstanceClass(); + var mod = api.createNewX(cls); + Assertions.assertInstanceOf(cls, mod); + } + + /** + * Ensures that api.createNewX() works as intended, where X is the type of the + * model element to create. + */ + @Test + public void testAPI_CreateNew() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var mod = api.createNewModule(); + Assertions.assertInstanceOf(org.emftext.language.java.containers.Module.class, mod); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPITest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPITest.java new file mode 100644 index 0000000000..94ceaa4e29 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPITest.java @@ -0,0 +1,276 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; +import java.util.stream.Collectors; + +import org.emftext.language.java.classifiers.ClassifiersFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelFilter; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelPackageProvider; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * A test class for fluent api class. + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPITest extends AbstractFluentAPITest { + /** + * Checks whether api.mark(key, obj) works as intended + */ + @Test + public void testAPI_Mark() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + Assertions.assertSame(cls, FluentAPIMarkExtension.getMarked(clsKey)); + } + + /** + * Ensures that different fluent api instances share the same marks. + */ + @Test + public void testAPI_Mark_DifferentAPIs() { + var keyOne = new Object(); + var apiOne = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsOne = ClassifiersFactory.eINSTANCE.createClass(); + + apiOne.mark(keyOne, clsOne); + + var keyTwo = new Object(); + var apiTwo = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var clsTwo = ClassifiersFactory.eINSTANCE.createClass(); + + apiTwo.mark(keyTwo, clsTwo); + + Assertions.assertSame(clsOne, apiOne.getMarkedX(keyOne)); + Assertions.assertSame(clsTwo, apiOne.getMarkedX(keyTwo)); + Assertions.assertSame(clsOne, apiTwo.getMarkedX(keyOne)); + Assertions.assertSame(clsTwo, apiTwo.getMarkedX(keyTwo)); + } + + /** + * Checks that api.unmark(key) works as intended. + */ + @Test + public void testAPI_Unmark_WithKey() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + Assertions.assertSame(cls, FluentAPIMarkExtension.getMarked(clsKey)); + api.unmark(clsKey); + Assertions.assertNull(FluentAPIMarkExtension.getMarked(clsKey)); + } + + /** + * Checks that api.unmark(key, val) works as intended. + */ + @Test + public void testAPI_Unmark_WithKeyAndValue() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + Assertions.assertSame(cls, FluentAPIMarkExtension.getMarked(clsKey)); + api.unmark(clsKey, cls); + Assertions.assertNull(FluentAPIMarkExtension.getMarked(clsKey)); + } + + /** + * Checks whether api.getMarkedX(key) works as intended + */ + @Test + public void testAPI_GetMarkedX() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + // Retrieve regardless of type + Assertions.assertSame(cls, api.getMarkedX(clsKey)); + } + + /** + * Checks whether api.getMarkedX(key) works as intended, where X is the exact + * type of the model element. + */ + @Test + public void testAPI_GetMarkedType_MatchingType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + Assertions.assertSame(cls, api.getMarkedClass(clsKey)); + } + + /** + * Checks whether api.getMarkedX(key) works as intended, where X is a super-type + * of the model element + */ + @Test + public void testAPI_GetMarkedType_AbstractSuperType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cls = ClassifiersFactory.eINSTANCE.createClass(); + var clsKey = new Object(); + + api.mark(clsKey, cls); + Assertions.assertSame(cls, api.getMarkedNamedElement(clsKey)); + } + + /** + * Checks whether api.modifyX(obj) works as intended. + */ + @Test + public void testAPI_ModifyX() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var cls = api.newClass().createNow(); + + var init = api.modifyX(cls); + // Ensure that modifyX() does not remove the Initialisation instance + Assertions.assertEquals(1, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertSame(init, FluentAPIInitialisationStorage.getOngoingInitialisations().get(0)); + + var cls2 = init.createNow(); + Assertions.assertSame(cls, cls2); + } + + /** + * Checks whether api.modifyX(obj) works as intended, where X is the type of + * obj. + */ + @Test + public void testAPI_ModifyType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var cls = api.newClass().createNow(); + + var init = api.modifyClass(cls); + // Ensure that modifyX() does not remove the Initialisation instance + Assertions.assertEquals(1, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertSame(init, FluentAPIInitialisationStorage.getOngoingInitialisations().get(0)); + + var cls2 = init.createNow(); + Assertions.assertSame(cls, cls2); + } + + /** + * Checks whether api.modifyMarkedX(obj) works as intended. + */ + @Test + public void testAPI_ModifyMarkedX() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsKey = new Object(); + var cls = api.newClass().markCurrentElement(clsKey).createNow(); + + Assertions.assertSame(cls, api.getMarkedClass(clsKey)); + + var cls2 = api.modifyMarkedX(clsKey).createNow(); + Assertions.assertSame(cls, cls2); + + } + + /** + * Checks whether api.modifyMarkedX(obj) works as intended, where X is the type + * of obj. + */ + @Test + public void testAPI_ModifyMarkedType() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsKey = new Object(); + var cls = api.newClass().markCurrentElement(clsKey).createNow(); + + Assertions.assertSame(cls, api.getMarkedClass(clsKey)); + + var cls2 = api.modifyMarkedClass(clsKey).createNow(); + Assertions.assertSame(cls, cls2); + + } + + /** + * Checks whether api.getAllSupportedClasses() works as intended. + */ + @Test + public void testAPI_GetAllSupportedClasses() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var supportedClasses = api.getAllSupportedClasses(); + var provider = new FluentAPIJavaMetamodelPackageProvider(); + var filter = new FluentAPIJavaMetamodelFilter(); + var expectedSupportedEClasses = provider.getAllTargetMetamodelConcreteEClasses().stream() + .filter((eCls) -> filter.isEClassEligible(eCls)).collect(Collectors.toList()); + var expectedSupportedClasses = expectedSupportedEClasses.stream().map((eCls) -> eCls.getInstanceClass()) + .collect(Collectors.toList()); + Assertions.assertEquals(expectedSupportedClasses.size(), supportedClasses.size()); + Assertions.assertTrue(supportedClasses.containsAll(expectedSupportedClasses)); + var originalEClasses = provider.getAllConcreteEClassesInOriginalMetamodel().stream() + .filter((eCls) -> filter.isEClassEligible(eCls)).collect(Collectors.toList()); + Assertions.assertEquals(originalEClasses.size(), supportedClasses.size()); + Assertions.assertTrue(originalEClasses.stream().allMatch( + (orECls) -> supportedClasses.stream().anyMatch((suCls) -> orECls.getInstanceClass().equals(suCls)))); + } + + /** + * Checks whether api.waitForMark(key, task) works as intended + */ + @Test + public void testAPI_WaitForMark_SingleKey() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var key = new Object(); + final var waitForMarkRan = new boolean[] { false }; + + api.waitForMark(key, () -> waitForMarkRan[0] = true); + Assertions.assertFalse(waitForMarkRan[0]); + api.newModule().markCurrentElement(key); + Assertions.assertTrue(waitForMarkRan[0]); + } + + /** + * Checks whether api.waitForMark(keyArray, task) works as intended + */ + @Test + public void testAPI_WaitForMark_KeyArray() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var key1 = new Object(); + var key2 = new Object(); + final var waitForMarkRan = new boolean[] { false }; + + api.waitForMark(new Object[] { key1, key2 }, () -> waitForMarkRan[0] = true); + Assertions.assertFalse(waitForMarkRan[0]); + api.newModule().markCurrentElement(key1); + Assertions.assertFalse(waitForMarkRan[0]); + api.newModule().markCurrentElement(key2); + Assertions.assertTrue(waitForMarkRan[0]); + } + + /** + * Checks whether api.waitForMark(keyCol, task) works as intended. + */ + @Test + public void testAPI_WaitForMark_KeyCollection() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var key1 = new Object(); + var key2 = new Object(); + final var waitForMarkRan = new boolean[] { false }; + + api.waitForMark(List.of(key1, key2), () -> waitForMarkRan[0] = true); + Assertions.assertFalse(waitForMarkRan[0]); + api.newModule().markCurrentElement(key1); + Assertions.assertFalse(waitForMarkRan[0]); + api.newModule().markCurrentElement(key2); + Assertions.assertTrue(waitForMarkRan[0]); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIWithTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIWithTest.java new file mode 100644 index 0000000000..5f75076fa9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPIRootAPIWithTest.java @@ -0,0 +1,245 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; + +import org.eclipse.emf.ecore.EStructuralFeature; +import org.emftext.language.java.classifiers.ClassifiersPackage; +import org.emftext.language.java.commons.CommonsPackage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; +import cipm.consistency.fluentapi.test.FluentAPITestUtils; + +/** + * A test class for the modification methods of the fluent api class: + * xWithFeat(...), xWithoutFeat(...), xWithAddedFeat(...), + * xWithRemovedFeat(...), xCleanFeat(). + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPIWithTest extends AbstractFluentAPITest { + private static final EStructuralFeature nameFeat = CommonsPackage.Literals.NAMED_ELEMENT__NAME; + private static final EStructuralFeature namespaceFeat = CommonsPackage.Literals.NAMESPACE_AWARE_ELEMENT__NAMESPACES; + private static final EStructuralFeature extendsFeat = ClassifiersPackage.Literals.CLASS__EXTENDS; + + /** + * Checks whether api.xWithFeat() works as intended on EAttributes. + */ + @Test + public void testAPI_WithFeat_EAttribute() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsName = "cls"; + var cls = api.createNewClass(); + + Assertions.assertNotEquals(clsName, cls.getName()); + api.xWithFeat(cls, nameFeat, clsName); + Assertions.assertEquals(clsName, cls.getName()); + } + + /** + * Checks whether api.xWithFeat() works as intended on EReferences. + */ + @Test + public void testAPI_WithFeat_EReference() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var extType = api.createNewClassifierReference(); + var cls = api.createNewClass(); + + api.xWithFeat(cls, extendsFeat, extType); + Assertions.assertSame(extType, cls.getExtends()); + } + + /** + * Checks whether api.xWithoutFeat() works as intended on EAttributes. + */ + @Test + public void testAPI_WithoutFeat_EAttribute() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsName = "cls"; + var cls = api.createNewClass(); + var feat = nameFeat; + + cls.setName(clsName); + + Assertions.assertEquals(clsName, cls.getName()); + api.xWithoutFeat(cls, feat); + Assertions.assertEquals(feat.getDefaultValueLiteral(), cls.getName()); + } + + /** + * Checks whether api.xWithFeat() works as intended on EReference. + */ + @Test + public void testAPI_WithoutFeat_EReference() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var clsExtendsVal = api.createNewClassifierReference(); + var cls = api.createNewClass(); + var feat = extendsFeat; + + cls.setExtends(clsExtendsVal); + + Assertions.assertSame(clsExtendsVal, cls.getExtends()); + api.xWithoutFeat(cls, feat); + Assertions.assertEquals(feat.getDefaultValue(), cls.getExtends()); + } + + /** + * Checks whether api.xWithAddedFeat(val) works as intended on many-valued + * features without any pre-existing values. + */ + @Test + public void testAPI_WithAddedFeat_SingleValue_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var ns = "ns"; + + api.xWithAddedFeat(pac, namespaceFeat, ns); + Assertions.assertEquals(1, pac.getNamespaces().size()); + Assertions.assertEquals(ns, pac.getNamespaces().get(0)); + } + + /** + * Checks whether api.xWithAddedFeat(val) works as intended on many-valued + * features with pre-existing values. + */ + @Test + public void testAPI_WithAddedFeat_SingleValue_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var pastNss = List.of("someNs1", "someNs2"); + pac.getNamespaces().addAll(pastNss); + var newNs = "newNs"; + + var expectedNss = List.of(pastNss.get(0), pastNss.get(1), newNs); + + api.xWithAddedFeat(pac, namespaceFeat, newNs); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, pac.getNamespaces()); + } + + /** + * Checks whether api.xWithAddedFeat(valArray) works as intended on many-valued + * features. + */ + @Test + public void testAPI_WithAddedFeat_MultipleValuesAsArray() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var nss = new String[] { "ns1", "ns2" }; + + api.xWithAddedFeat(pac, namespaceFeat, nss); + FluentAPITestUtils.assertPairwiseEqual(nss, pac.getNamespaces()); + } + + /** + * Checks whether api.xWithAddedFeat(valCollection) works as intended on + * many-valued features. + */ + @Test + public void testAPI_WithAddedFeat_MultipleValuesAsCollection() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var nss = List.of("ns1", "ns2"); + + api.xWithAddedFeat(pac, namespaceFeat, nss); + FluentAPITestUtils.assertPairwiseEqual(nss, pac.getNamespaces()); + } + + /** + * Checks whether api.xWithRemovedFeat(val) works as intended on many-valued + * features without any pre-existing values. + */ + @Test + public void testAPI_WithRemovedFeat_SingleValue_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var ns = "ns"; + + api.xWithRemovedFeat(pac, namespaceFeat, ns); + Assertions.assertEquals(0, pac.getNamespaces().size()); + } + + /** + * Checks whether api.xWithRemovedFeat(val) works as intended on many-valued + * features with pre-existing values. + */ + @Test + public void testAPI_WithRemovedFeat_SingleValue_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var pastNss = List.of("someNs1", "someNs2"); + pac.getNamespaces().addAll(pastNss); + var nsToBeRemoved = pastNss.get(0); + + var expectedNss = List.of(pastNss.get(1)); + + api.xWithRemovedFeat(pac, namespaceFeat, nsToBeRemoved); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, pac.getNamespaces()); + } + + /** + * Checks whether api.xWithRemovedFeat(valCollection) works as intended on + * many-valued features. + */ + @Test + public void testAPI_WithRemovedFeat_MultipleValuesAsCollection() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var pastNss = List.of("ns1", "ns2", "ns3"); + pac.getNamespaces().addAll(pastNss); + var nss = List.of(pastNss.get(0), pastNss.get(2)); + + api.xWithRemovedFeat(pac, namespaceFeat, nss); + FluentAPITestUtils.assertPairwiseEqual(List.of(pastNss.get(1)), pac.getNamespaces()); + } + + /** + * Checks whether api.xWithRemovedFeat(valArray) works as intended on + * many-valued features. + */ + @Test + public void testAPI_WithRemovedFeat_MultipleValuesAsArray() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var pastNss = new String[] { "ns1", "ns2", "ns3" }; + pac.getNamespaces().addAll(List.of(pastNss)); + + api.xWithRemovedFeat(pac, namespaceFeat, List.of(pastNss[0], pastNss[2])); + FluentAPITestUtils.assertPairwiseEqual(List.of(pastNss[1]), pac.getNamespaces()); + } + + /** + * Checks whether api.xCleanFeat() works as intended on many-valued features + * without any pre-existing values. + */ + @Test + public void testAPI_CleanFeat_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + + Assertions.assertEquals(0, pac.getNamespaces().size()); + api.xCleanFeat(pac, namespaceFeat); + Assertions.assertEquals(0, pac.getNamespaces().size()); + } + + /** + * Checks whether api.xCleanFeat() works as intended on many-valued features + * with pre-existing values. + */ + @Test + public void testAPI_CleanFeat_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var pac = api.newPackage().createNow(); + var pastNss = new String[] { "ns1", "ns2", "ns3" }; + pac.getNamespaces().addAll(List.of(pastNss)); + + FluentAPITestUtils.assertPairwiseEqual(pastNss, pac.getNamespaces()); + api.xCleanFeat(pac, namespaceFeat); + Assertions.assertEquals(0, pac.getNamespaces().size()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPISuperInitWithTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPISuperInitWithTest.java new file mode 100644 index 0000000000..7a22a5f8ad --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/FluentAPISuperInitWithTest.java @@ -0,0 +1,340 @@ +package cipm.consistency.fluentapi.java.test; + +import java.util.List; + +import org.eclipse.emf.ecore.EStructuralFeature; +import org.emftext.language.java.commons.CommonsPackage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; +import cipm.consistency.fluentapi.test.FluentAPITestUtils; + +/** + * A test class containing tests for modification methods of the abstract + * (super) initialisation class within the fluent api model. + *

+ *

+ * Although those modification methods are also accessible under the concrete + * initialisations, they are not intended to be used from the concrete + * initialisations. + * + * @author Alp Torac Genc + */ +public class FluentAPISuperInitWithTest extends AbstractFluentAPITest { + private static final EStructuralFeature nameFeat = CommonsPackage.Literals.NAMED_ELEMENT__NAME; + private static final EStructuralFeature namespaceFeat = CommonsPackage.Literals.NAMESPACE_AWARE_ELEMENT__NAMESPACES; + + /** + * Checks whether superInit.xWithFeat(feat, val) works as intended. + */ + @Test + public void testSuperInit_WithFeat() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var name = "cuName"; + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + Assertions.assertNull(cu.getName()); + + init.xWithFeat(nameFeat, name); + Assertions.assertEquals(name, cu.getName()); + } + + /** + * Checks whether superInit.xWithoutFeat(feat, val) works as intended. + */ + @Test + public void testSuperInit_WithoutFeat() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var name = "cuName"; + var cu = api.createNewCompilationUnit(); + cu.setName(name); + var init = api.modifyX(cu); + Assertions.assertEquals(name, cu.getName()); + + init.xWithoutFeat(nameFeat); + Assertions.assertNull(cu.getName()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, val) works as intended, if + * val is not an eligible value. + */ + @Test + public void testSuperInit_WithRemovedFeat_RemoveNonExistentValue() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithRemovedFeat(namespaceFeat, "ns4"); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, val) works as intended. + */ + @Test + public void testSuperInit_WithRemovedFeat_SingularParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var expectedNss = List.of(ns2, ns3); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithRemovedFeat(namespaceFeat, ns1); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, valArray) works as intended. + */ + @Test + public void testSuperInit_WithRemovedFeat_ArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var toRemove = new String[] { ns1, ns3 }; + var expectedNss = List.of(ns2); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithRemovedFeat(namespaceFeat, toRemove); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, valArray) works as intended, + * if valArray were empty. + */ + @Test + public void testSuperInit_WithRemovedFeat_EmptyArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + init.xWithRemovedFeat(namespaceFeat, new String[] {}); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, valCollection) works as + * intended. + */ + @Test + public void testSuperInit_WithRemovedFeat_CollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + var toRemove = List.of(ns1, ns3); + var expectedNss = List.of(ns2); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithRemovedFeat(namespaceFeat, toRemove); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithRemovedFeat(feat, valCollection) works as + * intended, if valCollection were empty. + */ + @Test + public void testSuperInit_WithRemovedFeat_EmptyCollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + init.xWithRemovedFeat(namespaceFeat, List.of()); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether superInit.xCleanFeat(feat) works as intended. + */ + @Test + public void testSuperInit_CleanFeat() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + var ns1 = "ns1"; + var ns2 = "ns2"; + var ns3 = "ns3"; + + var nss = new String[] { ns1, ns2, ns3 }; + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(List.of(nss)); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xCleanFeat(namespaceFeat); + Assertions.assertEquals(0, cu.getNamespaces().size()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, val) works as intended, if + * there were no prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_SingularParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var ns = "ns"; + + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.xWithAddedFeat(namespaceFeat, ns); + Assertions.assertEquals(1, cu.getNamespaces().size()); + Assertions.assertEquals(ns, cu.getNamespaces().get(0)); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, val) works as intended, if + * there were prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_SingularParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nsToAdd = "ns"; + var expectedNss = List.of("ns1", "ns2", nsToAdd); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithAddedFeat(namespaceFeat, nsToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valArray) works as intended, if + * there were no prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_ArrayParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = new String[] { "ns1", "ns2" }; + + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.xWithAddedFeat(namespaceFeat, nss); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valArray) works as intended, if + * there were prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_ArrayParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nssToAdd = new String[] { "ns3", "ns4" }; + var expectedNss = List.of("ns1", "ns2", "ns3", "ns4"); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithAddedFeat(namespaceFeat, nssToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valArray) works as intended, if + * valArray were empty. + */ + @Test + public void testSuperInit_WithAddedFeat_EmptyArrayParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + init.xWithAddedFeat(namespaceFeat, new String[] {}); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valCollection) works as + * intended, if there were no prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_CollectionParameter_NoPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + + init.xWithAddedFeat(namespaceFeat, nss); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valCollection) works as + * intended, if there were prior values. + */ + @Test + public void testSuperInit_WithAddedFeat_CollectionParameter_WithPriorValues() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var nss = List.of("ns1", "ns2"); + var nssToAdd = List.of("ns3", "ns4"); + var expectedNss = List.of("ns1", "ns2", "ns3", "ns4"); + + var cu = api.createNewCompilationUnit(); + cu.getNamespaces().addAll(nss); + var init = api.modifyX(cu); + FluentAPITestUtils.assertPairwiseEqual(nss, cu.getNamespaces()); + + init.xWithAddedFeat(namespaceFeat, nssToAdd); + FluentAPITestUtils.assertPairwiseEqual(expectedNss, cu.getNamespaces()); + } + + /** + * Checks whether superInit.xWithAddedFeat(feat, valCollection) works as + * intended, if valCollection were empty. + */ + @Test + public void testSuperInit_WithAddedFeat_EmptyCollectionParameter() { + var api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + var cu = api.createNewCompilationUnit(); + var init = api.modifyX(cu); + init.xWithAddedFeat(namespaceFeat, List.of()); + Assertions.assertTrue(cu.getNamespaces().isEmpty()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIInitialisationGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIInitialisationGenerationTest.java new file mode 100644 index 0000000000..3abd922ece --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIInitialisationGenerationTest.java @@ -0,0 +1,14 @@ +package cipm.consistency.fluentapi.java.test.metamodel; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIInitialisationGenerationTest; + +/** + * Implementation of {@link AbstractFluentAPIInitialisationGenerationTest} for + * JaMoPP. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitialisationGenerationTest extends AbstractFluentAPIInitialisationGenerationTest + implements IFluentJavaAPIMetamodelTest { + +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIMetamodelCoverageTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIMetamodelCoverageTest.java new file mode 100644 index 0000000000..e582937d9e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIMetamodelCoverageTest.java @@ -0,0 +1,12 @@ +package cipm.consistency.fluentapi.java.test.metamodel; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIMetamodelCoverageTest; + +/** + * Implementation of {@link AbstractFluentAPIMetamodelCoverageTest} for JaMoPP. + * + * @author Alp Torac Genc + */ +public class FluentAPIMetamodelCoverageTest extends AbstractFluentAPIMetamodelCoverageTest + implements IFluentJavaAPIMetamodelTest { +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIRootAPIGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIRootAPIGenerationTest.java new file mode 100644 index 0000000000..7268c5680f --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/FluentAPIRootAPIGenerationTest.java @@ -0,0 +1,13 @@ +package cipm.consistency.fluentapi.java.test.metamodel; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIRootAPIGenerationTest; + +/** + * Implementation of {@link AbstractFluentAPIRootAPIGenerationTest} for JaMoPP. + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPIGenerationTest extends AbstractFluentAPIRootAPIGenerationTest + implements IFluentJavaAPIMetamodelTest { + +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/IFluentJavaAPIMetamodelTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/IFluentJavaAPIMetamodelTest.java new file mode 100644 index 0000000000..816d4565ee --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/IFluentJavaAPIMetamodelTest.java @@ -0,0 +1,199 @@ +package cipm.consistency.fluentapi.java.test.metamodel; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + +import cipm.consistency.fluentapi.java.api.ApiFactory; +import cipm.consistency.fluentapi.java.api.FluentAPISuperInitialisation; +import cipm.consistency.fluentapi.java.api.FluentJavaAPI; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelFilter; +import cipm.consistency.fluentapi.java.metamodel.FluentAPIJavaMetamodelPackageProvider; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.test.metamodel.IFluentAPIMetamodelTest; + +/** + * An extension of {@link IFluentAPIMetamodelTest} for JaMoPP. + * + * @author Alp Torac Genc + */ +public interface IFluentJavaAPIMetamodelTest extends IFluentAPIMetamodelTest { + static final FluentAPITargetMetamodelFilter filter = new FluentAPIJavaMetamodelFilter(); + static final FluentAPITargetMetamodelPackageProvider metamodelProvider = new FluentAPIJavaMetamodelPackageProvider(); + static final FluentJavaAPI api = ApiFactory.eINSTANCE.createFluentJavaAPI(); + + private static FluentAPISuperInitialisation toSupInit(EObject init) { + return (FluentAPISuperInitialisation) init; + } + + @Override + public default FluentAPITargetMetamodelPackageProvider getProvider() { + return metamodelProvider; + } + + @Override + public default FluentAPITargetMetamodelFilter getFilter() { + return filter; + } + + @Override + public default EObject init_getCurrentElement(EObject init) { + return toSupInit(init).getCurrentElement(); + } + + @Override + public default void init_mark(EObject init, Object key) { + toSupInit(init).markCurrentElement(key); + } + + @Override + public default EObject init_unmark(EObject init, Object key) { + return toSupInit(init).unmarkCurrentElement(key); + } + + @Override + public default EObject init_createNow(EObject init) { + return toSupInit(init).createNow(); + } + + @Override + public default EObject api_newX(EClass eCls) { + return api.newX(eCls); + } + + @Override + public default EObject api_newX_createNow(EClass eCls) { + return api.newX(eCls).createNow(); + } + + @Override + public default EObject api_newX_createNow(Class cls) { + return api.newX(cls).createNow(); + } + + @Override + public default EObject api_createNewX(Class cls) { + return (EObject) api.createNewX(cls); + } + + @Override + public default EObject api_modifyX(EObject obj) { + return api.modifyX(obj); + } + + @Override + public default EObject api_modifyX_createNow(EObject obj) { + return api.modifyX(obj).createNow(); + } + + @Override + public default void api_modifyX_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithAddedFeat(feat, val); + } + + @Override + public default void api_modifyX_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithRemovedFeat(feat, val); + } + + @Override + public default void api_modifyX_xCleanFeat(EObject obj, EStructuralFeature feat) { + api.modifyX(obj).xCleanFeat(feat); + } + + @Override + public default void api_modifyX_xWithFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithFeat(feat, val); + } + + @Override + public default void api_modifyX_xWithoutFeat(EObject obj, EStructuralFeature feat) { + api.modifyX(obj).xWithoutFeat(feat); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(Class cls) { + return api.getInitialisationForX(cls).getInitialisedEClass(); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(EClass cls) { + return api.getInitialisationForX(cls).getInitialisedEClass(); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(EObject obj) { + return api.getInitialisationForX(obj).getInitialisedEClass(); + } + + @Override + public default EObject api_continueX(Class cls) { + return api.continueX(cls); + } + + @Override + public default void api_xWithFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithFeat(obj, feat, val); + } + + @Override + public default void api_xWithoutFeat(EObject obj, EStructuralFeature feat) { + api.xWithoutFeat(obj, feat); + } + + @Override + public default void api_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithAddedFeat(obj, feat, val); + } + + @Override + public default void api_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithRemovedFeat(obj, feat, val); + } + + @Override + public default void api_xCleanFeat(EObject obj, EStructuralFeature feat) { + api.xCleanFeat(obj, feat); + } + + @Override + public default void api_mark(Object key, EObject val) { + api.mark(key, val); + } + + @Override + public default EObject api_unmark(Object key) { + return api.unmark(key); + } + + @Override + public default EObject api_unmark(Object key, EObject val) { + return api.unmark(key, val); + } + + @Override + public default EObject api_getMarkedX(Object key) { + return api.getMarkedX(key); + } + + @Override + public default EObject api_modifyMarkedX(Object key) { + return api.modifyMarkedX(key); + } + + @Override + public default EObject api_continueMarkedX(Object key) { + return api.continueMarkedX(key); + } + + @Override + public default EObject getAPI() { + return api; + } + + @Override + public default EObject api_getInitialisationForX(EClass eCls) { + return api.getInitialisationForX(eCls); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/package-info.java new file mode 100644 index 0000000000..8e704f2af7 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/metamodel/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains implementations of {@link cipm.consistency.fluentapi.test.metamodel} + * for JaMoPP. + */ +package cipm.consistency.fluentapi.java.test.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/package-info.java new file mode 100644 index 0000000000..088563cb9e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.java/src/cipm/consistency/fluentapi/java/test/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains tests for the generated fluent api for JaMoPP. + */ +package cipm.consistency.fluentapi.java.test; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.classpath b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.classpath new file mode 100644 index 0000000000..bbfe5b0e61 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.project b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.project new file mode 100644 index 0000000000..0c4458c6f0 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.project @@ -0,0 +1,28 @@ + + + cipm.consistency.fluentapi.pcm + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.settings/org.eclipse.jdt.core.prefs b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..c9545f06a4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +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=11 diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/META-INF/MANIFEST.MF b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..4264bf8570 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/META-INF/MANIFEST.MF @@ -0,0 +1,38 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: cipm.consistency.fluentapi.pcm;singleton:=true +Bundle-Version: 0.2.0.qualifier +Bundle-ClassPath: . +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Export-Package: cipm.consistency.fluentapi.pcm.api, + cipm.consistency.fluentapi.pcm.api.impl, + cipm.consistency.fluentapi.pcm.api.inits, + cipm.consistency.fluentapi.pcm.api.inits.impl, + cipm.consistency.fluentapi.pcm.api.inits.util, + cipm.consistency.fluentapi.pcm.api.placeholderTypes, + cipm.consistency.fluentapi.pcm.api.placeholderTypes.impl, + cipm.consistency.fluentapi.pcm.api.util, + cipm.consistency.fluentapi.pcm.builder, + cipm.consistency.fluentapi.pcm.metamodel, + cipm.consistency.fluentapi.pcm.test, + cipm.consistency.fluentapi.pcm.test.metamodel +Automatic-Module-Name: cipm.consistency.fluentapi.pcm +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Require-Bundle: cipm.consistency.fluentapi, + junit-jupiter-api, + junit-jupiter-engine, + junit-jupiter-params, + org.apache.log4j, + org.eclipse.emf.codegen.ecore, + org.eclipse.core.runtime, + org.eclipse.emf.ecore;visibility:=reexport, + org.eclipse.emf.ecore.xmi;visibility:=reexport, + org.apache.commons.lang, + org.palladiosimulator.pcm;visibility:=reexport, + de.uka.ipd.sdq.stoex;visibility:=reexport, + de.uka.ipd.sdq.units;visibility:=reexport, + de.uka.ipd.sdq.identifier;visibility:=reexport, + de.uka.ipd.sdq.probfunction;visibility:=reexport +Bundle-ActivationPolicy: lazy diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/README.md b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/README.md new file mode 100644 index 0000000000..6e2ad8a299 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/README.md @@ -0,0 +1,7 @@ +# Fluent API for PCM + +This plug-in contains the implementation of the fluent API generation for PCM and considers the [(EMF-based) PCM metamodel](https://github.com/PalladioSimulator/Palladio-Core-PCM/blob/master/bundles/org.palladiosimulator.pcm/model/pcm.ecore). The generated fluent api for PCM will consider the entirety of the 'pcm' package. + +The fluent API class within this plug-in is called [FluentPcmAPI](./src-gen/cipm.consistency.fluentapi.pcm.api/FluentPcmAPI.java). Note that this class is not present in this plug-in by default will be generated from the fluent API model. + +For exemplary usage, refer to the test cases within the [test package of this plug-in](./src/cipm.consistency.fluentapi.pcm.test). For further details, refer to the [base plug-in for fluent API generation](../cipm.consistency.fluentapi/README.md). diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/build.properties b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/build.properties new file mode 100644 index 0000000000..29969d1711 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/build.properties @@ -0,0 +1,11 @@ +# + +bin.includes = .,\ + metamodel/,\ + META-INF/,\ + plugin.xml,\ + plugin.properties +jars.compile.order = . +source.. = src-gen/,\ + src/ +output.. = bin/ diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.properties b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.properties new file mode 100644 index 0000000000..513cf17f1a --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.properties @@ -0,0 +1,4 @@ +# + +pluginName = pcm-fluentapi Model +providerName = MCSE, KIT diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.xml b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.xml new file mode 100644 index 0000000000..d95d42bd5b --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/plugin.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/FluentPCMAPIBuilder.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/FluentPCMAPIBuilder.java new file mode 100644 index 0000000000..05f5a76025 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/FluentPCMAPIBuilder.java @@ -0,0 +1,88 @@ +package cipm.consistency.fluentapi.pcm.builder; + +import java.util.ArrayList; + +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.codegen.ecore.genmodel.GenModelFactory; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; + +import cipm.consistency.fluentapi.builder.FluentAPIAbstractBuilder; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerator; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.pcm.metamodel.FluentAPIPcmMetamodelFilter; +import cipm.consistency.fluentapi.pcm.metamodel.FluentAPIPcmMetamodelPackageProvider; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationBigNumberParameterPostProcessor; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationForEachOverloadPostProcessor; +import cipm.consistency.fluentapi.postprocessor.FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor; + +/** + * An implementation of {@link FluentAPIAbstractBuilder} for the PCM metamodel. + * + * @author Alp Torac Genc + */ +public class FluentPCMAPIBuilder extends FluentAPIAbstractBuilder { + private static final FluentAPITargetMetamodelPackageProvider provider = new FluentAPIPcmMetamodelPackageProvider(); + private static final FluentAPITargetMetamodelFilter filter = new FluentAPIPcmMetamodelFilter(); + + @Override + protected GenModel generateGenModel(Resource genModelRes, Resource ecoreRes, FluentAPIGenerationContext context) { + var genModel = GenModelFactory.eINSTANCE.createGenModel(); + genModelRes.getContents().add(genModel); + + genModel.setModelDirectory("/" + getGeneratedFluentAPIModelDirectoryPath().toString()); + genModel.setOperationReflection(true); + genModel.setImportOrganizing(true); + genModel.setComplianceLevel(getJDKVersion()); + genModel.setModelName(getModelName()); + genModel.setModelPluginID(getModelPluginID()); + genModel.getForeignModel().add(ecoreRes.getURI().lastSegment()); + + var targetMetamodelGenModel = provider.getTargetMetamodelGenModels().get(0); + genModel.getUsedGenPackages().addAll(targetMetamodelGenModel.getGenPackages()); + + var initEPacs = new ArrayList(); + var toGen = (EPackage) ecoreRes.getContents().get(0); + + initEPacs.add(toGen); + genModel.initialize(initEPacs); + + genModel.reconcile(); + + genModel.setCanGenerate(true); + + var apiGenPac = genModel.findGenPackage(toGen); + apiGenPac.setBasePackage(context.getBasePackageName()); + + return genModel; + } + + @Override + protected void generateEcoreModel(Resource ecoreRes, FluentAPIGenerationContext context) { + new FluentAPIGenerator().generateRootAPIPackages(context); + new FluentAPIGenerationBigNumberParameterPostProcessor(context.getAllInitEClss()).apply(); + + var allEClss = new ArrayList(); + allEClss.add(context.getFluentAPIECls()); + allEClss.add(context.getInitSuperECls()); + allEClss.addAll(context.getAllInitEClss()); + + new FluentAPIGenerationForEachOverloadPostProcessor(context, allEClss).apply(); + new FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor(context, allEClss).apply(); + + ecoreRes.getContents().add(context.getRootPackage()); + } + + @Override + protected FluentAPITargetMetamodelPackageProvider getTargetMetamodelPackageProvider() { + return provider; + } + + @Override + protected FluentAPITargetMetamodelFilter getTargetMetamodelFilter() { + return filter; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/package-info.java new file mode 100644 index 0000000000..b8bf5ffbac --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/builder/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains the builder class, which is responsible for building the fluent api + * model (for PCM) that consists of an ecore and a genmodel file. + */ +package cipm.consistency.fluentapi.pcm.builder; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelFilter.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelFilter.java new file mode 100644 index 0000000000..03b453ff65 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelFilter.java @@ -0,0 +1,44 @@ +package cipm.consistency.fluentapi.pcm.metamodel; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; + +/** + * An implementation of {@link FluentAPITargetMetamodelFilter} for PCM. + *

+ *

+ * Excludes the features ( {@link EStructuralFeature} ) that are present in + * {@link EObject} and its super-types. All other EClasses and features under + * the {@link PcmPackage} are included. + * + * @author Alp Torac Genc + */ +public class FluentAPIPcmMetamodelFilter extends FluentAPITargetMetamodelFilter { + /** + * {@inheritDoc} + *

+ *

+ * Excludes the features ( {@link EStructuralFeature} ) that are present in + * {@link EObject} and its super-types. All other features under the + * {@link PcmPackage} are included. + */ + @Override + public boolean isFeatureEligible(EClass holderOfFeat, EStructuralFeature feat) { + return isFeatureChangeable(feat) + && !feat.getEContainingClass().getName().equals(EcorePackage.Literals.EOBJECT.getName()); + } + + /** + * {@inheritDoc} + *

+ *

+ * All EClasses under the {@link PcmPackage} are included. + */ + @Override + public boolean isEClassEligible(EClass eCls) { + return true; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelPackageProvider.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelPackageProvider.java new file mode 100644 index 0000000000..bcbd8029d3 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/FluentAPIPcmMetamodelPackageProvider.java @@ -0,0 +1,145 @@ +package cipm.consistency.fluentapi.pcm.metamodel; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.palladiosimulator.pcm.PcmPackage; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.metamodel.MetamodelUtil; + +/** + * An implementation of {@link FluentAPITargetMetamodelPackageProvider} for PCM. + *

+ *

+ * This class internally "fixes" the Ecore and GenModel of JaMoPP that it + * parses, in order to avoid having duplicated Resource instances during fluent + * api generation. This is due to Eclipse plug-in limitations. + * + * @author Alp Torac Genc + */ +public class FluentAPIPcmMetamodelPackageProvider extends FluentAPITargetMetamodelPackageProvider { + /** + * The (plug-in based) URI to PCM's genmodel file + */ + private static final URI pcmMetamodelGenModelURI = URI + .createURI("platform:/plugin/org.palladiosimulator.pcm/model/pcm.genmodel"); + /** + * The (plug-in based) URI to PCM's ecore file + */ + private static final URI pcmMetamodelEcoreModelURI = URI + .createURI("platform:/plugin/org.palladiosimulator.pcm/model/pcm.ecore"); + + /** + * The ResourceSet, which will contain the Resources of the GenModel and the + * Ecore model parsed by this class ( {@link #ecoreRes} and {@link #genModelRes} + * ). Note that those Resources are not the original Resources of the JaMoPP + * model. + */ + private final ResourceSet metamodelResSet = new ResourceSetImpl(); + /** + * The Resource instance containing the parsed Ecore model of PCM. This is NOT + * the Resource instance of {@code PcmPackage.eINSTANCE}. + */ + private Resource ecoreRes; + /** + * The Resource instance containing the parsed GenModel of PCM. This does NOT + * use the Resource instance of {@code PcmPackage.eINSTANCE}, but + * {@link #ecoreRes}. + */ + private Resource genModelRes; + /** + * The list containing the original PCM EClasses that are available under + * {@code PcmPackage.eINSTANCE}. These EClasses are NOT the same as those in + * {@link #ecoreRes}. + */ + private List originalEClss; + + /** + * Caches the (original) EClasses found under the PCM metamodel ( under + * {@code PcmPackage.eINSTANCE} ) in {@link #originalEClss}, in order to spare + * constantly retrieving them from the Resource instances. + */ + private void cacheOriginalEClasses() { + if (originalEClss == null) { + originalEClss = new ArrayList(MetamodelUtil.getAllEClasses(PcmPackage.eINSTANCE)); + } + } + + @Override + public String getTargetMetamodelName() { + var topPac = getTargetMetamodelEcoreEPackages().get(0); + return topPac.getName(); + } + + @Override + public List getAllTargetMetamodelEClasses() { + var topPac = getTargetMetamodelEcoreEPackages().get(0); + return List.copyOf(MetamodelUtil.getAllEClasses(topPac)); + } + + @Override + public List getAllEClassesInOriginalMetamodel() { + cacheOriginalEClasses(); + return originalEClss; + } + + @Override + public List getTargetMetamodelGenModels() { + if (genModelRes == null) { + genModelRes = metamodelResSet.getResource(pcmMetamodelGenModelURI, true); + } + + var pcmGenModel = (GenModel) genModelRes.getContents().get(0); + pcmGenModel.setCanGenerate(false); + + return List.of(pcmGenModel); + } + + @Override + public List getTargetMetamodelEcoreEPackages() { + if (ecoreRes == null) { + ecoreRes = metamodelResSet.getResource(pcmMetamodelEcoreModelURI, true); + var parsedPcmPac = (EPackage) ecoreRes.getContents().get(0); + fixInstanceClasses(parsedPcmPac); + } + + return List.of((EPackage) ecoreRes.getContents().get(0)); + } + + /** + * Changes the instance classes within the EClasses under parsedPcmPac to the + * original EClasses from PCM ( {@link #originalEClss} ). + * + * @param parsedPcmPac The Ecore model of PCM, which has been parsed by this + * class. + */ + private void fixInstanceClasses(EPackage parsedPcmPac) { + var parsedEClss = MetamodelUtil.getAllEClasses(parsedPcmPac); + cacheOriginalEClasses(); + + if (parsedEClss.size() != originalEClss.size()) + throw new IllegalStateException( + "Parsed PCM package and the actual PCM package contain different amounts of EClasses"); + + for (var parsedECls : parsedEClss) { + var matchingActualECls = originalEClss.stream() + .filter((cls) -> cls.getEPackage().getName().equals(parsedECls.getEPackage().getName())) + .filter((cls) -> cls.getName().equals(parsedECls.getName())).toArray(EClass[]::new); + if (matchingActualECls.length != 1) + throw new IllegalStateException("Unknown EClass has been parsed"); + + var actualECls = matchingActualECls[0]; + parsedECls.setInstanceClassName(actualECls.getInstanceClassName()); + parsedECls.setInstanceTypeName(actualECls.getInstanceTypeName()); + parsedECls.setInstanceClass(actualECls.getInstanceClass()); + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/package-info.java new file mode 100644 index 0000000000..7a86c69f95 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/metamodel/package-info.java @@ -0,0 +1 @@ +package cipm.consistency.fluentapi.pcm.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/FluentAPIPcmManyValuedFeatureModificationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/FluentAPIPcmManyValuedFeatureModificationTest.java new file mode 100644 index 0000000000..e36f2165ce --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/FluentAPIPcmManyValuedFeatureModificationTest.java @@ -0,0 +1,99 @@ +package cipm.consistency.fluentapi.pcm.test; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.palladiosimulator.pcm.core.entity.EntityPackage; +import org.palladiosimulator.pcm.core.entity.ResourceRequiredRole; +import org.palladiosimulator.pcm.repository.RepositoryComponent; +import org.palladiosimulator.pcm.repository.RepositoryPackage; + +import cipm.consistency.fluentapi.pcm.api.ApiFactory; + +/** + * A test class containing test cases for the fluent api generated for the PCM + * metamodel. + *

+ *

+ * The main purpose of this test class is to show that many-valued feature + * modifications (in PCM) are not problematic in realistic cases, although they + * currently fail in metamodel tests of the fluent api for PCM. + * + * @author Alp Torac Genc + */ +public class FluentAPIPcmManyValuedFeatureModificationTest { + /** + * Ensures that adding an array of values to a many-valued PCM feature via its + * initialisation class works as intended. + */ + @Test + public void testInit_WithAddedArray() { + var api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + var repo = api.newRepository() + .withAddedComponents__Repository( + new RepositoryComponent[] { api.createNewBasicComponent(), api.createNewBasicComponent() }) + .createNow(); + Assertions.assertEquals(2, repo.getComponents__Repository().size()); + } + + /** + * Ensures that adding an array of values to a many-valued PCM feature via the + * abstract (super) initialisation class works as intended. + */ + @Test + public void testSuperInit_WithAddedArray() { + var api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + var repo = api.createNewRepository(); + api.modifyRepository(repo).xWithAddedFeat(RepositoryPackage.Literals.REPOSITORY__COMPONENTS_REPOSITORY, + new RepositoryComponent[] { api.createNewBasicComponent(), api.createNewBasicComponent() }); + Assertions.assertEquals(2, repo.getComponents__Repository().size()); + } + + /** + * Ensures that adding an array of values to a many-valued PCM feature via the + * fluent api class works as intended. + */ + @Test + public void testApi_WithAddedArray() { + var api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + var repo = api.createNewRepository(); + api.xWithAddedFeat(repo, RepositoryPackage.Literals.REPOSITORY__COMPONENTS_REPOSITORY, + new RepositoryComponent[] { api.createNewBasicComponent(), api.createNewBasicComponent() }); + Assertions.assertEquals(2, repo.getComponents__Repository().size()); + } + + /** + * This is a failing test scenario in metamodel tests for fluent api for PCM, + * but it works here => The initial value of + * NewResourceInterfaceRequiringEntity.getResourceRequiredRoles__ResourceInterfaceRequiringEntity + * is not appropriate + */ + @Test + public void testApi_WithAddedArray_Failing() { + var api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + var obj = api.createNewResourceInterfaceRequiringEntity(); + + api.xWithAddedFeat(obj, + EntityPackage.Literals.RESOURCE_INTERFACE_REQUIRING_ENTITY__RESOURCE_REQUIRED_ROLES_RESOURCE_INTERFACE_REQUIRING_ENTITY, + new ResourceRequiredRole[] { api.createNewResourceRequiredRole(), + api.createNewResourceRequiredRole() }); + Assertions.assertEquals(2, obj.getResourceRequiredRoles__ResourceInterfaceRequiringEntity().size()); + } + + /** + * This is a failing test scenario in metamodel tests for fluent api for PCM, + * but it works here => The initial value of + * NewResourceInterfaceRequiringEntity.getResourceRequiredRoles__ResourceInterfaceRequiringEntity + * is not appropriate + */ + @Test + public void testSuperInit_WithAddedArray_Failing() { + var api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + var obj = api.createNewResourceInterfaceRequiringEntity(); + + api.modifyResourceInterfaceRequiringEntity(obj).xWithAddedFeat( + EntityPackage.Literals.RESOURCE_INTERFACE_REQUIRING_ENTITY__RESOURCE_REQUIRED_ROLES_RESOURCE_INTERFACE_REQUIRING_ENTITY, + new ResourceRequiredRole[] { api.createNewResourceRequiredRole(), + api.createNewResourceRequiredRole() }); + Assertions.assertEquals(2, obj.getResourceRequiredRoles__ResourceInterfaceRequiringEntity().size()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIInitialisationGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIInitialisationGenerationTest.java new file mode 100644 index 0000000000..d65d85fe0f --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIInitialisationGenerationTest.java @@ -0,0 +1,14 @@ +package cipm.consistency.fluentapi.pcm.test.metamodel; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIInitialisationGenerationTest; + +/** + * Implementation of {@link AbstractFluentAPIInitialisationGenerationTest} for + * PCM. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitialisationGenerationTest extends AbstractFluentAPIInitialisationGenerationTest + implements IFluentPcmAPIMetamodelTest { + +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIMetamodelCoverageTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIMetamodelCoverageTest.java new file mode 100644 index 0000000000..e4599dd1d4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIMetamodelCoverageTest.java @@ -0,0 +1,34 @@ +package cipm.consistency.fluentapi.pcm.test.metamodel; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIMetamodelCoverageTest; + +/** + * Implementation of {@link AbstractFluentAPIMetamodelCoverageTest} for PCM. + * + * @author Alp Torac Genc + */ +public class FluentAPIMetamodelCoverageTest extends AbstractFluentAPIMetamodelCoverageTest + implements IFluentPcmAPIMetamodelTest { + /** + * Currently PCM is having issues casting Object[] into SomePCMModelElement[], + * hence this test is disabled + */ + @Disabled("Disabled until PCM-specific issues are dealt with") + @Override + @Test + public void concreteElementCoverageTest_SuperInit_xManyValuedFeature() { + } + + /** + * Currently PCM is having issues casting Object[] into SomePCMModelElement[], + * hence this test is disabled + */ + @Disabled("Disabled until PCM-specific issues are dealt with") + @Override + @Test + public void concreteElementCoverageTest_API_xManyValuedFeature() { + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIRootAPIGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIRootAPIGenerationTest.java new file mode 100644 index 0000000000..acf9dad180 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/FluentAPIRootAPIGenerationTest.java @@ -0,0 +1,20 @@ +package cipm.consistency.fluentapi.pcm.test.metamodel; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.test.metamodel.AbstractFluentAPIRootAPIGenerationTest; + +/** + * Implementation of {@link AbstractFluentAPIRootAPIGenerationTest} for PCM. + * + * @author Alp Torac Genc + */ +public class FluentAPIRootAPIGenerationTest extends AbstractFluentAPIRootAPIGenerationTest + implements IFluentPcmAPIMetamodelTest { + @Disabled("There are no elements with only one modifiable feature in PCM metamodel") + @Override + @Test + public void methodTest_API_NewX_WithParameter_SingleModifiableFeature() { + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/IFluentPcmAPIMetamodelTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/IFluentPcmAPIMetamodelTest.java new file mode 100644 index 0000000000..a75a0136eb --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/IFluentPcmAPIMetamodelTest.java @@ -0,0 +1,199 @@ +package cipm.consistency.fluentapi.pcm.test.metamodel; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; +import cipm.consistency.fluentapi.pcm.api.ApiFactory; +import cipm.consistency.fluentapi.pcm.api.FluentAPISuperInitialisation; +import cipm.consistency.fluentapi.pcm.api.FluentPcmAPI; +import cipm.consistency.fluentapi.pcm.metamodel.FluentAPIPcmMetamodelFilter; +import cipm.consistency.fluentapi.pcm.metamodel.FluentAPIPcmMetamodelPackageProvider; +import cipm.consistency.fluentapi.test.metamodel.IFluentAPIMetamodelTest; + +/** + * An extension of {@link IFluentAPIMetamodelTest} for PCM. + * + * @author Alp Torac Genc + */ +public interface IFluentPcmAPIMetamodelTest extends IFluentAPIMetamodelTest { + static final FluentAPITargetMetamodelFilter filter = new FluentAPIPcmMetamodelFilter(); + static final FluentAPITargetMetamodelPackageProvider metamodelProvider = new FluentAPIPcmMetamodelPackageProvider(); + static final FluentPcmAPI api = ApiFactory.eINSTANCE.createFluentPcmAPI(); + + private static FluentAPISuperInitialisation toSupInit(EObject init) { + return (FluentAPISuperInitialisation) init; + } + + @Override + public default FluentAPITargetMetamodelPackageProvider getProvider() { + return metamodelProvider; + } + + @Override + public default FluentAPITargetMetamodelFilter getFilter() { + return filter; + } + + @Override + public default EObject init_getCurrentElement(EObject init) { + return toSupInit(init).getCurrentElement(); + } + + @Override + public default void init_mark(EObject init, Object key) { + toSupInit(init).markCurrentElement(key); + } + + @Override + public default EObject init_unmark(EObject init, Object key) { + return toSupInit(init).unmarkCurrentElement(key); + } + + @Override + public default EObject init_createNow(EObject init) { + return toSupInit(init).createNow(); + } + + @Override + public default EObject api_newX(EClass eCls) { + return api.newX(eCls); + } + + @Override + public default EObject api_newX_createNow(EClass eCls) { + return api.newX(eCls).createNow(); + } + + @Override + public default EObject api_newX_createNow(Class cls) { + return api.newX(cls).createNow(); + } + + @Override + public default EObject api_createNewX(Class cls) { + return (EObject) api.createNewX(cls); + } + + @Override + public default EObject api_modifyX(EObject obj) { + return api.modifyX(obj); + } + + @Override + public default EObject api_modifyX_createNow(EObject obj) { + return api.modifyX(obj).createNow(); + } + + @Override + public default void api_modifyX_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithAddedFeat(feat, val); + } + + @Override + public default void api_modifyX_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithRemovedFeat(feat, val); + } + + @Override + public default void api_modifyX_xCleanFeat(EObject obj, EStructuralFeature feat) { + api.modifyX(obj).xCleanFeat(feat); + } + + @Override + public default void api_modifyX_xWithFeat(EObject obj, EStructuralFeature feat, Object val) { + api.modifyX(obj).xWithFeat(feat, val); + } + + @Override + public default void api_modifyX_xWithoutFeat(EObject obj, EStructuralFeature feat) { + api.modifyX(obj).xWithoutFeat(feat); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(Class cls) { + return api.getInitialisationForX(cls).getInitialisedEClass(); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(EClass cls) { + return api.getInitialisationForX(cls).getInitialisedEClass(); + } + + @Override + public default EClass api_getInitialisationForX_getInitialisedEClass(EObject obj) { + return api.getInitialisationForX(obj).getInitialisedEClass(); + } + + @Override + public default EObject api_continueX(Class cls) { + return api.continueX(cls); + } + + @Override + public default void api_xWithFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithFeat(obj, feat, val); + } + + @Override + public default void api_xWithoutFeat(EObject obj, EStructuralFeature feat) { + api.xWithoutFeat(obj, feat); + } + + @Override + public default void api_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithAddedFeat(obj, feat, val); + } + + @Override + public default void api_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val) { + api.xWithRemovedFeat(obj, feat, val); + } + + @Override + public default void api_xCleanFeat(EObject obj, EStructuralFeature feat) { + api.xCleanFeat(obj, feat); + } + + @Override + public default void api_mark(Object key, EObject val) { + api.mark(key, val); + } + + @Override + public default EObject api_unmark(Object key) { + return api.unmark(key); + } + + @Override + public default EObject api_unmark(Object key, EObject val) { + return api.unmark(key, val); + } + + @Override + public default EObject api_getMarkedX(Object key) { + return api.getMarkedX(key); + } + + @Override + public default EObject api_modifyMarkedX(Object key) { + return api.modifyMarkedX(key); + } + + @Override + public default EObject api_continueMarkedX(Object key) { + return api.continueMarkedX(key); + } + + @Override + public default EObject getAPI() { + return api; + } + + @Override + public default EObject api_getInitialisationForX(EClass eCls) { + return api.getInitialisationForX(eCls); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/package-info.java new file mode 100644 index 0000000000..6e01d624e5 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/metamodel/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains implementations of {@link cipm.consistency.fluentapi.test.metamodel} + * for PCM. + */ +package cipm.consistency.fluentapi.pcm.test.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/package-info.java new file mode 100644 index 0000000000..ed6de6d7ec --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi.pcm/src/cipm/consistency/fluentapi/pcm/test/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains tests for the generated fluent api for PCM. + */ +package cipm.consistency.fluentapi.pcm.test; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.classpath b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.classpath new file mode 100644 index 0000000000..4a00becd81 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.project b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.project new file mode 100644 index 0000000000..d0335b2fdd --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.project @@ -0,0 +1,28 @@ + + + cipm.consistency.fluentapi + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.settings/org.eclipse.jdt.core.prefs b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..c9545f06a4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +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=11 diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/META-INF/MANIFEST.MF b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..85848c01cf --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/META-INF/MANIFEST.MF @@ -0,0 +1,29 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: cipm.consistency.fluentapi;singleton:=true +Bundle-Version: 0.2.0.qualifier +Bundle-ClassPath: . +Bundle-Vendor: %providerName +Automatic-Module-Name: cipm.consistency.fluentapi +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Export-Package: cipm.consistency.fluentapi.builder, + cipm.consistency.fluentapi.extensions, + cipm.consistency.fluentapi.gen, + cipm.consistency.fluentapi.gen.init, + cipm.consistency.fluentapi.gen.rootapi, + cipm.consistency.fluentapi.gen.superinit, + cipm.consistency.fluentapi.metamodel, + cipm.consistency.fluentapi.postprocessor, + cipm.consistency.fluentapi.test, + cipm.consistency.fluentapi.test.metamodel +Require-Bundle: junit-jupiter-api, + junit-jupiter-engine, + junit-jupiter-params, + org.apache.log4j, + org.eclipse.emf.codegen.ecore, + org.eclipse.core.runtime, + org.eclipse.emf.ecore;visibility:=reexport, + org.eclipse.emf.ecore.xmi;visibility:=reexport, + org.apache.commons.lang +Bundle-ActivationPolicy: lazy diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/README.md b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/README.md new file mode 100644 index 0000000000..5b228ce383 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/README.md @@ -0,0 +1,65 @@ +# Fluent API + +This is the base plug-in of the fluent API generation, which contains the elements that the generation of fluent APIs for individual (EMF-based) metamodels require. The elements within this plug-in are mostly metamodel agnostic and must be extended with the means to access and work with concrete metamodels. It is intended to have one fluent API plug-in per concrete metamodel (or a sub-metamodel thereof). + +This plug-in considers the same setup as the [CIPM repository](../../../README.md). + +## Introduction + +The purpose of the fluent API is to facilitate building models from their (EMF-based) metamodels. To this end, the fluent API offers methods that use dynamic EMF under the hood, in order to instantiate model elements and modify their features. In doing so, fluent api hides the complication of dynamic EMF from outside and provides simpler to understand methods. In a sense, the fluent API implements an advanced builder pattern for building models. + +The core concept of the fluent API is to make dynamic model building more practical via chainable method calls, bookmarking (marking) certain model elements and deferring some model building steps till certain model elements are marked. The latter enables maintaining the flow of the model building in the face of dependencies between model elements, which require certain model elements to exist before others. + +Exemplary usage of the fluent API are present under test packages within the plug-ins of the fluent API generation implementations of individual metamodels. + +## Plug-in Structure + +This plug-in, as well as those of the extending plug-ins, have a package-based structure with the base package having the same name as the plug-in `cipm.consistency.fluentapi.`, where `` should be replaced with the name of the concrete metamodel, or be left out for the base plug-in. + +The fluent api generation code is inside the packages within the [src](./src) directory: + +- `cipm.consistency.fluentapi..builder`: Contains the test class that generates the fluent api model +- `cipm.consistency.fluentapi..extensions`: Contains some static classes with functions that the generated fluent api code uses (extension classes) +- `cipm.consistency.fluentapi..gen.*`: Contains the logic to generate the fluent api model. Currently only present in the base plug-in. +- `cipm.consistency.fluentapi..metamodel`: Contains the means to access and work with concrete (EMF-based) metamodels +- `cipm.consistency.fluentapi..postprocessor`: Contains classes that can be used to post-process the generated fluent api model, which adjust the generated fluent api model after its generation +- `cipm.consistency.fluentapi..test`: Contains tests for the extension classes and/or the generated fluent api code. This is an optional package and can be removed. +- `cipm.consistency.fluentapi..test.metamodel`: Contains tests that analyse the generated fluent api model code regarding its generated elements (such as the methods). This is an optional package and can be removed. + +The fluent api code will be generated within the [src-gen](./src-gen) directory, following the typical EMF code generation scheme. In most cases, only the `cipm.consistency.fluentapi..api` package is relevant from outside. It contains the fluent api class `FluentAPI`, which is the class that should be used as a facade of the fluent api. + +## Fluent API Generation + +Under normal circumstances, the following steps should generate the fluent api code in Eclipse IDE: + +1) Navigate to the `"cipm.consistency.fluentapi..builder"` package, where `` should be replaced with the name of the concrete metamodel +2) Run the test case in the builder test class (currently called `FluentAPIBuilder`), which inherits the template test case from the `cipm.consistency.fluentapi.builder.FluentAPIAbstractBuilder` class using Eclipse IDE: "Run As > JUnit Plug-in Test" +3) Navigate to the `cipm.consistency.fluentapi..builder/metamodel` folder +4) Open the `.genmodel` file (currently named `-fluentapi.genmodel`) and generate the fluent api model using Eclipse IDE: "Right Click on the only node > Generate Model Code". The fluent api files should be generated under the `src-gen` folder +5) (Optional) Refresh and clean the plug-in, then run the tests therein (excluding the test class from 2) ) + +## Implementing Fluent API for Further Metamodels + +Under normal circumstances, the following steps should suffice to implement the fluent API generation for an individual metamodel: + +1) Create a new plug-in `"cipm.consistency.fluentapi..builder"` package, where `` should be replaced with the name of the concrete metamodel +2) Add `"cipm.consistency.fluentapi"` as a required bundle, as well as other plug-ins that are needed for the concrete metamodel +3) Extend the classes `cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter` and `cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelProvider` accordingly +4) Extend the test class `cipm.consistency.fluentapi.builder.FluentAPIAbstractBuilder` accordingly + +If certain fluent API methods should be generated specifically for the concrete metamodel (such as convenience methods), consider implementing post-processors and using them in the test class from 4). + +## Limitations + +The current implementation of the fluent API has the following (non-exhaustive) list of limitations: + +- Fluent API generation is implemented in Java 11, which may or may not conform later Java versions + - The generated fluent api code also conforms Java 11 + +- Names of the metamodel elements have to be unique within the metamodel; i.e. having 2 metamodel elements "namespace1.elementName" and "namespace2.elementName" is not foreseen, only one metamodel element with the name "elementName" may exist + - Not abiding this may cause issues with the fluent api model generation, which requires explicit handling + +- No generic type support in metamodels, i.e. fluent api will not account for type parameters in metamodel elements, such as the "T" in `"Class"` + +- No (explicit) support for metamodel constraints and invariants (such as OCL constraints) + - All constraints have to be explicitly addressed in the fluent api model generation for individual metamodels diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/build.properties b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/build.properties new file mode 100644 index 0000000000..c4bc00d14e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/build.properties @@ -0,0 +1,7 @@ +# + +bin.includes = .,\ + META-INF/ +jars.compile.order = . +source.. = src/ +output.. = bin/ diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/FluentAPIAbstractBuilder.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/FluentAPIAbstractBuilder.java new file mode 100644 index 0000000000..db114eff59 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/FluentAPIAbstractBuilder.java @@ -0,0 +1,246 @@ +package cipm.consistency.fluentapi.builder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +import org.eclipse.emf.codegen.ecore.genmodel.GenJDKLevel; +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.osgi.framework.FrameworkUtil; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.ModelConstants; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; + +/** + * Encapsulates the means to generate a fluent api model for a metamodel, i.e. + * the ".ecore" and ".genmodel" files of the fluent api model. To keep the + * fluent api generation as metamodel agnostic as possible, integration of the + * specific metamodel is achieved via abstract methods. + *

+ * The generation of the fluent api model is encapsulated within the + * {@link #generateModelFiles()} test case. + * + * @author Alp Torac Genc + */ +public abstract class FluentAPIAbstractBuilder { + /** + * The (default) suffix all generated fluent api model files (i.e. the ecore and + * genmodel files) should have + */ + private static final String commonModelSuffix = "fluentapi"; + /** + * The (default) name of the directory, where the fluent api model files will be + * generated + */ + private static final String commonModelDirName = "metamodel"; + /** + * The (default) name of the ecore file associated with the fluent api + */ + private static final String commonEcoreModelFileName = commonModelSuffix + ".ecore"; + /** + * The (default) name of the genmodel file associated with the fluent api + */ + private static final String commonGenModelFileName = commonModelSuffix + ".genmodel"; + /** + * The (default) name of the directory, where the fluent api itself will be + * generated (i.e. the classes that can be used to construct models of the + * targeted metamodel) + */ + private static final String modelGenerationTargetDirName = "src-gen"; + + /** + * Generates the fluent API model files (i.e. the ecore and genmodel files). + *

+ * Can be overridden in concrete implementors to change how the model files are + * generated. If overridden, the overriding method should have the {@code @Test} + * annotation for JUnit to detect it as a test case. Otherwise, the overriding + * method will not be recognized as a test method and no model files will be + * generated. + */ + @Test + public void generateModelFiles() { + cleanPreviousModelFiles(); + + var context = new FluentAPIGenerationContext(); + context.setTargetMetamodelPackageProvider(getTargetMetamodelPackageProvider()); + context.setTargetMetamodelFilter(getTargetMetamodelFilter()); + context.setBasePackageName( + ModelConstants.BASE_PACKAGE_NAME.getFor(getTargetMetamodelPackageProvider().getTargetMetamodelName())); + + var modelResSet = new ResourceSetImpl(); + var ecoreRes = modelResSet.createResource(URI.createFileURI(getEcoreModelFilePath().toString())); + var genModelRes = modelResSet.createResource(URI.createFileURI(getGenModelFilePath().toString())); + + generateEcoreModel(ecoreRes, context); + generateGenModel(genModelRes, ecoreRes, context); + + try { + ecoreRes.save(null); + genModelRes.save(null); + } catch (IOException e) { + e.printStackTrace(); + Assertions.fail(e); + } + } + + /** + * @return The value of the "model name" property of the genmodel of the fluent + * api. + */ + protected String getModelName() { + return getTargetMetamodelPackageProvider().getTargetMetamodelName() + "-" + commonModelSuffix; + } + + /** + * @return The absolute path to the .genmodel file associated with the fluent + * API model + */ + protected Path getGenModelFilePath() { + return new File(getModelFilesDirName()).getAbsoluteFile().toPath().resolve(getGenModelFileName()); + } + + /** + * @return The name of the ".genmodel" file associated with the fluent API model + */ + protected String getGenModelFileName() { + return getTargetMetamodelPackageProvider().getTargetMetamodelName() + "-" + commonGenModelFileName; + } + + /** + * @return The name of the directory (only the name of the inner-most directory, + * not the path to it), where the .ecore and .genmodel file will be + * saved. + */ + protected String getModelFilesDirName() { + return commonModelDirName; + } + + /** + * @return The name of the ecore file (only the name of the ecore file, not the + * path to it) + */ + protected String getEcoreModelFileName() { + return getTargetMetamodelPackageProvider().getTargetMetamodelName() + "-" + commonEcoreModelFileName; + } + + /** + * @return The absolute path to the directory, where the model files ("ecore" + * and "genmodel" files) fill be saved. + */ + protected Path getModelFilesPath() { + return new File(getModelFilesDirName()).getAbsoluteFile().toPath(); + } + + /** + * @return The absolute path to the ecore model file associated with the fluent + * api + */ + protected Path getEcoreModelFilePath() { + return getModelFilesPath().resolve(getEcoreModelFileName()); + } + + /** + * Cleans up the potential previously created model files for this builder + * instance. + */ + protected void cleanPreviousModelFiles() { + var fluentAPIModelFilesDir = getModelFilesPath().toFile(); + if (fluentAPIModelFilesDir.exists() && fluentAPIModelFilesDir.listFiles() != null) { + for (var file : fluentAPIModelFilesDir.listFiles()) { + file.delete(); + } + fluentAPIModelFilesDir.delete(); + } + } + + /** + * @return The value of the "compliance level" property of the genmodel of + * fluent api. + */ + protected static GenJDKLevel getJDKVersion() { + var runtimeVer = Runtime.version().version().get(0); + GenJDKLevel lvl = null; + for (var ver : GenJDKLevel.values()) { + if (ver.getLiteral().startsWith(String.valueOf(runtimeVer.doubleValue()))) { + lvl = ver; + break; + } + } + return lvl; + } + + /** + * @return The value of the "model plugin ID" property of the genmodel of fluent + * api. + */ + protected String getModelPluginID() { + return ModelConstants.BASE_PACKAGE_NAME.getFor(getTargetMetamodelPackageProvider().getTargetMetamodelName()); + } + + /** + * Returns The path, at which the fluent API will be generated. + *

+ * Can be overridden in concrete implementors. + * + * @return The value of the "model directory" property of the genmodel of fluent + * api as Path. + */ + protected Path getGeneratedFluentAPIModelDirectoryPath() { + return Path.of(getCurrentPluginName(), modelGenerationTargetDirName); + } + + /** + * Meant to be used by {@link #getGeneratedFluentAPIModelDirectoryPath()}. + * + * @return The name of the current plug-in. + */ + private String getCurrentPluginName() { + var bundle = FrameworkUtil.getBundle(getClass()); + var name = bundle.getSymbolicName(); + return name; + } + + /** + * @return The object that grants access to the metamodel, for which the fluent + * api should be generated. + */ + protected abstract FluentAPITargetMetamodelPackageProvider getTargetMetamodelPackageProvider(); + + /** + * @return The object that is used to filter the elements of the metamodel. + */ + protected abstract FluentAPITargetMetamodelFilter getTargetMetamodelFilter(); + + /** + * Generates the {@link GenModel} instance of the fluent api model from the + * previously generated Ecore model + * ({@link #generateEcoreModel(Resource, FluentAPIGenerationContext)}). The + * return value allows access to the {@link GenModel} instance in the rest of + * the fluent api generation, in order to allow further modifications to it. + * + * @param genModelRes The Resource instance, in which the {@link GenModel} + * instance will be created + * @param ecoreRes The Resource instance of the fluent api model + * @param context The object that encapsulates the context of the fluent api + * generation + * @return The {@link GenModel} instance of the fluent api model + */ + protected abstract GenModel generateGenModel(Resource genModelRes, Resource ecoreRes, + FluentAPIGenerationContext context); + + /** + * Generates the fluent api model (as Ecore model). + * + * @param ecoreRes The Resource instance of the fluent api model + * @param context The object that encapsulates the context of the fluent api + * generation + */ + protected abstract void generateEcoreModel(Resource ecoreRes, FluentAPIGenerationContext context); +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/package-info.java new file mode 100644 index 0000000000..e2a1a16f20 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/builder/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains the (abstract) builder class, which is responsible for building the + * fluent api model that consists of an ecore and a genmodel file. + */ +package cipm.consistency.fluentapi.builder; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIInitialisationStorage.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIInitialisationStorage.java new file mode 100644 index 0000000000..9d1bd8bf10 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIInitialisationStorage.java @@ -0,0 +1,69 @@ +package cipm.consistency.fluentapi.extensions; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; + +/** + * The extension class of fluent api that stores Initialisation instances that + * are created within the fluent api and are actively being used to create model + * elements. Since the fluent api model is EMF-based, implementing static + * attributes in its EClasses is challenging. Making the attribute containing + * initialisation instances static is important, since all fluent api instances + * should have access to all ongoing initialisation instances (i.e. + * initialisation instances that are actively being used), hence the + * initialisation storing logic is moved to this class. This class does not + * allow duplicated Initialisation instances. + *

+ *

+ * The methods within this class are meant for Initialisation instances, + * although they take parameters of type EObject. This is due to the fact that + * fluent api model is generated dynamically (hence the Initialisation classes + * are not guaranteed to exist at this time). + *

+ *

+ * Note: Changing any public member within this file (i.e. either this class or + * its methods) requires adapting the generation of fluent api. This is due to + * Java limitations, which do not allow dynamically adjusting static elements, + * such as method or class names. + * + * @author Alp Torac Genc + */ +public final class FluentAPIInitialisationStorage { + private static final Collection ongoingInits = new LinkedHashSet<>(); + + /** + * @return An unmodifiable list of all ongoing initialisations. + */ + public static List getOngoingInitialisations() { + return List.copyOf(ongoingInits); + } + + /** + * Removes the given Initialisation instance from this class + * + * @param init A given Initialisation instance, potentially stored in this class + */ + public static void dropOngoingInitialisation(EObject init) { + ongoingInits.remove(init); + } + + /** + * Adds the given Initialisation instance to this class. Does nothing if init + * has already been added previously. + * + * @param init A given Initialisation instance + */ + public static void addOngoingInitialisation(EObject init) { + ongoingInits.add(init); + } + + /** + * Removes all stored Initialisation instances from this class + */ + public static void clearAllOngoingInitialisations() { + ongoingInits.clear(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIMarkExtension.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIMarkExtension.java new file mode 100644 index 0000000000..d425b01c37 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIMarkExtension.java @@ -0,0 +1,130 @@ +package cipm.consistency.fluentapi.extensions; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.emf.ecore.EObject; + +/** + * The extension class of fluent api that manages marks in form of (markKey, + * markValue) pairs, where markKey is the object that was used to mark the model + * element (markValue) via fluent api. + *

+ *

+ * Currently, marks are stored in a Map, meaning that a markKey may only be used + * to mark a single markValue. Using the same markKey to mark another model + * element markValue2 will override (markKey, markValue) to (markKey, + * markValue2). However, markValue can be marked with multiple markKeys. + *

+ *

+ * Note: Changing any public member within this file (i.e. either this class or + * its methods) requires adapting the generation of fluent api. This is due to + * Java limitations, which do not allow dynamically adjusting static elements, + * such as method or class names. + * + * @author Alp Torac Genc + * @see {@link FluentAPIWaitForMarkExtension} + */ +public class FluentAPIMarkExtension { + /** + * The map that contains marks in form of (markKey, markVal) pairs. + */ + private static final Map markToObj = new LinkedHashMap<>(); + + /** + * Adds the mark (markKey, markVal) to this class, overrides any existing mark + * of markKey. + * + * @param markKey An object that is to be associated with markVal + * @param markVal A model element to be marked with markKey + */ + public static void mark(Object markKey, EObject markVal) { + markToObj.put(markKey, markVal); + elementMarked(markKey, markVal); + } + + /** + * Removes the markKey from this class (if it exists), regardless of what + * markVal it was associated with. + * + * @param markKey An object that is potentially associated with a model element + * markVal + */ + public static EObject unmark(Object markKey) { + return unmark(markKey, null); + } + + /** + * Removes the mark (markKey, markVal) from this class, if it exists. Does + * nothing, if markKey is associated with another model element markVal2. + * + * @param markKey An object that is potentially associated with the model + * element markVal + * @param markVal A model element that is potentially marked with markKey + */ + public static EObject unmark(Object markKey, EObject markVal) { + var toUnmark = markToObj.get(markKey); + + if (markVal == null || markVal == toUnmark) { + return markToObj.remove(markKey); + } else { + return null; + } + } + + /** + * @param markKey An object that is potentially associated with a model element + * markVal + * @return markVal (if it exists) + */ + public static EObject getMarked(Object markKey) { + return getMarked(markKey, null); + } + + /** + * @param markKey An object that is potentially associated with a model element + * markVal + * @param cls The class of markVal + * @return markVal, if it exists and its type is cls or cls is a super-type + */ + public static EObject getMarked(Object markKey, Class cls) { + var markVal = markToObj.get(markKey); + if (cls != null && markVal != null && !(cls.isAssignableFrom(markVal.getClass()))) { + return null; + } + return markVal; + } + + /** + * @param markKey An object that is potentially associated with a model element + * @return Whether markKey is associated with a model element + */ + public static boolean hasMark(Object markKey) { + return markToObj.containsKey(markKey); + } + + /** + * @return All marks contained in this class (as an unmodifiable map instance), + * i.e. (markKey, markVal) pairs. + */ + public static Map getAllMarks() { + return Map.copyOf(markToObj); + } + + /** + * Notifies {@link FluentAPIWaitForMarkExtension} to the mark (markKey, markVal) + * + * @param markKey An object that is associated with the model element markVal + * @param markVal A model element that is marked with markKey + */ + private static void elementMarked(Object markKey, EObject markVal) { + FluentAPIWaitForMarkExtension.elementMarked(markKey, markVal); + } + + /** + * Removes all marks (markKey, markVal) from this class. + */ + public static void clearAllMarks() { + markToObj.clear(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIWaitForMarkExtension.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIWaitForMarkExtension.java new file mode 100644 index 0000000000..b3739287c1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentAPIWaitForMarkExtension.java @@ -0,0 +1,327 @@ +package cipm.consistency.fluentapi.extensions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EObject; + +/** + * The extension class of fluent api that manages model operations that are to + * be triggered upon certain marks (markKey, markVal) existing under + * {@link FluentAPIMarkExtension}. Since this class uses a map to store + * (requiredMarkKeys, modelBuildingTasks) pairs, where both requiredMarkKeys and + * modelBuildingTasks are lists, it is possible to duplicate tasks and consider + * subsets of requiredMarkKeys too. + *

+ *

+ * Note: Changing any public member within this file (i.e. either this class or + * its methods) requires adapting the generation of fluent api. This is due to + * Java limitations, which do not allow dynamically adjusting static elements, + * such as method or class names. + * + * @author Alp Torac Genc + * @see {@link FluentAPIMarkExtension} + */ +public class FluentAPIWaitForMarkExtension { + /** + * The map that contains model building tasks in form of (requiredMarkKeys, + * modelBuildingTasks) pairs. + */ + private static final Map, List> taskContainer = new LinkedHashMap<>(); + + /** + * @param markKey A list of required markKey + * @return The model building tasks that will trigger exactly when all markKeys + * exist. Tasks that require a subset of markKey are NOT included here. + */ + private static Map.Entry, List> getEntryFor(Collection markKey) { + return taskContainer.entrySet().stream() + .filter((e) -> e.getKey().size() == markKey.size() && e.getKey().containsAll(markKey)).findFirst() + .orElse(null); + } + + /** + * Adds the given task to this class and sets it to trigger, upon markKey being + * involved in a mark. + * + * @param markKey The markKey, upon which task should trigger + * @param task A model building task + * @return {@link #addTask(List, Runnable)} + */ + public static boolean addTask(Object markKey, Runnable task) { + return addTask(List.of(markKey), task); + } + + /** + * Adds the given task to this class and sets it to trigger, upon all markKeys + * being involved in a mark. + * + * @param markKey A list of markKeys, upon which task should trigger + * @param task A model building task + * @return Whether the task has been run or added (currently always returns + * true) + */ + public static boolean addTask(Collection markKey, Runnable task) { + // Check if the issued task can trigger before adding it + if (checkMarkPresence(markKey)) { + task.run(); + return true; + } + + var entry = getEntryFor(markKey); + if (entry == null) { + var runnableList = new ArrayList(); + runnableList.add(task); + taskContainer.put(List.copyOf(markKey), runnableList); + } else { + entry.getValue().add(task); + } + return true; + } + + /** + * Removes the given task from this class, which was supposed to trigger, upon + * markKey being involved in a mark. Currently, it will only remove a single + * occurrence of the task, if it is duplicated. + * + * @param markKey The markKey, upon which task should trigger + * @param task A model building task + * @return Whether task was removed for markKey + */ + public static boolean removeTask(Object markKey, Runnable task) { + return removeTask(List.of(markKey), task); + } + + /** + * Removes the given task from this class, which was supposed to trigger, upon + * all markKeys being involved in a mark. Currently, it will only remove a + * single occurrence of the task, if it is duplicated. + * + * @param markKey A list of markKeys, upon which task should trigger + * @param task A model building task + * @return Whether task was removed for markKey + */ + public static boolean removeTask(List markKey, Runnable task) { + var entry = getEntryFor(markKey); + if (entry == null) + return false; + + var runnableList = entry.getValue(); + + var isTaskRemoved = runnableList.remove(task); + + if (isTaskRemoved && runnableList.isEmpty()) { + taskContainer.remove(entry.getKey()); + } + + return isTaskRemoved; + } + + /** + * @return A map of all (requiredMarkKeys, modelBuildingTasks) pairs, i.e. all + * tasks that have been added to this class, which are still waiting on + * certain markKeys to be involved in a mark to be triggered. + */ + public static Map, List> getAllPendingTasks() { + var markKeysToTask = new LinkedHashMap, List>(); + for (var e : taskContainer.entrySet()) { + markKeysToTask.put(List.copyOf(e.getKey()), List.copyOf(e.getValue())); + } + return markKeysToTask; + } + + /** + * @param markKey The markKey, whose waiting tasks are sought + * @return All tasks that have been added to this class, which are still waiting + * on markKey to be involved in a mark to be triggered. + */ + public static List getPendingTasks(Object markKey) { + return getPendingTasks(List.of(markKey)); + } + + /** + * @param markKey A list of markKeys, whose waiting tasks are sought + * @return All tasks that have been added to this class, which are still waiting + * on markKeys to be involved in a mark to be triggered. + */ + public static List getPendingTasks(List markKey) { + var entry = getEntryFor(markKey); + return entry != null ? List.copyOf(entry.getValue()) : null; + } + + /** + * Notifies this class that the mark (markKey, markVal) exists. + * + * @param markKey An object that is associated with the model element markVal + * @param markVal A model element that is marked with markKey + */ + public static void elementMarked(Object markKey, EObject markVal) { + performIfMarkExists(); + } + + /** + * @param markKey A list of markKeys to look for in + * {@link FluentAPIMarkExtension} + * @return Whether all given markKeys are involved in marks. + */ + private static boolean checkMarkPresence(Collection markKey) { + return markKey.stream().allMatch((mk) -> FluentAPIMarkExtension.hasMark(mk)); + } + + /** + * Use to get the tasks that can be run. + * + * @return A map of all (requiredMarkKeys, modelBuildingTasks) pairs, i.e. all + * tasks that have been added to this class, whose required markKeys are + * involved in marks. + */ + @SuppressWarnings("unchecked") + private static Map, List> getExecutableTasks() { + var entries = taskContainer.entrySet().stream() + // Check whether each markKey has a markVal present. Must check for all entries + // of taskCon, in order to account for potentially nested tasks + .filter((e) -> checkMarkPresence(e.getKey())) + // Get all entries with executable tasks + .toArray(Map.Entry[]::new); + return Map.ofEntries(entries); + } + + /** + * Returns markKey combinations, such that executing the given task becomes + * possible. + * + * @param task A model building task + * @return A list MKLS of markKey lists MKL_i, such that upon all of one MKL_i's + * markKeys being involved in marks the task will trigger. + */ + public static List> getRequiredMarkKeysFor(Runnable task) { + return taskContainer.entrySet().stream().filter((e) -> e.getValue().contains(task)) + .map((e) -> e.getKey().stream().filter((mk) -> !FluentAPIMarkExtension.hasMark(mk)) + .collect(Collectors.toUnmodifiableList())) + .filter((l) -> !l.isEmpty()).collect(Collectors.toUnmodifiableList()); + } + + /** + * Upon one of the markKey lists being fulfilled, the task can be run. + * + * @return A map of tasks and all possible combinations of markKeys to trigger + * them (task, requiredMarkKeys). + */ + public static Map>> getAllRequiredMarkKeys() { + var result = new LinkedHashMap>>(); + + // Use a set as collector, since tasks cannot be duplicated in a map (as keys of + // the map) + var runnableList = taskContainer.values().stream().flatMap((rl) -> rl.stream()) + .collect(Collectors.toUnmodifiableSet()); + for (var r : runnableList) { + result.put(r, getRequiredMarkKeysFor(r)); + } + return result; + } + + /** + * Executes all tasks, whose required markKeys are all involved in marks. + *

+ *

+ * No need to pass markVal here (object marked with markKey), since it will have + * to be found later in code and is therefore irrelevant here. + */ + private static int performIfMarkExists() { + final var count = new int[1]; + + /* + * For each task with any mutual mark keys with markKey, check if all its mark + * keys are present. If so, perform the task(s). + */ + var cList = getExecutableTasks(); + if (!cList.isEmpty()) { + var entry = cList.entrySet().iterator().next(); + var runnables = entry.getValue(); + if (!runnables.isEmpty()) { + var c = runnables.get(0); + + /* + * Make sure the remove c before executing it, since nested task(s) with the + * same key may cause an endless loop otherwise + */ + removeTask(entry.getKey(), c); + c.run(); + count[0]++; + + // Re-call this method, since c could have made further task(s) + count[0] += performIfMarkExists(); + } + } + + return count[0]; + } + + /** + * Removes all model building tasks from this class. + */ + public static void clearAllTasks() { + taskContainer.clear(); + } + + /** + * Adds the given task to this class and sets it to trigger, upon all markKeys + * being involved in a mark. + * + * @param markKey A array of markKeys, upon which task should trigger + * @param task A model building task + * @return Whether the task has been run or added (currently always returns + * true) + */ + public static boolean addTask(Object[] markKey, Runnable task) { + return addTask(List.of(markKey), task); + } + + /** + * @param markKey An array of markKeys, whose waiting tasks are sought + * @return All tasks that have been added to this class, which are still waiting + * on markKeys to be involved in a mark to be triggered. + */ + public static List getPendingTasks(Object[] markKey) { + return getPendingTasks(List.of(markKey)); + } + + /** + * @param markKeys A list of markKeys + * @return Whether there are any tasks, which are waiting exactly on the given + * list of markKeys to all be involved in marks. Tasks that consider a + * subset of markKeys are NOT considered here. + */ + public static boolean hasPendingTasks(List markKeys) { + return markKeys != null && taskContainer.keySet().stream().anyMatch((kl) -> areAllElementsSame(kl, markKeys)) + && !getEntryFor(markKeys).getValue().isEmpty(); + } + + /** + * @return Whether there are any tasks that are waiting exactly on the given + * markKey to be involved in a mark. Tasks that require further markKeys + * are NOT considered here. + */ + public static boolean hasPendingTasks(Object markKey) { + return hasPendingTasks(List.of(markKey)); + } + + /** + * @param list1 A list + * @param list2 Another list + * @return Whether all elements of the given lists are reference-equal. + */ + private static boolean areAllElementsSame(List list1, List list2) { + if (list1 == list2) + return true; + if (list1 == null ^ list2 == null) + return false; + if (list1.size() != list2.size()) + return false; + return list2.stream().allMatch((sle) -> list1.stream().anyMatch((fle) -> fle == sle)); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentEObjectAPIMethods.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentEObjectAPIMethods.java new file mode 100644 index 0000000000..aaa21ad4e6 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/FluentEObjectAPIMethods.java @@ -0,0 +1,318 @@ +package cipm.consistency.fluentapi.extensions; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.common.util.BasicEList; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EStructuralFeature; + +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * Contains static methods that are used from within the generated fluent API. + * Since fluent API generation strives to be metamodel independent, the use of + * dynamic EMF and EMF-Reflection become a necessity, which allow navigating the + * contents of EMF model elements at runtime. Furthermore, using static methods + * to encapsulate messy or complicated operations reduces the amount of + * generated code. + *

+ *

+ * Note: Changing any public member within this file (i.e. either this class or + * its methods) requires adapting the generation of fluent api. This is due to + * Java limitations, which do not allow dynamically adjusting static elements, + * such as method or class names. + * + * @author Alp Torac Genc + */ +public final class FluentEObjectAPIMethods { + /** + * Invokes the method using EMF-Reflection, which sets + * {@code objToModify.feat = featVal}. Only usable on changeable single-valued + * features (feat) in objToModify, for featVal of a supported type. + * + * @return api + */ + public static EObject xWithFeat(EObject api, EObject objToModify, EStructuralFeature feat, Object featVal) { + var init = getInitialisationForX(api, objToModify); + + var opName = ModelConstants.Initialiation.With.NAME.getFor(StringUtils.capitalize(feat.getName())); + var withOp = init.eClass().getEOperations().stream().filter((op) -> op.getName().equals(opName)).findFirst() + .get(); + try { + init.eInvoke(withOp, new BasicEList<>(Collections.singleton(featVal))); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + FluentAPIInitialisationStorage.dropOngoingInitialisation(init); + return api; + } + + /** + * Invokes the method using EMF-Reflection, which sets + * {@code objToModify.feat = null}. Only usable on changeable single-valued + * features (feat) in objToModify. + * + * @return api + */ + public static EObject xWithoutFeat(EObject api, EObject objToModify, EStructuralFeature feat) { + var init = getInitialisationForX(api, objToModify); + var opName = ModelConstants.Initialiation.Without.NAME.getFor(StringUtils.capitalize(feat.getName())); + var withoutOp = init.eClass().getEOperations().stream().filter((op) -> op.getName().equals(opName)).findFirst() + .get(); + try { + init.eInvoke(withoutOp, new BasicEList<>()); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + FluentAPIInitialisationStorage.dropOngoingInitialisation(init); + return api; + } + + /** + * Invokes the method using EMF-Reflection, which sets + * {@code objToModify.feat = []}. Only usable on changeable many-valued features + * (feat) in objToModify. + * + * @return api + */ + public static EObject xCleanFeat(EObject api, EObject objToModify, EStructuralFeature feat) { + var init = getInitialisationForX(api, objToModify); + var opName = ModelConstants.Initialiation.Clean.NAME.getFor(StringUtils.capitalize(feat.getName())); + var cleanOp = init.eClass().getEOperations().stream().filter((op) -> op.getName().equals(opName)).findFirst() + .get(); + try { + init.eInvoke(cleanOp, new BasicEList<>()); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + FluentAPIInitialisationStorage.dropOngoingInitialisation(init); + return api; + } + + private static boolean isArrayType(EParameter p) { + return ((p.getEType() != null && p.getEType().getInstanceClass() != null + && p.getEType().getInstanceClass().isArray()) + || (p.getEGenericType() != null && p.getEGenericType().getERawType() != null + && p.getEGenericType().getERawType().getInstanceClass() != null + && p.getEGenericType().getERawType().getInstanceClass().isArray())); + } + + private static boolean isCollectionType(EParameter p) { + return ((p.getEType() != null && p.getEType().getInstanceClass() != null + && Collection.class.isAssignableFrom(p.getEType().getInstanceClass())) + || (p.getEGenericType() != null && p.getEGenericType().getERawType() != null + && p.getEGenericType().getERawType().getInstanceClass() != null + && Collection.class.isAssignableFrom(p.getEGenericType().getERawType().getInstanceClass()))); + } + + private static EOperation getArrayVariant(List ops) { + return ops.stream().filter((o) -> o.getEParameters().stream().anyMatch((p) -> !p.isMany() && isArrayType(p))) + .findFirst().get(); + } + + private static EOperation getCollectionVariant(List ops) { + return ops.stream() + .filter((o) -> o.getEParameters().stream().anyMatch((p) -> !p.isMany() && isCollectionType(p))) + .findFirst().get(); + } + + private static EOperation getSingleValueVariant(List ops) { + return ops.stream() + .filter((o) -> o.getEParameters().stream().anyMatch((p) -> !p.isMany() && !isCollectionType(p))) + .findFirst().get(); + } + + private static EOperation getVariantForFeatureValue(Object featVal, List ops) { + if (featVal.getClass().isArray()) { + return getArrayVariant(ops); + } else if (featVal instanceof Collection) { + return getCollectionVariant(ops); + } else { + return getSingleValueVariant(ops); + } + } + + /** + * Invokes the method using EMF-Reflection, which adds + * {@code objToModify.feat += featVal}. Only usable on changeable many-valued + * features (feat) in objToModify, for featVal of a supported type. + * + * @return api + */ + public static EObject xWithAddedFeat(EObject api, EObject objToModify, EStructuralFeature feat, Object featVal) { + var init = getInitialisationForX(api, objToModify); + var opName = ModelConstants.Initialiation.WithAdded.NAME.getFor(StringUtils.capitalize(feat.getName())); + var withAddedOps = init.eClass().getEOperations().stream().filter((op) -> op.getName().equals(opName)) + .collect(Collectors.toList()); + var op = getVariantForFeatureValue(featVal, withAddedOps); + var argList = new BasicEList<>(); + + argList.add(featVal); + try { + init.eInvoke(op, argList); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + FluentAPIInitialisationStorage.dropOngoingInitialisation(init); + return api; + } + + /** + * Invokes the method using EMF-Reflection, which removes + * {@code objToModify.feat -= featVal}. Only usable on changeable many-valued + * features (feat) in objToModify, for featVal of a supported type. + * + * @return api + */ + public static EObject xWithRemovedFeat(EObject api, EObject objToModify, EStructuralFeature feat, Object featVal) { + var init = getInitialisationForX(api, objToModify); + var opName = ModelConstants.Initialiation.WithRemoved.NAME.getFor(StringUtils.capitalize(feat.getName())); + var withRemovedOps = init.eClass().getEOperations().stream().filter((op) -> op.getName().equals(opName)) + .collect(Collectors.toList()); + var op = getVariantForFeatureValue(featVal, withRemovedOps); + var argList = new BasicEList<>(); + argList.add(featVal); + try { + init.eInvoke(op, argList); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + FluentAPIInitialisationStorage.dropOngoingInitialisation(init); + return api; + } + + /** + * @return A list of all classes that the given api instance supports the + * creation / modification of. + */ + @SuppressWarnings("unchecked") + public static EList> getAllSupportedClasses(EObject api) { + var result = new BasicEList>(); + + var initsPac = api.eClass().getEPackage().getESubpackages().stream() + .filter((pac) -> pac.getName().equals(ModelConstants.INITIALISATIONS_PACKAGE_NAME.get())).findFirst() + .get(); + + var initEClasses = initsPac.getEClassifiers().stream() + .filter((eCls) -> eCls instanceof EClass + && eCls.getName().endsWith(ModelConstants.INITIALISATION_NAME_SUFFIX.get())) + .map((eCls) -> (EClass) eCls).collect(Collectors.toList()); + + initEClasses.stream().map((eCls) -> eCls.getInstanceClass()).map((cls) -> { + try { + return cls.getDeclaredMethod(ModelConstants.SuperInitialisation.CreateNow.NAME.get()); + } catch (NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + throw new IllegalStateException(e); + } + }).map((met) -> (Class) met.getReturnType()).forEach(result::add); + + return result; + } + + /** + * The returned initialisation instance will not have a current element. + * + * @return An initialisation instance for the given class eobjCls, which can be + * used to create / modify an instance of that type. + */ + public static EObject getInitialisationInstanceForX(EObject api, Class eobjCls) { + var initsPac = api.eClass().getEPackage().getESubpackages().stream() + .filter((pac) -> pac.getName().equals(ModelConstants.INITIALISATIONS_PACKAGE_NAME.get())).findFirst() + .get(); + + var initEClass = (EClass) initsPac.getEClassifiers().stream().filter((eCls) -> eCls instanceof EClass) + .map((eCls) -> (EClass) eCls).filter((eCls) -> isInitialisationFor(eCls, eobjCls)).findFirst().get(); + var initInstance = initEClass.getEPackage().getEFactoryInstance().create(initEClass); + initInstance.eSet( + initInstance.eClass().getEStructuralFeature(ModelConstants.SuperInitialisation.RootAPI.NAME.get()), + api); + FluentAPIInitialisationStorage.addOngoingInitialisation(initInstance); + return initInstance; + } + + /** + * The returned initialisation instance will have a current element. + * + * @return An initialisation instance for the given class eobjCls, which can be + * used to create / modify an instance of that type + */ + public static EObject getInitialisationInstanceForXWithNewElement(EObject api, Class eobjCls) { + var initInstance = getInitialisationInstanceForX(api, eobjCls); + var newElemOp = initInstance.eClass().getEOperations().stream() + .filter((op) -> op.getName().equals(ModelConstants.Initialiation.NewElement.NAME.get())).findFirst() + .get(); + try { + initInstance.eInvoke(newElemOp, new BasicEList<>()); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return initInstance; + } + + /** + * The returned initialisation instance will have a current element. + * + * @return An initialisation instance for the given EClass eCls, which can be + * used to create / modify an instance of that type + */ + public static EObject getInitialisationForX(EObject api, EClass eCls) { + return getInitialisationForX(api, eCls.getInstanceClass()); + } + + /** + * The returned initialisation instance will have a current element. + * + * @return An initialisation instance for the given class eobjCls, which can be + * used to create / modify an instance of that type + */ + public static EObject getInitialisationForX(EObject api, Class eobjCls) { + return getInitialisationInstanceForXWithNewElement(api, eobjCls); + } + + /** + * The returned initialisation instance will have a current element. + * + * @return An initialisation instance for the given EObject eobjToInit, which + * can be used to create / modify an instance of that type + */ + public static EObject getInitialisationForX(EObject api, EObject eobjToInit) { + var initInstance = getInitialisationInstanceForX(api, eobjToInit.eClass().getInstanceClass()); + + initInstance.eSet(initInstance.eClass() + .getEStructuralFeature(ModelConstants.SuperInitialisation.CurrentElement.NAME.get()), eobjToInit); + + return initInstance; + } + + /** + * @return Whether the given initialisation EClass initECls can be used on an + * instance of the given type eobjCls + */ + public static boolean isInitialisationFor(EClass initECls, Class eobjCls) { + return !initECls.isAbstract() && initECls.getName() + .substring(0, initECls.getName().length() - ModelConstants.INITIALISATION_NAME_SUFFIX.get().length()) + .equals(eobjCls.getSimpleName()); + } + + /** + * @return The most recent ongoing initialisation instance for the given class + * eobjCls present under {@link FluentAPIInitialisationStorage} + */ + public static EObject continueElement(Class eobjCls) { + var initsOfMatchingType = FluentAPIInitialisationStorage.getOngoingInitialisations().stream() + .filter((i) -> isInitialisationFor(i.eClass(), eobjCls)) + .collect(Collectors.toCollection(ArrayList::new)); + return !initsOfMatchingType.isEmpty() ? initsOfMatchingType.get(initsOfMatchingType.size() - 1) : null; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/package-info.java new file mode 100644 index 0000000000..18252bf5e7 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/extensions/package-info.java @@ -0,0 +1,13 @@ +/** + * Contains extension classes, which offer static methods to the fluent api. The + * purpose of the extension classes is to both keep the generated fluent api + * code less verbose and to make fluent api more metamodel agnostic (through + * EMF-Reflection for instance). + *

+ *

+ * Note: Changing any public member within this package (i.e. classes or methods + * of the classes) requires adapting the generation of fluent api. This is due + * to Java limitations, which do not allow dynamically adjusting static + * elements, such as method or class names. + */ +package cipm.consistency.fluentapi.extensions; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIDocumentationUtil.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIDocumentationUtil.java new file mode 100644 index 0000000000..2365a569f9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIDocumentationUtil.java @@ -0,0 +1,60 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.Map; + +/** + * Utility class for assembling documentation strings during fluent API code + * generation. + * + * @author Alp Torac Genc + */ +public final class FluentAPIDocumentationUtil { + private static final String doNotUseFromOutsideDocumentationNote = "This method is not intended for outside use, but is generated as public because of code generation limitations."; + private static final String documentationParagraphSeparator = "

"; + + private static final String classMethodOverviewIntroTemplate = "In the following, replace '" + + ModelConstants.PLACEHOLDER.get() + "' in method names with the concrete feature name:" + + getDocParagraphSeparator() + "

    %s
"; + + /** + * @return Paragraph separator used in documentation. + */ + public static String getDocParagraphSeparator() { + return documentationParagraphSeparator; + } + + /** + * @return The warning to not use the method from outside. + */ + public static String getDoNotUseFromOutsideDocNote() { + return doNotUseFromOutsideDocumentationNote; + } + + /** + * @return Introduction to method list in class documentations. + */ + public static String getClassMethodOverviewIntroTemplate() { + return classMethodOverviewIntroTemplate; + } + + /** + * @param text A given text + * @return {@code text +} {@link #getDocParagraphSeparator()} + */ + public static String appendToDocumentationStart(String text) { + return text + getDocParagraphSeparator(); + } + + /** + * @param methodNameToSummaryMap A map of (method name, short description) + * entries + * @return A single string containing the given method names and their summaries + * in a formatted version. + */ + public static String serialiseSummaries(Map methodNameToSummaryMap) { + var sb = new StringBuilder(); + methodNameToSummaryMap + .forEach((metName, summary) -> sb.append("
  • ").append(metName).append(": ").append(summary)); + return sb.toString(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFeatureTemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFeatureTemplate.java new file mode 100644 index 0000000000..0b70bed2d7 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFeatureTemplate.java @@ -0,0 +1,16 @@ +package cipm.consistency.fluentapi.gen; + +/** + * A template class meant to hold the name of an EMF-feature and to provide + * utility methods for easier generation of getter/setter access/calls. + * + * @see FluentAPIFixTemplate + * @see IFluentAPIFeatureTemplate + * + * @author Alp Torac Genc + */ +public class FluentAPIFeatureTemplate extends FluentAPIFixTemplate implements IFluentAPIFeatureTemplate { + public FluentAPIFeatureTemplate(String template) { + super(template); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFillableTemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFillableTemplate.java new file mode 100644 index 0000000000..cb9ba49eff --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFillableTemplate.java @@ -0,0 +1,38 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.Collections; + +/** + * Concrete implementation of {@link IFluentAPIFillableTemplate} that stores a + * template string with formatting flags. + * + * @see IFluentAPIFillableTemplate + * @see {@link String#format(String, Object...)} + * + * @author Alp Torac Genc + */ +public class FluentAPIFillableTemplate implements IFluentAPIFillableTemplate { + private final String template; + + public FluentAPIFillableTemplate(String template) { + this.template = template; + } + + @Override + public String get() { + return this.template; + } + + @Override + public String getFor(Object... params) { + return String.format(this.template, params); + } + + @Override + public String getEmpty() { + // Over-approximate the amount of flags, since passing more parameters does not + // matter + return getFor(Collections.nCopies(this.template.length(), "").toArray()); + } + +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFixTemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFixTemplate.java new file mode 100644 index 0000000000..5188e8bddd --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIFixTemplate.java @@ -0,0 +1,25 @@ +package cipm.consistency.fluentapi.gen; + +/** + * A template class that holds a fixed string value, without any string + * formatting flags. + * + * @see IFluentAPITemplate + * @see FluentAPIFillableTemplate + * + * @see {@link String#format(String, Object...)} + * + * @author Alp Torac Genc + */ +public class FluentAPIFixTemplate implements IFluentAPITemplate { + private final String template; + + public FluentAPIFixTemplate(String template) { + this.template = template; + } + + @Override + public String get() { + return this.template; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGeneralParameterGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGeneralParameterGenerator.java new file mode 100644 index 0000000000..381249e30a --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGeneralParameterGenerator.java @@ -0,0 +1,185 @@ +package cipm.consistency.fluentapi.gen; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EcorePackage; + +/** + * Utility class for generating recurring EParameter instances used in fluent + * API generation. + * + * @author Alp Torac Genc + */ +public final class FluentAPIGeneralParameterGenerator { + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME} + * of the given type + */ + public static EParameter getEObjectParamOfType(EClass type) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get(), type); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME} + * of type EObject + */ + public static EParameter getEObjectParam() { + return getEObjectParamOfType(EcorePackage.Literals.EOBJECT); + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.MODIFIED_FEATURE_PARAMETER_NAME} + */ + public static EParameter getFeatParam() { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.MODIFIED_FEATURE_PARAMETER_NAME.get(), + EcorePackage.Literals.ESTRUCTURAL_FEATURE); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.MODIFIED_FEATURE_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME} + */ + public static EParameter getFeatValParam() { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get(), + EcorePackage.Literals.EJAVA_OBJECT); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @param context An object encapsulating the fluent api generation context + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME} + * for an array type + */ + public static EParameter getFeatValArrayParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateArrayValuedEParameter(context, + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get(), + EcorePackage.Literals.EJAVA_OBJECT); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @param context An object encapsulating the fluent api generation context + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME} + * for a collection type + */ + public static EParameter getFeatValColParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get(), + FluentAPIGenerationUtil.generateCollectionTypeParameter(context, null)); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME} + */ + public static EParameter getMarkKeyParam() { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get(), EcorePackage.Literals.EJAVA_OBJECT); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @param context An object encapsulating the fluent api generation context + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME} for + * passing a collection + */ + public static EParameter getMarkKeyColParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get(), + FluentAPIGenerationUtil.generateCollectionTypeParameter(context, null)); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @param context An object encapsulating the fluent api generation context + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME} for + * passing an array + */ + public static EParameter getMarkKeyArrayParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateArrayValuedEParameter(context, + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get(), EcorePackage.Literals.EJAVA_OBJECT); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @param context An object encapsulating the fluent api generation context + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_PARAMETER_NAME} + */ + public static EParameter getWaitForMarkTaskParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter(context, + ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_PARAMETER_NAME.get(), + ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_CLASS); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME} + */ + public static EParameter getMarkValParam() { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get(), EcorePackage.Literals.EOBJECT); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.FluentAPI.New.ECLASS_PARAMETER_NAME} + */ + public static EParameter getArbitraryEClassParam() { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_NAME.get(), EcorePackage.Literals.ECLASS); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_DOC.get()); + return param; + } + + /** + * @return An EParameter instance for + * {@link ModelConstants.FluentAPI.New.CLASS_PARAMETER_NAME} + */ + public static EParameter getArbitraryClassParam() { + var classParamType = FluentAPIGenerationUtil + .generateEGenericTypeWithClassifier(EcorePackage.Literals.EJAVA_CLASS); + FluentAPIGenerationUtil.addTypeArgument(classParamType, FluentAPIGenerationUtil.generateWildcardTypeArgument()); + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.GeneralParameters.ARBITRARY_CLASS_PARAMETER_NAME.get(), classParamType); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.GeneralParameters.ARBITRARY_CLASS_PARAMETER_DOC.get()); + return param; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationContext.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationContext.java new file mode 100644 index 0000000000..8e81c2af24 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationContext.java @@ -0,0 +1,259 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; + +/** + * Holds the state and resources required during the generation of the fluent + * API, including generated EMF model elements. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationContext { + private FluentAPITargetMetamodelPackageProvider targetMetamodelPackageProvider; + private FluentAPITargetMetamodelFilter targetMetamodelFilter; + + private EPackage placeholderEDataTypesPac; + + private EPackage rootPackage; + private EPackage apiPackage; + private EClass fluentAPIECls; + + private EClass initSuperECls; + private EReference initSuperEClsApiReference; + private EReference initSuperEClsCurrentElement; + + private EPackage initsPackage; + private final Map initEClss = new LinkedHashMap<>(); + + private String basePackageName; + + /** + * @return The name of the base package of the fluent API model + */ + public String getBasePackageName() { + return basePackageName; + } + + /** + * @see {@link #getBasePackageName()} + */ + public void setBasePackageName(String basePackageName) { + this.basePackageName = basePackageName; + } + + /** + * @return The EPackage, in which {@link #getInitsPackage()} and + * {@link #getPlaceholderEDataTypesPac()} reside + */ + public EPackage getRootPackage() { + return rootPackage; + } + + /** + * @see {@link #getRootPackage()} + */ + public void setRootPackage(EPackage rootPackage) { + this.rootPackage = rootPackage; + } + + /** + * @return The EPackage, in which the {@link #getFluentAPIECls()} resides. + */ + public EPackage getApiPackage() { + return apiPackage; + } + + /** + * @see {@link #getApiPackage()} + */ + public void setApiPackage(EPackage rootPackage) { + this.apiPackage = rootPackage; + } + + /** + * @return The EClass modelling the fluent api class + */ + public EClass getFluentAPIECls() { + return fluentAPIECls; + } + + /** + * @see {@link #getFluentAPIECls()} + */ + public void setFluentAPIECls(EClass fluentAPIECls) { + this.fluentAPIECls = fluentAPIECls; + } + + /** + * @return The EClass modelling the abstract (super) Initialisation class + */ + public EClass getInitSuperECls() { + return initSuperECls; + } + + /** + * @see {@link #getInitSuperECls()} + */ + public void setInitSuperECls(EClass initSuperECls) { + this.initSuperECls = initSuperECls; + } + + /** + * @return The EPackage, where {@link #getAllInitEClss()} reside + */ + public EPackage getInitsPackage() { + return initsPackage; + } + + /** + * @see {@link #getInitsPackage()} + */ + public void setInitsPackage(EPackage initsPackage) { + this.initsPackage = initsPackage; + } + + /** + * Adds initECls as Initialisation class for elemToInitECls + * + * @param elemToInitECls A given EObject sub-type + * @param initECls A given Initialisation class for elemToInitECls + */ + public void addInitECls(EClass elemToInitECls, EClass initECls) { + initEClss.put(elemToInitECls, initECls); + } + + /** + * @param elemToInitECls A given EObject sub-type + * @return The Initialisation class for elemToInitECls + */ + public EClass getInitEClsFor(EClass elemToInitECls) { + return initEClss.get(elemToInitECls); + } + + /** + * @param initECls A given Initialisation class + * @return The EObject type that initECls considers + */ + public EClass getElemToInitFor(EClass initECls) { + return initEClss.entrySet().stream().filter((e) -> e.getValue().equals(initECls)).map((e) -> e.getKey()) + .findFirst().orElse(null); + } + + /** + * @return An unmodifiable list of all Initialisation EClasses + */ + public List getAllInitEClss() { + return List.copyOf(initEClss.values()); + } + + /** + * @return The object that provides access to the metamodel the fluent api is + * meant for. + */ + public FluentAPITargetMetamodelPackageProvider getTargetMetamodelPackageProvider() { + return targetMetamodelPackageProvider; + } + + /** + * @return The object that filters the metamodel + * {@link #getTargetMetamodelPackageProvider()} during the generation of + * fluent api. + */ + public FluentAPITargetMetamodelFilter getTargetMetamodelFilter() { + return targetMetamodelFilter; + } + + /** + * Uses {@link #getTargetMetamodelPackageProvider()} in conjunction with + * {@link #getTargetMetamodelFilter()}. + * + * @return A list of all concrete EClasses of the target metamodel + */ + public List getAllEligibleTargetMetamodelConcreteEClasses() { + return this.getTargetMetamodelPackageProvider().getAllTargetMetamodelConcreteEClasses().stream() + .filter((eCls) -> this.getTargetMetamodelFilter().isEClassEligible(eCls)).collect(Collectors.toList()); + } + + /** + * Uses {@link #getTargetMetamodelPackageProvider()} in conjunction with + * {@link #getTargetMetamodelFilter()}. + * + * @return A list of all EClasses of the target metamodel + */ + public List getAllEligibleTargetMetamodelEClasses() { + return this.getTargetMetamodelPackageProvider().getAllTargetMetamodelEClasses().stream() + .filter((eCls) -> this.getTargetMetamodelFilter().isEClassEligible(eCls)).collect(Collectors.toList()); + } + + /** + * Use this EPackage to gather all non-EMF types (such as array types). + * + * @return The EPackage, which aggregates placeholder EDataTypes during fluent + * api generation. + */ + public EPackage getPlaceholderEDataTypesPac() { + return placeholderEDataTypesPac; + } + + /** + * @see {@link #getPlaceholderEDataTypesPac()} + */ + public void setPlaceholderEDataTypesPac(EPackage placeholderEDataTypesPac) { + this.placeholderEDataTypesPac = placeholderEDataTypesPac; + } + + /** + * @return The EReference in {@link #getInitSuperECls()}, which refers to the + * fluent api class ({@link #getFluentAPIECls()}) that created it. + */ + public EReference getInitSuperEClsApiReference() { + return initSuperEClsApiReference; + } + + /** + * @see {@link #getInitSuperEClsApiReference()} + */ + public void setInitSuperEClsApiReference(EReference initSuperEClsApiReference) { + this.initSuperEClsApiReference = initSuperEClsApiReference; + } + + /** + * @return The EReference in {@link #getInitSuperECls()}, which refers to the + * EObject that is currently being created / modified. + */ + public EReference getInitSuperEClsCurrentElement() { + return initSuperEClsCurrentElement; + } + + /** + * @see {@link #getInitSuperEClsCurrentElement()} + */ + public void setInitSuperEClsCurrentElement(EReference initSuperEClsCurrentElement) { + this.initSuperEClsCurrentElement = initSuperEClsCurrentElement; + } + + /** + * @see {@link #getTargetMetamodelPackageProvider()} + */ + public void setTargetMetamodelPackageProvider( + FluentAPITargetMetamodelPackageProvider targetMetamodelPackageProvider) { + this.targetMetamodelPackageProvider = targetMetamodelPackageProvider; + } + + /** + * @see {@link #getTargetMetamodelFilter()} + */ + public void setTargetMetamodelFilter(FluentAPITargetMetamodelFilter targetMetamodelFilter) { + this.targetMetamodelFilter = targetMetamodelFilter; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationUtil.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationUtil.java new file mode 100644 index 0000000000..d81f09eb71 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerationUtil.java @@ -0,0 +1,518 @@ +package cipm.consistency.fluentapi.gen; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EGenericType; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.ETypeParameter; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.EcorePackage; + +/** + * Utility class providing static methods for generating EMF elements during + * fluent API code generation. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationUtil { + /** + * A map of EClass representations of primitive types to EClass representations + * of their wrapper type. + */ + private static final Map primitiveEClsToWrapperEClsMap; + + static { + primitiveEClsToWrapperEClsMap = new HashMap<>(); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.EINT, EcorePackage.Literals.EINTEGER_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.ELONG, EcorePackage.Literals.ELONG_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.EFLOAT, EcorePackage.Literals.EFLOAT_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.EDOUBLE, EcorePackage.Literals.EDOUBLE_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.EBOOLEAN, EcorePackage.Literals.EBOOLEAN_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.EBYTE, EcorePackage.Literals.EBYTE_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.ESHORT, EcorePackage.Literals.ESHORT_OBJECT); + primitiveEClsToWrapperEClsMap.put(EcorePackage.Literals.ECHAR, EcorePackage.Literals.ECHARACTER_OBJECT); + } + + /** + * Note: This method yields a best-effort result by looking at the EPackage + * structure of the given EClass. To this end, the assumption is that all + * (parent) packages of the class represented by the given EClass also have a + * corresponding EPackage and that the Java code structure of the generated + * class is reflected in its EMF model: + *

    + *

    + * Given EClass eCls representing the Java class ns1.ns2.ns3.Cls, if the EMF + * containment tree of eCls is not ns1 -> ns2 -> ns3 -> eCls (with nsI being + * EPackages) and {@code eCls.getInstanceClass() == null}, the returned fully + * qualified name will be incorrect. + * + * @return The fully qualified name for the given EClass. If + * {@code eCls.getInstanceClass() != null}, returns the name of the + * contained instance class. Otherwise, returns the fully qualified name + * based on the EPackage of the given EClass and super EPackages + * thereof. + */ + public static String getFullyQualifiedEClassName(FluentAPIGenerationContext context, EClass eCls) { + // Attempt to get the namespaces from the potentially underlying instance class + if (eCls.getInstanceClass() != null) + return eCls.getInstanceClass().getName(); + if (eCls.getInstanceClassName() != null) + return eCls.getInstanceClassName(); + if (eCls.getInstanceTypeName() != null) + return eCls.getInstanceTypeName(); + + // Attempt to get the namespaces from super packages + String result = eCls.getName(); + var pac = eCls.getEPackage(); + while (pac != null) { + result = pac.getName() + "." + result; + pac = pac.getESuperPackage(); + } + + // Append the base package name, if set + if (context != null) { + result = context.getBasePackageName() + "." + result; + } + return result; + } + + /** + * A variant of + * {@link #getFullyQualifiedEClassName(FluentAPIGenerationContext, EClass)} + * without a context object. This variant is meant for EClasses that do not + * belong to the fluent API. + */ + public static String getFullyQualifiedEClassName(EClass eCls) { + return getFullyQualifiedEClassName(null, eCls); + } + + /** + * Use {@code genTypeArgument == null} in order to generate a wildcard type + * argument. + * + * @return An EGenericType instance representing + * {@code genericType} + */ + public static EGenericType generateEGenericTypeWithTypeArgument(FluentAPIGenerationContext context, + Class genericType, EGenericType genTypeArgument) { + var pureGenType = createOrGetEDataType(context, genericType, + new ETypeParameter[] { FluentAPIGenerationUtil.generateETypeParameter("T") }); + var genType = FluentAPIGenerationUtil.generateEGenericTypeWithClassifier(pureGenType); + FluentAPIGenerationUtil.addTypeArgument(genType, genTypeArgument); + return genType; + } + + /** + * Use {@code colTypeArgument == null} in order to generate a wildcard type + * argument. + * + * @return An EGenericType instance representing + * {@code Collection} + */ + public static EGenericType generateCollectionTypeWithTypeArgument(FluentAPIGenerationContext context, + EGenericType colTypeArgument) { + return generateEGenericTypeWithTypeArgument(context, Collection.class, colTypeArgument); + } + + /** + * Use {@code colTypeArgument == null} in order to generate a wildcard type + * argument. + * + * @return An EGenericType instance representing + * {@code Collection} + */ + public static EGenericType generateCollectionTypeParameter(FluentAPIGenerationContext context, + EClassifier colTypeArgument) { + // Wrap the given EClassifier, if it is a primitive type + var colExtendsType = primitiveEClsToWrapperEClsMap.getOrDefault(colTypeArgument, colTypeArgument); + var colGenTypeArgument = colExtendsType != null + ? FluentAPIGenerationUtil.generateEGenericTypeWithBounds(null, + FluentAPIGenerationUtil.generateEGenericTypeWithClassifier(colExtendsType)) + : FluentAPIGenerationUtil.generateWildcardTypeArgument(); + return generateCollectionTypeWithTypeArgument(context, colGenTypeArgument); + } + + /** + * The type of the EParameter must be set separately. + * + * @param name Name of the EParameter + * @return An EParameter instance that considers a single object as value + */ + public static EParameter generateSingleValuedEParameter(String name) { + var param = EcoreFactory.eINSTANCE.createEParameter(); + param.setName(name); + param.setLowerBound(1); + param.setUpperBound(1); + return param; + } + + /** + * @param context The object encapsulating the context of fluent api generation. + * Needed to adapt the given type for EMF. + * @param name Name of the EParameter + * @param type Type of the EParameter + * @return An EParameter instance that considers a single instance of the given + * type as value + */ + public static EParameter generateSingleValuedEParameter(FluentAPIGenerationContext context, String name, + Class type) { + return generateSingleValuedEParameter(name, createOrGetEDataType(context, type)); + } + + /** + * @param name Name of the EParameter + * @param type Type of the EParameter + * @return An EParameter instance that considers a single instance of the given + * type as value + */ + public static EParameter generateSingleValuedEParameter(String name, EClassifier type) { + var param = generateSingleValuedEParameter(name); + param.setEType(type); + return param; + } + + /** + * @param name Name of the EParameter + * @param type Type of the EParameter + * @return An EParameter instance that considers a single instance of the given + * type as value + */ + public static EParameter generateSingleValuedEParameter(String name, EGenericType type) { + var param = generateSingleValuedEParameter(name); + param.setEGenericType(type); + return param; + } + + /** + * @param elem A given EMF element + * @param documentation The documentation to add + * @return elem + */ + public static T addDocumentation(T elem, String documentation) { + var anno = createOrGetEAnnotation(elem); + // Add the documentation + anno.getDetails().put(ModelConstants.GEN_MODEL_DOC_KEY.get(), documentation); + if (!elem.getEAnnotations().contains(anno)) + elem.getEAnnotations().add(anno); + return elem; + } + + /** + * @param elem A given EMF element + * @param docSource Another given EMF element, whose documentation will be + * copied and used in elem + * @return elem + */ + public static T useDocumentationOf(T elem, EModelElement docSource) { + if (!docSource.getEAnnotations().isEmpty()) { + var anno = docSource.getEAnnotations().get(0); + if (anno.getDetails().containsKey(ModelConstants.GEN_MODEL_DOC_KEY.get())) { + var doc = anno.getDetails().get(ModelConstants.GEN_MODEL_DOC_KEY.get()); + addDocumentation(elem, doc); + } + } + return elem; + } + + /** + * @param elem A given EMF element + * @return The documentation of elem. If there elem has no documentation, + * returns null. + */ + public static String getDocumentationOf(T elem) { + var genModelSourceURL = ModelConstants.GEN_MODEL_SOURCE_URL.get(); + var anno = elem.getEAnnotation(genModelSourceURL); + if (anno == null) { + return null; + } else { + return anno.getDetails().get(ModelConstants.GEN_MODEL_DOC_KEY.get()); + } + } + + /** + * @param elem A given EMF element + * @param typeParams Type parameters to add to elem + * @return elem + */ + public static T addTypeParameters(T elem, ETypeParameter... typeParams) { + if (typeParams != null) + for (var tp : typeParams) + elem.getETypeParameters().add(tp); + return elem; + } + + /** + * @param context The object encapsulating the context of fluent api generation. + * Needed to adapt the array type to EMF. + * @param name Name of the EParameter + * @param type Type of the EParameter + * @return An EParameter that takes an array of given type + */ + public static EParameter generateArrayValuedEParameter(FluentAPIGenerationContext context, String name, + EClassifier type) { + var arrayType = createOrGetArrayEDataType(context, type); + var param = EcoreFactory.eINSTANCE.createEParameter(); + param.setName(name); + param.setEType(arrayType); + param.setLowerBound(1); + param.setUpperBound(1); + return param; + } + + /** + * @param name Name of the EOperation + * @return An EOperation with the given name + */ + public static EOperation generateEOperation(String name) { + var op = EcoreFactory.eINSTANCE.createEOperation(); + op.setName(name); + return op; + } + + /** + * @param name Name of the EOperation + * @param returnType The return type of the EOperation + * @return An EOperation with the given name and return type + */ + public static EOperation generateEOperation(String name, EClassifier returnType) { + var op = EcoreFactory.eINSTANCE.createEOperation(); + op.setEType(returnType); + op.setName(name); + return op; + } + + /** + * @param name Name of the EOperation + * @param returnType The return type of the EOperation + * @return An EOperation with the given name and return type + */ + public static EOperation generateEOperation(String name, EGenericType returnType) { + var op = EcoreFactory.eINSTANCE.createEOperation(); + op.setEGenericType(returnType); + op.setName(name); + return op; + } + + /** + * @param typeParameterName The name of the ETypeParameter + * @return An ETypeParameter with the given name + */ + public static ETypeParameter generateETypeParameter(String typeParameterName) { + var typeParam = EcoreFactory.eINSTANCE.createETypeParameter(); + typeParam.setName(typeParameterName); + return typeParam; + } + + /** + * @return An EGenericType instance representing + * {@code genericType} + */ + public static EGenericType generateWildcardTypeArgument() { + return generateEGenericTypeWithBounds(null, null); + } + + /** + * The EClassifier ECls of the generated EGenericType must be set separately + *

    + *

    + * Do not use with {@code T = eStructuralFeature.getEGenericType()}, as it will + * move the type of the feature into the generated EGenericType instance. Use a + * fresh EGenericType that uses T as its EClassifier instead. + *

    + *

    + * {@code lowerBound = upperBound = null} will result in wildcard "?" + * + * @param lowerBound "T" in "? super T" + * @param upperBound "T" in "? extends T" + * @return An EGenericType instance representing a bounded generic type A, which + * is between lowerBound and upperBound in its type hierarchy. + */ + public static EGenericType generateEGenericTypeWithBounds(EGenericType lowerBound, EGenericType upperBound) { + var genericParamTypeForJavaClass = EcoreFactory.eINSTANCE.createEGenericType(); + genericParamTypeForJavaClass.setELowerBound(lowerBound); + genericParamTypeForJavaClass.setEUpperBound(upperBound); + return genericParamTypeForJavaClass; + } + + /** + * @param genericType "Type" in "Type<...>" + * @return An EGenericType instance representing {@code genericType<...>} + */ + public static EGenericType generateEGenericTypeWithClassifier(EClassifier genericType) { + var genericParamTypeForJavaClass = EcoreFactory.eINSTANCE.createEGenericType(); + genericParamTypeForJavaClass.setEClassifier(genericType); + return genericParamTypeForJavaClass; + } + + /** + * Similar to {@link #generateEGenericTypeWithClassifier(EClassifier)} but uses + * ETypeParameter instead of EClassifier. + * + * @param genericType "Type" in "Type<...>" + * @return An EGenericType instance representing {@code genericType<...>} + */ + public static EGenericType generateEGenericTypeWithTypeParameter(ETypeParameter typeParameter) { + var genericParamTypeForJavaClass = EcoreFactory.eINSTANCE.createEGenericType(); + genericParamTypeForJavaClass.setETypeParameter(typeParameter); + return genericParamTypeForJavaClass; + } + + /** + * @param genericType "Type" in "Type<...>" + * @param typeArguments "..." in "Type<...>" + * @return An EGenericType representing {@code genericType} + */ + public static EGenericType addTypeArgument(EGenericType genericType, EGenericType... typeArguments) { + if (typeArguments != null) { + for (var ta : typeArguments) + genericType.getETypeArguments().add(ta); + } + return genericType; + } + + /** + * @param elem A given EMF element + * @return Returns the EAnnotation with the source + * {@link ModelConstants.GEN_MODEL_SOURCE_URL} in elem. If non-existent, + * creates the EAnnotation first. + */ + private static EAnnotation createOrGetEAnnotation(EModelElement elem) { + EAnnotation anno = null; + var genModelSourceURL = ModelConstants.GEN_MODEL_SOURCE_URL.get(); + if (elem.getEAnnotation(genModelSourceURL) == null) { + anno = EcoreFactory.eINSTANCE.createEAnnotation(); + anno.setSource(genModelSourceURL); + } else { + anno = elem.getEAnnotation(genModelSourceURL); + } + return anno; + } + + /** + * Adds the "body" key to the EAnnotation of the given elem, usually + * EOperations. Using this operation on EOperations sets their method body. + * + * @param elem A given EMF element + * @param body The value of the "body" key + * @return elem + */ + public static T addBody(T elem, String body) { + var anno = createOrGetEAnnotation(elem); + // Add the body + anno.getDetails().put(ModelConstants.GEN_MODEL_BODY_KEY.get(), body); + if (!elem.getEAnnotations().contains(anno)) + elem.getEAnnotations().add(anno); + return elem; + } + + /** + * Adds the given params to the given op + * + * @param op A given EOperation + * @param params The EParameters of the EOperation + * @return op + */ + public static EOperation addEParameters(EOperation op, EParameter... params) { + if (params != null) { + for (var param : params) + op.getEParameters().add(param); + } + return op; + } + + /** + * @param parentPac The parent EPackage that will contain the generated + * EPackage + * @param subPackageName The name of the EPackage to generate (not its fully + * qualified name) + * @return The generated EPackage + */ + public static EPackage generateSubPackage(EPackage parentPac, String subPackageName) { + var pac = EcoreFactory.eINSTANCE.createEPackage(); + pac.setName(subPackageName); + pac.setNsPrefix(subPackageName); + + var nsUri = URI.createURI(parentPac.getNsURI()).appendSegment(subPackageName); + pac.setNsURI(nsUri.toString()); + parentPac.getESubpackages().add(pac); + return pac; + } + + /** + * @param context The object encapsulating the context of fluent api + * generation. Needed to adapt the given type for EMF. + * @param type A given type that will be adapted for EMF + * @param typeParameters ETypeParameter instances representing the type + * parameters of the given type + * @return An EDataType instance representing {@code type} + */ + public static EDataType createOrGetEDataType(FluentAPIGenerationContext context, Class type, + ETypeParameter[] typeParameters) { + var eDataTypeName = type.getSimpleName() + ModelConstants.EDATATYPE_WRAPPER_NAME_SUFFIX.get(); + EDataType eDataType = (EDataType) context.getPlaceholderEDataTypesPac().getEClassifier(eDataTypeName); + + if (eDataType == null) { + eDataType = EcoreFactory.eINSTANCE.createEDataType(); + eDataType.setSerializable(false); + eDataType.setName(eDataTypeName); + eDataType.setInstanceTypeName(eDataTypeName); + eDataType.setInstanceClassName(eDataTypeName); + eDataType.setInstanceClass(type); + + if (typeParameters != null) + for (var t : typeParameters) + eDataType.getETypeParameters().add(t); + + context.getPlaceholderEDataTypesPac().getEClassifiers().add(eDataType); + } + + return eDataType; + } + + /** + * @param context The object encapsulating the context of fluent api generation. + * Needed to adapt the given type for EMF. + * @param type A given type that will be adapted for EMF + * @return An EDataType that adapts type for EMF + */ + public static EDataType createOrGetEDataType(FluentAPIGenerationContext context, Class type) { + return createOrGetEDataType(context, type, null); + } + + /** + * @param context The object encapsulating the context of fluent api generation. + * Needed to adapt the array type for EMF. + * @param type A given EMF type, for which an array type will be found / + * generated + * @return An EDataType that represents an array of given type + */ + public static EDataType createOrGetArrayEDataType(FluentAPIGenerationContext context, EClassifier type) { + var arrayEDataTypeName = type.getName() + ModelConstants.EDATATYPE_ARRAY_WRAPPER_NAME_SUFFIX.get(); + var arrayTypeInstanceTypeName = type.getName() + ModelConstants.EDATATYPE_ARRAY_WRAPPER_TYPE_NAME_SUFFIX.get(); + EDataType arrayType = (EDataType) context.getPlaceholderEDataTypesPac().getEClassifier(arrayEDataTypeName); + + if (arrayType == null) { + arrayType = EcoreFactory.eINSTANCE.createEDataType(); + arrayType.setSerializable(false); + arrayType.setName(arrayEDataTypeName); + arrayType.setInstanceTypeName(arrayTypeInstanceTypeName); + arrayType.setInstanceClassName(arrayTypeInstanceTypeName); + // Get array type this way, since cls.arrayType() is introduced in Java 12 + arrayType.setInstanceClass(Array.newInstance(type.getInstanceClass(), 0).getClass()); + context.getPlaceholderEDataTypesPac().getEClassifiers().add(arrayType); + } + + return arrayType; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerator.java new file mode 100644 index 0000000000..29a4d5ea1e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIGenerator.java @@ -0,0 +1,116 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.List; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcoreFactory; + +import cipm.consistency.fluentapi.gen.init.FluentAPIInitialisationEClassGenerator; +import cipm.consistency.fluentapi.gen.rootapi.FluentAPIRootAPIEClassGenerator; +import cipm.consistency.fluentapi.gen.superinit.FluentAPISuperInitialisationEClassGenerator; + +/** + * The top-most generator class responsible for creating the EMF model of the + * fluent API in a hierarchical way by working with further generator classes. + *

    + *

    + * During EMF model generation, uses a {@link FluentAPIGenerationContext} object + * that stores the (so far) generated EMF model elements for the fluent API as + * well as further information. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerator { + /** + * Generates the fluent api model. + * + * @param context An object that contains state information on the fluent api + * generation + * @return A list of generated root packages, currently only one + */ + public List generateRootAPIPackages(FluentAPIGenerationContext context) { + // Generate fluent API packages + var rootPac = generateFluentAPIRootPackage(context); + + // Add fluent API packages into context + context.setRootPackage(rootPac); + context.setApiPackage(rootPac); + context.setInitsPackage(generateInitialisationsPackage(context)); + context.setPlaceholderEDataTypesPac(generatePlaceholderTypesPackage(context)); + + // Generate the facade class (without setting it up) and add it into context + var rootAPIEClassGen = new FluentAPIRootAPIEClassGenerator(); + context.setFluentAPIECls(rootAPIEClassGen.generateRootAPIEClass(context)); + context.getApiPackage().getEClassifiers().add(context.getFluentAPIECls()); + + // Generate the super initialisation class (without setting it up) and add it + // into context + var superInitEClassGen = new FluentAPISuperInitialisationEClassGenerator(); + context.setInitSuperECls(superInitEClassGen.generateSuperInitialisationEClass()); + context.getApiPackage().getEClassifiers().add(context.getInitSuperECls()); + + // Generate the super initialisation class (without setting them up) and add + // them into context + var initEClassesGen = new FluentAPIInitialisationEClassGenerator(); + initEClassesGen.generateFluentAPIInitialisationClasses(context); + context.getInitsPackage().getEClassifiers().addAll(context.getAllInitEClss()); + + // Setup the facade class (now that the classes required to do so exist) + rootAPIEClassGen.setupRootAPIEClass(context); + + // Setup the super initialisation class (now that the classes required to do so + // exist and are set up) + superInitEClassGen.setupSuperInitialisationEClass(context); + + // Setup the concrete initialisation classes (now that the classes required to + // do so exist and are set up) + context.getAllInitEClss().forEach((cls) -> { + initEClassesGen.setupFluentAPIInitialisationFor(cls, context.getElemToInitFor(cls), context); + cls.getESuperTypes().add(context.getInitSuperECls()); + }); + + return List.of(rootPac); + } + + /** + * Generates the top-most EPackage of the fluent api model. + * + * @param context An object that contains state information on the fluent api + * generation + * @return The root EPackage + */ + private EPackage generateFluentAPIRootPackage(FluentAPIGenerationContext context) { + var pac = EcoreFactory.eINSTANCE.createEPackage(); + var pacName = ModelConstants.ROOT_PACKAGE_NAME.get(); + var pacURI = URI.createURI(ModelConstants.ROOT_PACKAGE_URI + .getFor(context.getTargetMetamodelPackageProvider().getTargetMetamodelName())); + + pac.setName(pacName); + pac.setNsPrefix(pacName); + pac.setNsURI(pacURI.toString()); + + return pac; + } + + /** + * @param context An object that contains state information on the fluent api + * generation + * @return The EPackage that contains the generated Initialisation classes + */ + private EPackage generateInitialisationsPackage(FluentAPIGenerationContext context) { + return FluentAPIGenerationUtil.generateSubPackage(context.getApiPackage(), + ModelConstants.INITIALISATIONS_PACKAGE_NAME.get()); + } + + /** + * @param context An object that contains state information on the fluent api + * generation + * @return The EPackage that contains the generated EDataTypes, which wrap + * non-EMF types and adapt them + */ + private EPackage generatePlaceholderTypesPackage(FluentAPIGenerationContext context) { + return FluentAPIGenerationUtil.generateSubPackage(context.getApiPackage(), + ModelConstants.EDATATYPE_WRAPPERS_PACKAGE_NAME.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIMethodsUtil.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIMethodsUtil.java new file mode 100644 index 0000000000..ca4f0305cf --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIMethodsUtil.java @@ -0,0 +1,29 @@ +package cipm.consistency.fluentapi.gen; + +/** + * A utility class for generating method bodies for EOperations. + * + * @author Alp Torac Genc + */ +public class FluentAPIMethodsUtil { + private static final String semicolon = ";"; + + private static final String newLine = System.lineSeparator(); + private static final String endLine = semicolon + newLine; + + /** + * Constructs the method body consisting of the given lines. + * + * @param loc Lines of code (without semicolon and new line characters), where + * each line is a string + * @return The method body as a single string instance. + */ + public static String joinLOC(String... loc) { + var result = ""; + for (int i = 0; i < loc.length - 1; i++) { + result += loc[i] + endLine; + } + result += loc[loc.length - 1] + semicolon; + return result; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIParameterUtil.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIParameterUtil.java new file mode 100644 index 0000000000..3d15b39eb6 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/FluentAPIParameterUtil.java @@ -0,0 +1,51 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.List; + +import org.eclipse.emf.ecore.EOperation; + +/** + * Utility class for operations on EOperation parameters (EParameters) during + * fluent API generation. + * + * @author Alp Torac Genc + */ +public class FluentAPIParameterUtil { + /** + * @param op A given EOperation + * @return A string containing the names of op's EParameters, delimited with + * commas: {@code op_param1,op_param2,...,op_paramN} + */ + public static String getSerialisedParametersFor(EOperation op) { + return String.join(",", op.getEParameters().stream().map((p) -> p.getName()).toArray(String[]::new)); + } + + /** + * Use to check, whether EOperation signatures are duplicated in a given set of + * EOperations. + * + * @param allOps EOperations, among which matching signatures will + * be sought (must not contain opToCheckForClashes + * itself) + * @param opToCheckForClashes The EOperation, for which EOperations with + * matching signatures will be sought + * @return Whether the signature of opToCheckForClashes is duplicated within + * allOps. + */ + public static boolean hasClashingMethods(List allOps, EOperation opToCheckForClashes) { + return allOps.stream() + // Look for EOperations with the same name + .filter((op) -> op.getName().equals(opToCheckForClashes.getName())) + // Look for EOperations with the same parameter count + .filter((op) -> op.getEParameters().size() == opToCheckForClashes.getEParameters().size()) + // Look for EOperations with the same parameter names + .anyMatch((op) -> op.getEParameters().stream() + // Check equity of all parameters + .allMatch((opParam) -> opToCheckForClashes.getEParameters().stream().anyMatch((clashOpParam) -> + // Check parameter name equity + opParam.getName().equals(clashOpParam.getName()) && + // Check parameter type equity + opParam.getEType().getInstanceClass() + .equals(clashOpParam.getEType().getInstanceClass())))); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFeatureTemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFeatureTemplate.java new file mode 100644 index 0000000000..f21fbaa0a6 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFeatureTemplate.java @@ -0,0 +1,101 @@ +package cipm.consistency.fluentapi.gen; + +import org.apache.commons.lang.StringUtils; + +/** + * A template interface for EMF feature names that provides utility methods for + * generating common getter and setter patterns. + *

    + * This interface is primarily used in the fluent API code generation to + * generate proper method calls for features stored within the generated model. + * + * @see IFluentAPITemplate + * + * @author Alp Torac Genc + */ +public interface IFluentAPIFeatureTemplate extends IFluentAPITemplate { + /** + * Prefix for generating "this" references + */ + static final String THIS_PREFIX = "this."; + /** + * Prefix for getter method names + */ + static final String GETTER_PREFIX = "get"; + /** + * Prefix for setter method names + */ + static final String SETTER_PREFIX = "set"; + + /** + * Direct access to the feature of "this" + * + * @return {@code this.featureName} + */ + public default String inThis() { + return THIS_PREFIX + this.get(); + } + + /** + * The (supposed) name of the getter function of the feature. + * + * @return {@code getFeatureName} + */ + public default String getter() { + return GETTER_PREFIX + StringUtils.capitalize(this.get()); + } + + /** + * The (supposed) name of the setter function of the feature. + * + * @return {@code this.setFeatureName} + */ + public default String setter() { + return SETTER_PREFIX + StringUtils.capitalize(this.get()); + } + + /** + * Combines the feature's getter function with method call syntax. + * + * @return {@code .getFeatureName()} + */ + public default String getterCall() { + return "." + getter() + "()"; + } + + /** + * Combines the feature's setter function with method call syntax and the given + * argument. + * + * @return {@code .setFeatureName(param)} + */ + public default String setterCall(String param) { + return "." + setter() + "(" + param + ")"; + } + + /** + * Combines the feature's getter function with method call syntax on "this". + * + * @return {@code this.getFeatureName()} + */ + public default String thisGetterCall() { + return "this" + getterCall(); + } + + /** + * Combines the feature's setter function with method call syntax and the given + * argument on "this". + * + * @return {@code this.setFeatureName(param)} + */ + public default String thisSetterCall(String param) { + return "this" + setterCall(param); + } + + /** + * @return {@link #get()} with the first letter capitalised. + */ + public default String getCapitalised() { + return StringUtils.capitalize(this.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFillableTemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFillableTemplate.java new file mode 100644 index 0000000000..dd344a7c45 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIFillableTemplate.java @@ -0,0 +1,64 @@ +package cipm.consistency.fluentapi.gen; + +/** + * A template interface for strings that contain string formatting flags that + * should be filled with parameter values. + * + * @see {@link IFluentAPITemplate} + * @see {@link String#format(String, Object...)} + * + * @author Alp Torac Genc + */ +public interface IFluentAPIFillableTemplate extends IFluentAPITemplate { + /** + * Fills the template's string formatting flags with the given parameters. + * + * @param params The parameters to plug to the stored template, in order to fill + * in the flags. Superfluous parameters will be ignored. + * @return The string filled with the given params + * + * @see {@link String#format(String, Object...)} + */ + public String getFor(Object... params); + + /** + * Returns the template string with all format specifiers replaced by empty + * strings. + * + * @return The template string with all specifiers replaced by empty strings + */ + public String getEmpty(); + + /** + * Combines the template's filled value with method call syntax. + * + * @param templateParams The parameters to fill the template's flags with. + * Superfluous parameters will be ignored. + * @param methodParams The parameters to include in the method call as + * arguments + * @return {@code .filledTemplate(methodParams0, methodParams1, ..., methodParamsN-1)} + */ + public default String callFor(Object[] templateParams, String... methodParams) { + var callRoot = "." + getFor(templateParams); + var args = "("; + + if (methodParams != null && methodParams.length > 0) { + args += String.join(",", methodParams); + } + + return callRoot + args + ")"; + } + + /** + * Combines the template's filled value with method call syntax to "this". + * + * @param templateParams The parameters to fill the template's flags with. + * Superfluous parameters will be ignored. + * @param methodParams The parameters to include in the method call as + * arguments + * @return {@code this.filledTemplate(methodParams0, methodParams1, ..., methodParamsN-1)} + */ + public default String thisCallFor(Object[] templateParams, String... methodParams) { + return "this" + callFor(templateParams, methodParams); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIMethodGenerator.java new file mode 100644 index 0000000000..8d96a3b586 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPIMethodGenerator.java @@ -0,0 +1,20 @@ +package cipm.consistency.fluentapi.gen; + +import java.util.Map; + +/** + * An interface meant to be implemented by generators of EOperations that + * represent methods of classes within the fluent API model. Implemented to + * facilitate generating overviews of methods for each fluent API class. + * + * @author Alp Torac Genc + */ +public interface IFluentAPIMethodGenerator { + /** + * @return A map containing (method name, method description) pairs that this + * method generator creates. Note that method descriptions are only + * meant for methods, which are intended to be used from the outside the + * class declaring it. + */ + public Map getMethodNamesToDescriptions(); +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPITemplate.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPITemplate.java new file mode 100644 index 0000000000..e93e460bde --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/IFluentAPITemplate.java @@ -0,0 +1,62 @@ +package cipm.consistency.fluentapi.gen; + +/** + * Base interface for template classes that hold string templates used in fluent + * API code generation. + *

    + * Templates represent string patterns (such as; method names, method calls, + * documentation fragments, EMF feature names) that can be retrieved and + * combined to form complete code strings. The template string itself is + * obtained via {@link #get()}, while the helper methods + * {@link #call(String...)} and {@link #thisCall(String...)} facilitate + * composing method invocation strings by appending the template to a + * dot-prefixed method call with optional parameters. + *

    + * There are two kinds of templates: + *

      + *
    • Fixed templates ({@link FluentAPIFixTemplate}): Holds a template + * as a string that does not require any further parameters
    • + *
    • Fillable templates ({@link IFluentAPIFillableTemplate}): Holds a + * (partial) template as a string, which requires further parameters to be + * complete
    • + *
    + * + * @author Alp Torac Genc + */ +public interface IFluentAPITemplate { + /** + * @return The raw template string, may contain string formatting flags + * @see {@link String#format(String, Object...)} + */ + public String get(); + + /** + * Combines the template with method call syntax, uses the given string + * parameters as its arguments. + * + * @param params Method call parameters, can be null if the method should take + * no arguments + * @return {@code .storedTemplate(param0, param1, ..., paramN-1)} + */ + public default String call(String... params) { + var callRoot = "." + get(); + var args = "("; + + if (params != null && params.length > 0) { + args += String.join(",", params); + } + + return callRoot + args + ")"; + } + + /** + * Combines the template with a "this" prefix and method call syntax. + * + * @param params Method call parameters, can be null if the method should take + * no arguments + * @return {@code this.storedTemplate(param0, param1, ..., paramN-1)} + */ + public default String thisCall(String... params) { + return "this" + call(params); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/ModelConstants.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/ModelConstants.java new file mode 100644 index 0000000000..84146d10aa --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/ModelConstants.java @@ -0,0 +1,762 @@ +package cipm.consistency.fluentapi.gen; + +import org.apache.commons.lang.StringUtils; + +/** + * Contains all constants associated with the fluent API. All of the constant + * templates are stored within {@link IFluentAPITemplate} instances, where some + * templates are fixed (meaning that their values are not changeable) and some + * templates are fillable with parameters ({@link IFluentAPIFillableTemplate}). + * For fillable templates, refer to their JavaDoc to find out what (String) + * parameters they need. + * + * @author Alp Torac Genc + */ +public class ModelConstants { + /** + * The URI of the root package of the fluent API, which contains the fluent API + * class. + *

    + * %s: Metamodel name (lower case) + */ + public static final IFluentAPIFillableTemplate ROOT_PACKAGE_URI = new FluentAPIFillableTemplate( + "https://CIPM-tools.github.io/metamodels/fluentapi/1.0/%s/api"); + + /** + * The base package name for the fluent API. Note that the base packages will + * not be explicitly generated during the generation process. + *

    + * %s: Metamodel name (lower case) + */ + public static final IFluentAPIFillableTemplate BASE_PACKAGE_NAME = new FluentAPIFillableTemplate( + "cipm.consistency.fluentapi.%s"); + /** + * The name of the root package of the fluent API (does not include its + * namespaces). This is where the fluent API class (facade of the fluent API) + * will be located. + * + * @see {@link #BASE_PACKAGE_NAME} + * @see {@link #FULL_ROOT_PACKAGE_NAME} + */ + public static final IFluentAPITemplate ROOT_PACKAGE_NAME = new FluentAPIFixTemplate("api"); + /** + * The name of the root package of the fluent API (includes its namespaces). + * This is where the fluent API class (facade of the fluent API) will be + * located. + *

    + * %s: Metamodel name (lower case) + * + * @see {@link #BASE_PACKAGE_NAME} + * @see {@link #ROOT_PACKAGE_NAME} + */ + public static final IFluentAPIFillableTemplate FULL_ROOT_PACKAGE_NAME = new FluentAPIFillableTemplate( + BASE_PACKAGE_NAME.get() + "." + ROOT_PACKAGE_NAME.get()); + + /** + * The name of the package, which will contain the Initialisation classes. This + * package is nested under {@link #ROOT_PACKAGE_NAME}. + */ + public static final IFluentAPITemplate INITIALISATIONS_PACKAGE_NAME = new FluentAPIFixTemplate("inits"); + + /** + * An indicator in operation names in fluent api, signaling that the method can + * be used for arbitrary types of the metamodel the fluent api is meant for. + */ + public static final IFluentAPITemplate PLACEHOLDER = new FluentAPIFixTemplate("x"); + /** + * The "body" key of the EAnnotation, whose value stands for the method body in + * EOperations + */ + public static final IFluentAPITemplate GEN_MODEL_BODY_KEY = new FluentAPIFixTemplate("body"); + /** + * The "documentation" key of the EAnnotation, whose value stands for the + * documentation of the EMF elements of the fluent api model + */ + public static final IFluentAPITemplate GEN_MODEL_DOC_KEY = new FluentAPIFixTemplate("documentation"); + /** + * The source of the EAnnotation that is used in EMF elements of the fluent api + * model + */ + public static final IFluentAPITemplate GEN_MODEL_SOURCE_URL = new FluentAPIFixTemplate( + "http://www.eclipse.org/emf/2002/GenModel"); + /** + * The suffix in the names of classes in fluent api, which are responsible for + * creating / modifying elements of the metamodel the fluent api is meant for. + */ + public static final IFluentAPITemplate INITIALISATION_NAME_SUFFIX = new FluentAPIFixTemplate("Initialisation"); + + /** + * The suffix, which EDataTypes for array-types will get in their name + */ + public static final IFluentAPITemplate EDATATYPE_ARRAY_WRAPPER_NAME_SUFFIX = new FluentAPIFixTemplate("Array"); + /** + * The suffix, which type names in EDataTypes for array-types will get + */ + public static final IFluentAPITemplate EDATATYPE_ARRAY_WRAPPER_TYPE_NAME_SUFFIX = new FluentAPIFixTemplate("[]"); + /** + * The name suffix, which EDataTypes adapting non-EMF types for EMF will have + */ + public static final IFluentAPITemplate EDATATYPE_WRAPPER_NAME_SUFFIX = new FluentAPIFixTemplate( + "EDataTypePlaceholder"); + /** + * The name of the package, which contains the EDataTypes adapting non-EMF types + * for EMF + */ + public static final IFluentAPITemplate EDATATYPE_WRAPPERS_PACKAGE_NAME = new FluentAPIFixTemplate( + "placeholderTypes"); + + /** + * An inner class for recurring EParameters within the fluent api model + * + * @author Alp Torac Genc + */ + public static class GeneralParameters { + public static final IFluentAPITemplate USED_EOBJECT_PARAMETER_NAME = new FluentAPIFixTemplate("eobj"); + public static final IFluentAPITemplate USED_EOBJECT_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The EObject that this method will use"); + + public static final IFluentAPITemplate MODIFIED_FEATURE_PARAMETER_NAME = new FluentAPIFixTemplate( + "featToModify"); + public static final IFluentAPITemplate MODIFIED_FEATURE_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The feature, whose value in " + MODIFIED_FEATURE_PARAMETER_NAME.get() + " will be modified"); + + public static final IFluentAPITemplate FEATURE_VALUE_PARAMETER_NAME = new FluentAPIFixTemplate("featVal"); + public static final IFluentAPITemplate FEATURE_VALUE_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The value of the feature, which will be used to modify the given feature in certain ways, denoted by the method name. If " + + FEATURE_VALUE_PARAMETER_NAME.get() + + " is an array or collection, its contents will be used as feature values instead."); + + public static final IFluentAPITemplate MARK_VALUE_PARAMETER_NAME = new FluentAPIFixTemplate("markVal"); + public static final IFluentAPITemplate MARK_VALUE_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The object that is / will be marked / unmarked."); + + public static final IFluentAPITemplate MARK_KEY_PARAMETER_NAME = new FluentAPIFixTemplate("markKey"); + public static final IFluentAPITemplate MARK_KEY_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The object instance (key), whose memory address is serving / will serve as a key in mark-related operations. Note that the contents of the key are fully irrelevant here, only its memory address matters. A key may only mark one element at a time, attempting to use the same key in multiple marks will override its previous mark. If the key is array-typed or collection-typed, it will be interpret as a container of keys and its contents will be used instead. To bypass this behaviour and use the key (the array / collection) itself, nest the key in another array or collection."); + + public static final Class WAIT_FOR_MARK_TASK_CLASS = Runnable.class; + public static final IFluentAPITemplate WAIT_FOR_MARK_TASK_PARAMETER_NAME = new FluentAPIFixTemplate("task"); + public static final IFluentAPITemplate WAIT_FOR_MARK_TASK_PARAMETER_NAME_DOC = new FluentAPIFixTemplate( + "The model construction task, which will be executed upon the given " + MARK_KEY_PARAMETER_NAME.get() + + "(s) getting used in marks."); + + public static final IFluentAPITemplate ARBITRARY_ECLASS_PARAMETER_NAME = new FluentAPIFixTemplate("eObjEClass"); + public static final IFluentAPITemplate ARBITRARY_ECLASS_PARAMETER_DOC = new FluentAPIFixTemplate( + "The EClass of the element"); + public static final IFluentAPITemplate ARBITRARY_CLASS_PARAMETER_NAME = new FluentAPIFixTemplate("eObjCls"); + public static final IFluentAPITemplate ARBITRARY_CLASS_PARAMETER_DOC = new FluentAPIFixTemplate( + "The class of the element"); + + public static final IFluentAPITemplate TYPE_PARAMETER_NAME = new FluentAPIFixTemplate("T"); + } + + private static String getMethodName(Class cls) { + return StringUtils.uncapitalize(cls.getSimpleName()); + } + + private static String getFeatureName(Class cls) { + return StringUtils.uncapitalize(cls.getSimpleName()); + } + + private static String getTopMethodName(Class cls) { + return getMethodName(cls) + StringUtils.capitalize(ModelConstants.PLACEHOLDER.get()); + } + + /** + * Aggregates constants of the fluent api class. Inner classes are the names of + * the methods of the fluent api class. + *

    + *

    + * The fluent api class serves as the facade of the fluent api and provides + * methods to ease construction of models of the metamodel that the fluent api + * is generated for. + * + * @author Alp Torac Genc + */ + public static class FluentAPI { + /** + * %s: Metamodel name (capitalised) + */ + public static final IFluentAPIFillableTemplate CLASS_NAME = new FluentAPIFillableTemplate("Fluent%sAPI"); + + public static final IFluentAPIFillableTemplate CLASS_DOC = new FluentAPIFillableTemplate("

    " + + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + + " is at the center of the fluent API and enables creation of EObject sub-types within the EMF-based metamodel MM this API targets. To this end, this class offers various methods that lead to underlying " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " classes, each being responsible for a concrete element from MM. For more information on what individual EObject sub-types and their features represent, refer to MM's documentation." + + FluentAPIDocumentationUtil.getClassMethodOverviewIntroTemplate()); + + public static class Continue { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate( + getMethodName(Continue.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate( + getTopMethodName(Continue.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate("Delegates to the last " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " instance for the element of a certain type, allowing the ongoing element creation to continue"); + } + + public static class ContinueMarked { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate( + getMethodName(ContinueMarked.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate( + getTopMethodName(ContinueMarked.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Delegates to the " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " instance for the element of a certain type, which has been marked with the given " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " , allowing the ongoing element creation to continue"); + } + + public static class CreateNew { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate( + getMethodName(CreateNew.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate( + getTopMethodName(CreateNew.class)); + + /** + * %s: Class name + *

    + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate SUMMARY = new FluentAPIFillableTemplate( + "Creates a minimal, fully uninitialised %s instance, in order to compact the following into a single method call: " + + ModelConstants.FluentAPI.New.NAME.thisCallFor(new String[] { "%s" }) + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + } + + public static class DropInitialisation { + public static final IFluentAPITemplate INITIALISATION_PARAMETER_NAME = new FluentAPIFixTemplate( + "initToDrop"); + public static final IFluentAPITemplate INITIALISATION_PARAMETER_DOC = new FluentAPIFixTemplate( + "The " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " instance to drop from ongoing " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + "s"); + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(DropInitialisation.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Removes the given " + INITIALISATION_PARAMETER_NAME.get() + " from the list of ongoing " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + "s, meaning that " + + INITIALISATION_PARAMETER_NAME.get() + " will no longer be retrievable from the api."); + } + + public static class GetAllSupportedClasses { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(GetAllSupportedClasses.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Returns a list of all Classes that this " + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + + " instance supports"); + } + + public static class GetInitialisationFor { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(GetInitialisationFor.class) + + StringUtils.capitalize(ModelConstants.PLACEHOLDER.get())); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate("Returns an instance of " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " matching the given parameter, which can be used to create or modify a certain element. Not intended to be called directly from outside under normal circumstances"); + } + + public static class GetMarked { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate( + getMethodName(GetMarked.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate( + getTopMethodName(GetMarked.class)); + public static final IFluentAPIFillableTemplate SUMMARY = new FluentAPIFillableTemplate( + "Returns the element of a certain type, which has been marked with the given " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get()); + } + + public static class Unmark { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(Unmark.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Removes the given " + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + "'s marking, does not modify the (formerly) marked object."); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate(FluentAPIDocumentationUtil + .appendToDocumentationStart(SUMMARY.get()) + "Removes any associations between the given " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " and its corresponding EObject obj. Doing so unmarks obj, meaning that " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " can no longer be used to retrieve obj. Does nothing, if this API instance did not mark obj with " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + "."); + } + + public static class Modify { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate(getMethodName(Modify.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate(getTopMethodName(Modify.class)); + public static final IFluentAPIFillableTemplate SUMMARY = new FluentAPIFillableTemplate( + "Returns a matching " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " instance for the given " + + ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get() + + ", which can be used to modify it"); + } + + public static class ModifyMarked { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate( + getMethodName(ModifyMarked.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate( + getTopMethodName(ModifyMarked.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Returns a matching " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " instance for the element marked with the given " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + ", which can be used to modify the marked element further"); + } + + public static class New { + public static final IFluentAPITemplate NAME_PREFIX = new FluentAPIFixTemplate(getMethodName(New.class)); + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + NAME_PREFIX.get() + "%s"); + public static final IFluentAPITemplate TOP_NAME = new FluentAPIFixTemplate(getTopMethodName(New.class)); + + public static final IFluentAPITemplate FEATURE_VALUE_PARAMETER_NAME = new FluentAPIFixTemplate("featVal"); + /** + * %s: Class name + *

    + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate FEATURE_VALUE_PARAMETER_DOC = new FluentAPIFillableTemplate( + "The value of the feature '%s.%s'. If " + FEATURE_VALUE_PARAMETER_NAME.get() + + " is an array or collection, its contents will be used as the feature values instead."); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Returns a matching " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " instance, which can be used to create an element of a certain type from scratch"); + } + + public static class WaitForMark { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(WaitForMark.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Postphones certain model construction steps till the given mark(s) (" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ") exist."); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + "Allows specifying model construction steps as a " + + ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_CLASS.getSimpleName() + + " instance, which this " + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + + " will execute after the given " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + "(s) have been used to mark objects. This method enables preserving the flow of model construction by enabling the specification of construction steps on objects that may not yet exist. The main purpose of this method is to facilitate model constructions, where dependencies between model elements either forcefully require bottom-up approaches or require mixing the construction of several model elements, which depend on one another (especially circular dependencies)."); + } + + public static class WithFeat { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + ModelConstants.PLACEHOLDER.get() + WithFeat.class.getSimpleName()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Sets the given value of a certain single-valued EStructuralFeature for a certain EObject."); + } + + public static class WithoutFeat { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + ModelConstants.PLACEHOLDER.get() + WithoutFeat.class.getSimpleName()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Unsets the value of a certain single-valued EStructuralFeature for a certain EObject."); + } + + public static class WithAddedFeat { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + ModelConstants.PLACEHOLDER.get() + WithAddedFeat.class.getSimpleName()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Adds the given value(s) to a certain many-valued EStructuralFeature for a certain EObject."); + } + + public static class WithRemovedFeat { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + ModelConstants.PLACEHOLDER.get() + WithRemovedFeat.class.getSimpleName()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Removes the given value(s) from a certain many-valued EStructuralFeature for a certain EObject."); + } + + public static class CleanFeat { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + ModelConstants.PLACEHOLDER.get() + CleanFeat.class.getSimpleName()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Removes all values of a certain many-valued EStructuralFeature for a certain EObject."); + } + + public static class Mark { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(Mark.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Marks the given " + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + " with " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ", does not modify " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + "."); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(SUMMARY.get()) + "Associates the given " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + " with " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + ". Doing so marks the given " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + ", meaning that using " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " in mark-related operations will result in retrieving " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + ". Note that " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " can only mark one object (" + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + ") at a time. Using " + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + + " in another mark will override its previous mark."); + } + + public static class GetOngoingInitialisations { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(GetOngoingInitialisations.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Returns an unmodifiable list of all ongoing " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + ", i.e. all non-finished element constructions. Note that all " + + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + + " instances have access to the same list of ongoing " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get()); + } + + public static class ClearAllOngoingInitialisations { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(ClearAllOngoingInitialisations.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Clears all ongoing " + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + ", i.e. all non-finished element constructions. This is equivalent to calling " + + ModelConstants.FluentAPI.DropInitialisation.NAME.get() + " on all ongoing " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + "s"); + } + } + + /** + * Aggregates constants of the abstract (super) initialisation class. Inner + * classes are the names of the methods of the abstract (super) initialisation + * class. + *

    + *

    + * The super initialisation class is the parent class of all initialisation + * classes. + * + * @author Alp Torac Genc + */ + public static class SuperInitialisation { + public static final IFluentAPITemplate CLASS_NAME = new FluentAPIFixTemplate("FluentAPISuperInitialisation"); + + public static final IFluentAPIFillableTemplate CLASS_DOC = new FluentAPIFillableTemplate("The top-most " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " class, which all concrete " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " classes extend. Contains various methods that facilitate the programmatic construction of model object instances." + + FluentAPIDocumentationUtil.getClassMethodOverviewIntroTemplate()); + + /** + * Aggregates constants of the reference to the fluent api class from within the + * super initialisation class. + * + * @author Alp Torac Genc + */ + public static class RootAPI { + public static final IFluentAPIFeatureTemplate NAME = new FluentAPIFeatureTemplate( + getFeatureName(RootAPI.class)); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + "The " + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + ", which created this"); + } + + /** + * Aggregates constants of the reference to the current element from within the + * super initialisation class. The current element is the element, which the + * (super) initialisation instance is currently creating / modifying. + * + * @author Alp Torac Genc + */ + public static class CurrentElement { + public static final IFluentAPIFeatureTemplate NAME = new FluentAPIFeatureTemplate( + getFeatureName(CurrentElement.class)); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate("The element this " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " is currently creating or modifying"); + } + + public static class ToAPI { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(ToAPI.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Swaps to " + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + ", usually the " + + ModelConstants.FluentAPI.CLASS_NAME.getEmpty() + " that created this."); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(SUMMARY.get()) + "Swaps from this to " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + + ". This method is currently the same as this" + + ModelConstants.SuperInitialisation.RootAPI.NAME.getterCall() + + ". Its purpose is to isolate the use of this" + + ModelConstants.SuperInitialisation.RootAPI.NAME.getterCall() + + ", which is a getter method that is generated by EMF."); + } + + public static class Drop { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(Drop.class)); + } + + public static class GetInitialisedEClass { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + getMethodName(GetInitialisedEClass.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate("Returns the EClass that this " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " is meant for"); + } + + public static class NewElement { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(NewElement.class)); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Creates a minimal instance of the targeted type within this " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " instance and sets it as the value of " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + "."); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate(FluentAPIDocumentationUtil + .appendToDocumentationStart(SUMMARY.get()) + + "Creates a minimal EObject instance, without modifying any of its features, and sets it as the value of " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + " in concrete " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + " classes. Does nothing in " + + ModelConstants.SuperInitialisation.CLASS_NAME.get() + "."); + } + + public static class Mark extends FluentAPI.Mark { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + FluentAPI.Mark.NAME.get() + CurrentElement.NAME.getCapitalised()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(FluentAPI.Mark.SUMMARY.get()) + + "Delegates to " + ModelConstants.SuperInitialisation.RootAPI.NAME.get() + " and " + + ModelConstants.FluentAPI.Mark.NAME.get() + "s " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.get()); + } + + public static class Unmark extends FluentAPI.Unmark { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate( + FluentAPI.Unmark.NAME.get() + CurrentElement.NAME.getCapitalised()); + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(FluentAPI.Unmark.SUMMARY.get()) + + "Delegates to " + ModelConstants.SuperInitialisation.RootAPI.NAME.get() + " and " + + ModelConstants.FluentAPI.Unmark.NAME.get() + "s " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.get()); + } + + public static class Reset { + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(Reset.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Sets " + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + + " (the element that this is currently building) to null"); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(SUMMARY.get()) + "Discards " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + ". Does not " + + ModelConstants.FluentAPI.DropInitialisation.NAME.get() + " this from " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + + ", meaning that it will still be accessible via " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + + ". This can then be re-used by calling " + + ModelConstants.SuperInitialisation.NewElement.NAME.thisCall()); + } + + public static class CreateNow { + public static final IFluentAPITemplate CLASS_PARAMETER_NAME = new FluentAPIFixTemplate("returnTypeCls"); + public static final IFluentAPITemplate NAME = new FluentAPIFixTemplate(getMethodName(CreateNow.class)); + + public static final IFluentAPITemplate SUMMARY = new FluentAPIFixTemplate( + "Finalises and returns " + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis()); + public static final IFluentAPITemplate DOC = new FluentAPIFixTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart(SUMMARY.get()) + + "Finalises the construction of " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + + " and returns it. " + ModelConstants.FluentAPI.DropInitialisation.NAME.get() + + "s this from " + ModelConstants.SuperInitialisation.ToAPI.NAME.thisCall() + + ", meaning that this will no longer be accessible from " + + ModelConstants.SuperInitialisation.ToAPI.NAME.thisCall()); + + public static final IFluentAPITemplate CLASS_PARAMETER_DOC = new FluentAPIFixTemplate(DOC.get() + + FluentAPIDocumentationUtil.getDocParagraphSeparator() + + "EMF-based metamodels consider interfaces, which allow diamond structures in the type hierarchy of their implementors. To spare type casting in model construction, this method can be given a class parameter, to which the returned value will be cast."); + } + } + + /** + * Aggregates common constants of the initialisation classes. Inner classes are + * the names of the methods of the initialisation class. + *

    + *

    + * Initialisation classes aid in the creation and modification of model elements + * of the metamodel that the fluent api is meant for. + * + * @author Alp Torac Genc + */ + public static class Initialiation { + /** + * %s: Class name (capitalised) + */ + public static final IFluentAPIFillableTemplate CLASS_NAME = new FluentAPIFillableTemplate( + "%s" + ModelConstants.INITIALISATION_NAME_SUFFIX.get()); + + /** + * %s: Initialised class name + *

    + * %s: Metamodel name + *

    + * %s: Initialised class name + *

    + * %s: Serialised method names and summaries + */ + public static final IFluentAPIFillableTemplate CLASS_DOC = new FluentAPIFillableTemplate("An " + + ModelConstants.INITIALISATION_NAME_SUFFIX.get() + + " class that targets the type '%s' within the '%s' metamodel. Contains various methods that facilitate the programmatic construction of '%s' instances." + + FluentAPIDocumentationUtil.getClassMethodOverviewIntroTemplate()); + + public static class NewElement extends ModelConstants.SuperInitialisation.NewElement { + /** + * %s: Class name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil + .appendToDocumentationStart(ModelConstants.SuperInitialisation.NewElement.SUMMARY.get()) + + "Creates a minimal %s instance, without modifying any of its features, and sets it as " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis()); + } + + public static class With { + public static final IFluentAPITemplate PARAMETER_NAME = new FluentAPIFixTemplate("newFeatVal"); + /** + * %s: Feature name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + getMethodName(With.class) + "%s"); + + public static final IFluentAPITemplate SUMMARY = ModelConstants.FluentAPI.WithFeat.SUMMARY; + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil.appendToDocumentationStart( + ModelConstants.FluentAPI.WithFeat.SUMMARY.get()) + "Sets the value of the feature '%s' in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + " to the given value."); + + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate PARAMETER_DOC = new FluentAPIFillableTemplate( + "The new value of the feature '%s', which will replace its current value in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis()); + } + + public static class Without { + /** + * %s: Feature name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + getMethodName(Without.class) + "%s"); + + public static final IFluentAPITemplate SUMMARY = ModelConstants.FluentAPI.WithoutFeat.SUMMARY; + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil + .appendToDocumentationStart(ModelConstants.FluentAPI.WithoutFeat.SUMMARY.get()) + + "Unsets the value of the feature '%s' in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + + ", which sets its value to null."); + } + + public static class WithAdded { + public static final IFluentAPITemplate PARAMETER_NAME = new FluentAPIFixTemplate("featValToAdd"); + /** + * %s: Feature name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + getMethodName(WithAdded.class) + "%s"); + + public static final IFluentAPITemplate SUMMARY = ModelConstants.FluentAPI.WithAddedFeat.SUMMARY; + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil + .appendToDocumentationStart(ModelConstants.FluentAPI.WithAddedFeat.SUMMARY.get()) + + "Adds the given values to the current values of the feature '%s' in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + "."); + + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate PARAMETER_DOC = new FluentAPIFillableTemplate( + "Value(s) for the feature '%s', which will be added to its current values in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + "."); + } + + public static class WithRemoved { + public static final IFluentAPITemplate PARAMETER_NAME = new FluentAPIFixTemplate("featValToRemove"); + /** + * %s: Feature name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + getMethodName(WithRemoved.class) + "%s"); + + public static final IFluentAPITemplate SUMMARY = ModelConstants.FluentAPI.WithRemovedFeat.SUMMARY; + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil + .appendToDocumentationStart(ModelConstants.FluentAPI.WithRemovedFeat.SUMMARY.get()) + + "Removes the given values from the current values of the feature '%s' in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis()); + + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate PARAMETER_DOC = new FluentAPIFillableTemplate( + "Value(s) for the feature '%s', which will be removed from its current values in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis() + "."); + } + + public static class Clean { + /** + * %s: Feature name (capitalised) + */ + public static final IFluentAPIFillableTemplate NAME = new FluentAPIFillableTemplate( + getMethodName(Clean.class) + "%s"); + + public static final IFluentAPITemplate SUMMARY = ModelConstants.FluentAPI.CleanFeat.SUMMARY; + /** + * %s: Feature name + */ + public static final IFluentAPIFillableTemplate DOC = new FluentAPIFillableTemplate( + FluentAPIDocumentationUtil + .appendToDocumentationStart(ModelConstants.FluentAPI.CleanFeat.SUMMARY.get()) + + "Clears all values of the (many-valued) feature '%s' in " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.inThis()); + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationEClassGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationEClassGenerator.java new file mode 100644 index 0000000000..57f1703fbc --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationEClassGenerator.java @@ -0,0 +1,118 @@ +package cipm.consistency.fluentapi.gen.init; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EcoreFactory; + +import cipm.consistency.fluentapi.gen.FluentAPIDocumentationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; +import cipm.consistency.fluentapi.gen.superinit.FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator; +import cipm.consistency.fluentapi.gen.superinit.FluentAPISuperInitialisationResetOperationGenerator; +import cipm.consistency.fluentapi.gen.superinit.FluentAPISuperInitialisationToAPIMethodGenerator; + +/** + * The generator class responsible for generating the EClasses, which represent + * the parts (Initialisation classes) of the fluent api that are responsible for + * creating / modifying model elements of the metamodel, which the fluent api is + * generated for. This generator class will only generate the Initialisation + * classes' EClasses and their EOperations, nothing else. Therefore, this + * generator class alone is not enough to generate the fluent api model. + *

    + *

    + * Note that both methods + * {@link #generateFluentAPIInitialisationClasses(FluentAPIGenerationContext)} + * and + * {@link #setupFluentAPIInitialisationFor(EClass, EClass, FluentAPIGenerationContext)} + * have to be called (for each generated initialisation EClass), in order to + * generate the EClasses of the Initialisation classes. + * + * @author Alp Torac Genc + * + * @see {@link cipm.consistency.fluentapi.gen.FluentAPIGenerator} For more + * details on the flow of the fluent api generation. + */ +public class FluentAPIInitialisationEClassGenerator { + private static final Map summaries = new LinkedHashMap<>(); + + /** + * @param context The object encapsulating the context of fluent api generation. + * @return The list of initialisation classes' EClasses generated for the model + * elements of the metamodel the fluent api is generated for. + */ + public List generateFluentAPIInitialisationClasses(FluentAPIGenerationContext context) { + var initSubClss = new ArrayList(); + + for (var initialisedEClass : context.getAllEligibleTargetMetamodelConcreteEClasses()) { + var initSubCls = generateInitialisationEClass(initialisedEClass, context); + context.addInitECls(initialisedEClass, initSubCls); + initSubClss.add(initSubCls); + } + + return initSubClss; + } + + private void addXInitEClassDocumentation(EClass xInitEClass, EClass initialisedEClass, + FluentAPIGenerationContext context) { + + var doc = ModelConstants.Initialiation.CLASS_DOC.getFor(initialisedEClass.getName(), + context.getTargetMetamodelPackageProvider().getTargetMetamodelName(), initialisedEClass.getName(), + FluentAPIDocumentationUtil.serialiseSummaries(summaries)); + FluentAPIGenerationUtil.addDocumentation(xInitEClass, doc); + } + + private EClass generateInitialisationEClass(EClass initialisedEClass, FluentAPIGenerationContext context) { + var xInitEClass = EcoreFactory.eINSTANCE.createEClass(); + xInitEClass.setName( + ModelConstants.Initialiation.CLASS_NAME.getFor(StringUtils.capitalize(initialisedEClass.getName()))); + return xInitEClass; + } + + /** + * Sets up xInitEClass as EClass of the initialisation class for the given + * initialisedEClass. + * + * @param xInitEClass An initialisation EClass + * @param initialisedEClass The EClass of the model element, which xInitEClass + * is meant for + * @param context The object encapsulating the context of fluent api + * generation. + */ + public void setupFluentAPIInitialisationFor(EClass xInitEClass, EClass initialisedEClass, + FluentAPIGenerationContext context) { + xInitEClass.getEOperations().addAll(new FluentAPIInitialisationReturnTypeOverrideGenerator() + .generateMethodsWithOverridingReturnType(context, xInitEClass, initialisedEClass)); + + addNonOverriddenInheritedMethodSummaries(); + addOperations(xInitEClass, initialisedEClass, context); + addXInitEClassDocumentation(xInitEClass, initialisedEClass, context); + } + + private void addNonOverriddenInheritedMethodSummaries() { + var getInitEClsGen = new FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator(); + summaries.putAll(getInitEClsGen.getMethodNamesToDescriptions()); + + var resetGen = new FluentAPISuperInitialisationResetOperationGenerator(); + summaries.putAll(resetGen.getMethodNamesToDescriptions()); + + var toAPIGen = new FluentAPISuperInitialisationToAPIMethodGenerator(); + summaries.putAll(toAPIGen.getMethodNamesToDescriptions()); + } + + private void addOperations(EClass xInitEClass, EClass initialisedEClass, FluentAPIGenerationContext context) { + var newElementGen = new FluentAPIInitialisationNewElementOperationGenerator(); + xInitEClass.getEOperations().add(newElementGen.getNewElementOperationFor(xInitEClass, initialisedEClass)); + summaries.putAll(newElementGen.getMethodNamesToDescriptions()); + + var withGen = new FluentAPIInitialisationWithOperationGenerator(); + xInitEClass.getEOperations() + .addAll(withGen.generateAllWithOperationsFor(context, xInitEClass, initialisedEClass)); + summaries.putAll(withGen.getMethodNamesToDescriptions()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationNewElementOperationGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationNewElementOperationGenerator.java new file mode 100644 index 0000000000..96059a3b5c --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationNewElementOperationGenerator.java @@ -0,0 +1,39 @@ +package cipm.consistency.fluentapi.gen.init; + +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIInitialisationNewElementOperationGenerator implements IFluentAPIMethodGenerator { + private static final String newElementMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Ns URI of the package of the element to initialise + "var pac = org.eclipse.emf.ecore.EPackage.Registry.INSTANCE.getEPackage(\"%s\")", + // %s: Fully qualified name of EClass class + // %s: Name of the EClass of the element to initialise + ModelConstants.SuperInitialisation.CurrentElement.NAME + .thisSetterCall("(pac.getEFactoryInstance().create((%s) pac.getEClassifier(\"%s\")))"), + // + "return this"); + + public EOperation getNewElementOperationFor(EClass initEClass, EClass elemToInit) { + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.NewElement.NAME.get(), + initEClass); + FluentAPIGenerationUtil.addBody(op, String.format(newElementMethodBodyTemplate, + elemToInit.getEPackage().getNsURI(), EClass.class.getName(), elemToInit.getName())); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.Initialiation.NewElement.DOC.getFor(elemToInit.getName())); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.Initialiation.NewElement.NAME.get(), + ModelConstants.Initialiation.NewElement.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationReturnTypeOverrideGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationReturnTypeOverrideGenerator.java new file mode 100644 index 0000000000..f631ffdf57 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationReturnTypeOverrideGenerator.java @@ -0,0 +1,96 @@ +package cipm.consistency.fluentapi.gen.init; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.FluentAPIParameterUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * Generates overridden methods with more specific return types. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitialisationReturnTypeOverrideGenerator { + private static final Pattern initReturnTypeOverridePattern = Pattern.compile(String.join("|", + new String[] { ModelConstants.SuperInitialisation.Reset.NAME.get(), + ModelConstants.FluentAPI.DropInitialisation.NAME.get(), + ModelConstants.FluentAPI.WaitForMark.NAME.get(), ModelConstants.SuperInitialisation.Mark.NAME.get(), + ModelConstants.SuperInitialisation.Unmark.NAME.get(), })); + + private static final Pattern initialisedElementReturnTypeOverridePattern = Pattern + .compile(ModelConstants.SuperInitialisation.CreateNow.NAME.get()); + + private static final String methodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Returned class + // %s: Method call string (with parameters in brackets) + "return (%s) %s"); + + private EOperation generateInitReturnTypeOverridingMethod(List overridingOps, EOperation opToOverride, + EClass initType, FluentAPIGenerationContext context) { + var serialisedOriginalMethodCall = "super." + opToOverride.getName() + "(" + + FluentAPIParameterUtil.getSerialisedParametersFor(opToOverride) + ")"; + var copier = new EcoreUtil.Copier(); + var overridingOp = (EOperation) copier.copy(opToOverride); + copier.copyReferences(); + + if (FluentAPIParameterUtil.hasClashingMethods(overridingOps, overridingOp)) + return null; + + // Adjust return type + overridingOp.setEType(initType); + + // Adjust body + FluentAPIGenerationUtil.addBody(overridingOp, String.format(methodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initType), serialisedOriginalMethodCall)); + + return overridingOp; + } + + private EOperation generateInitialisedElementReturnTypeOverridingMethod(List overridingOps, + EOperation opToOverride, EClass initialisedElemType) { + var serialisedOriginalMethodCall = "super." + opToOverride.getName() + "(" + + FluentAPIParameterUtil.getSerialisedParametersFor(opToOverride) + ")"; + var copier = new EcoreUtil.Copier(); + var overridingOp = (EOperation) copier.copy(opToOverride); + copier.copyReferences(); + + if (FluentAPIParameterUtil.hasClashingMethods(overridingOps, overridingOp)) + return null; + + // Adjust return type + overridingOp.setEType(initialisedElemType); + + // Adjust body + FluentAPIGenerationUtil.addBody(overridingOp, String.format(methodBodyTemplate, + initialisedElemType.getInstanceTypeName(), serialisedOriginalMethodCall)); + + return overridingOp; + } + + public List generateMethodsWithOverridingReturnType(FluentAPIGenerationContext context, EClass initType, + EClass initialisedElemType) { + var overridingOps = new ArrayList(); + for (var opToOverride : context.getInitSuperECls().getEOperations()) { + // Skip non-concrete return types (such as type parameters) + if (opToOverride.getEGenericType() != null && opToOverride.getEGenericType().getETypeParameter() != null) + continue; + if (initReturnTypeOverridePattern.matcher(opToOverride.getName()).matches()) { + overridingOps + .add(generateInitReturnTypeOverridingMethod(overridingOps, opToOverride, initType, context)); + } else if (initialisedElementReturnTypeOverridePattern.matcher(opToOverride.getName()).matches()) { + overridingOps.add(generateInitialisedElementReturnTypeOverridingMethod(overridingOps, opToOverride, + initialisedElemType)); + } + } + return overridingOps; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationWithOperationGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationWithOperationGenerator.java new file mode 100644 index 0000000000..61f9a3d328 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/FluentAPIInitialisationWithOperationGenerator.java @@ -0,0 +1,184 @@ +package cipm.consistency.fluentapi.gen.init; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EStructuralFeature; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIInitialisationWithOperationGenerator implements IFluentAPIMethodGenerator { + private static final String withXFeatMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC(ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + ".eSet(" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + // %s: Feature name + // %s: Feature value parameter (although the parameter name is known, there are + // overloading methods process the parameter) + + ".eClass().getEStructuralFeature(\"%s\"), %s)", "return this"); + + private static final String withoutXFeatMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC(ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + ".eUnset(" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + // %s: Feature name + + ".eClass().getEStructuralFeature(\"%s\"))", "return this"); + + private static final String withAddedXFeatMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC("((org.eclipse.emf.common.util.EList) " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + ".eGet(" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + // %s: Feature name + + ".eClass().getEStructuralFeature(\"%s\"))).add(" + + ModelConstants.Initialiation.WithAdded.PARAMETER_NAME.get() + ")", "return this"); + + private static final String withRemovedXFeatMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC("((org.eclipse.emf.common.util.EList) " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + ".eGet(" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + // %s: Feature name + + ".eClass().getEStructuralFeature(\"%s\"))).remove(" + + ModelConstants.Initialiation.WithRemoved.PARAMETER_NAME.get() + ")", "return this"); + + private static final String cleanXFeatMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC("var list = (org.eclipse.emf.common.util.EList) " + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + ".eGet(" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall() + // %s: Feature name + + ".eClass().getEStructuralFeature(\"%s\"))", "list.clear()", "return this"); + + private List getAllEligibleFeats(FluentAPIGenerationContext context, EClass elemToInit) { + return elemToInit.getEAllStructuralFeatures().stream() + .filter((feat) -> context.getTargetMetamodelFilter().isFeatureEligible(elemToInit, feat)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + public List generateAllWithOperationsFor(FluentAPIGenerationContext context, EClass initECls, + EClass elemToInit) { + var feats = this.getAllEligibleFeats(context, elemToInit); + var ops = new ArrayList(); + + for (var feat : feats) { + if (!feat.isMany()) { + ops.addAll(this.generateWithXFeat(context, initECls, elemToInit, feat)); + ops.add(this.generateWithoutXFeat(initECls, elemToInit, feat)); + } else { + ops.add(this.generateWithAddedXFeat(context, initECls, elemToInit, feat)); + ops.add(this.generateWithRemovedXFeat(context, initECls, elemToInit, feat)); + ops.add(this.generateCleanXFeat(context, initECls, elemToInit, feat)); + } + } + return ops; + } + + private List generateWithXFeat(FluentAPIGenerationContext context, EClass initECls, EClass elemToInit, + EStructuralFeature feat) { + var ops = new ArrayList(); + + BiFunction opGenerator = (featValParam, featValParamPlugin) -> { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.Initialiation.With.NAME.getFor(StringUtils.capitalize(feat.getName())), initECls); + FluentAPIGenerationUtil.addBody(op, + String.format(withXFeatMethodBodyTemplate, feat.getName(), featValParamPlugin)); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.Initialiation.With.DOC.getFor(feat.getName())); + FluentAPIGenerationUtil.addEParameters(op, featValParam); + return op; + }; + + var originalOpNewFeatValParam = getNewFeatValParam(feat); + var originalOp = opGenerator.apply(originalOpNewFeatValParam, originalOpNewFeatValParam.getName()); + ops.add(originalOp); + + return ops; + } + + private EOperation generateWithoutXFeat(EClass initECls, EClass elemToInit, EStructuralFeature feat) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.Initialiation.Without.NAME.getFor(StringUtils.capitalize(feat.getName())), initECls); + FluentAPIGenerationUtil.addBody(op, String.format(withoutXFeatMethodBodyTemplate, feat.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.Initialiation.Without.DOC.getFor(feat.getName())); + return op; + } + + private EOperation generateWithAddedXFeat(FluentAPIGenerationContext context, EClass initECls, EClass elemToInit, + EStructuralFeature feat) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.Initialiation.WithAdded.NAME.getFor(StringUtils.capitalize(feat.getName())), initECls); + + FluentAPIGenerationUtil.addBody(op, String.format(withAddedXFeatMethodBodyTemplate, feat.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.Initialiation.WithAdded.DOC.getFor(feat.getName())); + FluentAPIGenerationUtil.addEParameters(op, getAddedFeatValParam(feat)); + return op; + } + + private EOperation generateWithRemovedXFeat(FluentAPIGenerationContext context, EClass initECls, EClass elemToInit, + EStructuralFeature feat) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.Initialiation.WithRemoved.NAME.getFor(StringUtils.capitalize(feat.getName())), initECls); + FluentAPIGenerationUtil.addBody(op, String.format(withRemovedXFeatMethodBodyTemplate, feat.getName())); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.Initialiation.WithRemoved.DOC.getFor(feat.getName())); + FluentAPIGenerationUtil.addEParameters(op, getRemovedFeatValParam(feat)); + return op; + } + + private EOperation generateCleanXFeat(FluentAPIGenerationContext context, EClass initECls, EClass elemToInit, + EStructuralFeature feat) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.Initialiation.Clean.NAME.getFor(StringUtils.capitalize(feat.getName())), initECls); + FluentAPIGenerationUtil.addBody(op, String.format(cleanXFeatMethodBodyTemplate, feat.getName())); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.Initialiation.Clean.DOC.getFor(feat.getName(), feat.getName())); + return op; + } + + private EParameter getNewFeatValParam(EStructuralFeature feat) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.Initialiation.With.PARAMETER_NAME.get(), feat.getEType()); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.Initialiation.With.PARAMETER_DOC.getFor(feat.getName())); + return param; + } + + private EParameter getAddedFeatValParam(EStructuralFeature feat) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.Initialiation.WithAdded.PARAMETER_NAME.get(), feat.getEType()); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.Initialiation.WithAdded.PARAMETER_DOC.getFor(feat.getName())); + return param; + } + + private EParameter getRemovedFeatValParam(EStructuralFeature feat) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.Initialiation.WithRemoved.PARAMETER_NAME.get(), feat.getEType()); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.Initialiation.WithRemoved.PARAMETER_DOC.getFor(feat.getName())); + return param; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.Initialiation.With.NAME.getEmpty(), + ModelConstants.Initialiation.With.SUMMARY.get(), + + ModelConstants.Initialiation.Without.NAME.getEmpty(), + ModelConstants.Initialiation.Without.SUMMARY.get(), + + ModelConstants.Initialiation.WithAdded.NAME.getEmpty(), + ModelConstants.Initialiation.WithAdded.SUMMARY.get(), + + ModelConstants.Initialiation.WithRemoved.NAME.getEmpty(), + ModelConstants.Initialiation.WithRemoved.SUMMARY.get(), + + ModelConstants.Initialiation.Clean.NAME.getEmpty(), ModelConstants.Initialiation.Clean.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/package-info.java new file mode 100644 index 0000000000..b42cccbf85 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/init/package-info.java @@ -0,0 +1,26 @@ +/** + * Contains the generator classes responsible for generating the EMF model + * elements of the Initialisation classes of the fluent API, i.e. the classes + * that facilitate the creation and modification of elements of a certain type. + *

    + *

    + * The generation-related code within the generator classes is written in a + * modular way, such that modifying {@link ModelConstants} via refactoring + * operations should reflect MOST of the changes to the generated model. + * Due to the potential usage of static methods SM within the generated + * fluent API code, it is NOT NECESSARILY guaranteed to generate valid methods + * after changes to the generation code: If the name of any SM changes, their + * occurrences within the generation code must be changed MANUALLY. As + * Java currently does not allow controlling names of static methods through + * constant variables in the code, this issue cannot be addressed without + * generating all SM alongside fluent API. + *

    + *

    + * In the current version, the FluentAPIInitialisationEClassGenerator class is + * the top-most generator within this package. It is responsible for generating + * the EClass of each Initialisation class. The rest of the classes within this + * package are responsible for generating EOperation instances of various + * methods of the Initialisation classes. Names of these classes denote which + * methods they are responsible for. + */ +package cipm.consistency.fluentapi.gen.init; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/package-info.java new file mode 100644 index 0000000000..45aa60a000 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/package-info.java @@ -0,0 +1,39 @@ +/** + * Contains the generator class responsible for generating the EMF model + * elements of the fluent API, as well as other constructs assisting this + * process. + *

    + *

    + * The generation-related code within the generator classes is written in a + * modular way, such that modifying {@link ModelConstants} via refactoring + * operations should reflect MOST of the changes to the generated model. + * Due to the potential usage of static methods SM within the generated + * fluent API code, it is NOT NECESSARILY guaranteed to generate valid methods + * after changes to the generation code: If the name of any SM changes, their + * occurrences within the generation code must be changed MANUALLY. As + * Java currently does not allow controlling names of static methods through + * constant variables in the code, this issue cannot be addressed without + * generating all SM alongside fluent API. + *

    + *

    + * In the current version, the FluentAPIGenerator class is the top-most + * generator, which by working with other generators creates the EMF model of + * the fluent API: + *

      + *
    • {@link cipm.consistency.fluentapi.gen.rootapi}: Contains the generator + * classes associated with the fluent API's facade class, which allows using the + * fluent API from outside conveniently. + *
    • {@link cipm.consistency.fluentapi.gen.superinit}: Contains the generator + * classes associated with the super type of all initialisation classes. + *
    • {@link cipm.consistency.fluentapi.gen.init}: Contains the generator + * classes associated with the concrete implementations of initialisation + * classes, each responsible for creating and modifying elements of certain + * types. + *
    + *

    + *

    + * Note: Some of the generator classes may implement multiple methods for + * generation purposes. If that is the case, refer to their documentation for + * further details / instructions. + */ +package cipm.consistency.fluentapi.gen; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator.java new file mode 100644 index 0000000000..40d2187962 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator.java @@ -0,0 +1,32 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator implements IFluentAPIMethodGenerator { + private static final String clearAllOngoingInitsMethodBody = FluentAPIMethodsUtil.joinLOC( + FluentAPIInitialisationStorage.class.getName() + ".clearAllOngoingInitialisations()", "return this"); + + public EOperation generateClearAllOngoingInitsMethod(FluentAPIGenerationContext context) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.ClearAllOngoingInitialisations.NAME.get(), context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, clearAllOngoingInitsMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.FluentAPI.ClearAllOngoingInitialisations.SUMMARY.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.ClearAllOngoingInitialisations.NAME.get(), + ModelConstants.FluentAPI.ClearAllOngoingInitialisations.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIContinueMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIContinueMethodGenerator.java new file mode 100644 index 0000000000..db5bf3aebc --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIContinueMethodGenerator.java @@ -0,0 +1,123 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.extensions.FluentEObjectAPIMethods; +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIContinueMethodGenerator implements IFluentAPIMethodGenerator { + private static final String continueMethodBodyTemplate = + // %s: Full Initialisation class name + // %s: Initialised element class (statically, i.e. either via method parameter + // or via .class) + FluentAPIMethodsUtil + .joinLOC("return (%s) " + FluentEObjectAPIMethods.class.getName() + ".continueElement(%s)"); + + private static final String continueMarkedMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + "var markedElem = " + ModelConstants.FluentAPI.GetMarked.TOP_NAME + .thisCall(ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get()), + // %s: Init class name + "return markedElem == null ? null : (%s) " + FluentAPIInitialisationStorage.class.getName() + + ".getOngoingInitialisations().stream().filter((i) -> ((" + // %s: Target metamodel package name (lower case) + + ModelConstants.FULL_ROOT_PACKAGE_NAME.getFor("%s") + "." + + ModelConstants.SuperInitialisation.CLASS_NAME.get() + ") i)" + + ModelConstants.SuperInitialisation.CurrentElement.NAME.getterCall() + + " == markedElem).findFirst().get()"); + + public List generateAllContinueMethods(FluentAPIGenerationContext context) { + var eObjEClss = context.getAllEligibleTargetMetamodelConcreteEClasses(); + var ops = new ArrayList(); + + ops.add(generateTopLevelContinueMethod(context)); + ops.add(generateTopLevelContinueMarkedMethod(context)); + + for (int i = 0; i < eObjEClss.size(); i++) { + var eObjEClass = eObjEClss.get(i); + var initEClass = context.getAllInitEClss().get(i); + if (context.getTargetMetamodelFilter().hasModifiableFeatures(eObjEClass)) { + ops.add(generateContinueMethod(context, eObjEClass, initEClass)); + ops.add(generateContinueMarkedMethod(eObjEClass, initEClass, context)); + } + } + + return ops; + } + + private EOperation generateTopLevelContinueMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getArbitraryClassParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.Continue.TOP_NAME.get(), + context.getInitSuperECls()); + + FluentAPIGenerationUtil.addEParameters(op, param); + FluentAPIGenerationUtil.addBody(op, + String.format(continueMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + param.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Continue.SUMMARY.get()); + return op; + } + + private EOperation generateContinueMethod(FluentAPIGenerationContext context, EClass elemToInit, EClass initECls) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.Continue.NAME.getFor(StringUtils.capitalize(elemToInit.getName())), initECls); + + FluentAPIGenerationUtil.addBody(op, + String.format(continueMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + elemToInit.getInstanceClass().getName() + ".class")); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Continue.SUMMARY.get()); + return op; + } + + private EOperation generateContinueMarkedMethod(EClass elemToInit, EClass initECls, + FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.ContinueMarked.NAME.getFor(StringUtils.capitalize(elemToInit.getName())), + initECls); + + FluentAPIGenerationUtil.addBody(op, + String.format(continueMarkedMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + context.getTargetMetamodelPackageProvider().getTargetMetamodelName())); + FluentAPIGenerationUtil.addEParameters(op, param); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.ContinueMarked.SUMMARY.get()); + return op; + } + + private EOperation generateTopLevelContinueMarkedMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.ContinueMarked.TOP_NAME.get(), + context.getInitSuperECls()); + + FluentAPIGenerationUtil.addBody(op, + String.format(continueMarkedMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + context.getTargetMetamodelPackageProvider().getTargetMetamodelName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.ContinueMarked.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.Continue.NAME.getEmpty(), + ModelConstants.FluentAPI.Continue.SUMMARY.get(), + ModelConstants.FluentAPI.ContinueMarked.NAME.getEmpty(), + ModelConstants.FluentAPI.ContinueMarked.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPICreateNewMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPICreateNewMethodGenerator.java new file mode 100644 index 0000000000..bdb789d2e1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPICreateNewMethodGenerator.java @@ -0,0 +1,104 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/* + * createNew() generation is separated from FluentAPIRootAPINewMethodGenerator, since the "createNewX" + * methods cannot share their name with "newX" methods, due to Java limitations. + */ + +public class FluentAPIRootAPICreateNewMethodGenerator implements IFluentAPIMethodGenerator { + private static final String createNewXMethodBodyTemplate = FluentAPIMethodsUtil + // %s: Fully qualified class name + // %s: Fully qualified class name + .joinLOC("return (%s) " + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall("%s.class") + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + + private static final String createNewXWithClassParamMethodBody = FluentAPIMethodsUtil + .joinLOC("return (" + ModelConstants.GeneralParameters.TYPE_PARAMETER_NAME.get() + ") " + + ModelConstants.FluentAPI.GetInitialisationFor.NAME + .thisCall(ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_NAME.get()) + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + + public List generateAllCreateNewMethods(FluentAPIGenerationContext context) { + var eObjEClss = context.getAllEligibleTargetMetamodelConcreteEClasses(); + var ops = new ArrayList(); + + ops.add(generateGenericCreateNewMethod()); + + for (var eObjEClass : eObjEClss) { + ops.add(generateCreateNewMethod(eObjEClass)); + } + + return ops; + } + + private EOperation generateCreateNewMethod(EClass eObjEClass) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.CreateNew.NAME.getFor(eObjEClass.getInstanceClass().getSimpleName()), + eObjEClass); + FluentAPIGenerationUtil.addBody(op, String.format(createNewXMethodBodyTemplate, + eObjEClass.getInstanceClass().getName(), eObjEClass.getInstanceClass().getName())); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.FluentAPI.CreateNew.SUMMARY.getFor(eObjEClass.getInstanceClass().getName(), + StringUtils.capitalize(eObjEClass.getInstanceClass().getSimpleName()))); + return op; + } + + private EOperation generateGenericCreateNewMethod() { + // Goal: T createNewX(Class createNewXWithClassParamParamName) + + var typeParam = FluentAPIGenerationUtil + .generateETypeParameter(ModelConstants.GeneralParameters.TYPE_PARAMETER_NAME.get()); + + // Make sure to create 2 generic types, one for the method parameter (Class) + // and one for the return type of the method (T) + + // "" in " T createNewX..." + var methodTypeParam = FluentAPIGenerationUtil.generateEGenericTypeWithTypeParameter(typeParam); + + // "T" in "Class ..." + var methodParamTypeArgument = FluentAPIGenerationUtil.generateEGenericTypeWithTypeParameter(typeParam); + + // "Class" in "Class createNewXWithClassParamParamName" + var methodParamType = FluentAPIGenerationUtil + .generateEGenericTypeWithClassifier(EcorePackage.Literals.EJAVA_CLASS); + FluentAPIGenerationUtil.addTypeArgument(methodParamType, methodParamTypeArgument); + + // "createNewXWithClassParamParamName" in "Class + // createNewXWithClassParamParamName" + var methodParam = FluentAPIGenerationUtil + .generateSingleValuedEParameter(ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_NAME.get()); + FluentAPIGenerationUtil.addDocumentation(methodParam, + ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_DOC.get()); + methodParam.setEGenericType(methodParamType); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.CreateNew.TOP_NAME.get()); + FluentAPIGenerationUtil.addBody(op, createNewXWithClassParamMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.FluentAPI.CreateNew.SUMMARY.getFor(methodParam.getName(), methodParam.getName())); + op.setEGenericType(methodTypeParam); + op.getETypeParameters().add(typeParam); + op.getEParameters().add(methodParam); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.CreateNow.NAME.get(), + ModelConstants.SuperInitialisation.CreateNow.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIDropInitialisationMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIDropInitialisationMethodGenerator.java new file mode 100644 index 0000000000..d8100fbc5f --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIDropInitialisationMethodGenerator.java @@ -0,0 +1,46 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIDropInitialisationMethodGenerator implements IFluentAPIMethodGenerator { + private static final String dropInitialisationMethodBody = FluentAPIMethodsUtil.joinLOC( + FluentAPIInitialisationStorage.class.getName() + ".dropOngoingInitialisation(" + + ModelConstants.FluentAPI.DropInitialisation.INITIALISATION_PARAMETER_NAME.get() + ")", + // + "return this"); + + public EOperation generateDropInitialisationMethod(FluentAPIGenerationContext context) { + var param = getInitialisationParam(context); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.DropInitialisation.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, dropInitialisationMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.DropInitialisation.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EParameter getInitialisationParam(FluentAPIGenerationContext context) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.FluentAPI.DropInitialisation.INITIALISATION_PARAMETER_NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addDocumentation(param, + ModelConstants.FluentAPI.DropInitialisation.INITIALISATION_PARAMETER_DOC.get()); + return param; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.DropInitialisation.NAME.get(), + ModelConstants.FluentAPI.DropInitialisation.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIEClassGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIEClassGenerator.java new file mode 100644 index 0000000000..cfa1689cfd --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIEClassGenerator.java @@ -0,0 +1,120 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EcoreFactory; + +import cipm.consistency.fluentapi.gen.FluentAPIDocumentationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * The generator class responsible for generating the EClass, which represents + * the facade of the fluent API. This generator class will only generate the + * fluent api's EClass and its EOperations, nothing else. Therefore, this + * generator class alone is not enough to generate the fluent api model. + *

    + *

    + * Note that the methods {@link #generateRootAPIEClass()} and + * {@link #setupRootAPIEClass(FluentAPIGenerationContext)} have to be called, in + * order to generate the EClass of the fluent api. + * + * @see {@link cipm.consistency.fluentapi.gen.FluentAPIGenerator} For more + * details on the flow of the fluent api generation. + * + * @author Alp Torac Genc + * + */ +public class FluentAPIRootAPIEClassGenerator { + private static final Map summaries = new LinkedHashMap<>(); + + /** + * + * @param context The object encapsulating the context of fluent api generation. + * @return + */ + public EClass generateRootAPIEClass(FluentAPIGenerationContext context) { + var fluentAPIECls = EcoreFactory.eINSTANCE.createEClass(); + fluentAPIECls.setAbstract(false); + fluentAPIECls.setInterface(false); + fluentAPIECls.setName(ModelConstants.FluentAPI.CLASS_NAME + .getFor(StringUtils.capitalize(context.getTargetMetamodelPackageProvider().getTargetMetamodelName()))); + return fluentAPIECls; + } + + private void addEClassDoc(FluentAPIGenerationContext context) { + var doc = ModelConstants.FluentAPI.CLASS_DOC.getFor(FluentAPIDocumentationUtil.serialiseSummaries(summaries)); + FluentAPIGenerationUtil.addDocumentation(context.getFluentAPIECls(), doc); + } + + private void addOperations(FluentAPIGenerationContext context) { + var createNewMetGen = new FluentAPIRootAPICreateNewMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll(createNewMetGen.generateAllCreateNewMethods(context)); + summaries.putAll(createNewMetGen.getMethodNamesToDescriptions()); + + var newMetGen = new FluentAPIRootAPINewMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll(newMetGen.getAllRootAPINewOperations(context)); + summaries.putAll(newMetGen.getMethodNamesToDescriptions()); + + var modElemMetGen = new FluentAPIRootAPIModifyElementMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll(modElemMetGen.getAllRootAPIModifyElementOperations(context)); + summaries.putAll(modElemMetGen.getMethodNamesToDescriptions()); + + var conMetGen = new FluentAPIRootAPIContinueMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll(conMetGen.generateAllContinueMethods(context)); + summaries.putAll(conMetGen.getMethodNamesToDescriptions()); + + var dropInitMetGen = new FluentAPIRootAPIDropInitialisationMethodGenerator(); + context.getFluentAPIECls().getEOperations().add(dropInitMetGen.generateDropInitialisationMethod(context)); + summaries.putAll(dropInitMetGen.getMethodNamesToDescriptions()); + + var markMetGen = new FluentAPIRootAPIMarkMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll(markMetGen.generateAllMarkMethods(context)); + summaries.putAll(markMetGen.getMethodNamesToDescriptions()); + + var waitForMarkGen = new FluentAPIRootAPIWaitForMarkMethodGenerator(); + context.getFluentAPIECls().getEOperations().add(waitForMarkGen.generateAllWaitForMarkMethods(context)); + summaries.putAll(waitForMarkGen.getMethodNamesToDescriptions()); + + var getInitMetGen = new FluentAPIRootAPIGetInitialisationForMethodGenerator(); + context.getFluentAPIECls().getEOperations().addAll( + new FluentAPIRootAPIGetInitialisationForMethodGenerator().getAllInitialisationForMethods(context)); + summaries.putAll(getInitMetGen.getMethodNamesToDescriptions()); + + var withOpMetGen = new FluentAPIRootAPIWithOperationGenerator(); + context.getFluentAPIECls().getEOperations() + .addAll(new FluentAPIRootAPIWithOperationGenerator().getAllAPITopLevelWithOperations(context)); + summaries.putAll(withOpMetGen.getMethodNamesToDescriptions()); + + var getAllSupClsMetGen = new FluentAPIRootAPIGetAllSupportedClassesMethodGenerator(); + context.getFluentAPIECls().getEOperations().add(new FluentAPIRootAPIGetAllSupportedClassesMethodGenerator() + .generateGetAllSupportedClassesMethodGenerator()); + summaries.putAll(getAllSupClsMetGen.getMethodNamesToDescriptions()); + + var getOngInitMetGen = new FluentAPIRootAPIGetOngoingInitsMethodGenerator(); + context.getFluentAPIECls().getEOperations() + .add(new FluentAPIRootAPIGetOngoingInitsMethodGenerator().generateGetOngoingInitsMethod(context)); + summaries.putAll(getOngInitMetGen.getMethodNamesToDescriptions()); + + var clrOngInitMetGen = new FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator(); + context.getFluentAPIECls().getEOperations() + .add(new FluentAPIRootAPIClearAllOngoingInitialisationsMethodGenerator() + .generateClearAllOngoingInitsMethod(context)); + summaries.putAll(clrOngInitMetGen.getMethodNamesToDescriptions()); + } + + /** + * Sets up the fluent api class' EClass by generating and adding its + * EOperations, as well as documenting them. + * + * @param context The object encapsulating the context of fluent api generation. + */ + public void setupRootAPIEClass(FluentAPIGenerationContext context) { + addOperations(context); + addEClassDoc(context); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetAllSupportedClassesMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetAllSupportedClassesMethodGenerator.java new file mode 100644 index 0000000000..3c6b867698 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetAllSupportedClassesMethodGenerator.java @@ -0,0 +1,43 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.extensions.FluentEObjectAPIMethods; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIGetAllSupportedClassesMethodGenerator implements IFluentAPIMethodGenerator { + private static final String getAllSupportedClassesMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC("return " + FluentEObjectAPIMethods.class.getName() + ".getAllSupportedClasses(this)"); + + public EOperation generateGetAllSupportedClassesMethodGenerator() { + var javaClassType = FluentAPIGenerationUtil + .generateEGenericTypeWithClassifier(EcorePackage.Literals.EJAVA_CLASS); + + var eObjLowerBound = FluentAPIGenerationUtil.generateEGenericTypeWithBounds(null, + FluentAPIGenerationUtil.generateEGenericTypeWithClassifier(EcorePackage.Literals.EOBJECT)); + FluentAPIGenerationUtil.addTypeArgument(javaClassType, eObjLowerBound); + + var eListType = FluentAPIGenerationUtil.generateEGenericTypeWithClassifier(EcorePackage.Literals.EE_LIST); + FluentAPIGenerationUtil.addTypeArgument(eListType, javaClassType); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.GetAllSupportedClasses.NAME.get(), + eListType); + + FluentAPIGenerationUtil.addBody(op, getAllSupportedClassesMethodBodyTemplate); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetAllSupportedClasses.SUMMARY.get()); + + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.GetAllSupportedClasses.NAME.get(), + ModelConstants.FluentAPI.GetAllSupportedClasses.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetInitialisationForMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetInitialisationForMethodGenerator.java new file mode 100644 index 0000000000..4563767891 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetInitialisationForMethodGenerator.java @@ -0,0 +1,84 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentEObjectAPIMethods; +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIGetInitialisationForMethodGenerator implements IFluentAPIMethodGenerator { + private static final String getInitialisationMethodBodyTemplate = FluentAPIMethodsUtil + // %s: Initialisation class + // %s: EClass / class / EObject parameter name + .joinLOC("return (%s)" + FluentEObjectAPIMethods.class.getName() + ".getInitialisationForX(this, %s)"); + + public List getAllInitialisationForMethods(FluentAPIGenerationContext context) { + var ops = new ArrayList(); + ops.add(getInitialisationForEClassMethod(context)); + ops.add(getInitialisationForClassMethod(context)); + ops.add(getInitialisationForEObjectMethod(context)); + return ops; + } + + private EOperation getInitialisationForEClassMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getArbitraryEClassParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.GetInitialisationFor.NAME.get(), + context.getInitSuperECls()); + + FluentAPIGenerationUtil.addBody(op, + String.format(getInitialisationMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + param.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetInitialisationFor.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + + return op; + } + + private EOperation getInitialisationForClassMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getArbitraryClassParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.GetInitialisationFor.NAME.get(), + context.getInitSuperECls()); + + FluentAPIGenerationUtil.addBody(op, + String.format(getInitialisationMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + param.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetInitialisationFor.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + + return op; + } + + private EOperation getInitialisationForEObjectMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getEObjectParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.GetInitialisationFor.NAME.get(), + context.getInitSuperECls()); + + FluentAPIGenerationUtil.addBody(op, + String.format(getInitialisationMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + param.getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetInitialisationFor.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + // Leave it empty, since this method is not intended to be used from outside + return Map.of(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetOngoingInitsMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetOngoingInitsMethodGenerator.java new file mode 100644 index 0000000000..af897b454b --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIGetOngoingInitsMethodGenerator.java @@ -0,0 +1,37 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIGetOngoingInitsMethodGenerator implements IFluentAPIMethodGenerator { + private static final String getOngoingInitsMethodBody = FluentAPIMethodsUtil + // %s: Fully qualified super initialisation class name + .joinLOC("return " + FluentAPIInitialisationStorage.class.getName() + + ".getOngoingInitialisations().stream().map((i) -> (%s) i).collect(" + + java.util.stream.Collectors.class.getName() + ".toList())"); + + public EOperation generateGetOngoingInitsMethod(FluentAPIGenerationContext context) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.GetOngoingInitialisations.NAME.get(), + FluentAPIGenerationUtil.generateEGenericTypeWithTypeArgument(context, java.util.List.class, + FluentAPIGenerationUtil.generateEGenericTypeWithClassifier(context.getInitSuperECls()))); + FluentAPIGenerationUtil.addBody(op, String.format(getOngoingInitsMethodBody, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetOngoingInitialisations.SUMMARY.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.GetOngoingInitialisations.NAME.get(), + ModelConstants.FluentAPI.GetOngoingInitialisations.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIMarkMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIMarkMethodGenerator.java new file mode 100644 index 0000000000..8331e3725e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIMarkMethodGenerator.java @@ -0,0 +1,115 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIMarkMethodGenerator implements IFluentAPIMethodGenerator { + private static final String unmarkMethodBody = FluentAPIMethodsUtil.joinLOC(FluentAPIMarkExtension.class.getName() + + ".unmark(" + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ")", "return this"); + + private static final String unmarkFullMethodBody = FluentAPIMethodsUtil + .joinLOC(FluentAPIMarkExtension.class.getName() + ".unmark(" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + ")", "return this"); + + private static final String markMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC(FluentAPIMarkExtension.class.getName() + ".mark(" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get() + ")", "return this"); + + private static final String getMarkedMethodBody = FluentAPIMethodsUtil + .joinLOC("return " + FluentAPIMarkExtension.class.getName() + ".getMarked(" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ")"); + + private static final String getMarkedXMethodBodyTemplate = + // %s: Element's class + // %s: Element's class + FluentAPIMethodsUtil.joinLOC("return (%s) " + FluentAPIMarkExtension.class.getName() + ".getMarked(" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ", %s.class)"); + + public List generateAllMarkMethods(FluentAPIGenerationContext context) { + var allEClss = context.getAllEligibleTargetMetamodelEClasses(); + var ops = new ArrayList(); + ops.add(generateUnmarkMethod(context)); + ops.add(generateUnmarkFullMethod(context)); + ops.add(generateMarkMethod(context)); + ops.add(generateGetMarkedMethod()); + allEClss.forEach((eCls) -> ops.add(generateGetMarkedXMethod(eCls))); + return ops; + } + + private EOperation generateMarkMethod(FluentAPIGenerationContext context) { + var markKeyParam = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var markValParam = FluentAPIGeneralParameterGenerator.getMarkValParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.Mark.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, markMethodBodyTemplate); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Mark.DOC.get()); + FluentAPIGenerationUtil.addEParameters(op, markKeyParam, markValParam); + return op; + } + + private EOperation generateUnmarkMethod(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.Unmark.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, unmarkMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Unmark.DOC.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation generateUnmarkFullMethod(FluentAPIGenerationContext context) { + var markKeyParam = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var markValParam = FluentAPIGeneralParameterGenerator.getMarkValParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.Unmark.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, unmarkFullMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Unmark.DOC.get()); + FluentAPIGenerationUtil.addEParameters(op, markKeyParam, markValParam); + return op; + } + + private EOperation generateGetMarkedMethod() { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.GetMarked.TOP_NAME.get(), + EcorePackage.Literals.EOBJECT); + FluentAPIGenerationUtil.addBody(op, getMarkedMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetMarked.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation generateGetMarkedXMethod(EClass elemToInit) { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.GetMarked.NAME.getFor(StringUtils.capitalize(elemToInit.getName())), + elemToInit); + FluentAPIGenerationUtil.addBody(op, String.format(getMarkedXMethodBodyTemplate, + elemToInit.getInstanceClass().getName(), elemToInit.getInstanceClass().getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.GetMarked.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.Mark.NAME.get(), ModelConstants.FluentAPI.Mark.SUMMARY.get(), + ModelConstants.FluentAPI.Unmark.NAME.get(), ModelConstants.FluentAPI.Unmark.SUMMARY.get(), + ModelConstants.FluentAPI.GetMarked.NAME.getEmpty(), ModelConstants.FluentAPI.GetMarked.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIModifyElementMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIModifyElementMethodGenerator.java new file mode 100644 index 0000000000..07f3ecf417 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIModifyElementMethodGenerator.java @@ -0,0 +1,103 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIModifyElementMethodGenerator implements IFluentAPIMethodGenerator { + private static final String modifyElementMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Initialisation class name + "return (%s) " + ModelConstants.FluentAPI.GetInitialisationFor.NAME + .thisCall(ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get())); + + private static final String modifyMarkedElementMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Initialisation type class name + // %s: GetMarked method name + "return (%s) " + ModelConstants.FluentAPI.GetInitialisationFor.NAME + .thisCall("this.%s(" + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ")")); + + public List getAllRootAPIModifyElementOperations(FluentAPIGenerationContext context) { + var eObjEClss = context.getAllEligibleTargetMetamodelConcreteEClasses(); + var ops = new ArrayList(); + ops.add(getRootAPITopLevelModifyElementOperation(context)); + ops.add(getRootAPITopLevelModifyMarkedElementOperation(context)); + + for (int i = 0; i < eObjEClss.size(); i++) { + var elemToInitECls = eObjEClss.get(i); + var initEClass = context.getAllInitEClss().get(i); + ops.add(getRootAPIModifyElementOperationForEClass(context, elemToInitECls, initEClass)); + ops.add(getRootAPIModifyMarkedElementOperationForEClass(context, elemToInitECls, initEClass)); + } + + return ops; + } + + private EOperation getRootAPIModifyMarkedElementOperationForEClass(FluentAPIGenerationContext context, + EClass elemToInitECls, EClass initECls) { + var markKeyParam = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.ModifyMarked.NAME.getFor(StringUtils.capitalize(elemToInitECls.getName())), + initECls); + FluentAPIGenerationUtil.addBody(op, String.format(modifyMarkedElementMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + ModelConstants.FluentAPI.GetMarked.NAME.getFor(StringUtils.capitalize(elemToInitECls.getName())))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.ModifyMarked.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, markKeyParam); + return op; + } + + private EOperation getRootAPIModifyElementOperationForEClass(FluentAPIGenerationContext context, + EClass elemToInitECls, EClass initECls) { + var param = FluentAPIGeneralParameterGenerator.getEObjectParamOfType(elemToInitECls); + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.Modify.NAME.getFor(StringUtils.capitalize(elemToInitECls.getName())), + initECls); + FluentAPIGenerationUtil.addBody(op, String.format(modifyElementMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Modify.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation getRootAPITopLevelModifyElementOperation(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.Modify.TOP_NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addBody(op, String.format(modifyElementMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.Modify.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation getRootAPITopLevelModifyMarkedElementOperation(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getMarkKeyParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.ModifyMarked.TOP_NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addBody(op, + String.format(modifyMarkedElementMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()), + ModelConstants.FluentAPI.GetMarked.TOP_NAME.get())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.ModifyMarked.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.Modify.NAME.getEmpty(), ModelConstants.FluentAPI.Modify.SUMMARY.get(), + ModelConstants.FluentAPI.ModifyMarked.NAME.getEmpty(), + ModelConstants.FluentAPI.ModifyMarked.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPINewMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPINewMethodGenerator.java new file mode 100644 index 0000000000..579ab820df --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPINewMethodGenerator.java @@ -0,0 +1,235 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPINewMethodGenerator implements IFluentAPIMethodGenerator { + private static final String newXMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Fully qualified Initialisation super type class name + "return (%s)" + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall( + ModelConstants.GeneralParameters.ARBITRARY_ECLASS_PARAMETER_NAME.get() + ".getInstanceClass()")); + + private static final String newXWithClassParamMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Fully qualified Initialisation super type class name + "return (%s)" + ModelConstants.FluentAPI.GetInitialisationFor.NAME + .thisCall(ModelConstants.GeneralParameters.ARBITRARY_CLASS_PARAMETER_NAME.get())); + + private static final String newXWithModifiableFeatsMethodBodyTemplate = FluentAPIMethodsUtil + // %s: Fully qualified Initialisation type class name + .joinLOC("return (%s) " + + // %s: Fully qualified Initialisation type class name + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall("%s.class")); + + private static final String newXWithOnlyOneModifiableSingleValuedFeatsMethodBodyTemplate = FluentAPIMethodsUtil + // %s: Fully qualified type class name + // %s: Fully qualified Initialisation type class name + .joinLOC("return (%s) ((%s) " + + // %s: Fully qualified type class name + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall("%s.class") + ")." + + ModelConstants.Initialiation.With.NAME.get() + "(" + + ModelConstants.FluentAPI.New.FEATURE_VALUE_PARAMETER_NAME.get() + ")" + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + + private static final String newXWithOnlyOneModifiableManyValuedFeatsMethodBodyTemplate_singleValue = FluentAPIMethodsUtil + // %s: Fully qualified type class name + // %s: Fully qualified Initialisation type class name + .joinLOC("return (%s) ((%s) " + + // %s: Fully qualified type class name + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall("%s.class") + ")." + + ModelConstants.Initialiation.WithAdded.NAME.get() + "(" + + ModelConstants.FluentAPI.New.FEATURE_VALUE_PARAMETER_NAME.get() + ")" + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + + private static final String newXWithoutModifiableFeatsMethodBodyTemplate = FluentAPIMethodsUtil + // %s: Fully qualified type class name + // %s: Fully qualified Initialisation type class name + .joinLOC("return (%s) ((%s) " + + // %s: Fully qualified type class name + ModelConstants.FluentAPI.GetInitialisationFor.NAME.thisCall("%s.class") + ")" + + ModelConstants.SuperInitialisation.CreateNow.NAME.call()); + + public List getAllRootAPINewOperations(FluentAPIGenerationContext context) { + var eObjEClss = context.getAllEligibleTargetMetamodelConcreteEClasses(); + var ops = new ArrayList(); + ops.add(getRootAPITopLevelNewOperation(context)); + ops.add(getRootAPITopLevelNewOperationWithClassParameter(context)); + + for (int i = 0; i < eObjEClss.size(); i++) { + var eObjEClass = eObjEClss.get(i); + var initEClass = context.getAllInitEClss().get(i); + + var modifiableFeatures = context.getTargetMetamodelFilter().getModifiableFeatures(eObjEClass); + var modifiableFeatureCount = modifiableFeatures.size(); + + if (modifiableFeatureCount == 0) { + // Add direct creation methods, if there are no modifiable features + ops.add(getRootAPINewOperationForEClassWithoutModifiableFeats(eObjEClass, initEClass, context)); + } else { + // Add methods that lead to XInitialisation instances, if there are multiple + // modifiable features + ops.add(getRootAPINewOperationForEClassWithModifiableFeats(context, eObjEClass, initEClass)); + } + + // Add overloading method for types with only one modifiable feature to reduce + // verbosity + if (modifiableFeatureCount == 1) { + ops.addAll(getRootAPINewOperationForEClassWithOnlyOneModifiableFeat(context, eObjEClass, + modifiableFeatures.get(0), initEClass)); + } + + } + + return ops; + } + + private EOperation getRootAPITopLevelNewOperation(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getArbitraryEClassParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.New.TOP_NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addBody(op, String.format(newXMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.New.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation getRootAPITopLevelNewOperationWithClassParameter(FluentAPIGenerationContext context) { + var param = FluentAPIGeneralParameterGenerator.getArbitraryClassParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.New.TOP_NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addBody(op, String.format(newXWithClassParamMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, context.getInitSuperECls()))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.New.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, param); + return op; + } + + private EOperation getRootAPINewOperationForEClassWithModifiableFeats(FluentAPIGenerationContext context, + EClass eObjEClass, EClass initECls) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.New.NAME.getFor(StringUtils.capitalize(eObjEClass.getName())), initECls); + FluentAPIGenerationUtil.addBody(op, + String.format(newXWithModifiableFeatsMethodBodyTemplate, + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + eObjEClass.getInstanceClass().getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.New.SUMMARY.get()); + return op; + } + + private List getRootAPINewOperationForEClassWithOnlyOneModifiableFeat( + FluentAPIGenerationContext context, EClass eObjEClass, EStructuralFeature modifiableFeature, + EClass initECls) { + + var ops = new ArrayList(); + + if (modifiableFeature.isMany()) { + // Many-valued features should also have a method that accepts one value (for + // convenience) + ops.addAll(getRootAPINewOperationForEClassWithOnlyOneModifiableManyValuedFeat(eObjEClass, modifiableFeature, + initECls, context)); + } else { + ops.addAll(getRootAPINewOperationForEClassWithOnlyOneModifiableSingleValuedFeat(eObjEClass, + modifiableFeature, initECls, context)); + } + + return ops; + } + + private List getRootAPINewOperationForEClassWithOnlyOneModifiableSingleValuedFeat(EClass eObjEClass, + EStructuralFeature modifiableFeature, EClass initECls, FluentAPIGenerationContext context) { + var ops = new ArrayList(); + + // Extract and re-use the EOperation generation, since only the parameter type + // is changed + Function opGenerator = (p) -> { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.New.NAME.getFor(StringUtils.capitalize(eObjEClass.getName())), eObjEClass); + FluentAPIGenerationUtil.addBody(op, + String.format(newXWithOnlyOneModifiableSingleValuedFeatsMethodBodyTemplate, + eObjEClass.getInstanceClass().getName(), + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + eObjEClass.getInstanceClass().getName(), + StringUtils.capitalize(modifiableFeature.getName()))); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.New.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(op, p); + return op; + }; + + var originalOpFeatureValParam = getSingleValuedFeatValParam(eObjEClass, modifiableFeature); + var originalOp = opGenerator.apply(originalOpFeatureValParam); + ops.add(originalOp); + + if (originalOpFeatureValParam.getEType().equals(EcorePackage.Literals.EBIG_INTEGER)) { + var longOpNewFeatValParam = getSingleValuedFeatValParam(eObjEClass, modifiableFeature); + longOpNewFeatValParam.setEType(EcorePackage.Literals.ELONG); + var longOp = opGenerator.apply(longOpNewFeatValParam); + ops.add(longOp); + + var intOpNewFeatValParam = getSingleValuedFeatValParam(eObjEClass, modifiableFeature); + intOpNewFeatValParam.setEType(EcorePackage.Literals.EINT); + var intOp = opGenerator.apply(intOpNewFeatValParam); + ops.add(intOp); + } + return ops; + } + + private List getRootAPINewOperationForEClassWithOnlyOneModifiableManyValuedFeat(EClass eObjEClass, + EStructuralFeature modifiableFeature, EClass initECls, FluentAPIGenerationContext context) { + var ops = new ArrayList(); + + var featureValParam = getSingleValuedFeatValParam(eObjEClass, modifiableFeature); + var listOp = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.New.NAME.getFor(StringUtils.capitalize(eObjEClass.getName())), eObjEClass); + FluentAPIGenerationUtil.addBody(listOp, + String.format(newXWithOnlyOneModifiableManyValuedFeatsMethodBodyTemplate_singleValue, + eObjEClass.getInstanceClass().getName(), + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + eObjEClass.getInstanceClass().getName(), StringUtils.capitalize(modifiableFeature.getName()))); + FluentAPIGenerationUtil.addDocumentation(listOp, ModelConstants.FluentAPI.New.SUMMARY.get()); + FluentAPIGenerationUtil.addEParameters(listOp, featureValParam); + ops.add(listOp); + + return ops; + } + + private EOperation getRootAPINewOperationForEClassWithoutModifiableFeats(EClass eObjEClass, EClass initECls, + FluentAPIGenerationContext context) { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.FluentAPI.New.NAME.getFor(StringUtils.capitalize(eObjEClass.getName())), eObjEClass); + FluentAPIGenerationUtil.addBody(op, + String.format(newXWithoutModifiableFeatsMethodBodyTemplate, eObjEClass.getInstanceClass().getName(), + FluentAPIGenerationUtil.getFullyQualifiedEClassName(context, initECls), + eObjEClass.getInstanceClass().getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.New.SUMMARY.get()); + return op; + } + + private EParameter getSingleValuedFeatValParam(EClass holderOfFeat, EStructuralFeature feat) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.FluentAPI.New.FEATURE_VALUE_PARAMETER_NAME.get(), feat.getEType()); + FluentAPIGenerationUtil.addDocumentation(param, ModelConstants.FluentAPI.New.FEATURE_VALUE_PARAMETER_DOC + .getFor(holderOfFeat.getName(), feat.getName())); + return param; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.New.NAME.getEmpty(), ModelConstants.FluentAPI.New.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWaitForMarkMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWaitForMarkMethodGenerator.java new file mode 100644 index 0000000000..91535e863f --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWaitForMarkMethodGenerator.java @@ -0,0 +1,38 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentAPIWaitForMarkExtension; +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIWaitForMarkMethodGenerator implements IFluentAPIMethodGenerator { + private static final String waitForMarkMethodBody = FluentAPIMethodsUtil + .joinLOC( + FluentAPIWaitForMarkExtension.class.getName() + ".addTask(" + + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.WAIT_FOR_MARK_TASK_PARAMETER_NAME.get() + ")", + "return this"); + + public EOperation generateAllWaitForMarkMethods(FluentAPIGenerationContext context) { + var taskParam = FluentAPIGeneralParameterGenerator.getWaitForMarkTaskParam(context); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.WaitForMark.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, waitForMarkMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.WaitForMark.DOC.get()); + FluentAPIGenerationUtil.addEParameters(op, FluentAPIGeneralParameterGenerator.getMarkKeyParam(), taskParam); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.WaitForMark.NAME.get(), + ModelConstants.FluentAPI.WaitForMark.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWithOperationGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWithOperationGenerator.java new file mode 100644 index 0000000000..c75f867c78 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/FluentAPIRootAPIWithOperationGenerator.java @@ -0,0 +1,119 @@ +package cipm.consistency.fluentapi.gen.rootapi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.extensions.FluentEObjectAPIMethods; +import cipm.consistency.fluentapi.gen.FluentAPIGeneralParameterGenerator; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPIRootAPIWithOperationGenerator implements IFluentAPIMethodGenerator { + private static final String featureValModificationMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Corresponding method's name in FluentEObjectAPIMethods + FluentEObjectAPIMethods.class.getName() + ".%s(this, " + + ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.MODIFIED_FEATURE_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get() + ")", + "return this"); + + private static final String featureValCleaningMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Corresponding method's name in FluentEObjectAPIMethods + FluentEObjectAPIMethods.class.getName() + ".%s(this, " + + ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get() + ", " + + ModelConstants.GeneralParameters.MODIFIED_FEATURE_PARAMETER_NAME.get() + ")", + "return this"); + + public List getAllAPITopLevelWithOperations(FluentAPIGenerationContext context) { + var ops = new ArrayList(); + + ops.add(getWithFeatOp(context)); + ops.add(getWithoutFeatOp(context)); + ops.add(getWithAddedFeatOp(context)); + ops.add(getWithRemovedFeatOp(context)); + ops.add(getCleanFeatOp(context)); + + return ops; + } + + private EOperation getWithFeatOp(FluentAPIGenerationContext context) { + var eobjParam = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var featParam = FluentAPIGeneralParameterGenerator.getFeatParam(); + var featValParam = FluentAPIGeneralParameterGenerator.getFeatValParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.WithFeat.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, String.format(featureValModificationMethodBodyTemplate, "xWithFeat")); + FluentAPIGenerationUtil.addEParameters(op, eobjParam, featParam, featValParam); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.WithFeat.SUMMARY.get()); + return op; + } + + private EOperation getWithoutFeatOp(FluentAPIGenerationContext context) { + var eobjParam = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var featParam = FluentAPIGeneralParameterGenerator.getFeatParam(); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.WithoutFeat.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, String.format(featureValCleaningMethodBodyTemplate, "xWithoutFeat")); + FluentAPIGenerationUtil.addEParameters(op, eobjParam, featParam); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.WithoutFeat.SUMMARY.get()); + return op; + } + + private EOperation getWithAddedFeatOp(FluentAPIGenerationContext context) { + var eobjParam = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var featParam = FluentAPIGeneralParameterGenerator.getFeatParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.WithAddedFeat.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, String.format(featureValModificationMethodBodyTemplate, "xWithAddedFeat")); + FluentAPIGenerationUtil.addEParameters(op, eobjParam, featParam, + FluentAPIGeneralParameterGenerator.getFeatValParam()); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.WithAddedFeat.SUMMARY.get()); + return op; + } + + private EOperation getWithRemovedFeatOp(FluentAPIGenerationContext context) { + var eobjParam = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var featParam = FluentAPIGeneralParameterGenerator.getFeatParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.WithRemovedFeat.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, + String.format(featureValModificationMethodBodyTemplate, "xWithRemovedFeat")); + FluentAPIGenerationUtil.addEParameters(op, eobjParam, featParam, + FluentAPIGeneralParameterGenerator.getFeatValParam()); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.WithRemovedFeat.SUMMARY.get()); + return op; + } + + private EOperation getCleanFeatOp(FluentAPIGenerationContext context) { + var eobjParam = FluentAPIGeneralParameterGenerator.getEObjectParam(); + var featParam = FluentAPIGeneralParameterGenerator.getFeatParam(); + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.FluentAPI.CleanFeat.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, String.format(featureValCleaningMethodBodyTemplate, "xCleanFeat")); + FluentAPIGenerationUtil.addEParameters(op, eobjParam, featParam); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.FluentAPI.CleanFeat.SUMMARY.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.FluentAPI.WithFeat.NAME.get(), ModelConstants.FluentAPI.WithFeat.SUMMARY.get(), + + ModelConstants.FluentAPI.WithoutFeat.NAME.get(), ModelConstants.FluentAPI.WithoutFeat.SUMMARY.get(), + + ModelConstants.FluentAPI.WithAddedFeat.NAME.get(), ModelConstants.FluentAPI.WithAddedFeat.SUMMARY.get(), + + ModelConstants.FluentAPI.WithRemovedFeat.NAME.get(), + ModelConstants.FluentAPI.WithRemovedFeat.SUMMARY.get(), + + ModelConstants.FluentAPI.CleanFeat.NAME.get(), ModelConstants.FluentAPI.CleanFeat.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/package-info.java new file mode 100644 index 0000000000..fe793fc5db --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/rootapi/package-info.java @@ -0,0 +1,31 @@ +/** + * Contains the generator classes responsible for generating the EMF model + * elements of the root class of the fluent API, i.e. the facade of the fluent + * API, along with its methods. + *

    + *

    + * The generation-related code within the generator classes is written in a + * modular way, such that modifying {@link ModelConstants} via refactoring + * operations should reflect MOST of the changes to the generated model. + * Due to the potential usage of static methods SM within the generated + * fluent API code, it is NOT NECESSARILY guaranteed to generate valid methods + * after changes to the generation code: If the name of any SM changes, their + * occurrences within the generation code must be changed MANUALLY. As + * Java currently does not allow controlling names of static methods through + * constant variables in the code, this issue cannot be addressed without + * generating all SM alongside fluent API. + *

    + *

    + * In the current version, the FluentAPIRootAPIEClassGenerator class is the + * top-most generator within this package. It is responsible for generating the + * EClass of the facade of the fluent API. The rest of the classes within this + * package are responsible for generating EOperation instances of various + * methods of the fluent API. Names of these classes denote which methods they + * are responsible for. + *

    + *

    + * Note: Some of the generator classes may implement multiple methods for + * generation purposes. If that is the case, refer to their documentation for + * further details / instructions. + */ +package cipm.consistency.fluentapi.gen.rootapi; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationCreateNowMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationCreateNowMethodGenerator.java new file mode 100644 index 0000000000..7b156d6f69 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationCreateNowMethodGenerator.java @@ -0,0 +1,84 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPISuperInitialisationCreateNowMethodGenerator implements IFluentAPIMethodGenerator { + private static final String createNowMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + ModelConstants.SuperInitialisation.ToAPI.NAME.thisCall() + + ModelConstants.FluentAPI.DropInitialisation.NAME.call("this"), + // %s: Element class + "return (%s) " + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall()); + + public List generateAllCreateNowMethods(EClass elemToInit) { + var ops = new ArrayList(); + + ops.add(generateCreateNowMethod(elemToInit)); + ops.add(generateGenericCreateNowMethod(elemToInit)); + + return ops; + } + + private EOperation generateCreateNowMethod(EClass elemToInit) { + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.CreateNow.NAME.get(), + elemToInit); + FluentAPIGenerationUtil.addBody(op, + String.format(createNowMethodBodyTemplate, elemToInit.getInstanceClass().getName())); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.SuperInitialisation.CreateNow.DOC.get()); + return op; + } + + private EOperation generateGenericCreateNowMethod(EClass elemToInit) { + // Goal: T createNow(Class createNowMethodParamName) + + // "" in " T createNow(...)" + var methodTypeParam = FluentAPIGenerationUtil + .generateETypeParameter(ModelConstants.GeneralParameters.TYPE_PARAMETER_NAME.get()); + + // Make sure to create 2 generic types, one for the method parameter (Class) + // and one for the return type of the method (T) + + // "T" in "...createNow(Class createNowMethodParamName)" + var genericParamTypeForJavaClass = FluentAPIGenerationUtil + .generateEGenericTypeWithTypeParameter(methodTypeParam); + + // "Class" in "...createNow(Class createNowMethodParamName)" + var genericClassType = FluentAPIGenerationUtil + .generateEGenericTypeWithClassifier(EcorePackage.Literals.EJAVA_CLASS); + FluentAPIGenerationUtil.addTypeArgument(genericClassType, genericParamTypeForJavaClass); + + // "T" in "... T createNow(...)" + var genericParamTypeForOp = FluentAPIGenerationUtil.generateEGenericTypeWithTypeParameter(methodTypeParam); + + // "Class createNowMethodParamName" in "...createNow(Class + // createNowMethodParamName)" + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter( + ModelConstants.SuperInitialisation.CreateNow.CLASS_PARAMETER_NAME.get(), genericClassType); + + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.CreateNow.NAME.get(), + genericParamTypeForOp); + FluentAPIGenerationUtil.addBody(op, String.format(createNowMethodBodyTemplate, methodTypeParam.getName())); + FluentAPIGenerationUtil.addTypeParameters(op, methodTypeParam); + FluentAPIGenerationUtil.addEParameters(op, param); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.SuperInitialisation.CreateNow.CLASS_PARAMETER_DOC.get()); + + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.CreateNow.NAME.get(), + ModelConstants.SuperInitialisation.CreateNow.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationDelegateMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationDelegateMethodGenerator.java new file mode 100644 index 0000000000..b0c1981e19 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationDelegateMethodGenerator.java @@ -0,0 +1,126 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import cipm.consistency.fluentapi.gen.FluentAPIDocumentationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.FluentAPIParameterUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * Generates delegation methods that forward calls to the API class. + * + * @author Alp Torac Genc + */ +public class FluentAPISuperInitialisationDelegateMethodGenerator { + private static final Pattern methodsToDelegate = Pattern.compile(String.join("|", + new String[] { ModelConstants.FluentAPI.WithFeat.NAME.get(), + ModelConstants.FluentAPI.WithoutFeat.NAME.get(), ModelConstants.FluentAPI.WithAddedFeat.NAME.get(), + ModelConstants.FluentAPI.WithRemovedFeat.NAME.get(), ModelConstants.FluentAPI.CleanFeat.NAME.get(), + ModelConstants.FluentAPI.DropInitialisation.NAME.get(), + ModelConstants.FluentAPI.WaitForMark.NAME.get(), ModelConstants.FluentAPI.Mark.NAME.get(), + ModelConstants.FluentAPI.Unmark.NAME.get() })); + + @SuppressWarnings("serial") + private static final Map parameterOverrideMap = new LinkedHashMap<>() { + { + put(Pattern.compile(ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get()), + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall()); + put(Pattern.compile(ModelConstants.GeneralParameters.MARK_VALUE_PARAMETER_NAME.get()), + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisGetterCall()); + put(Pattern.compile(ModelConstants.FluentAPI.DropInitialisation.INITIALISATION_PARAMETER_NAME.get()), + "this"); + } + }; + + @SuppressWarnings("serial") + private static final Map methodNameOverrideMap = new LinkedHashMap<>() { + { + put(Pattern.compile(ModelConstants.FluentAPI.Mark.NAME.get()), + ModelConstants.SuperInitialisation.Mark.NAME.get()); + put(Pattern.compile(ModelConstants.FluentAPI.Unmark.NAME.get()), + ModelConstants.SuperInitialisation.Unmark.NAME.get()); + } + }; + + private static final String docPrefix = "Delegates to the matching method in " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + + ". Replaces the parameters and method names as follows ('in " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + "' with 'in this'): " + + String.join(", ", + parameterOverrideMap.entrySet().stream() + .map((e) -> String.format("'%s' with '%s'", e.getKey().toString(), e.getValue())) + .toArray(String[]::new)) + + ", " + + String.join(", ", + methodNameOverrideMap.entrySet().stream() + .map((e) -> String.format("'%s' with '%s'", e.getKey().toString(), e.getValue())) + .toArray(String[]::new)) + + FluentAPIDocumentationUtil.getDocParagraphSeparator() + "Documentation from " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis() + ": "; + + private static final String delegateMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + // %s: Method call string (with parameters in brackets) + ModelConstants.SuperInitialisation.ToAPI.NAME.thisCall() + ".%s", "return this"); + + private String replaceParameters(String serialisedParameters) { + var result = serialisedParameters; + for (var e : parameterOverrideMap.entrySet()) { + var pattern = e.getKey(); + var replacement = e.getValue(); + result = result.replaceAll(pattern.pattern(), replacement); + } + return result; + } + + public List generateAllDelegateMethods(FluentAPIGenerationContext context) { + var delegateOps = new ArrayList(); + var fluentAPIECls = context.getFluentAPIECls(); + for (var op : fluentAPIECls.getEOperations()) { + if (methodsToDelegate.matcher(op.getName()).matches()) { + var serialisedOriginalMethodCall = op.getName() + "(" + + FluentAPIParameterUtil.getSerialisedParametersFor(op) + ")"; + var copier = new EcoreUtil.Copier(); + var delegateOp = (EOperation) copier.copy(op); + copier.copyReferences(); + + // Adjust method name + for (var e : methodNameOverrideMap.entrySet()) { + var mnp = e.getKey(); + if (mnp.matcher(delegateOp.getName()).matches()) { + delegateOp.setName(e.getValue()); + } + } + + // Adjust parameters + delegateOp.getEParameters().removeIf((p) -> parameterOverrideMap.keySet().stream() + .anyMatch((pattern) -> pattern.matcher(p.getName()).matches())); + + if (FluentAPIParameterUtil.hasClashingMethods(delegateOps, delegateOp)) + continue; + + // Adjust return type + delegateOp.setEType(context.getInitSuperECls()); + + // Adjust body + FluentAPIGenerationUtil.addBody(delegateOp, + String.format(delegateMethodBodyTemplate, replaceParameters(serialisedOriginalMethodCall))); + var opDoc = FluentAPIGenerationUtil.getDocumentationOf(op); + var delegateOpDoc = opDoc != null ? docPrefix + opDoc : docPrefix; + FluentAPIGenerationUtil.addDocumentation(delegateOp, delegateOpDoc); + + delegateOps.add(delegateOp); + } + } + return delegateOps; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationEClassGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationEClassGenerator.java new file mode 100644 index 0000000000..abf55f680c --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationEClassGenerator.java @@ -0,0 +1,129 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.gen.FluentAPIDocumentationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * The generator class responsible for generating the EClass, which represents + * the abstract (super) initialisation class. This generator class will only + * generate the EClass of the super initialisation class and its EOperations, + * nothing else. Therefore, this generator class alone is not enough to generate + * the fluent api model. + *

    + *

    + * Note that the methods {@link #generateSuperInitialisationEClass()} and + * {@link #setupSuperInitialisationEClass(FluentAPIGenerationContext)} have to + * be called, in order to generate the EClass of the super initialisation class. + * + * @see {@link cipm.consistency.fluentapi.gen.FluentAPIGenerator} For more + * details on the flow of the fluent api generation. + * + * @author Alp Torac Genc + * + */ +public class FluentAPISuperInitialisationEClassGenerator { + private static final String delegatedDoc = "Delegates to its correspondent in " + + ModelConstants.SuperInitialisation.RootAPI.NAME.inThis(); + private static final Map summaries = new LinkedHashMap<>(); + + private EReference getCurrentElementReference() { + var currentElementReference = EcoreFactory.eINSTANCE.createEReference(); + currentElementReference.setChangeable(true); + currentElementReference.setContainment(false); + currentElementReference.setEType(EcorePackage.Literals.EOBJECT); + currentElementReference.setName(ModelConstants.SuperInitialisation.CurrentElement.NAME.get()); + currentElementReference.setLowerBound(1); + currentElementReference.setUpperBound(1); + FluentAPIGenerationUtil.addDocumentation(currentElementReference, + ModelConstants.SuperInitialisation.CurrentElement.DOC.get()); + return currentElementReference; + } + + private EReference getRootAPIReference(FluentAPIGenerationContext context) { + var rootAPIRef = EcoreFactory.eINSTANCE.createEReference(); + rootAPIRef.setChangeable(true); + rootAPIRef.setContainment(false); + rootAPIRef.setEType(context.getFluentAPIECls()); + rootAPIRef.setName(ModelConstants.SuperInitialisation.RootAPI.NAME.get()); + rootAPIRef.setLowerBound(1); + rootAPIRef.setUpperBound(1); + FluentAPIGenerationUtil.addDocumentation(rootAPIRef, ModelConstants.SuperInitialisation.RootAPI.DOC.get()); + return rootAPIRef; + } + + /** + * @return The EClass of the super initialisation class. + */ + public EClass generateSuperInitialisationEClass() { + var superType = EcoreFactory.eINSTANCE.createEClass(); + superType.setAbstract(true); + superType.setInterface(false); + superType.setName(ModelConstants.SuperInitialisation.CLASS_NAME.get()); + return superType; + } + + private void addEClassDoc(FluentAPIGenerationContext context) { + var doc = ModelConstants.SuperInitialisation.CLASS_DOC + .getFor(FluentAPIDocumentationUtil.serialiseSummaries(summaries)); + FluentAPIGenerationUtil.addDocumentation(context.getInitSuperECls(), doc); + } + + private void addRefs(FluentAPIGenerationContext context) { + context.setInitSuperEClsApiReference(getRootAPIReference(context)); + context.getInitSuperECls().getEStructuralFeatures().add(context.getInitSuperEClsApiReference()); + + context.setInitSuperEClsCurrentElement(getCurrentElementReference()); + context.getInitSuperECls().getEStructuralFeatures().add(context.getInitSuperEClsCurrentElement()); + } + + private void addOperations(FluentAPIGenerationContext context) { + var getInitEClsGen = new FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator(); + context.getInitSuperECls().getEOperations().add(getInitEClsGen.generateGetInitialisedEClassMethod()); + summaries.putAll(getInitEClsGen.getMethodNamesToDescriptions()); + + var createNowGen = new FluentAPISuperInitialisationCreateNowMethodGenerator(); + context.getInitSuperECls().getEOperations() + .addAll(createNowGen.generateAllCreateNowMethods(EcorePackage.Literals.EOBJECT)); + summaries.putAll(createNowGen.getMethodNamesToDescriptions()); + + var newElemGen = new FluentAPISuperInitialisationNewElementMethodGenerator(); + context.getInitSuperECls().getEOperations().add(newElemGen.generateNewElementMethod(context)); + summaries.putAll(newElemGen.getMethodNamesToDescriptions()); + + var resetGen = new FluentAPISuperInitialisationResetOperationGenerator(); + context.getInitSuperECls().getEOperations() + .add(resetGen.generateResetInitialisationMethod(context.getInitSuperECls())); + summaries.putAll(resetGen.getMethodNamesToDescriptions()); + + var toAPIGen = new FluentAPISuperInitialisationToAPIMethodGenerator(); + context.getInitSuperECls().getEOperations().add(toAPIGen.generateToAPIMethod(context)); + summaries.putAll(toAPIGen.getMethodNamesToDescriptions()); + + var delegateOpGen = new FluentAPISuperInitialisationDelegateMethodGenerator(); + var delegateOps = delegateOpGen.generateAllDelegateMethods(context); + context.getInitSuperECls().getEOperations().addAll(delegateOps); + delegateOps.forEach((op) -> summaries.put(op.getName(), delegatedDoc)); + } + + /** + * Sets up the super initialisation class' EClass by generating and adding its + * EOperations, EReferences, as well as documenting them. + * + * @param context The object encapsulating the context of fluent api generation. + */ + public void setupSuperInitialisationEClass(FluentAPIGenerationContext context) { + addRefs(context); + addOperations(context); + addEClassDoc(context); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator.java new file mode 100644 index 0000000000..50731e7b91 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator.java @@ -0,0 +1,32 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPISuperInitialisationGetInitialisedEClassMethodGenerator implements IFluentAPIMethodGenerator { + private static final String getInitialisedEClassMethodBody = FluentAPIMethodsUtil + .joinLOC("return " + ModelConstants.SuperInitialisation.NewElement.NAME.thisCall() + + ModelConstants.SuperInitialisation.CreateNow.NAME.call() + ".eClass()"); + + public EOperation generateGetInitialisedEClassMethod() { + var op = FluentAPIGenerationUtil.generateEOperation( + ModelConstants.SuperInitialisation.GetInitialisedEClass.NAME.get(), EcorePackage.Literals.ECLASS); + FluentAPIGenerationUtil.addBody(op, getInitialisedEClassMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, + ModelConstants.SuperInitialisation.GetInitialisedEClass.SUMMARY.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.GetInitialisedEClass.NAME.get(), + ModelConstants.SuperInitialisation.GetInitialisedEClass.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationNewElementMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationNewElementMethodGenerator.java new file mode 100644 index 0000000000..e3c808f253 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationNewElementMethodGenerator.java @@ -0,0 +1,29 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPISuperInitialisationNewElementMethodGenerator implements IFluentAPIMethodGenerator { + private static final String newElementMethodBody = FluentAPIMethodsUtil.joinLOC("return this"); + + public EOperation generateNewElementMethod(FluentAPIGenerationContext context) { + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.NewElement.NAME.get(), + context.getInitSuperECls()); + FluentAPIGenerationUtil.addBody(op, newElementMethodBody); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.SuperInitialisation.NewElement.DOC.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.NewElement.NAME.get(), + ModelConstants.SuperInitialisation.NewElement.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationResetOperationGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationResetOperationGenerator.java new file mode 100644 index 0000000000..a2d8de9ed1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationResetOperationGenerator.java @@ -0,0 +1,32 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPISuperInitialisationResetOperationGenerator implements IFluentAPIMethodGenerator { + private static final String resetMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + ModelConstants.SuperInitialisation.CurrentElement.NAME.thisSetterCall("null"), + // + "return this"); + + public EOperation generateResetInitialisationMethod(EClass initType) { + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.Reset.NAME.get(), + initType); + FluentAPIGenerationUtil.addBody(op, resetMethodBodyTemplate); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.SuperInitialisation.Reset.DOC.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.Reset.NAME.get(), + ModelConstants.SuperInitialisation.Reset.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationToAPIMethodGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationToAPIMethodGenerator.java new file mode 100644 index 0000000000..4c74c14f09 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/FluentAPISuperInitialisationToAPIMethodGenerator.java @@ -0,0 +1,30 @@ +package cipm.consistency.fluentapi.gen.superinit; + +import java.util.Map; + +import org.eclipse.emf.ecore.EOperation; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.IFluentAPIMethodGenerator; +import cipm.consistency.fluentapi.gen.ModelConstants; + +public class FluentAPISuperInitialisationToAPIMethodGenerator implements IFluentAPIMethodGenerator { + private static final String toAPIMethodBodyTemplate = FluentAPIMethodsUtil + .joinLOC("return " + ModelConstants.SuperInitialisation.RootAPI.NAME.thisGetterCall()); + + public EOperation generateToAPIMethod(FluentAPIGenerationContext context) { + var op = FluentAPIGenerationUtil.generateEOperation(ModelConstants.SuperInitialisation.ToAPI.NAME.get(), + context.getFluentAPIECls()); + FluentAPIGenerationUtil.addBody(op, toAPIMethodBodyTemplate); + FluentAPIGenerationUtil.addDocumentation(op, ModelConstants.SuperInitialisation.ToAPI.DOC.get()); + return op; + } + + @Override + public Map getMethodNamesToDescriptions() { + return Map.of(ModelConstants.SuperInitialisation.ToAPI.NAME.get(), + ModelConstants.SuperInitialisation.ToAPI.SUMMARY.get()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/package-info.java new file mode 100644 index 0000000000..6cb42fde5d --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/gen/superinit/package-info.java @@ -0,0 +1,26 @@ +/** + * Contains the generator classes responsible for generating the EMF model + * elements of the super Initialisation class of the fluent API, i.e. the + * abstract super type of all Initialisation classes. + *

    + *

    + * The generation-related code within the generator classes is written in a + * modular way, such that modifying {@link ModelConstants} via refactoring + * operations should reflect MOST of the changes to the generated model. + * Due to the potential usage of static methods SM within the generated + * fluent API code, it is NOT NECESSARILY guaranteed to generate valid methods + * after changes to the generation code: If the name of any SM changes, their + * occurrences within the generation code must be changed MANUALLY. As + * Java currently does not allow controlling names of static methods through + * constant variables in the code, this issue cannot be addressed without + * generating all SM alongside fluent API. + *

    + *

    + * In the current version, the FluentAPISuperInitialisationEClassGenerator class + * is the top-most generator within this package. It is responsible for + * generating the EClass of the super Initialisation class. The rest of the + * classes within this package are responsible for generating EOperation + * instances of various methods of the super Initialisation class. Names of + * these classes denote which methods they are responsible for. + */ +package cipm.consistency.fluentapi.gen.superinit; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelFilter.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelFilter.java new file mode 100644 index 0000000000..2cd4430afb --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelFilter.java @@ -0,0 +1,84 @@ +package cipm.consistency.fluentapi.metamodel; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * An abstract class meant to be implemented by classes, which filter the + * (EMF-based) metamodels with respect to their EClasses and EStructuralFeatures + * thereof. If constraints and invariants are considered within the metamodel, + * they have to be explicitly checked. + *

    + *

    + * Note: Do not use the original metamodel packages while type-checking or + * filtering, because {@link FluentAPITargetMetamodelPackageProvider} may or may + * not use the original metamodel packages. Attempting to use + * {@code originalECls.isSuperTypeOf(givenECls)} or vice versa may result in + * false, due to the original EClass and the given EClass being in different + * Resources entirely. Instead, use their EAttributes for type-checking (such as + * their name); excluding {@code eCls.getInstanceClass()} and related methods, + * since they are not guaranteed to exist in parsed models. + * + * @author Alp Torac Genc + */ +public abstract class FluentAPITargetMetamodelFilter { + /** + * @param eCls A given EClass + * @return Whether eCls should be considered in the fluent api + */ + public abstract boolean isEClassEligible(EClass eCls); + + /** + * @param holderOfFeat The EClass that contains the feat. Must be specified, + * since feat may also be declared in a super EClass. + * @param feat A feature of the metamodel + * @return Whether the given feature should be considered in fluent api for the + * given EClass (holderOfFeat) + */ + public abstract boolean isFeatureEligible(EClass holderOfFeat, EStructuralFeature feat); + + /** + * @param eObjEClass An EClass within the metamodel. + * @return All features of the given EClass that are considered in fluent api + * (according to {@link #isFeatureEligible(EClass, EStructuralFeature)}) + */ + public List getModifiableFeatures(EClass eObjEClass) { + return eObjEClass.getEAllStructuralFeatures().stream().filter((f) -> this.isFeatureEligible(eObjEClass, f)) + .collect(Collectors.toList()); + } + + /** + * @param eObjEClass An EClass within the metamodel. + * @return The number of features of the given EClass that are considered in + * fluent api (according to + * {@link #isFeatureEligible(EClass, EStructuralFeature)}) + */ + public long getModifiableFeatureCount(EClass eObjEClass) { + return eObjEClass.getEAllStructuralFeatures().stream().filter((f) -> this.isFeatureEligible(eObjEClass, f)) + .count(); + } + + /** + * @param eObjEClass An EClass within the metamodel. + * @return Whether the given EClass has any features that are considered in + * fluent api (according to + * {@link #isFeatureEligible(EClass, EStructuralFeature)}) + */ + public boolean hasModifiableFeatures(EClass eObjEClass) { + return eObjEClass.getEAllStructuralFeatures().stream().anyMatch((f) -> this.isFeatureEligible(eObjEClass, f)); + } + + /** + * Not declared as static, because the underlying metamodel could introduce + * constraints regarding this. This is a default implementation. + * + * @param feat A feature within the metamodel + * @return Whether the given feat can be modified + */ + public boolean isFeatureChangeable(EStructuralFeature feat) { + return feat.isChangeable() && !feat.isDerived(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelPackageProvider.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelPackageProvider.java new file mode 100644 index 0000000000..8b9535810f --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/FluentAPITargetMetamodelPackageProvider.java @@ -0,0 +1,126 @@ +package cipm.consistency.fluentapi.metamodel; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; + +/** + * An abstract class meant to be implemented by classes, which provide access to + * (EMF-based) metamodels. It is recommended to have the implementing classes + * parse the entire (or a valid subset) of the metamodel, in order to avoid + * potential EMF errors due to model invalidity. Instead, + * {@link FluentAPITargetMetamodelFilter} can be implemented and used to + * filter undesired EClasses and EStructuralFeatures. + *

    + *

    + * Note: Implementors of this class may or may not use the original metamodel + * packages, due to Eclipse limitations. Attempting to use + * {@code originalECls.isSuperTypeOf(givenECls)} or vice versa may result in + * false, due to the original EClass and the given EClass being in different + * Resources entirely. Instead, use their EAttributes for type-checking (such as + * their name); excluding {@code eCls.getInstanceClass()} and related methods, + * since they are not guaranteed to exist in parsed models. + * + * @author Alp Torac Genc + */ +public abstract class FluentAPITargetMetamodelPackageProvider { + /** + * @return The name of the metamodel + */ + public abstract String getTargetMetamodelName(); + + /** + * Considers the Resource instance(s) of the metamodel that this object is + * actually using. Due to limitations, this object is not guaranteed to use the + * original metamodel. + * + * @return All EClasses of the metamodel (including those for abstract classes + * and interfaces) + */ + public abstract List getAllTargetMetamodelEClasses(); + + /** + * Considers the Resource instance(s) of the metamodel that this object is + * actually using. Due to limitations, this object is not guaranteed to use the + * original metamodel. + * + * @return All concrete EClasses of the metamodel, i.e. EClasses of classes + * within the metamodel that can be instantiated. + */ + public List getAllTargetMetamodelConcreteEClasses() { + var topPac = getTargetMetamodelEcoreEPackages().get(0); + return List.copyOf(MetamodelUtil.getAllConcreteEClasses(topPac)); + } + + /** + * Implemented as non-static, in order to enable implementors to override the + * EClass seeking logic. + * + * @param eClss A given collection of EClasses + * @param eClsName The name of the EClass to look for in eClss + * @return The sought EClass in eClss, if it exists; otherwise null + */ + protected EClass getEClassIn(Collection eClss, String eClsName) { + return eClss.stream().filter((eCls) -> eCls.getName().equals(eClsName)).findFirst().orElse(null); + } + + /** + * @param eClsName The name of the EClass to look for in + * {@link #getAllTargetMetamodelEClasses()} + * @return The sought EClass in {@link #getAllTargetMetamodelEClasses()}, if it + * exists; otherwise null + */ + public EClass getEClass(String eClsName) { + return this.getEClassIn(this.getAllTargetMetamodelEClasses(), eClsName); + } + + /** + * Similar to {@link #getAllTargetMetamodelEClasses()}, but retrieves EClasses + * from the original metamodel instead. Due to limitations, this object is not + * guaranteed to use the original metamodel. + * + * @return All EClasses of the original metamodel (including those for abstract + * classes and interfaces) + */ + public abstract List getAllEClassesInOriginalMetamodel(); + + /** + * Similar to {@link #getAllTargetMetamodelConcreteEClasses()}, but retrieves + * concrete EClasses from the original metamodel instead. Due to limitations, + * this object is not guaranteed to use the original metamodel. + * + * @return All concrete EClasses of the original metamodel (including those for + * abstract classes and interfaces) + */ + public List getAllConcreteEClassesInOriginalMetamodel() { + var allEClss = getAllEClassesInOriginalMetamodel(); + return List + .of(allEClss.stream().filter((cls) -> !cls.isInterface() && !cls.isAbstract()).toArray(EClass[]::new)); + } + + /** + * Meant to provide access to foreign GenModels, which must be integrated to the + * GenModel of the fluent api. + *

    + *

    + * Considers the Resource instance(s) of the metamodel that this object is + * actually using. Due to limitations, this object is not guaranteed to use the + * original metamodel. + * + * @return A list of all GenModels of the metamodel, which encapsulate the means + * to generate code from the model. + */ + public abstract List getTargetMetamodelGenModels(); + + /** + * Considers the Resource instance(s) of the metamodel that this object is + * actually using. Due to limitations, this object is not guaranteed to use the + * original metamodel. + * + * @return A list of all EPackages from the ecore file(s) of the metamodel + */ + public abstract List getTargetMetamodelEcoreEPackages(); +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/MetamodelUtil.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/MetamodelUtil.java new file mode 100644 index 0000000000..c89e151aa6 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/MetamodelUtil.java @@ -0,0 +1,39 @@ +package cipm.consistency.fluentapi.metamodel; + +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; + +/** + * A utility class for metamodel operations, such as accessing their EClasses. + * + * @author Alp Torac Genc + */ +public final class MetamodelUtil { + /** + * @return All {@link EClass}es accessible under the topPac as well as its + * sub-packages. + */ + public static Collection getAllEClasses(EPackage topPac) { + var res = new ArrayList(); + var topPacEClsfiers = topPac.getEClassifiers(); + if (topPacEClsfiers != null) + topPacEClsfiers.stream().filter((cls) -> cls instanceof EClass).map((cls) -> (EClass) cls) + .forEach((cls) -> res.add(cls)); + var ePacs = topPac.getESubpackages(); + ePacs.forEach((pac) -> res.addAll(getAllEClasses(pac))); + return res; + } + + /** + * @return All concrete {@link EClass}es accessible under the topPac as well as + * its sub-packages. + */ + public static Collection getAllConcreteEClasses(EPackage topPac) { + var res = getAllEClasses(topPac); + res.removeIf((c) -> c.isAbstract() || c.isInterface()); + return res; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/package-info.java new file mode 100644 index 0000000000..8a591d30af --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/metamodel/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains classes that provide access to EMF-based metamodels, as well as + * those providing utility operations on them. The classes within this package + * can be used for fluent api model generation, in order to access the metamodel + * the fluent api is being generated for. + */ +package cipm.consistency.fluentapi.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationBigNumberParameterPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationBigNumberParameterPostProcessor.java new file mode 100644 index 0000000000..d8a17f5ca1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationBigNumberParameterPostProcessor.java @@ -0,0 +1,132 @@ +package cipm.consistency.fluentapi.postprocessor; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; + +/** + * A post-processor that adds overloading variants of methods, which take + * {@link BigInteger} or {@link BigDecimal} as parameters, for convenience. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationBigNumberParameterPostProcessor implements FluentAPIGenerationPostProcessor { + /** + * Replaces the original parameter in the method call string + *

    + * %s: Name of the original parameter + */ + private static final String adaptedBigDecimalParameterTemplate = java.math.BigDecimal.class.getName() + + ".valueOf(%s)"; + /** + * Replaces the original parameter in the method call string + *

    + * %s: Name of the original parameter + */ + private static final String adaptedBigIntegerParameterTemplate = java.math.BigInteger.class.getName() + + ".valueOf(%s)"; + + /** + * Original parameter type to overload -> List of types to overload for + */ + private static final Map> paramTypeOverrides = Map.of( + EcorePackage.Literals.EBIG_INTEGER, List.of(EcorePackage.Literals.EINT, EcorePackage.Literals.ELONG), + EcorePackage.Literals.EBIG_DECIMAL, List.of(EcorePackage.Literals.EDOUBLE, EcorePackage.Literals.EFLOAT)); + + /** + * {@link #getEClsScope()} + */ + private List eClsScope; + + /** + * @param eClsScope {@link #getEClsScope()} + */ + public FluentAPIGenerationBigNumberParameterPostProcessor(List eClsScope) { + this.eClsScope = eClsScope; + } + + /** + * @return The list of {@link EClass}es that this post-processor should apply to + */ + public List getEClsScope() { + return this.eClsScope; + } + + /** + * @return The appropriate replacement template for the original parameter for + * the given EClassifier + */ + private String getParameterAdaptationTemplate(EClassifier paramTypeToReplace) { + return paramTypeToReplace.equals(EcorePackage.Literals.EBIG_INTEGER) ? adaptedBigIntegerParameterTemplate + : adaptedBigDecimalParameterTemplate; + } + + private EOperation createOverloadingMethodFor(EOperation opToOverload, EClassifier paramTypeToReplace, + EClassifier newParamType) { + var copier = new EcoreUtil.Copier(); + var overloadingOp = (EOperation) copier.copy(opToOverload); + copier.copyReferences(); + + // Adjust parameters + var params = overloadingOp.getEParameters(); + var oldTypes = new LinkedHashMap(); + + for (var param : params) { + if (param.getEType().equals(paramTypeToReplace)) { + oldTypes.put(param, param.getEType()); + param.setEType(newParamType); + } + } + + // Adjust method body + var serialisedParameters = String.join(",", + params.stream() + .map((p) -> oldTypes.containsKey(p) + ? String.format(getParameterAdaptationTemplate(oldTypes.get(p)), p.getName()) + : p.getName()) + .toArray(String[]::new)); + FluentAPIGenerationUtil.addBody(overloadingOp, FluentAPIMethodsUtil + .joinLOC("return this." + overloadingOp.getName() + "(" + serialisedParameters + ")")); + FluentAPIGenerationUtil.useDocumentationOf(overloadingOp, opToOverload); + + return overloadingOp; + } + + private List createOverloadingMethodsFor(EOperation opToOverload) { + var overloadingOpList = new ArrayList(); + + for (var paramTypeOverride : paramTypeOverrides.entrySet()) { + var paramTypeToReplace = paramTypeOverride.getKey(); + var newParamTypes = paramTypeOverride.getValue(); + if (opToOverload.getEParameters().stream() + .anyMatch((p) -> p.getEType() != null && p.getEType().equals(paramTypeToReplace))) { + for (var newParamType : newParamTypes) { + overloadingOpList.add(createOverloadingMethodFor(opToOverload, paramTypeToReplace, newParamType)); + } + } + } + + return overloadingOpList; + } + + @Override + public void apply() { + for (var eCls : this.getEClsScope()) { + for (var op : new ArrayList<>(eCls.getEOperations())) { + eCls.getEOperations().addAll(createOverloadingMethodsFor(op)); + } + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationForEachOverloadPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationForEachOverloadPostProcessor.java new file mode 100644 index 0000000000..c8ac473f34 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationForEachOverloadPostProcessor.java @@ -0,0 +1,87 @@ +package cipm.consistency.fluentapi.postprocessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; +import cipm.consistency.fluentapi.gen.FluentAPIMethodsUtil; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * An implementation of + * {@link FluentAPIGenerationMultipleValueParameterPostProcessor} that uses a + * for-each loop in overloading EOperations' method bodies, effectively calling + * the original EOperation for each given element in its parameter. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationForEachOverloadPostProcessor + extends FluentAPIGenerationMultipleValueParameterPostProcessor { + + private static final Pattern methodNamePatternToOverload = Pattern.compile(String.join("|", + ModelConstants.FluentAPI.WithAddedFeat.NAME.get(), ModelConstants.FluentAPI.WithRemovedFeat.NAME.get(), + ModelConstants.Initialiation.WithAdded.NAME.getFor(".*"), + ModelConstants.Initialiation.WithRemoved.NAME.getFor(".*"))); + + private static final Pattern paramNamePatternToOverload = Pattern + .compile(String.join("|", ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get(), + ModelConstants.Initialiation.With.PARAMETER_NAME.get(), + ModelConstants.Initialiation.WithAdded.PARAMETER_NAME.get(), + ModelConstants.Initialiation.WithRemoved.PARAMETER_NAME.get())); + + private static final String iterationParamName = "e"; + /** + * The method body template for method overloads with collections / lists / + * arrays of feature values as parameters. + */ + private static final String multipleValueMethodBodyTemplate = FluentAPIMethodsUtil.joinLOC( + "for (var " + iterationParamName + " : %s) this.%s(%s)", + // %s: Overloaded parameter name + // %s: Original method name + // %s: Serialised arguments + "return this"); + + public FluentAPIGenerationForEachOverloadPostProcessor(FluentAPIGenerationContext context, List eClsScope) { + super(context, eClsScope); + } + + /** + * {@inheritDoc} + *

    + * Uses a for-each loop in the method body of overloadingOp and calls the + * original EOperation for each element in its parameter. + */ + @Override + protected EOperation overloadMethodBody(EOperation overloadingOp, EParameter newParam) { + var serialisedArguments = new ArrayList(); + for (var p : overloadingOp.getEParameters()) { + if (p != newParam) { + serialisedArguments.add(p.getName()); + } else { + serialisedArguments.add(iterationParamName); + } + } + FluentAPIGenerationUtil.addBody(overloadingOp, + String.format(multipleValueMethodBodyTemplate, newParam.getName(), overloadingOp.getName(), + String.join(",", serialisedArguments.toArray(String[]::new)))); + return overloadingOp; + } + + @Override + protected boolean shouldOverloadParameter(EParameter param) { + return paramNamePatternToOverload.matcher(param.getName()).matches(); + } + + @Override + protected boolean shouldOverloadMethod(EOperation op) { + return methodNamePatternToOverload.matcher(op.getName()).matches() + && op.getEParameters().stream().anyMatch(this::shouldOverloadParameter) + && op.getEType().equals(op.getEContainingClass()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterPostProcessor.java new file mode 100644 index 0000000000..cacdfa97f5 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterPostProcessor.java @@ -0,0 +1,197 @@ +package cipm.consistency.fluentapi.postprocessor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.FluentAPIGenerationUtil; + +/** + * Introduces overloading methods for certain original methods that consider + * singular parameters: Given an EOperation op, where param is its only + * EParameter of type T, overloads op regarding param with 2 overloading + * methods: opArr with paramArr of type {@code T[]} and opCol with paramCol of + * type {@code Collection}. + *

    + *

    + * For more details on the method bodies of overloading methods, refer to the + * concrete implementor's documentation. + *

    + *

    + * Due to type erasure related limitations of Java, results of applying this + * post-processor might cause compilation errors, if there are multiple variants + * of a method with a Collection parameter. In such cases, the methods in this + * post-processor can be overridden to fix those cases. + * + * @author Alp Torac Genc + */ +public abstract class FluentAPIGenerationMultipleValueParameterPostProcessor + implements FluentAPIGenerationPostProcessor { + /** + * {@link #getContext()} + */ + private FluentAPIGenerationContext context; + /** + * {@link #getEClsScope()} + */ + private List eClsScope; + + /** + * @param context {@link #getContext()} + * @param eClsScope {@link #getEClsScope()} + */ + public FluentAPIGenerationMultipleValueParameterPostProcessor(FluentAPIGenerationContext context, + List eClsScope) { + this.context = context; + this.eClsScope = eClsScope; + } + + /** + * @return The list of {@link EClass}es that this post-processor should apply to + */ + public List getEClsScope() { + return this.eClsScope; + } + + /** + * @return The generation context of the model + */ + public FluentAPIGenerationContext getContext() { + return this.context; + } + + /** + * @param oldParam A given EParameter (with type T) + * @return An EParameter with an array-typed version of oldParam (T[]) + */ + private EParameter getArrayVersion(EParameter oldParam) { + var param = FluentAPIGenerationUtil.generateArrayValuedEParameter(this.getContext(), oldParam.getName(), + oldParam.getEType()); + FluentAPIGenerationUtil.useDocumentationOf(param, oldParam); + return param; + } + + /** + * @param oldParam A given EParameter (with type T) + * @return An EParameter with an collection-typed version of oldParam + * (Collection) + */ + private EParameter getColVersion(EParameter oldParam) { + var param = FluentAPIGenerationUtil.generateSingleValuedEParameter(oldParam.getName(), + FluentAPIGenerationUtil.generateCollectionTypeParameter(this.getContext(), oldParam.getEType())); + FluentAPIGenerationUtil.useDocumentationOf(param, oldParam); + return param; + } + + /** + * Contains the method overloading logic.Prepares overloadingOp as an + * overloading EOperation of an original EOperation. + * + * @param overloadingOp The EOperation, which will overload the original + * EOperation + * @param newParam The EParameter that the overloadingOp will use + * @return overloadingOp + */ + protected abstract EOperation overloadMethodBody(EOperation overloadingOp, EParameter newParam); + + private EOperation createOverloadingMultipleValueMethodFor(EOperation opToOverload, EParameter paramToOverload, + EParameter newParam) { + var copier = new EcoreUtil.Copier(); + var overloadingOp = (EOperation) copier.copy(opToOverload); + copier.copyReferences(); + + // Adjust parameter + var oldParamIdx = opToOverload.getEParameters().indexOf(paramToOverload); + var oldParam = overloadingOp.getEParameters().get(oldParamIdx); + + overloadingOp.getEParameters().add(oldParamIdx, newParam); + overloadingOp.getEParameters().remove(oldParam); + + // Adjust method body + overloadMethodBody(overloadingOp, newParam); + FluentAPIGenerationUtil.useDocumentationOf(overloadingOp, opToOverload); + + return overloadingOp; + } + + /** + * @return Whether op already has an overloading array-typed method with respect + * to EParameter p, or another method with a clashing signature + */ + private boolean hasArrayOverload(EOperation op, EParameter p) { + var pIdx = op.getEParameters().indexOf(p); + return op.getEContainingClass().getEOperations().stream().anyMatch((opTwo) -> op != opTwo + && op.getName().equals(opTwo.getName()) && op.getEType().equals(opTwo.getEType()) + && opTwo.getEParameters().get(pIdx).getEGenericType().getEClassifier().getInstanceClass().isArray() + && p.getEType().getInstanceClass().equals(opTwo.getEParameters().get(pIdx).getEGenericType() + .getEClassifier().getInstanceClass().getComponentType())); + } + + /** + * @return Whether op already has an overloading collection-typed method with + * respect to EParameter p, or another method with a clashing signature + */ + private boolean hasColOverload(EOperation op, EParameter p) { + var pIdx = op.getEParameters().indexOf(p); + return op.getEContainingClass().getEOperations().stream() + .anyMatch((opTwo) -> op != opTwo && op.getName().equals(opTwo.getName()) + && op.getEType().equals(opTwo.getEType()) && opTwo.getEParameters().get(pIdx).getEGenericType() + .getEClassifier().getInstanceClass().equals(Collection.class)); + } + + /** + * Given an EOperation op, where param is one of its parameters, decides whether + * op should be overloaded with respect to param. For more details about how it + * is overloaded, refer to the documentation of the concrete implementor. + * + * @param param The EParameter of an EOperation op, for which op will + * potentially be overloaded + * @return Whether op should be overloaded for param + */ + protected abstract boolean shouldOverloadParameter(EParameter param); + + private List createOverloadingMethodsFor(EOperation opToOverload) { + var ops = new ArrayList(); + for (var p : opToOverload.getEParameters().stream().filter(this::shouldOverloadParameter) + .collect(Collectors.toList())) { + if (!hasArrayOverload(opToOverload, p)) { + ops.add(createOverloadingMultipleValueMethodFor(opToOverload, p, getArrayVersion(p))); + } + if (!hasColOverload(opToOverload, p)) { + ops.add(createOverloadingMultipleValueMethodFor(opToOverload, p, getColVersion(p))); + } + } + return ops; + } + + /** + * Given an EOperation op, decides whether op should be overloaded. For more + * details about how it is overloaded, refer to the documentation of the + * concrete implementor. + * + * @param op The EOperation to potentially overload + * @return Whether the given EOperation should be overloaded + */ + protected abstract boolean shouldOverloadMethod(EOperation op); + + @Override + public void apply() { + for (var eCls : this.getEClsScope()) { + // Only consider EOperations, which have a single parameter and whose return + // type is their containing EClass + var opsToOverload = eCls.getEOperations().stream().filter((op) -> shouldOverloadMethod(op)) + .collect(Collectors.toList()); + for (var op : opsToOverload) { + op.getEContainingClass().getEOperations().addAll(createOverloadingMethodsFor(op)); + } + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor.java new file mode 100644 index 0000000000..8419e543e0 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor.java @@ -0,0 +1,71 @@ +package cipm.consistency.fluentapi.postprocessor; + +import java.util.List; +import java.util.regex.Pattern; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; + +import cipm.consistency.fluentapi.gen.FluentAPIGenerationContext; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * An implementation of + * {@link FluentAPIGenerationMultipleValueParameterPostProcessor} that uses a + * the same method body as the original EOperation in the overloading + * EOperations' method bodies. + *

    + *

    + * Note: Even though the original and overloading methods have the same body, + * their behaviour may differ, if changing the EParameter in overloading methods + * results in calling different methods (despite having identical method + * bodies). + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor + extends FluentAPIGenerationMultipleValueParameterPostProcessor { + private static final Pattern newMethodPatternToSkip = Pattern.compile(String.join("|", + ModelConstants.FluentAPI.New.TOP_NAME.get(), ModelConstants.SuperInitialisation.NewElement.NAME.get())); + + private static final Pattern newMethodPatternToOverload = Pattern + .compile(ModelConstants.FluentAPI.New.NAME.getFor(".*")); + + private static final Pattern waitForMarkMethodPatternToOverload = Pattern + .compile(ModelConstants.FluentAPI.WaitForMark.NAME.get()); + + private static final Pattern paramNamePatternToOverload = Pattern + .compile(String.join("|", ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get(), + ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get())); + + public FluentAPIGenerationMultipleValueParameterSameMethodBodyOverloadPostProcessor( + FluentAPIGenerationContext context, List eClsScope) { + super(context, eClsScope); + } + + /** + * {@inheritDoc} + *

    + * Does not modify overloadingOp, since its method body should remain the same. + */ + @Override + protected EOperation overloadMethodBody(EOperation overloadingOp, EParameter newParam) { + return overloadingOp; + } + + @Override + protected boolean shouldOverloadParameter(EParameter param) { + return paramNamePatternToOverload.matcher(param.getName()).matches(); + } + + @Override + protected boolean shouldOverloadMethod(EOperation op) { + return ((!newMethodPatternToSkip.matcher(op.getName()).matches() + && newMethodPatternToOverload.matcher(op.getName()).matches() + && op.getEAnnotations().get(0).getDetails().get(ModelConstants.GEN_MODEL_BODY_KEY.get()) + .contains("." + ModelConstants.Initialiation.WithAdded.NAME.getEmpty())) + || (waitForMarkMethodPatternToOverload.matcher(op.getName()).matches())) + && op.getEParameters().stream().anyMatch(this::shouldOverloadParameter); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationPostProcessor.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationPostProcessor.java new file mode 100644 index 0000000000..8b727977e1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/FluentAPIGenerationPostProcessor.java @@ -0,0 +1,20 @@ +package cipm.consistency.fluentapi.postprocessor; + +/** + * An interface for classes that are meant to post-process the generated fluent + * API model. Refer to the documentation of individual concrete implementors for + * more information. + *

    + *

    + * Note: The concrete implementors of this class are likely to be affected by + * changes to the fluent API model, therefore they might require adaptations. + * + * @author Alp Torac Genc + */ +public interface FluentAPIGenerationPostProcessor { + /** + * Applies this post-processor to model elements passed to this instance. Refer + * to the documentation of the concrete implementor for more details. + */ + public void apply(); +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/package-info.java new file mode 100644 index 0000000000..1c8f152c15 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/postprocessor/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains classes that can be used to post-process the generated fluent api + * model in various ways. Refer to the documentation of the individual + * post-processors for more details. + */ +package cipm.consistency.fluentapi.postprocessor; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/AbstractFluentAPITest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/AbstractFluentAPITest.java new file mode 100644 index 0000000000..0d21415897 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/AbstractFluentAPITest.java @@ -0,0 +1,37 @@ +package cipm.consistency.fluentapi.test; + +import org.junit.jupiter.api.BeforeEach; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; +import cipm.consistency.fluentapi.extensions.FluentAPIWaitForMarkExtension; + +/** + * An abstract test case class that concrete test case classes for testing + * fluent api should consider implementing. The purpose of this class is to + * aggregate common fluent api testing operations. + *

    + *

    + * It is recommended to override any methods with {@code @BeforeEach} and + * {@code @AfterEach} annotations, as well as calling their super versions as + * the first statement (for BeforeEach methods) or as the last statement (for + * AfterEach methods). + * + * @author Alp Torac Genc + */ +public abstract class AbstractFluentAPITest { + /** + * Prepares for the upcoming test case. + *

    + *

    + * AbstractFluentAPITest: Resets all extension classes that can be reset. + * + * @see {@link cipm.consistency.fluentapi.extensions} + */ + @BeforeEach + public void setUp() { + FluentAPIMarkExtension.clearAllMarks(); + FluentAPIWaitForMarkExtension.clearAllTasks(); + FluentAPIInitialisationStorage.clearAllOngoingInitialisations(); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIInitialisationStorageTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIInitialisationStorageTest.java new file mode 100644 index 0000000000..33f56f83f9 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIInitialisationStorageTest.java @@ -0,0 +1,164 @@ +package cipm.consistency.fluentapi.test; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EcoreFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.extensions.FluentAPIInitialisationStorage; + +/** + * A test case for testing {@link FluentAPIInitialisationStorage}. + * + * @author Alp Torac Genc + */ +public class FluentAPIInitialisationStorageTest { + /** + * Resets {@link FluentAPIInitialisationStorage} + */ + @BeforeEach + public void setUp() { + FluentAPIInitialisationStorage.clearAllOngoingInitialisations(); + } + + /** + * Ensures that adding a single (supposed) initialisation instance works as + * intended. + */ + @Test + public void testAddOngoingInitialisations_SingleInitialisation() { + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + + var supposedInit = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit); + + Assertions.assertEquals(1, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertTrue(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit)); + } + + /** + * Ensures that adding duplicated initialisation instances is handled + * accordingly. + */ + @Test + public void testAddOngoingInitialisations_NoDuplicatedInitialisation() { + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + + var supposedInit = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit); + + Assertions.assertEquals(1, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertTrue(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit)); + } + + /** + * Ensures that adding multiple (supposed) initialisation instances works as + * intended. + */ + @Test + public void testAddOngoingInitialisations_MultipleInitialisations() { + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + + var supposedInit1 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit1); + var supposedInit2 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit2); + + Assertions.assertEquals(2, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertTrue(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit1)); + Assertions.assertTrue(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit2)); + } + + /** + * Ensures that the (supposed) initialisation instances are retrieved in the + * order they were added. + */ + @Test + public void testAddOngoingInitialisations_Order() { + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + + var supposedInit1 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit1); + var supposedInit2 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit2); + + Assertions.assertEquals(2, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertSame(supposedInit1, FluentAPIInitialisationStorage.getOngoingInitialisations().get(0)); + Assertions.assertSame(supposedInit2, FluentAPIInitialisationStorage.getOngoingInitialisations().get(1)); + } + + /** + * Ensures that removing a single (supposed) initialisation instance works as + * intended. + */ + @Test + public void testDropInitialisation_SingleInitialisation() { + var supposedInit = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit); + FluentAPIInitialisationStorage.dropOngoingInitialisation(supposedInit); + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + } + + /** + * Ensures that removing multiple (supposed) initialisation instances works as + * intended. + */ + @Test + public void testDropInitialisation_MultipleInitialisations() { + var supposedInit1 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit1); + var supposedInit2 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit2); + + FluentAPIInitialisationStorage.dropOngoingInitialisation(supposedInit1); + Assertions.assertEquals(1, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertFalse(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit1)); + Assertions.assertTrue(FluentAPIInitialisationStorage.getOngoingInitialisations().contains(supposedInit2)); + + FluentAPIInitialisationStorage.dropOngoingInitialisation(supposedInit2); + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + } + + /** + * Ensures that the order of (supposed) initialisation instances are retained + * when they are removed (up to the one initialisation instance that was + * removed). + */ + @Test + public void testDropInitialisation_Order() { + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + + var supposedInit1 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit1); + var supposedInit2 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit2); + var supposedInit3 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit3); + + Assertions.assertArrayEquals(new EObject[] { supposedInit1, supposedInit2, supposedInit3 }, + FluentAPIInitialisationStorage.getOngoingInitialisations().toArray()); + + FluentAPIInitialisationStorage.dropOngoingInitialisation(supposedInit2); + + Assertions.assertEquals(2, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + Assertions.assertSame(supposedInit1, FluentAPIInitialisationStorage.getOngoingInitialisations().get(0)); + Assertions.assertSame(supposedInit3, FluentAPIInitialisationStorage.getOngoingInitialisations().get(1)); + } + + /** + * Ensures that removing all (supposed) initialisation instances works as + * intended. + */ + @Test + public void testClear() { + var supposedInit1 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit1); + var supposedInit2 = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIInitialisationStorage.addOngoingInitialisation(supposedInit2); + + FluentAPIInitialisationStorage.clearAllOngoingInitialisations(); + Assertions.assertEquals(0, FluentAPIInitialisationStorage.getOngoingInitialisations().size()); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIMarkExtensionTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIMarkExtensionTest.java new file mode 100644 index 0000000000..8c4416be8e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIMarkExtensionTest.java @@ -0,0 +1,333 @@ +package cipm.consistency.fluentapi.test; + +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EcoreFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; + +/** + * A test class meant to test {@link FluentAPIMarkExtensionTest}. + * + * @author Alp Torac Genc + */ +public class FluentAPIMarkExtensionTest { + /** + * Resets {@link FluentAPIMarkExtension} + */ + @BeforeEach + public void setUp() { + FluentAPIMarkExtension.clearAllMarks(); + } + + /** + * Asserts that the mark (key, val) exists and that all methods can find / + * retrieve the mark. + */ + private void assertContainsMark(Object key, EObject val) { + Assertions.assertTrue(FluentAPIMarkExtension.hasMark(key)); + + Assertions.assertTrue(FluentAPIMarkExtension.getAllMarks().entrySet().stream() + .anyMatch((e) -> e.getKey() == key && e.getValue() == val)); + Assertions.assertSame(val, FluentAPIMarkExtension.getMarked(key)); + Assertions.assertSame(val, FluentAPIMarkExtension.getMarked(key, val.getClass())); + } + + /** + * Asserts that the mark (key, val) does not and that no method can find / + * retrieve the mark. + */ + private void assertDoesNotContainMark(Object key, EObject val) { + Assertions.assertTrue(!FluentAPIMarkExtension.hasMark(key) || FluentAPIMarkExtension.getMarked(key) != val); + Assertions.assertFalse(FluentAPIMarkExtension.getAllMarks().entrySet().stream() + .anyMatch((e) -> e.getKey() == key && e.getValue() == val)); + Assertions.assertNotSame(val, FluentAPIMarkExtension.getMarked(key)); + Assertions.assertNotSame(val, FluentAPIMarkExtension.getMarked(key, val.getClass())); + } + + /** + * Ensures for non-existing marks that FluentAPIMarkExtension.getMarked(key) + * returns null. + */ + @Test + public void getMarkedTest_NoMark() { + Assertions.assertNull(FluentAPIMarkExtension.getMarked(new Object())); + } + + /** + * Ensures that for existing marks (key, val) that + * FluentAPIMarkExtension.getMarked(key) returns val. + */ + @Test + public void getMarkedTest_ExistingMark() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIMarkExtension.mark(key, val); + Assertions.assertSame(val, FluentAPIMarkExtension.getMarked(key)); + } + + /** + * Ensures that for existing marks (key, val) that + * FluentAPIMarkExtension.getMarked(key, cls) returns null, if cls is not in the + * type hierarchy of val's type. + */ + @Test + public void getMarkedTest_WithType_ExistingMark_MismatchingType() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIMarkExtension.mark(key, val); + Assertions.assertNull(FluentAPIMarkExtension.getMarked(key, int.class)); + } + + /** + * Ensures that for existing marks (key, val) that + * FluentAPIMarkExtension.getMarked(key, cls) returns val, if val's type is cls. + */ + @Test + public void getMarkedTest_WithType_ExistingMark_MatchingType() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIMarkExtension.mark(key, val); + Assertions.assertSame(val, FluentAPIMarkExtension.getMarked(key, val.getClass())); + } + + /** + * Ensures that for existing marks (key, val) that + * FluentAPIMarkExtension.getMarked(key, cls) returns val, if cls is a + * super-type of val's type. + */ + @Test + public void getMarkedTest_WithType_ExistingMark_SuperType() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIMarkExtension.mark(key, val); + Assertions.assertSame(val, FluentAPIMarkExtension.getMarked(key, Object.class)); + } + + /** + * Ensures that for existing marks (key, val) that + * FluentAPIMarkExtension.getMarked(key, cls) returns null, if cls is a sub-type + * of val's type. + */ + @Test + public void getMarkedTest_WithType_ExistingMark_SubType() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + FluentAPIMarkExtension.mark(key, val); + Assertions.assertNull(FluentAPIMarkExtension.getMarked(key, EAnnotation.class)); + } + + /** + * Ensures that adding and retrieving a single mark works as intended. + */ + @Test + public void markTest_OneMark() { + var key = new Object(); + var val = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key, val); + assertContainsMark(key, val); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + } + + /** + * Ensures that adding and retrieving multiple marks works as intended. + */ + @Test + public void markTest_DifferentMarks() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + + assertContainsMark(key1, val1); + assertContainsMark(key2, val2); + assertDoesNotContainMark(key1, val2); + assertDoesNotContainMark(key2, val1); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + } + + /** + * Ensures that overriding an existing mark (key, val1) to (key, val2) works as + * intended. + */ + @Test + public void markTest_OverridingMark() { + var key = new Object(); + + var val1 = EcoreFactory.eINSTANCE.createEObject(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key, val1); + FluentAPIMarkExtension.mark(key, val2); + + assertContainsMark(key, val2); + assertDoesNotContainMark(key, val1); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + } + + /** + * Ensures that marking an object with multiple different keys works as + * intended. + */ + @Test + public void markTest_OneValueMultipleMarks() { + var keyOne = new Object(); + var keyTwo = new Object(); + + var val = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(keyOne, val); + FluentAPIMarkExtension.mark(keyTwo, val); + + assertContainsMark(keyOne, val); + assertContainsMark(keyTwo, val); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + } + + /** + * Ensures that removing all existing marks works as intended. + */ + @Test + public void cleanMarksTest() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + FluentAPIMarkExtension.clearAllMarks(); + Assertions.assertEquals(0, FluentAPIMarkExtension.getAllMarks().size()); + } + + /** + * Ensures that unmarking based on only mark keys works as intended. + */ + @Test + public void unmarkTest_KeyOnly() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + + Assertions.assertSame(val1, FluentAPIMarkExtension.unmark(key1)); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + } + + /** + * Ensures that attempting to unmark non-existing marks works as intended. + */ + @Test + public void unmarkTest_KeyOnly_NonExistingMark() { + Assertions.assertNull(FluentAPIMarkExtension.unmark(new Object())); + } + + /** + * Ensures that calling unmark only has an effect for the first time and that + * duplicated unmark calls do not throw exceptions. + */ + @Test + public void unmarkTest_KeyOnly_RepeatedUnmarkCall() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + + Assertions.assertSame(val1, FluentAPIMarkExtension.unmark(key1)); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + + FluentAPIMarkExtension.unmark(key1); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + } + + /** + * Ensures that unmarking based on mark key and value works as intended, if the + * mark exists. + */ + @Test + public void unmarkTest_KeyAndValue_Matching() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + + Assertions.assertSame(val1, FluentAPIMarkExtension.unmark(key1, val1)); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + } + + /** + * Ensures that unmarking based on mark key and value works as intended, if the + * mark does not exist. + */ + @Test + public void unmarkTest_KeyAndValue_Mismatching() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + + Assertions.assertNull(FluentAPIMarkExtension.unmark(key1, val2)); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + } + + /** + * Ensures that calling unmark only has an effect for the first time and that + * duplicated unmark calls do not throw exceptions. + */ + @Test + public void unmarkTest_KeyAndValue_RepeatedUnmarkCall() { + var key1 = new Object(); + var val1 = EcoreFactory.eINSTANCE.createEObject(); + + var key2 = new Object(); + var val2 = EcoreFactory.eINSTANCE.createEObject(); + + FluentAPIMarkExtension.mark(key1, val1); + FluentAPIMarkExtension.mark(key2, val2); + Assertions.assertEquals(2, FluentAPIMarkExtension.getAllMarks().size()); + + Assertions.assertSame(val1, FluentAPIMarkExtension.unmark(key1, val1)); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + + FluentAPIMarkExtension.unmark(key1, val1); + Assertions.assertEquals(1, FluentAPIMarkExtension.getAllMarks().size()); + assertContainsMark(key2, val2); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPITestUtils.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPITestUtils.java new file mode 100644 index 0000000000..8f61d56367 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPITestUtils.java @@ -0,0 +1,40 @@ +package cipm.consistency.fluentapi.test; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; + +/** + * A utility class for the fluent api tests. + * + * @author Alp Torac Genc + */ +public class FluentAPITestUtils { + /** + * Asserts that the contents of the given array and the list are pairwise equal + * (in the sense of {@code Assertions.assertEquals(...)}. + * + * @param arr A given array + * @param list A given list + */ + public static void assertPairwiseEqual(Object[] arr, List list) { + Assertions.assertEquals(arr.length, list.size()); + for (int i = 0; i < list.size(); i++) { + Assertions.assertEquals(arr[i], list.get(i)); + } + } + + /** + * Asserts that the contents of the given lists are pairwise equal (in the sense + * of {@code Assertions.assertEquals(...)}. + * + * @param list1 A given list + * @param list2 Another given list + */ + public static void assertPairwiseEqual(List list1, List list2) { + Assertions.assertEquals(list1.size(), list2.size()); + for (int i = 0; i < list1.size(); i++) { + Assertions.assertEquals(list1.get(i), list2.get(i)); + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIWaitForMarkExtensionTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIWaitForMarkExtensionTest.java new file mode 100644 index 0000000000..ade7a08df4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/FluentAPIWaitForMarkExtensionTest.java @@ -0,0 +1,725 @@ +package cipm.consistency.fluentapi.test; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EcoreFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.extensions.FluentAPIMarkExtension; +import cipm.consistency.fluentapi.extensions.FluentAPIWaitForMarkExtension; + +/** + * A test class meant to test {@link FluentAPIWaitForMarkExtensionTest}. + * + * @author Alp Torac Genc + */ +public class FluentAPIWaitForMarkExtensionTest { + /** + * Resets {@link FluentAPIMarkExtension} and + * {@link FluentAPIWaitForMarkExtension} + */ + @BeforeEach + public void setUp() { + FluentAPIMarkExtension.clearAllMarks(); + FluentAPIWaitForMarkExtension.clearAllTasks(); + } + + /** + * @param list1 A list + * @param list2 Another list + * @return Whether all elements of the lists are reference-equal + */ + private boolean areAllElementsSame(List list1, List list2) { + if (list1 == list2) + return true; + if (list1 == null ^ list2 == null) + return false; + if (list1.size() != list2.size()) + return false; + return list2.stream().allMatch((sle) -> list1.stream().anyMatch((fle) -> fle == sle)); + } + + /** + * Asserts that all given tasks have been added and are waiting on exactly the + * given markKeys to be involved in a mark. + */ + private void assertTaskPending(List markKeys, List tasks) { + Assertions.assertTrue(FluentAPIWaitForMarkExtension.getAllPendingTasks().entrySet().stream() + .anyMatch((e) -> areAllElementsSame(e.getKey(), markKeys) && areAllElementsSame(e.getValue(), tasks))); + Assertions.assertTrue(FluentAPIWaitForMarkExtension.hasPendingTasks(markKeys)); + + Assertions.assertTrue(areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKeys))); + Assertions.assertTrue( + areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKeys.toArray()))); + + var allRequiredKeys = FluentAPIWaitForMarkExtension.getAllRequiredMarkKeys(); + for (var r : tasks) { + var requiredKeys = FluentAPIWaitForMarkExtension.getRequiredMarkKeysFor(r); + Assertions.assertTrue(allRequiredKeys.containsKey(r)); + Assertions.assertNotEquals(0, requiredKeys.size()); + } + } + + /** + * Asserts that all given tasks have been added and are waiting on exactly the + * given markKey to be involved in a mark. + */ + private void assertTaskPending(Object markKey, List tasks) { + assertTaskPending(List.of(markKey), tasks); + + Assertions.assertTrue(areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKey))); + Assertions.assertTrue(FluentAPIWaitForMarkExtension.hasPendingTasks(markKey)); + } + + /** + * Asserts that the given task has been added and is waiting on exactly the + * given markKey to be involved in a mark. + */ + private void assertTaskPending(Object markKey, Runnable task) { + assertTaskPending(markKey, List.of(task)); + } + + /** + * Asserts that the given task has been added and is waiting on exactly the + * given markKeys to be involved in a mark. + */ + private void assertTaskPending(List markKey, Runnable task) { + assertTaskPending(markKey, List.of(task)); + } + + /** + * Asserts that the given task has been added and is waiting on exactly the + * given markKeys to be involved in a mark. + */ + private void assertTaskPending(Object[] markKey, Runnable task) { + assertTaskPending(List.of(markKey), List.of(task)); + } + + /** + * Asserts that the given task has been added exactly duplicateCount times and + * is waiting for key. + * + * @param key The markKey + * @param task The task + * @param duplicateCount Amount of duplicated task occurrences + */ + private void assertPendingTaskCountEquals(Object key, Runnable task, int duplicateCount) { + var pendingTasks = FluentAPIWaitForMarkExtension.getPendingTasks(key); + var pendingDuplicatedTasks = pendingTasks != null + ? pendingTasks.stream().filter((r) -> r == task).collect(Collectors.toList()) + : List.of(); + Assertions.assertEquals(duplicateCount, pendingDuplicatedTasks.size()); + } + + /** + * Asserts that none of the given tasks are added and are not waiting on + * markKeys to be involved in a mark. + */ + private void assertTaskNotPending(List markKeys, List tasks) { + Assertions.assertTrue(FluentAPIWaitForMarkExtension.getAllPendingTasks().entrySet().stream() + .noneMatch((e) -> areAllElementsSame(e.getKey(), markKeys) && areAllElementsSame(e.getValue(), tasks))); + Assertions.assertFalse(areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKeys))); + Assertions.assertFalse( + areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKeys.toArray()))); + + var allRequiredKeys = FluentAPIWaitForMarkExtension.getAllRequiredMarkKeys(); + for (var r : tasks) { + var requiredKeys = FluentAPIWaitForMarkExtension.getRequiredMarkKeysFor(r); + Assertions.assertTrue(requiredKeys.isEmpty()); + Assertions.assertNull(allRequiredKeys.get(r)); + } + } + + /** + * Asserts that none of the given tasks are added and are not waiting on markKey + * to be involved in a mark. + */ + private void assertTaskNotPending(Object markKey, List tasks) { + assertTaskNotPending(List.of(markKey), tasks); + + Assertions.assertFalse(areAllElementsSame(tasks, FluentAPIWaitForMarkExtension.getPendingTasks(markKey))); + } + + /** + * Asserts that the given task is not added and is not waiting on markKey to be + * involved in a mark. + */ + private void assertTaskNotPending(Object markKey, Runnable task) { + assertTaskNotPending(markKey, List.of(task)); + } + + /** + * Asserts that the given task is not added and is not waiting on markKeys to be + * involved in a mark. + */ + private void assertTaskNotPending(List markKeys, Runnable task) { + assertTaskNotPending(markKeys, List.of(task)); + } + + /** + * Asserts that adding a single task with a single key works as intended, + * regardless of it being triggered. + */ + @Test + public void testAddTask_SingleKey() { + var key = new Object(); + Runnable r = () -> { + }; + + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + var neededKeys = FluentAPIWaitForMarkExtension.getRequiredMarkKeysFor(r); + Assertions.assertEquals(1, neededKeys.size()); + Assertions.assertEquals(1, neededKeys.get(0).size()); + Assertions.assertSame(key, neededKeys.get(0).get(0)); + } + + /** + * Asserts that adding a single task with an array of keys works as intended, + * regardless of it being triggered. Since + * {@link FluentAPIWaitForMarkExtension#addTask(Object, Runnable)} considers + * Object as its first parameter and is overloaded, ensuring that the correct + * method is used becomes particularly important. Otherwise, a key array keyArr + * = [key1, key2] itself can be considered as one key (so instead of key1 and + * key2, keyCol itself would mark the object). + */ + @Test + public void testAddTask_KeyArray() { + var keyOne = new Object(); + var keyTwo = new Object(); + var key = new Object[] { keyOne, keyTwo }; + Runnable r = () -> { + }; + + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + var neededKeys = FluentAPIWaitForMarkExtension.getRequiredMarkKeysFor(r); + Assertions.assertEquals(1, neededKeys.size()); + Assertions.assertEquals(2, neededKeys.get(0).size()); + for (int i = 0; i < key.length; i++) { + Assertions.assertSame(key[i], neededKeys.get(0).get(i)); + } + } + + /** + * Asserts that adding a single task with a collection of keys works as + * intended, regardless of it being triggered. Since + * {@link FluentAPIWaitForMarkExtension#addTask(Object, Runnable)} considers + * Object as its first parameter and is overloaded, ensuring that the correct + * method is used becomes particularly important. Otherwise, a key collection + * keyCol = [key1, key2] itself can be considered as one key (so instead of key1 + * and key2, keyCol itself would mark the object). + */ + @Test + public void testAddTask_KeyCollection() { + var keyOne = new Object(); + var keyTwo = new Object(); + var key = List.of(keyOne, keyTwo); + Runnable r = () -> { + }; + + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + var neededKeys = FluentAPIWaitForMarkExtension.getRequiredMarkKeysFor(r); + Assertions.assertEquals(1, neededKeys.size()); + Assertions.assertEquals(2, neededKeys.get(0).size()); + for (int i = 0; i < key.size(); i++) { + Assertions.assertSame(key.get(i), neededKeys.get(0).get(i)); + } + } + + /** + * Ensures that adding a single task waiting on a single markKey, as well as + * triggering it, work as intended. + */ + @Test + public void testAddTask_OneKeyOneTask() { + final var ran = new boolean[] { false }; + var key = new Object(); + Runnable r = () -> ran[0] = true; + + assertTaskNotPending(key, r); + + // Add the task, make sure it does not trigger + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + Assertions.assertFalse(ran[0]); + + // Add the mark, ensure task triggers + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(key, r); + Assertions.assertTrue(ran[0]); + } + + /** + * Ensures that adding multiple task waiting on a single markKey, as well as + * triggering them, work as intended. + */ + @Test + public void testAddTask_OneKeyManyTask() { + final var ran = new boolean[] { false, false }; + var key = new Object(); + + Runnable r1 = () -> ran[0] = true; + Runnable r2 = () -> ran[1] = true; + + // Add the tasks, make sure they do not trigger + FluentAPIWaitForMarkExtension.addTask(key, r1); + FluentAPIWaitForMarkExtension.addTask(key, r2); + assertTaskPending(key, List.of(r1, r2)); + Assertions.assertFalse(ran[0]); + Assertions.assertFalse(ran[1]); + + // Add the mark, ensure both tasks trigger + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(key, r1); + assertTaskNotPending(key, r2); + Assertions.assertTrue(ran[0]); + Assertions.assertTrue(ran[1]); + } + + /** + * Ensures that unmarking an existing key and then adding a task waiting on that + * key results in the task not running. + */ + @Test + public void testAddTask_OneKeyManyTask_UnmarkInBetween() { + final var ran = new boolean[] { false, false }; + var key = new Object(); + + Runnable r1 = () -> ran[0] = true; + Runnable r2 = () -> ran[1] = true; + + // Add first task and trigger it as usual, make sure second task does not + // trigger + FluentAPIWaitForMarkExtension.addTask(key, r1); + assertTaskPending(key, r1); + Assertions.assertFalse(ran[0]); + + // Add the mark, ensure that the first task triggers and second task does not + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(ran[0]); + Assertions.assertFalse(ran[1]); + + // Unmark the mutual key + FluentAPIMarkExtension.unmark(key); + + // Add second task and ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(key, r2); + assertTaskPending(key, r2); + Assertions.assertFalse(ran[1]); + + // Re-add the mark and ensure the second task triggers + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(key, r2); + Assertions.assertTrue(ran[1]); + } + + /** + * Ensures that adding a task waiting on multiple keys, as well as triggering + * it, work as intended. + */ + @Test + public void testAddTask_ManyKeyOneTask() { + final var ran = new boolean[] { false }; + var key1 = new Object(); + var key2 = new Object(); + Runnable r = () -> ran[0] = true; + + var keyList = List.of(key1, key2); + + assertTaskNotPending(keyList, r); + + // Add the task, make sure it does not trigger + FluentAPIWaitForMarkExtension.addTask(keyList, r); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Add the first mark, make sure task does not trigger + FluentAPIMarkExtension.mark(key1, EcoreFactory.eINSTANCE.createEObject()); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Add the second mark, make sure task triggers + FluentAPIMarkExtension.mark(key2, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(keyList, r); + Assertions.assertTrue(ran[0]); + } + + /** + * Ensures that adding a task waiting on multiple keys, as well as triggering + * it, work as intended; if one of the keys gets unmarked before all marks are + * present. + */ + @Test + public void testAddTask_ManyKeyOneTask_UnmarkInBetween() { + final var ran = new boolean[] { false }; + var key1 = new Object(); + var key2 = new Object(); + var key3 = new Object(); + Runnable r = () -> ran[0] = true; + + var keyList = List.of(key1, key2, key3); + + assertTaskNotPending(keyList, r); + + // Add the task, make sure it does not trigger + FluentAPIWaitForMarkExtension.addTask(keyList, r); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Add the first mark, make sure task does not trigger + FluentAPIMarkExtension.mark(key1, EcoreFactory.eINSTANCE.createEObject()); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Add the second mark, make sure task does not trigger + FluentAPIMarkExtension.mark(key2, EcoreFactory.eINSTANCE.createEObject()); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Remove the first mark + FluentAPIMarkExtension.unmark(key1); + + // Add the third mark, make sure task does not trigger (since the second mark is + // unmarked) + FluentAPIMarkExtension.mark(key3, EcoreFactory.eINSTANCE.createEObject()); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Re-add the first mark, make sure task triggers (since all marks are present) + FluentAPIMarkExtension.mark(key1, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(keyList, r); + Assertions.assertTrue(ran[0]); + } + + /** + * Checks whether nested tasks for the same key work as intended, i.e. both of + * them trigger upon the given key getting used to mark an element. + */ + @Test + public void testAddTask_OneKeyOneNestedTask() { + var keyOne = new Object(); + final var taskRan = new boolean[] { false, false }; + + /* + * outerTask runs and adds innerTask during its execution (for the same key, + * keyOne) + */ + Runnable innerTask = () -> taskRan[1] = true; + Runnable outerTask = () -> { + taskRan[0] = true; + FluentAPIWaitForMarkExtension.addTask(keyOne, innerTask); + }; + + // Add the outerTask, ensure that neither it nor innerTask trigger + FluentAPIWaitForMarkExtension.addTask(keyOne, outerTask); + Assertions.assertFalse(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the mark, ensure that outerTask and innerTask trigger + FluentAPIMarkExtension.mark(keyOne, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[0]); + Assertions.assertTrue(taskRan[1]); + } + + /** + * Checks whether nested tasks for the same key work as intended, if the + * corresponding mark is unmarked; i.e. the outer task triggers upon the given + * key getting used to mark an element, the inner task triggers once that key is + * re-used to mark an element. + */ + @Test + public void testAddTask_OneKeyOneNestedTask_UnmarkInBetween() { + var keyOne = new Object(); + final var taskRan = new boolean[] { false, false }; + + /* + * outerTask runs, unmarks its key (keyOne) and then adds innerTask during its + * execution (for the same key, keyOne) + */ + Runnable innerTask = () -> taskRan[1] = true; + Runnable outerTask = () -> { + taskRan[0] = true; + FluentAPIMarkExtension.unmark(keyOne); + FluentAPIWaitForMarkExtension.addTask(keyOne, innerTask); + }; + + // Add the outerTask, ensure that neither it nor innerTask trigger + FluentAPIWaitForMarkExtension.addTask(keyOne, outerTask); + Assertions.assertFalse(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the mark, ensure that outerTask triggers but not innerTask (since + // outerTask unmarks keyOne) + FluentAPIMarkExtension.mark(keyOne, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Re-add the mark, ensure that innerTask triggers + FluentAPIMarkExtension.mark(keyOne, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[1]); + } + + /** + * Checks whether nested tasks for different keys work as intended, if first the + * outer task' key and then the inner task' key is used to mark an element. In + * this case, first the outer task triggers and issues the inner task, then the + * inner task triggers. + */ + @Test + public void testAddTask_ManyKeyOneNestedTask_TriggerInOrder() { + var keyOne = new Object(); + var keyTwo = new Object(); + final var taskRan = new boolean[] { false, false }; + + /* + * outerTask runs and adds innerTask during its execution (for a different key, + * keyTwo) + */ + Runnable innerTask = () -> taskRan[1] = true; + Runnable outerTask = () -> { + taskRan[0] = true; + FluentAPIWaitForMarkExtension.addTask(keyTwo, innerTask); + }; + + // Add the outerTask, ensure that neither it nor innerTask trigger + FluentAPIWaitForMarkExtension.addTask(keyOne, outerTask); + Assertions.assertFalse(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the first mark, ensure that outerTask triggers and that innerTask is + // present but does not trigger (since keyTwo is missing) + FluentAPIMarkExtension.mark(keyOne, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the second mark, ensure that innreTask triggers + FluentAPIMarkExtension.mark(keyTwo, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[1]); + } + + /** + * Checks whether nested tasks for different keys work as intended, if first the + * inner task' key and then the outer task' key is used to mark an element. In + * this case, the inner task must wait on the outer task to trigger (even if the + * inner task' key is used to mark an element), because the outer task issues + * the inner task. Once the outer task triggers, the inner task triggers + * immediately afterward. + */ + @Test + public void testAddTask_ManyKeyOneNestedTask_InnerWaitsOnOuter() { + var keyOne = new Object(); + var keyTwo = new Object(); + final var taskRan = new boolean[] { false, false }; + + /* + * outerTask runs and adds innerTask during its execution (for a different key, + * keyTwo) + */ + Runnable innerTask = () -> taskRan[1] = true; + Runnable outerTask = () -> { + taskRan[0] = true; + FluentAPIWaitForMarkExtension.addTask(keyTwo, innerTask); + }; + + // Add the outerTask, ensure that neither it nor innerTask trigger + FluentAPIWaitForMarkExtension.addTask(keyOne, outerTask); + Assertions.assertFalse(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the second mark, ensure that neither outerTask nor innerTask trigger + // (since keyOne is missing for outerTask and innerTask is not yet added by the + // outerTask) + FluentAPIMarkExtension.mark(keyTwo, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertFalse(taskRan[0]); + Assertions.assertFalse(taskRan[1]); + + // Add the first mark, ensure that both tasks trigger (since both keyOne and + // keyTwo exist) + FluentAPIMarkExtension.mark(keyOne, EcoreFactory.eINSTANCE.createEObject()); + Assertions.assertTrue(taskRan[0]); + Assertions.assertTrue(taskRan[1]); + } + + /** + * Ensures that the same task can be added multiple times for the same key. + */ + @Test + public void testAddTask_DuplicatedTask() { + final var runCount = new int[] { 0 }; + var key = new Object(); + Runnable r = () -> runCount[0]++; + + assertTaskNotPending(key, r); + + // Add the task once, ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + Assertions.assertEquals(0, runCount[0]); + + // Add the task another time, ensure it is present twice and does not trigger + FluentAPIWaitForMarkExtension.addTask(key, r); + assertPendingTaskCountEquals(key, r, 2); + Assertions.assertEquals(0, runCount[0]); + + // Add the mark, ensure the task runs twice + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(key, r); + Assertions.assertEquals(2, runCount[0]); + } + + /** + * Ensures that the tasks are triggered immediately, if their required keys are + * present. + */ + @Test + public void testAddTask_TriggerUponAddingIfMarkExists() { + final var ran = new boolean[] { false }; + var key = new Object(); + Runnable r = () -> ran[0] = true; + + // Ensure that the task is not added and add the mark + assertTaskNotPending(key, r); + FluentAPIMarkExtension.mark(key, EcoreFactory.eINSTANCE.createEObject()); + assertTaskNotPending(key, r); + + // Add the task, ensure that it triggers immediately + FluentAPIWaitForMarkExtension.addTask(key, r); + Assertions.assertTrue(ran[0]); + assertTaskNotPending(key, r); + } + + /** + * Ensures that removing a task waiting on a single key works as intended. + */ + @Test + public void testRemoveTask_SingleKey() { + final var ran = new boolean[] { false }; + var key = new Object(); + Runnable r = () -> ran[0] = true; + + assertTaskNotPending(key, r); + + // Add the task for key, ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(key, r); + assertTaskPending(key, r); + Assertions.assertFalse(ran[0]); + + // Remove the task for key, ensure that it is neither present nor triggered + FluentAPIWaitForMarkExtension.removeTask(key, r); + assertTaskNotPending(key, r); + Assertions.assertFalse(ran[0]); + } + + /** + * Ensures that removing a task waiting on multiple keys (and exactly those + * keys) works as intended. + */ + @Test + public void testRemoveTask_MultipleKeys_RemoveTaskForExactKeys() { + final var ran = new boolean[] { false }; + var key1 = new Object(); + var key2 = new Object(); + var keyList = List.of(key1, key2); + Runnable r = () -> ran[0] = true; + + assertTaskNotPending(keyList, r); + + // Add the task for keyList, ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(keyList, r); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Remove the task for keyList, ensure that it is neither present nor triggered + FluentAPIWaitForMarkExtension.removeTask(keyList, r); + assertTaskNotPending(keyList, r); + Assertions.assertFalse(ran[0]); + } + + /** + * Ensures that a task waiting on multiple keys is not removed, if it is + * attempted to be removed for a strict subset of the keys. + */ + @Test + public void testRemoveTask_MultipleKeys_RemoveTaskForKeySubset() { + final var ran = new boolean[] { false }; + var key1 = new Object(); + var key2 = new Object(); + var keyList = List.of(key1, key2); + Runnable r = () -> ran[0] = true; + + assertTaskNotPending(keyList, r); + + // Add the task for keyList [key1, key2], ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(keyList, r); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + + // Attempt to remove the task for key1, ensure it is present but does not + // trigger + FluentAPIWaitForMarkExtension.removeTask(key1, r); + assertTaskPending(keyList, r); + Assertions.assertFalse(ran[0]); + } + + /** + * Ensures that a task waiting on multiple keys is not removed, if it is + * attempted to be removed for a strict superset of the keys. + */ + @Test + public void testRemoveTask_MultipleKeys_RemoveTaskForKeySuperset() { + final var ran = new boolean[] { false }; + var key1 = new Object(); + var key2 = new Object(); + var key3 = new Object(); + var keyListAdd = List.of(key1, key2); + var keyListRemove = List.of(key1, key2, key3); + Runnable r = () -> ran[0] = true; + + assertTaskNotPending(keyListAdd, r); + + // Add the task for keyListAdd [key1, key2], ensure it does not trigger + FluentAPIWaitForMarkExtension.addTask(keyListAdd, r); + assertTaskPending(keyListAdd, r); + Assertions.assertFalse(ran[0]); + + // Attempt to remove the task for keyListRemove [key1, key2, key3], ensure it is + // present but does not trigger + FluentAPIWaitForMarkExtension.removeTask(keyListRemove, r); + assertTaskPending(keyListAdd, r); + Assertions.assertFalse(ran[0]); + } + + /** + * Ensures that for duplicated tasks, only one occurrence is removed at a time. + */ + @Test + public void testRemoveTask_DuplicatedTask() { + final var runCount = new int[] { 0 }; + var key = new Object(); + Runnable r = () -> runCount[0]++; + + // Add the task for key twice, ensure that it is duplicated + FluentAPIWaitForMarkExtension.addTask(key, r); + FluentAPIWaitForMarkExtension.addTask(key, r); + assertPendingTaskCountEquals(key, r, 2); + // Ensure that the task is not triggered + Assertions.assertEquals(0, runCount[0]); + + // Remove one occurrence of task, ensure one occurrence is still present + FluentAPIWaitForMarkExtension.removeTask(key, r); + assertPendingTaskCountEquals(key, r, 1); + // Ensure that the task is not triggered + Assertions.assertEquals(0, runCount[0]); + + // Remove the last occurrence of task, ensure it is no longer present + FluentAPIWaitForMarkExtension.removeTask(key, r); + assertTaskNotPending(key, r); + // Ensure that the task is not triggered + Assertions.assertEquals(0, runCount[0]); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIInitialisationGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIInitialisationGenerationTest.java new file mode 100644 index 0000000000..f80a2a7291 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIInitialisationGenerationTest.java @@ -0,0 +1,211 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import cipm.consistency.fluentapi.gen.ModelConstants; +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * An abstract test class that implements test cases for the initialisations of + * the generated fluent api model, which ensure that certain initialisation + * operations (used for modifying model elements) are generated. Also contains a + * mutation test to check whether the other test cases work as intended. + * + * @author Alp Torac Genc + */ +public abstract class AbstractFluentAPIInitialisationGenerationTest extends AbstractFluentAPITest + implements IFluentAPIMetamodelTest, IFluentAPIMutationTest { + /** + * A mutation test to make sure that the other test cases within this class + * function as intended. + * + * @param info An object that contains information on the currently running test + * case + */ + @Test + public void mutationTest(TestInfo info) { + var eClssToMutate = new FluentAPIMutationTestRepresentativesGenerator() + .getRepresentativeTargetMetamodelConcreteEClasses_BasedOnModifiability().stream() + .map((eCls) -> FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls)) + .collect(Collectors.toSet()); + + var opsToRemove = new LinkedHashMap>(); + eClssToMutate.stream().forEach((eCls) -> opsToRemove.put(eCls, new ArrayList<>())); + eClssToMutate.stream() + .forEach((eCls) -> eCls.getEOperations().stream() + .filter((op) -> op.getName().startsWith(ModelConstants.Initialiation.With.NAME.getEmpty())) + .forEach((op) -> opsToRemove.get(eCls).add(op))); + eClssToMutate.stream() + .forEach((eCls) -> eCls.getEOperations().stream() + .filter((op) -> op.getName().startsWith(ModelConstants.Initialiation.Without.NAME.getEmpty())) + .forEach((op) -> opsToRemove.get(eCls).add(op))); + eClssToMutate.stream() + .forEach((eCls) -> eCls.getEOperations().stream() + .filter((op) -> op.getName().startsWith(ModelConstants.Initialiation.WithAdded.NAME.getEmpty())) + .forEach((op) -> opsToRemove.get(eCls).add(op))); + eClssToMutate.stream() + .forEach((eCls) -> eCls.getEOperations().stream().filter( + (op) -> op.getName().startsWith(ModelConstants.Initialiation.WithRemoved.NAME.getEmpty())) + .forEach((op) -> opsToRemove.get(eCls).add(op))); + eClssToMutate.stream() + .forEach((eCls) -> eCls.getEOperations().stream() + .filter((op) -> op.getName().startsWith(ModelConstants.Initialiation.Clean.NAME.getEmpty())) + .forEach((op) -> opsToRemove.get(eCls).add(op))); + + var oldOps = new LinkedHashMap>(); + eClssToMutate.stream().forEach((eCls) -> oldOps.put(eCls, List.copyOf(eCls.getEOperations()))); + + // Mutate the existing EMF model by removing the methods from above + opsToRemove.forEach((eCls, toRemove) -> eCls.getEOperations().removeAll(toRemove)); + + var mutTestRes = performMutationTesting(info); + + // Revert the mutation to EMF model + oldOps.forEach((eCls, ops) -> { + eCls.getEOperations().clear(); + eCls.getEOperations().addAll(ops); + }); + + assertTestsFailed(mutTestRes); + } + + /** + * {@inheritDoc} + *

    + *

    + * AbstractFluentAPIInitialisationGenerationTest: Sets up + * {@link FluentAPIGenerationTestSettings} for the current fluent api and the + * metamodel it is meant for. + */ + @BeforeEach + public void setUp() { + super.setUp(); + FluentAPIGenerationTestSettings.setFluentAPI(getAPI()); + FluentAPIGenerationTestSettings.setTargetMetamodelEClsToInitEClsFunc((eCls) -> api_getInitialisationForX(eCls).eClass()); + FluentAPIGenerationTestSettings.setMetamodelFilter(getFilter()); + FluentAPIGenerationTestSettings.setPackageProvider(getProvider()); + } + + /** + * Ensures that the Initialisation EClass initECls that is meant to create and + * modify instances of elemToInitECls contains certain methods. These methods + * should have the mutual name prefix methodNamePrefix. There should be at least + * one method for each feature in expectedFeats. + */ + private void methodTestTemplate(EClass elemToInitECls, EClass initECls, String methodNamePrefix, + String expectedParamName, List expectedFeats) { + for (var feature : expectedFeats) { + var paramName = expectedParamName; + var hasParams = paramName != null; + var paramType = feature.getEType(); + var expectMultiValueVariants = feature.isMany(); + var expectBigNumberVariants = FluentAPIGenerationTestSettings.getBigNumberVariantsFunc() + .apply(elemToInitECls); + + var currentMetName = methodNamePrefix + StringUtils.capitalize(feature.getName()); + var currentEClssOps = initECls.getEOperations().stream().filter((op) -> op.getName().equals(currentMetName)) +// .filter((op) -> op.getEType().equals(initECls)) + .collect(Collectors.toList()); + + var paramNames = hasParams ? List.of(paramName) : List.of(); + var paramTypes = hasParams ? List.of(paramType) : List.of(); + + // Ensure that the original method is present + FluentAPIGenerationTestAssertions.assertOriginalMethodExists(initECls, currentEClssOps, paramNames, + paramTypes); + if (hasParams && expectMultiValueVariants) { + FluentAPIGenerationTestAssertions.assertMultiValueVariantsExist(initECls, currentEClssOps, paramNames, + paramTypes); + } + if (hasParams && expectBigNumberVariants) { + FluentAPIGenerationTestAssertions.assertBigNumberVariantsExist(initECls, currentEClssOps, paramNames, + paramTypes); + } + } + } + + /** + * Checks whether{@code withX(featVal) : XInitialisation} methods for all + * supported EClasses exist in XInitialisation + */ + @Test + public void methodTest_Initialisation_WithX() { + for (var eCls : FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()) { + methodTestTemplate(eCls, FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls), + ModelConstants.Initialiation.With.NAME.getFor(""), + ModelConstants.Initialiation.With.PARAMETER_NAME.get(), + FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls).stream() + .filter((f) -> !f.isMany()).collect(Collectors.toList())); + } + } + + /** + * Checks whether{@code withoutX() : XInitialisation} methods for all supported + * EClasses exist in XInitialisation + */ + @Test + public void methodTest_Initialisation_WithoutX() { + for (var eCls : FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()) { + methodTestTemplate(eCls, FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls), + ModelConstants.Initialiation.Without.NAME.getFor(""), null, + FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls).stream() + .filter((f) -> !f.isMany()).collect(Collectors.toList())); + } + } + + /** + * Checks whether{@code withAddedX(featVals) : XInitialisation} methods for all + * supported EClasses exist in XInitialisation + */ + @Test + public void methodTest_Initialisation_WithAddedX() { + for (var eCls : FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()) { + methodTestTemplate(eCls, FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls), + ModelConstants.Initialiation.WithAdded.NAME.getFor(""), + ModelConstants.Initialiation.WithAdded.PARAMETER_NAME.get(), + FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls).stream() + .filter((f) -> f.isMany()).collect(Collectors.toList())); + } + } + + /** + * Checks whether{@code withRemovedX(featVals) : XInitialisation} methods for + * all supported EClasses exist in XInitialisation + */ + @Test + public void methodTest_Initialisation_WithRemovedX() { + for (var eCls : FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()) { + methodTestTemplate(eCls, FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls), + ModelConstants.Initialiation.WithRemoved.NAME.getFor(""), + ModelConstants.Initialiation.WithRemoved.PARAMETER_NAME.get(), + FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls).stream() + .filter((f) -> f.isMany()).collect(Collectors.toList())); + } + } + + /** + * Checks whether{@code cleanX() : XInitialisation} methods for all supported + * EClasses exist in XInitialisation + */ + @Test + public void methodTest_Initialisation_CleanX() { + for (var eCls : FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()) { + methodTestTemplate(eCls, FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc().apply(eCls), + ModelConstants.Initialiation.Clean.NAME.getFor(""), null, + FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls).stream() + .filter((f) -> f.isMany()).collect(Collectors.toList())); + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIMetamodelCoverageTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIMetamodelCoverageTest.java new file mode 100644 index 0000000000..3c2ef63ace --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIMetamodelCoverageTest.java @@ -0,0 +1,302 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; + +/** + * An abstract test class that implements test cases for fluent api class and + * the abstract (super) initialisation class' methods. Their purpose is to make + * sure that those methods have been generated and that they can be used (for + * trivial input). + * + * @author Alp Torac Genc + */ +public abstract class AbstractFluentAPIMetamodelCoverageTest extends AbstractFluentAPITest + implements IFluentAPIMetamodelTest { + + /** + * Uses {@link #getProvider()} to retrieve the EClasses and {@link #getFilter()} + * to filter them. + * + * @return A list of all eligible concrete EClasses in the metamodel the fluent + * api is generated for. + */ + private List getAllEligibleConcreteEClss() { + return getProvider().getAllConcreteEClassesInOriginalMetamodel().stream() + .filter((eCls) -> getFilter().isEClassEligible(eCls)).collect(Collectors.toList()); + } + + /** + * Ensures that each concrete class within the target metamodel can be + * instantiated via api.newX().createNow(). + */ + @Test + public void concreteElementCoverageTest_API_NewX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + Assertions.assertInstanceOf(eCls.getInstanceClass(), api_newX_createNow(eCls)); + Assertions.assertInstanceOf(eCls.getInstanceClass(), api_newX_createNow(eCls.getInstanceClass())); + } + } + + /** + * Ensures that each concrete class within the target metamodel can be + * instantiated via api.createNewX(). + */ + @Test + public void concreteElementCoverageTest_API_CreateNewX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + Assertions.assertInstanceOf(eCls.getInstanceClass(), api_createNewX(eCls.getInstanceClass())); + Assertions.assertInstanceOf(eCls.getInstanceClass(), + api_modifyX_createNow(api_createNewX(eCls.getInstanceClass()))); + } + } + + /** + * Ensures that each concrete class within the target metamodel has its own + * XInitialisation class. + */ + @Test + public void concreteElementCoverageTest_API_XInitialisationExistence() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var initEClsViaClass = api_getInitialisationForX_getInitialisedEClass(eCls.getInstanceClass()); + var initEClsViaEClass = api_getInitialisationForX_getInitialisedEClass(eCls); + var initEClsViaObj = api_getInitialisationForX_getInitialisedEClass( + eCls.getEPackage().getEFactoryInstance().create(eCls)); + Assertions.assertEquals(eCls, initEClsViaClass); + Assertions.assertEquals(eCls, initEClsViaEClass); + Assertions.assertEquals(eCls, initEClsViaObj); + + Assertions.assertEquals(initEClsViaEClass, initEClsViaClass); + Assertions.assertEquals(initEClsViaEClass, initEClsViaObj); + } + } + + /** + * Ensures that each concrete class within the target metamodel can be modified + * via the api.modifyX() method + */ + @Test + public void concreteElementCoverageTest_API_ModifyX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var init = api_newX(eCls); + var instance = init_createNow(init); + Assertions.assertInstanceOf(init.getClass(), api_modifyX(instance)); + Assertions.assertEquals(instance, api_modifyX_createNow(instance)); + } + } + + /** + * Ensures that each concrete class within the target metamodel may have its + * construction be continued via the api.continueX method. + */ + @Test + public void concreteElementCoverageTest_API_ContinueX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var init = api_newX(eCls); + Assertions.assertSame(init, api_continueX(eCls.getInstanceClass())); + } + } + + /** + * Ensures that each many-valued modifiable feature of each concrete class + * within the target metamodel can be modified via the api (with methods + * xWithAddedFeat(), xWithRemovedFeat(), xCleanFeat()). + */ + @Test + public void concreteElementCoverageTest_API_xManyValuedFeature() { + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var instance = (EObject) api_createNewX(eCls.getInstanceClass()); + for (var feat : getFilter().getModifiableFeatures(eCls)) { + if (feat.isMany()) { + api_xWithAddedFeat(instance, feat, instance.eGet(feat)); + api_xWithAddedFeat(instance, feat, ((EList) instance.eGet(feat)).toArray()); + api_xWithAddedFeat(instance, feat, List.copyOf(((EList) instance.eGet(feat)))); + + api_xWithRemovedFeat(instance, feat, instance.eGet(feat)); + api_xWithRemovedFeat(instance, feat, ((EList) instance.eGet(feat)).toArray()); + api_xWithRemovedFeat(instance, feat, List.copyOf(((EList) instance.eGet(feat)))); + + api_xCleanFeat(instance, feat); + } + } + } + } + + /** + * Ensures that each modifiable feature of each concrete class within the target + * metamodel can be modified via the api (with methods xWithFeat(), + * xWithoutFeat()). + */ + @Test + public void concreteElementCoverageTest_API_xSingleValuedFeature() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var instance = (EObject) api_createNewX(eCls.getInstanceClass()); + for (var feat : getFilter().getModifiableFeatures(eCls)) { + if (!feat.isMany()) { + api_xWithFeat(instance, feat, instance.eGet(feat)); + api_xWithoutFeat(instance, feat); + } + } + } + } + + /** + * Ensures that the fluent api methods getMarkedX(), mark() and unmark() are + * enabled for each concrete class of the target metamodel. + */ + @Test + public void concreteElementCoverageTest_API_Mark() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var keyAPI = new Object(); + + var instance = eCls.getEPackage().getEFactoryInstance().create(eCls); + Assertions.assertInstanceOf(eCls.getInstanceClass(), instance); + + api_mark(keyAPI, instance); + Assertions.assertSame(instance, api_getMarkedX(keyAPI)); + + api_unmark(keyAPI); + Assertions.assertNull(api_getMarkedX(keyAPI)); + + api_mark(keyAPI, instance); + Assertions.assertSame(instance, api_getMarkedX(keyAPI)); + + api_unmark(keyAPI, instance); + Assertions.assertNull(api_getMarkedX(keyAPI)); + } + } + + /** + * Ensures that the fluent api methods modifyMarkedX() is enabled for each + * concrete class of the target metamodel. + */ + @Test + public void concreteElementCoverageTest_API_ModifyMarkedX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var keyAPI = new Object(); + + var instance = eCls.getEPackage().getEFactoryInstance().create(eCls); + Assertions.assertInstanceOf(eCls.getInstanceClass(), instance); + + api_mark(keyAPI, instance); + + // modifyMarkedX call creates a new initialisation instance + var modMarkedInit = api_modifyMarkedX(keyAPI); + Assertions.assertSame(instance, init_createNow(modMarkedInit)); + } + } + + /** + * Ensures that the fluent api methods continueMarkedX() are enabled for each + * concrete class of the target metamodel. + */ + @Test + public void concreteElementCoverageTest_API_ContinueMarkedX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var keyAPI = new Object(); + var init = api_newX(eCls); + var instance = init_getCurrentElement(init); + + api_mark(keyAPI, instance); + + Assertions.assertSame(init, api_continueMarkedX(keyAPI)); + Assertions.assertSame(instance, init_createNow(init)); + } + } + + /** + * Ensures that each single-valued modifiable feature of each concrete class + * within the target metamodel can be modified via the superInit (with methods + * xWithFeat(), xWithoutFeat()). + */ + @Test + public void concreteElementCoverageTest_SuperInit_xSingleValuedFeature() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var instance = (EObject) api_createNewX(eCls.getInstanceClass()); + for (var feat : getFilter().getModifiableFeatures(eCls)) { + if (!feat.isMany()) { + api_modifyX_xWithFeat(instance, feat, instance.eGet(feat)); + api_modifyX_xWithoutFeat(instance, feat); + } + } + } + } + + /** + * Ensures that each modifiable many-valued feature of each concrete class + * within the target metamodel can be modified via the superInit (with methods + * xWithAddedFeat(), xWithRemovedFeat(), xCleanFeat()). + */ + @Test + public void concreteElementCoverageTest_SuperInit_xManyValuedFeature() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var instance = (EObject) api_createNewX(eCls.getInstanceClass()); + for (var feat : getFilter().getModifiableFeatures(eCls)) { + if (feat.isMany()) { + api_modifyX_xWithAddedFeat(instance, feat, instance.eGet(feat)); + api_modifyX_xWithAddedFeat(instance, feat, ((EList) instance.eGet(feat)).toArray()); + api_modifyX_xWithAddedFeat(instance, feat, List.copyOf(((EList) instance.eGet(feat)))); + + api_modifyX_xWithRemovedFeat(instance, feat, instance.eGet(feat)); + api_modifyX_xWithRemovedFeat(instance, feat, ((EList) instance.eGet(feat)).toArray()); + api_modifyX_xWithRemovedFeat(instance, feat, List.copyOf(((EList) instance.eGet(feat)))); + + api_modifyX_xCleanFeat(instance, feat); + } + } + } + } + + /** + * Ensures that the super initialisation methods mark() and unmark() are enabled + * for each concrete class of the target metamodel + */ + @Test + public void concreteElementCoverageTest_SuperInit_MarkX() { + + var allConcreteEClss = getAllEligibleConcreteEClss(); + for (var eCls : allConcreteEClss) { + var keySuperInit = new Object(); + + var init = api_newX(eCls); + var instance = init_getCurrentElement(init); + + init_mark(init, keySuperInit); + Assertions.assertSame(instance, api_getMarkedX(keySuperInit)); + + init_unmark(init, keySuperInit); + Assertions.assertNull(api_getMarkedX(keySuperInit)); + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIRootAPIGenerationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIRootAPIGenerationTest.java new file mode 100644 index 0000000000..2a9c288875 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/AbstractFluentAPIRootAPIGenerationTest.java @@ -0,0 +1,245 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EcorePackage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import cipm.consistency.fluentapi.test.AbstractFluentAPITest; +import cipm.consistency.fluentapi.gen.ModelConstants; + +/** + * An abstract test class that implements test cases for the fluent api class of + * the generated fluent api model, which ensure that certain fluent api clss + * operations are generated. Also contains a mutation test to check whether the + * other test cases work as intended. + * + * @author Alp Torac Genc + */ +public abstract class AbstractFluentAPIRootAPIGenerationTest extends AbstractFluentAPITest + implements IFluentAPIMetamodelTest, IFluentAPIMutationTest { + + /** + * A mutation test to make sure that the other test cases within this class + * function as intended. + * + * @param info An object that contains information on the currently running test + * case + */ + @Test + public void mutationTest(TestInfo info) { + var api = getAPI(); + var eClssToMutate = new FluentAPIMutationTestRepresentativesGenerator() + .getRepresentativeTargetMetamodelConcreteEClasses_BasedOnModifiability(); + + var eClssToMutateNames = eClssToMutate.stream().map((eCls) -> eCls.getName()).collect(Collectors.toList()); + + // Remove EOperations for certain types + var opsToRemove = api.eClass().getEOperations().stream() + .filter((op) -> eClssToMutateNames.stream().anyMatch((eClsName) -> op.getName().endsWith(eClsName))) + .collect(Collectors.toList()); + + // Mutate the existing EMF model by removing the methods from above + var oldOps = List.copyOf(api.eClass().getEOperations()); + api.eClass().getEOperations().removeAll(opsToRemove); + FluentAPIGenerationTestSettings.setFluentAPI(api); + + var mutTestRes = performMutationTesting(info); + + // Revert the mutation to EMF model + api.eClass().getEOperations().clear(); + api.eClass().getEOperations().addAll(oldOps); + FluentAPIGenerationTestSettings.setFluentAPI(api); + + assertTestsFailed(mutTestRes); + } + + /** + * {@inheritDoc} + *

    + *

    + * AbstractFluentAPIInitialisationGenerationTest: Sets up + * {@link FluentAPIGenerationTestSettings} for the current fluent api and the + * metamodel it is meant for. + */ + @BeforeEach + public void setUp() { + super.setUp(); + FluentAPIGenerationTestSettings.setFluentAPI(getAPI()); + FluentAPIGenerationTestSettings.setTargetMetamodelEClsToInitEClsFunc((eCls) -> api_getInitialisationForX(eCls).eClass()); + FluentAPIGenerationTestSettings.setMetamodelFilter(getFilter()); + FluentAPIGenerationTestSettings.setPackageProvider(getProvider()); + } + + /** + * Ensures that certain methods have been generated for the fluent api class + * according to the given testData. + */ + private void methodTestTemplate(FluentAPIMethodTestData testData) { + Assertions.assertFalse(testData.getEClssToCheckFor().isEmpty()); + + for (var eCls : testData.getEClssToCheckFor()) { + var paramNames = testData.getParamNames(eCls); + var paramTypes = testData.getParamTypes(eCls); + var expectMultiValueVariants = testData.getExpectMultiValueVariants(eCls); + var expectBigNumberVariants = testData.getExpectBigNumberVariants(eCls); + if (paramNames.size() != paramTypes.size()) + throw new IllegalArgumentException(); + + var currentEClssOpName = testData.getMethodName(eCls); + var currentEClssOps = FluentAPIGenerationTestSettings.getAllFluentAPIOps().stream() + .filter((op) -> op.getName().equals(currentEClssOpName)) + .filter((op) -> op.getEParameters().size() == paramNames.size()) +// .filter((op) -> op.getEType().equals(returnTypeOfOpFunc.apply(eCls)) + .collect(Collectors.toList()); + + // Ensure that the original method is present + FluentAPIGenerationTestAssertions.assertOriginalMethodExists(eCls, currentEClssOps, paramNames, paramTypes); + + if (expectMultiValueVariants) { + FluentAPIGenerationTestAssertions.assertMultiValueVariantsExist(eCls, currentEClssOps, paramNames, + paramTypes); + } + if (expectBigNumberVariants) { + FluentAPIGenerationTestAssertions.assertBigNumberVariantsExist(eCls, currentEClssOps, paramNames, + paramTypes); + } + } + } + + /** + * Checks whether{@code createNewX() : X} methods for all supported EClasses + * exist in fluent api class. + */ + @Test + public void methodTest_API_CreateNewX() { + + var testData = new FluentAPIMethodTestData(); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.CreateNew.NAME.getFor(eCls.getName())); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code newX() : XInitialisation} methods for all supported + * EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_NewX_WithoutParameters() { + + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.New.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code newX() : X} methods for all supported EClasses (without + * any modifiable features according to the metamodel feature filter) exist in + * fluent api class. + */ + @Test + public void methodTest_API_NewX_WithoutParameters_NoModifiableFeatures() { + + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.New.NAME.getFor(eCls.getName())); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code newX(Y) : X} methods for all supported EClasses (with + * exactly one modifiable feature according to the metamodel feature filter) + * exist in fluent api class. + */ + @Test + public void methodTest_API_NewX_WithParameter_SingleModifiableFeature() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.New.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setParamNameFunc((eCls) -> List.of(ModelConstants.GeneralParameters.FEATURE_VALUE_PARAMETER_NAME.get())); + testData.setParamTypeFunc((eCls) -> List.of(eCls)); + testData.setEClssToCheckFor( + FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat()); + testData.setExpectMultiValueVariantsFunc(FluentAPIGenerationTestSettings.getMultiValFunc()); + testData.setExpectBigNumberVariantsFunc(FluentAPIGenerationTestSettings.getBigNumberVariantsFunc()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code modifyX(X) : XInitialisation} methods for all supported + * EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_modifyX() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.Modify.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setParamNameFunc((eCls) -> List.of(ModelConstants.GeneralParameters.USED_EOBJECT_PARAMETER_NAME.get())); + testData.setParamTypeFunc((eCls) -> List.of(eCls)); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code modifyMarkedX(markKey) : XInitialisation} methods for + * all supported EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_modifyMarkedX() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.ModifyMarked.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setParamNameFunc((eCls) -> List.of(ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get())); + testData.setParamTypeFunc((eCls) -> List.of(EcorePackage.Literals.EJAVA_OBJECT)); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code continueX(X) : XInitialisation} methods for all + * supported EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_continueX() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.Continue.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodelWithModifiableFeats()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code continueMarkedX(markKey) : XInitialisation} methods for + * all supported EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_continueMarkedX() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.ContinueMarked.NAME.getFor(eCls.getName())); + testData.setReturnTypeOfOpFunc(FluentAPIGenerationTestSettings.getTargetMetamodelEClsToInitEClsFunc()); + testData.setParamNameFunc((eCls) -> List.of(ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get())); + testData.setParamTypeFunc((eCls) -> List.of(EcorePackage.Literals.EJAVA_OBJECT)); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodelWithModifiableFeats()); + methodTestTemplate(testData); + } + + /** + * Checks whether {@code getMarkedX(markKey) : X} methods for all supported + * EClasses exist in fluent api class. + */ + @Test + public void methodTest_API_getMarkedX() { + var testData = new FluentAPIMethodTestData(); + testData.setMethodNamePrefixFunc((eCls) -> ModelConstants.FluentAPI.GetMarked.NAME.getFor(eCls.getName())); + testData.setParamNameFunc((eCls) -> List.of(ModelConstants.GeneralParameters.MARK_KEY_PARAMETER_NAME.get())); + testData.setParamTypeFunc((eCls) -> List.of(EcorePackage.Literals.EJAVA_OBJECT)); + testData.setEClssToCheckFor(FluentAPIGenerationTestSettings.getAllSupportedConcreteEClssInTargetMetamodel()); + methodTestTemplate(testData); + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestAssertions.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestAssertions.java new file mode 100644 index 0000000000..0d5c248cc7 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestAssertions.java @@ -0,0 +1,132 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EcorePackage; +import org.junit.jupiter.api.Assertions; + +/** + * An utility class that contains assertion methods for the test classes, which + * test the fluent api model. + *

    + *

    + * Currently, checking types of EParameters of EOperations does not work, due to + * them being proxy objects. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationTestAssertions { + /** + * Asserts that eCls has an EOperation, whose signature matches the given + * paramNames and paramTypes. + */ + public static void assertOriginalMethodExists(EClass eCls, List currentEClssOps, + List paramNames, List paramTypes) { + Assertions.assertTrue(currentEClssOps.stream().anyMatch((op) -> assertParamsEqual(op, paramNames, paramTypes)), + "For eCls " + eCls.getName()); + } + + /** + * Asserts that eCls has an EOperation op, whose signature matches the given + * paramNames and paramTypes. Further asserts that there is a variant of op for + * array-valued parameters, as well as another variant of op for + * collection-valued parameters. + */ + public static void assertMultiValueVariantsExist(EClass eCls, List currentEClssOps, + List paramNames, List paramTypes) { + Assertions.assertTrue(3 <= currentEClssOps.size(), "For eCls " + eCls.getName()); + Assertions.assertTrue( + currentEClssOps.stream().anyMatch((op) -> assertArrayValuedParamsEqual(op, paramNames, paramTypes))); + Assertions.assertTrue(currentEClssOps.stream() + .anyMatch((op) -> assertCollectionValuedParamsEqual(op, paramNames, paramTypes))); + } + + /** + * Asserts that eCls has an EOperation op, whose signature matches the given + * paramNames and paramTypes. Further asserts that there are variants of op for + * the primitive types int and long if op considers BigInteger parameters, or + * float and double if op considers BigDecimal parameters. + */ + public static void assertBigNumberVariantsExist(EClass eCls, List currentEClssOps, + List paramNames, List paramTypes) { + Assertions.assertTrue(3 <= currentEClssOps.size(), "For eCls " + eCls.getName()); + Assertions.assertTrue(currentEClssOps.stream() + .anyMatch((op) -> assertParamsEqual(op, paramNames, + paramTypes.stream() + .map((t) -> t == EcorePackage.Literals.EBIG_INTEGER ? EcorePackage.Literals.EINT : t) + .collect(Collectors.toList())))); + Assertions + .assertTrue(currentEClssOps.stream() + .anyMatch((op) -> assertParamsEqual(op, paramNames, paramTypes.stream() + .map((t) -> t == EcorePackage.Literals.EBIG_INTEGER ? EcorePackage.Literals.ELONG : t) + .collect(Collectors.toList())))); + Assertions + .assertTrue(currentEClssOps.stream() + .anyMatch((op) -> assertParamsEqual(op, paramNames, paramTypes.stream() + .map((t) -> t == EcorePackage.Literals.EBIG_DECIMAL ? EcorePackage.Literals.EFLOAT : t) + .collect(Collectors.toList())))); + Assertions + .assertTrue(currentEClssOps.stream() + .anyMatch((op) -> assertParamsEqual(op, paramNames, paramTypes.stream() + .map((t) -> t == EcorePackage.Literals.EBIG_DECIMAL ? EcorePackage.Literals.EDOUBLE : t) + .collect(Collectors.toList())))); + } + + /** + * @return Whether the EParameters of op have the given parameter names and + * parameter types (in the given order). + */ + public static boolean assertParamsEqual(EOperation op, List expectedParamNames, + List expectedParamTypes) { + for (int i = 0; i < expectedParamNames.size(); i++) { + var currentParam = op.getEParameters().get(i); + if (!expectedParamNames.get(i).equals(currentParam.getName())) + return false; + + // Add return type checking here in the future, if possible + } + return true; + } + + /** + * @return Whether the EParameters of op have the given parameter names and + * array-type versions of the given parameter types (in the given + * order). + */ + public static boolean assertArrayValuedParamsEqual(EOperation op, List expectedParamNames, + List expectedParamTypes) { + for (int i = 0; i < expectedParamNames.size(); i++) { + var currentParam = op.getEParameters().get(i); + if (!expectedParamNames.get(i).equals(currentParam.getName())) + return false; + + // Add return type checking here in the future, if possible. + // Either take the array-valued EDataTypes as another parameter, or derive them + // from expectedParamTypes + } + return true; + } + + /** + * @return Whether the EParameters of op have the given parameter names and + * collection-type versions of the given parameter types (in the given + * order). + */ + public static boolean assertCollectionValuedParamsEqual(EOperation op, List expectedParamNames, + List expectedParamTypes) { + for (int i = 0; i < expectedParamNames.size(); i++) { + var currentParam = op.getEParameters().get(i); + if (!expectedParamNames.get(i).equals(currentParam.getName())) + return false; + + // Add return type checking here in the future, if possible. + // Either take the collection-valued EDataTypes as another parameter, or derive + // them from expectedParamTypes + } + return true; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestSettings.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestSettings.java new file mode 100644 index 0000000000..5273b009fa --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIGenerationTestSettings.java @@ -0,0 +1,235 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.List; +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.EOperation; +import org.eclipse.emf.ecore.EcorePackage; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; + +/** + * A singleton class for encapsulating details on the fluent api model and those + * of the metamodel the fluent api was generated for. + * + * @author Alp Torac Genc + */ +public class FluentAPIGenerationTestSettings { + private static FluentAPITargetMetamodelFilter targetMetamodelFilter; + private static FluentAPITargetMetamodelPackageProvider targetMetamodelProvider; + + private static List allFluentAPIOps; + + private static List allSupportedConcreteEClssInTargetMetamodel; + + private static List allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats; + private static List allSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat; + private static List allSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat; + + private static Function targetMetamodelEClsToInitEClsFunc; + + private static Function multiValFunc; + private static Function bigNumberVariantsFunc; + + /** + * @see {@link #getTargetMetamodelEClsToInitEClsFunc()} + */ + public static void setTargetMetamodelEClsToInitEClsFunc(Function func) { + targetMetamodelEClsToInitEClsFunc = func; + } + + /** + * Takes a fluent api instance and derives its relevant attributes. Currently + * derives all EOperations in api and saves them in this class. + * + * @param api The fluent api instance to be considered + */ + public static void setFluentAPI(EObject api) { + allFluentAPIOps = List.copyOf(api.eClass().getEOperations()); + } + + /** + * @see {@link #getMetamodelFilter()} + */ + public static void setMetamodelFilter(FluentAPITargetMetamodelFilter filter) { + targetMetamodelFilter = filter; + + computeVariantFunctions(); + computeAllSupportedConcreteEClss(); + } + + /** + * @see {@link #getMetamodelProvider()} + */ + public static void setPackageProvider(FluentAPITargetMetamodelPackageProvider provider) { + targetMetamodelProvider = provider; + computeAllSupportedConcreteEClss(); + } + + private static void computeAllSupportedConcreteEClss() { + if (targetMetamodelProvider != null && targetMetamodelFilter != null) { + // Compute all concrete EClasses within the metamodel that the fluent api was + // generated for + allSupportedConcreteEClssInTargetMetamodel = targetMetamodelProvider.getAllTargetMetamodelConcreteEClasses() + .stream().filter((eCls) -> targetMetamodelFilter.isEClassEligible(eCls)) + .collect(Collectors.toList()); + } + if (allSupportedConcreteEClssInTargetMetamodel != null && targetMetamodelFilter != null) { + // Compute all concrete EClasses with modifiable features + allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats = allSupportedConcreteEClssInTargetMetamodel + .stream().filter(targetMetamodelFilter::hasModifiableFeatures).collect(Collectors.toList()); + // Compute all concrete EClasses with only one modifiable feature + // Re-use allSupportedConcreteEClssWithModifiableFeats + allSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat = allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats + .stream().filter((eCls) -> targetMetamodelFilter.getModifiableFeatureCount(eCls) == 1) + .collect(Collectors.toList()); + // Compute all concrete EClasses without any modifiable features + // Re-use allSupportedConcreteEClssWithModifiableFeats + allSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat = allSupportedConcreteEClssInTargetMetamodel + .stream() + .filter((eCls) -> !allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats.contains(eCls)) + .collect(Collectors.toList()); + } + } + + private static void computeVariantFunctions() { + if (targetMetamodelFilter != null) { + multiValFunc = (eCls) -> targetMetamodelFilter.getModifiableFeatures(eCls).stream() + .anyMatch((f) -> f.isMany()); + bigNumberVariantsFunc = (eCls) -> targetMetamodelFilter.getModifiableFeatures(eCls).stream() + .anyMatch((f) -> f.getEType().equals(EcorePackage.Literals.EBIG_INTEGER)) + || targetMetamodelFilter.getModifiableFeatures(eCls).stream() + .anyMatch((f) -> f.getEType().equals(EcorePackage.Literals.EBIG_DECIMAL)); + } + } + + /** + * @return The object that filters the metamodel the fluent api was generated + * for. + */ + public static FluentAPITargetMetamodelFilter getMetamodelFilter() { + return targetMetamodelFilter; + } + + /** + * @return The object that provides access to the metamodel the fluent api was + * generated for. + */ + public static FluentAPITargetMetamodelPackageProvider getMetamodelProvider() { + return targetMetamodelProvider; + } + + /** + * @return A list of all EOperations that the fluent api instance has. + * @see {@link #setFluentAPI(EObject)} + */ + public static List getAllFluentAPIOps() { + return allFluentAPIOps; + } + + /** + * @return A list of all concrete EClasses within the metamodel that the fluent + * api instance was generated for. + * @see {@link #getMetamodelFilter()} for what EClasses and features are + * supported + * @see {@link #getMetamodelProvider()} for the metamodel + */ + public static List getAllSupportedConcreteEClssInTargetMetamodel() { + return allSupportedConcreteEClssInTargetMetamodel; + } + + /** + * @return A list of all concrete EClasses within the metamodel that the fluent + * api was generated for, which have modifiable features. + * @see {@link #getMetamodelFilter()} for what EClasses and features are + * supported + * @see {@link #getMetamodelProvider()} for the metamodel + */ + public static List getAllSupportedConcreteEClssInTargetMetamodelWithModifiableFeats() { + return allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats; + } + + /** + * @return A list of all concrete EClasses within the metamodel that the fluent + * api was generated for, which have exactly one modifiable feature. + * @see {@link #getMetamodelFilter()} for what EClasses and features are + * supported + * @see {@link #getMetamodelProvider()} for the metamodel + */ + public static List getAllSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat() { + return allSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat; + } + + /** + * The returned map can be used to map EClasses to their corresponding + * initialisation EClass. + * + * @return The mapping between the EClasses within the metamodel that the fluent + * api was generated for and the initialisation EClasses within the + * fluent api model. + */ + public static Function getTargetMetamodelEClsToInitEClsFunc() { + return targetMetamodelEClsToInitEClsFunc; + } + + /** + * The returned map can be used to determine, whether to expect overloading + * modification methods (with array or collection types) in corresponding + * initialisation classes for individual EClasses of the metamodel, which the + * fluent api was generated for. + * + * @return A map that denotes for EClasses of the metamodel, which the fluent + * api was generated for, whether any of their features should have + * overloading modification methods (with array and collection types) in + * their corresponding initialisation class. + */ + public static Function getMultiValFunc() { + return multiValFunc; + } + + /** + * The returned map can be used to determine, whether to expect overloading + * modification methods (with primitive types, such as int or long) in + * corresponding initialisation classes for individual EClasses of the + * metamodel, which the fluent api was generated for. + * + * @return A map that denotes for EClasses of the metamodel, which the fluent + * api was generated for, whether any of their features should have + * overloading modification methods (with primitive types, such as int + * or long) in their corresponding initialisation class. + */ + public static Function getBigNumberVariantsFunc() { + return bigNumberVariantsFunc; + } + + /** + * @return A list of all concrete EClasses within the metamodel that the fluent + * api was generated for, which have no modifiable features. + * @see {@link #getMetamodelFilter()} for what EClasses and features are + * supported + * @see {@link #getMetamodelProvider()} for the metamodel + */ + public static List getAllSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat() { + return allSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat; + } + + /** + * Resets all attributes of this class. + */ + public static void clear() { + allFluentAPIOps = null; + allSupportedConcreteEClssInTargetMetamodel = null; + allSupportedConcreteEClssInTargetMetamodelWithModifiableFeats = null; + allSupportedConcreteEClssInTargetMetamodelWithNoModifiableFeat = null; + allSupportedConcreteEClssInTargetMetamodelWithOnlyOneModifiableFeat = null; + bigNumberVariantsFunc = null; + targetMetamodelEClsToInitEClsFunc = null; + targetMetamodelFilter = null; + targetMetamodelProvider = null; + multiValFunc = null; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMethodTestData.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMethodTestData.java new file mode 100644 index 0000000000..8ed392ee14 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMethodTestData.java @@ -0,0 +1,177 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; + +/** + * A class that encapsulates which methods the metamodel-related tests should + * consider. Those tests should take an instance of this class and use it as + * information source. + *

    + *

    + * Note that several settable attributes of this class have default values for + * convenience purposes, in cases where they are irrelevant. + * + * @author Alp Torac Genc + */ +public class FluentAPIMethodTestData { + private List eClssToCheckFor; + + private Function methodNamePrefixFunc; + private Function returnTypeOfOpFunc = (eCls) -> eCls; + private Function> paramNameFunc = (eCls) -> List.of(); + private Function> paramTypeFunc = (eCls) -> List.of(); + private Function expectMultiValueVariantsFunc = (eCls) -> false; + private Function expectBigNumberVariantsFunc = (eCls) -> false; + + /** + * @param eCls A given EClass + * @return The name of the considered method for the given EClass + * + * @see {@link #setMethodNamePrefixFunc(Function)} + */ + public String getMethodName(EClass eCls) { + return methodNamePrefixFunc.apply(eCls); + } + + /** + * Sets the logic of retrieving methods' names for the considered EClasses, in + * cases where the method's name changes based on the individual EClasses. + * + * @param methodNamePrefixFunc A function for retrieving the considered method's + * name for the considered EClasses + */ + public void setMethodNamePrefixFunc(Function methodNamePrefixFunc) { + this.methodNamePrefixFunc = methodNamePrefixFunc; + } + + /** + * @param eCls A given EClass + * @return The return type of the considered method for the given EClass + * + * @see {@link #setReturnTypeOfOpFunc(Function)} + */ + public EClassifier getReturnTypeOfOp(EClass eCls) { + return returnTypeOfOpFunc.apply(eCls); + } + + /** + * Sets the logic of retrieving methods' return types for the considered + * EClasses, in cases where the method return types change based on the + * individual EClasses. + * + * @param returnTypeOfOpFunc A function for retrieving the considered method's + * return type for the considered EClasses + */ + public void setReturnTypeOfOpFunc(Function returnTypeOfOpFunc) { + this.returnTypeOfOpFunc = (eCls) -> (T) returnTypeOfOpFunc.apply(eCls); + } + + /** + * @param eCls A given EClass + * @return The parameter names of the considered method for the given EClass + * + * @see {@link #setParamNameFunc(Function)} + */ + public List getParamNames(EClass eCls) { + return paramNameFunc.apply(eCls); + } + + /** + * Sets the logic of retrieving methods' parameters' names for the considered + * EClasses, in cases where they change based on the individual EClasses. + * + * @param paramNameFunc A function for retrieving the considered method's + * parameter names for the considered EClasses + */ + public void setParamNameFunc(Function> paramNameFunc) { + this.paramNameFunc = paramNameFunc; + } + + /** + * @param eCls A given EClass + * @return The parameter types of the considered method for the given EClass + * + * @see {@link #setParamTypeFunc(Function)} + */ + public List getParamTypes(EClass eCls) { + return paramTypeFunc.apply(eCls); + } + + /** + * Sets the logic of retrieving methods' parameters' types for the considered + * EClasses, in cases where they change based on the individual EClasses. + * + * @param paramTypeFunc A function for retrieving the considered method's + * parameter types for the considered EClasses + */ + public void setParamTypeFunc(Function> paramTypeFunc) { + this.paramTypeFunc = paramTypeFunc; + } + + /** + * @see {@link #setEClssToCheckFor(List)} + */ + public List getEClssToCheckFor() { + return eClssToCheckFor; + } + + /** + * @param eClssToCheckFor A list of EClasses that should be considered in the + * scope denoted by this instance. + */ + public void setEClssToCheckFor(List eClssToCheckFor) { + this.eClssToCheckFor = eClssToCheckFor; + } + + /** + * @param eCls A given EClass + * @return Whether the corresponding initialisation class should contain + * overloading modification methods (with array and collection types) + */ + public Boolean getExpectMultiValueVariants(EClass eCls) { + return expectMultiValueVariantsFunc.apply(eCls); + } + + /** + * Sets the logic of determining, whether overloading modification methods in + * the initialisation classes for the considered EClasses should be expected. + * + * @param expectMultiValueVariantsFunc A function for determining whether the + * initialisation class for the considered + * EClasses should have any overloading + * modification methods (with array and + * collection types) + */ + public void setExpectMultiValueVariantsFunc(Function expectMultiValueVariantsFunc) { + this.expectMultiValueVariantsFunc = expectMultiValueVariantsFunc; + } + + /** + * @param eCls A given EClass + * @return Whether the corresponding initialisation class should contain + * overloading modification methods (with primitive types, such as int + * or long) + */ + public Boolean getExpectBigNumberVariants(EClass eCls) { + return expectBigNumberVariantsFunc.apply(eCls); + } + + /** + * Sets the logic of determining, whether overloading modification methods in + * the initialisation classes for the considered EClasses should be expected. + * + * @param expectBigNumberVariantsFunc A function for determining whether the + * initialisation class for the considered + * EClasses should have any overloading + * modification methods (with primitive + * types, such as int or long) + */ + public void setExpectBigNumberVariantsFunc(Function expectBigNumberVariantsFunc) { + this.expectBigNumberVariantsFunc = expectBigNumberVariantsFunc; + } + +} \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMutationTestRepresentativesGenerator.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMutationTestRepresentativesGenerator.java new file mode 100644 index 0000000000..bfd4e564fa --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/FluentAPIMutationTestRepresentativesGenerator.java @@ -0,0 +1,55 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; + +import org.junit.jupiter.api.Assertions; + +/** + * A class that computes a representative set of EClasses of the metamodel, for + * which the fluent api was generated. + * + * @author Alp Torac Genc + */ +public class FluentAPIMutationTestRepresentativesGenerator { + /** + * @return A minimal yet representative set of target metamodel's concrete + * EClasses. Currently 1 EClass with no modifiable features, 1 EClass + * with multiple modifiable features, 1 EClass with at least one + * non-many-valued feature, 1 EClass with at least one many-valued + * feature. + */ + public Set getRepresentativeTargetMetamodelConcreteEClasses_BasedOnModifiability() { + var allConcreteEClss = FluentAPIGenerationTestSettings.getMetamodelProvider() + .getAllConcreteEClassesInOriginalMetamodel(); + var eClssToMutate = new LinkedHashSet(); + + // EClass without modifiable features + allConcreteEClss.stream().filter((eCls) -> !eClssToMutate.contains(eCls)).filter( + (eCls) -> FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatureCount(eCls) == 0) + .limit(1).forEach(eClssToMutate::add); + + // EClass with multiple modifiable features + allConcreteEClss.stream().filter((eCls) -> !eClssToMutate.contains(eCls)).filter( + (eCls) -> FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatureCount(eCls) > 1) + .limit(1).forEach(eClssToMutate::add); + + // EClass with at least one single-valued modifiable feature + allConcreteEClss.stream().filter((eCls) -> !eClssToMutate.contains(eCls)) + .filter((eCls) -> FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls) + .stream().filter((f) -> !f.isMany()).count() > 0) + .limit(1).forEach(eClssToMutate::add); + + // EClass with at least one many-valued modifiable feature + allConcreteEClss.stream().filter((eCls) -> !eClssToMutate.contains(eCls)) + .filter((eCls) -> FluentAPIGenerationTestSettings.getMetamodelFilter().getModifiableFeatures(eCls) + .stream().filter((f) -> f.isMany()).count() > 0) + .limit(1).forEach(eClssToMutate::add); + + Assertions.assertEquals(4, eClssToMutate.size(), "Not all supported EClasses are represented"); + + return eClssToMutate; + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMetamodelTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMetamodelTest.java new file mode 100644 index 0000000000..89c87e63e4 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMetamodelTest.java @@ -0,0 +1,99 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelFilter; +import cipm.consistency.fluentapi.metamodel.FluentAPITargetMetamodelPackageProvider; + +/** + * An interface that hides the concrete elements of the fluent api. Implementors + * can then implement the methods of this interface and re-use the tests for the + * generated fluent api model. Most methods within this interface hide fluent + * api operations (or combinations of fluent api operations), whose names denote + * their purpose. + * + * @author Alp Torac Genc + */ +public interface IFluentAPIMetamodelTest { + + /** + * @return The object that grants access to the metamodel, for which the fluent + * api was generated. + */ + public FluentAPITargetMetamodelPackageProvider getProvider(); + + /** + * @return The object that filters the metamodel, for which the fluent api was + * generated. + */ + public FluentAPITargetMetamodelFilter getFilter(); + + /** + * @return The concrete fluent api class instance + */ + public EObject getAPI(); + + public EObject init_getCurrentElement(EObject init); + + public void init_mark(EObject init, Object key); + + public EObject init_unmark(EObject init, Object key); + + public EObject init_createNow(EObject init); + + public EObject api_newX(EClass eCls); + + public EObject api_newX_createNow(EClass eCls); + + public EObject api_newX_createNow(Class cls); + + public EObject api_createNewX(Class cls); + + public EObject api_modifyX(EObject obj); + + public EObject api_modifyX_createNow(EObject obj); + + public void api_modifyX_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_modifyX_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_modifyX_xCleanFeat(EObject obj, EStructuralFeature feat); + + public void api_modifyX_xWithFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_modifyX_xWithoutFeat(EObject obj, EStructuralFeature feat); + + public EObject api_getInitialisationForX(EClass eCls); + + public EClass api_getInitialisationForX_getInitialisedEClass(Class cls); + + public EClass api_getInitialisationForX_getInitialisedEClass(EClass cls); + + public EClass api_getInitialisationForX_getInitialisedEClass(EObject obj); + + public EObject api_continueX(Class cls); + + public void api_xWithFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_xWithoutFeat(EObject obj, EStructuralFeature feat); + + public void api_xWithAddedFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_xWithRemovedFeat(EObject obj, EStructuralFeature feat, Object val); + + public void api_xCleanFeat(EObject obj, EStructuralFeature feat); + + public void api_mark(Object key, EObject val); + + public EObject api_unmark(Object key); + + public EObject api_unmark(Object key, EObject val); + + public EObject api_getMarkedX(Object key); + + public EObject api_modifyMarkedX(Object key); + + public EObject api_continueMarkedX(Object key); +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMutationTest.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMutationTest.java new file mode 100644 index 0000000000..ca2b11a00e --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/IFluentAPIMutationTest.java @@ -0,0 +1,103 @@ +package cipm.consistency.fluentapi.test.metamodel; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.TestInfo; + +/** + * An interface meant for fluent API related tests, which contain mutation + * tests. Mutation tests ensure that the other test cases within the implementor + * fail, given the preceding mutations. + *

    + *

    + * Note: The methods in this interface NEITHER perform modifications (i.e. + * mutations) to existing EMF elements NOR do they revert the performed + * mutations. Those should be done within the concrete implementor of this + * interface. + * + * @author Alp Torac Genc + */ +public interface IFluentAPIMutationTest { + /** + * Ensures that all non-disabled tests other than the currently running test + * case fail, given the preceding mutations. Note: This method itself NEITHER + * performs modifications (i.e. mutations) to existing EMF elements NOR does it + * remove the performed mutations. + *

    + *

    + * Instead of throwing an assertion error after running the test cases on the + * mutated EMF model, returns test results so that the mutations can be + * reverted, before any further test case is run. Otherwise, the mutations may + * not always be reverted and normal test runs might fail. + * + * @param info An object that yields information on the currently running test + * method and various other aspects during testing + * @return A map containing (test case, passed) pairs from test cases, which + * were run within this mutation test. True means that the test case + * passed, false means that the test case failed in the face of + * mutations. + */ + public default Map performMutationTesting(TestInfo info) { + var testMethodsToRun = List.of(this.getClass().getMethods()).stream() + // Filters out the mutation test case itself, which should be the currently + // running test case + .filter((tm) -> !tm.getName().equals(info.getDisplayName())) + // Consider only test methods (annotated with @Test), which are not disabled + // (annotated with @Disabled) + .filter((tm) -> tm.isAnnotationPresent(org.junit.jupiter.api.Test.class) + && !tm.isAnnotationPresent(org.junit.jupiter.api.Disabled.class)) + .collect(Collectors.toList()); + // Ensure that either trivial mutation testing is allowed or that there is at + // least one test case, which should fail for the previously performed mutations + Assertions.assertTrue(allowTrivialMutationTest() || testMethodsToRun.size() > 0, + "No test methods detected, even though trivial mutation test is not allowed"); + + var tmRuns = new HashMap(); + for (var tm : testMethodsToRun) { + try { + tm.invoke(this); + tmRuns.put(tm, Boolean.TRUE); + } catch (Exception e) { + tmRuns.put(tm, Boolean.FALSE); + } + } + return tmRuns; + } + + /** + * Can be overridden to stop the mutation test from failing due to not finding + * any non-mutation tests. + * + * @return Whether there should exist at least one test case, which should fail + * after mutation is performed. + */ + public default boolean allowTrivialMutationTest() { + return false; + } + + /** + * Asserts that all test cases failed in the face of the preceding mutations to + * the EMF structure. This method is split from + * {@link #performMutationTesting(TestInfo)}, in order to allow reverting the + * mutations. + *

    + *

    + * This method should be called after the preceding mutations have been + * reverted, in order to allow following test cases to run without mutations. + * + * @param mutTestRes The return value of + * {@link #performMutationTesting(TestInfo)} + */ + public default void assertTestsFailed(Map mutTestRes) { + for (var e : mutTestRes.entrySet()) { + if (e.getValue()) { + Assertions.fail("The test case " + e.getKey().getName() + " was expected to fail, but it passed"); + } + } + } +} diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/package-info.java new file mode 100644 index 0000000000..94e6b76dc1 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/metamodel/package-info.java @@ -0,0 +1 @@ +package cipm.consistency.fluentapi.test.metamodel; \ No newline at end of file diff --git a/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/package-info.java b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/package-info.java new file mode 100644 index 0000000000..e647e79a34 --- /dev/null +++ b/commit-based-cipm/fluentapi/cipm.consistency.fluentapi/src/cipm/consistency/fluentapi/test/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains tests for the extension classes for the fluent api, as well as + * mutual abstract classes and tools for fluent api tests. + * + * @see {@link cipm.consistency.fluentapi.extensions} + */ +package cipm.consistency.fluentapi.test; \ No newline at end of file