diff --git a/.gitignore b/.gitignore index 55075ebd7..3c43e07c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ gen/ .settings/ .classpath *.log +*.pyc *.iml .idea/ .gradle @@ -14,6 +15,7 @@ gradlew gradlew.bat ~$* .intellijPlatform/ +docs_wd # AI .claude/** \ No newline at end of file diff --git a/README.md b/README.md index 310a6a813..52530d05c 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Similarly, the IntelliJ plugin is provided as a `.zip` file and can be installed ## An Example Model -The following example CD [`MyCompany`](doc/MyCompany.cd) illustrates the textual +The following example CD [`MyCompany`](docs/MyCompany.cd) illustrates the textual syntax of CDs: ``` package corp; @@ -143,7 +143,7 @@ java -jar MCCD.jar -i src/MyLife.cd --path symbols -pp ``` To try them out for yourself download and put the files -[MyCompany.cd](doc/MyCompany.cd), [MyAddress.cd](doc/MyAddress.cd), and [MyLife.cd](doc/MyLife.cd) into your `src` directory. +[MyCompany.cd](docs/MyCompany.cd), [MyAddress.cd](docs/MyAddress.cd), and [MyLife.cd](docs/MyLife.cd) into your `src` directory. The second command needs to be executed before the third. The possible options are: @@ -177,12 +177,12 @@ The possible options are: | `--reference ` | Parses the file as a reference CD and checks if the the input CD specified by `-i` is conform to it. | | `--map ` | Specifies the names of stereotypes that are used as incarnation mappings in the concrete model. Default: 'incarnates' | | `--complete` | Completes a possible incomplete CD specified by `-i` such that it conforms to the reference CD specified by `--reference`. | -| `--ref-param` | Specifies the conformance parameters to use for reference CD conformance checking and completion. Default: 'INHERITANCE, METHOD_OVERLOADING, SRC_TARGET_ASSOC_MAPPING, NAME_MAPPING, ALLOW_CARD_RESTRICTION, STEREOTYPE_MAPPING' | +| `--ref-param` | Specifies the conformance parameters to use for reference CD conformance checking and completion. Default: 'INHERITANCE, METHOD_OVERLOADING, SRC_TARGET_ASSOC_MAPPING, NAME_MAPPING, ALLOW_CARD_RESTRICTION, STYPE_MAPPING' | | `--anytype` | Specifies the placeholder type name used for underspecified types in the reference CD specified by `--reference`. Default: 'anytype'. | ### Building the Tool from the Sources (if desired) -As alternative to a download, +As an alternative to a download, it is possible to build an executable JAR of the tool from the source files located in GitHub. Make sure you have installed the following dependencies: @@ -239,7 +239,7 @@ all context conditions. If you want to try this out yourself, copy the `MCCD.jar` into a directory of your choice. Then create a text file `src/MyExample.cd` -([also available here](doc/MyExample.cd)) in a `src` subdirectory of the +([also available here](docs/MyExample.cd)) in a `src` subdirectory of the directory where `MCCD.jar` is located containing e.g. the following simple CD (please note that, like in Java, filename and model name in the file have to be the same): @@ -384,7 +384,7 @@ The CD languages are participating in the symbol exchange infrastructure. We import a symbol file defining type symbols that are used by a CD. Let us now consider the example `MyLife` from above. -Please, copy the file [`MyLife.cd`](doc/MyLife.cd) and save it +Please, copy the file [`MyLife.cd`](docs/MyLife.cd) and save it in a file `src/monticore/MyLife.cd`. Execute the following command for processing the file `MyLife.cd`: @@ -402,7 +402,7 @@ the model: The missing class `Address` is currently not imported. `MyLife` already has an `import` statement to another class diagram -included ([available here](doc/MyAddress.cd)): +included ([available here](docs/MyAddress.cd)): (content of src/MyAddress.cd) ``` @@ -474,7 +474,7 @@ java -jar MCCD.jar -i src/MyExample.cd --gen -o out Note that the option `--fieldfromrole` must be used with the appropriate argument in order to generate attributes for associations contained in the input CD. Use the following commands in order to generate .java-files for the CD -`MyCompany`([available here](doc/MyCompany.cd)): +`MyCompany`([available here](docs/MyCompany.cd)): ```shell java -jar MCCD.jar -i src/MyCompany.cd -o out --gen --fieldfromrole navigable @@ -493,12 +493,12 @@ The option `--semdiff ` computes the semantic difference of the current CD in memory and the CD specified by the argument. For the following examples, download the files -[MyEmployees1.cd](doc/MyEmployees1.cd) and [MyEmployees2.cd](doc/MyEmployees2.cd) +[MyEmployees1.cd](docs/MyEmployees1.cd) and [MyEmployees2.cd](docs/MyEmployees2.cd) and save them in `src`: ```shell -java -jar MCCD.jar -i src/MyEmployees1.cd --semdiff scr/MyEmployees2.cd +java -jar MCCD.jar -i src/MyEmployees1.cd --semdiff src/MyEmployees2.cd ``` We can use the option `difflimit` to specify the maximum number of witnesses @@ -524,7 +524,7 @@ argument iff the two are semantically compatible The result is stored in memory as the current CD. For the following examples, download the files -[Teaching.cd](doc/Teaching.cd) and [Management.cd](doc/Management.cd) and +[Teaching.cd](docs/Teaching.cd) and [Management.cd](docs/Management.cd) and save them in `src`: ```shell diff --git a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/AbstractMethodDecorator.java b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/AbstractMethodDecorator.java index b1e3a2697..d56523ea0 100644 --- a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/AbstractMethodDecorator.java +++ b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/AbstractMethodDecorator.java @@ -1,13 +1,18 @@ /* (c) https://github.com/MontiCore/monticore */ package de.monticore.cd.codegen.decorators; +import de.monticore.cd.codegen.TopDecorator; import de.monticore.cd.codegen.decorators.data.AbstractDecorator; import de.monticore.cd.methodtemplates.CD4C; +import de.monticore.cd4code.CD4CodeMill; import de.monticore.cd4code._visitor.CD4CodeTraverser; import de.monticore.cd4codebasis._ast.ASTCDClass; import de.monticore.cd4codebasis._ast.ASTCDMethod; import de.monticore.cd4codebasis._visitor.CD4CodeBasisVisitor2; import de.monticore.generating.templateengine.TemplateHookPoint; +import de.monticore.umlstereotype._ast.ASTStereoValue; +import de.monticore.umlstereotype._ast.ASTStereotype; + import java.util.Stack; /** @@ -49,6 +54,16 @@ public void visit(ASTCDMethod method) { // And also mark the parent (class) as abstract decoratorData.getAsDecorated(classStack.peek()).getModifier().setAbstract(true); + + // Add TOPTrafo.NEEDS_TOP_IDENTIFIER stereotype for improved error messages + if (!classStack.peek().getModifier().isAbstract()) { + ASTStereoValue astStereoValue = TopDecorator.NEEDS_TOP_STEREO_BUILDER.apply( + "In the class %s a method needs to be topped"); + ASTStereotype astStereotype = CD4CodeMill.stereotypeBuilder().addValues(astStereoValue) + .build(); + decoratorData.getAsDecorated(classStack.peek()).getModifier().setStereotype( + astStereotype); + } } else { // static methods can not be turned abstract: @@ -57,8 +72,6 @@ public void visit(ASTCDMethod method) { decoratorData.getAsDecorated(method), new TemplateHookPoint( "methods.EmptyBodyThrowError"))); } - // We could add the TOPTrafo.NEEDS_TOP_IDENTIFIER stereotype for improved error messages, - // but we have to ensure quickFail is disabled during the TOPTrafo } } diff --git a/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java b/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java index 6b648136c..f59047b07 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java +++ b/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java @@ -150,7 +150,7 @@ else if (!cmd.hasOption("i") || cmd.hasOption("h")) { } } - if (cmd.hasOption("o")) { + if (cmd.hasOption("o") || cmd.hasOption("pp")) { // Where to load additional templates from List additionalTemplatePaths = cmd.hasOption("fp") ? Arrays.stream(cmd .getOptionValues("fp")).map(Paths::get).map(Path::toFile).collect(Collectors.toList()) @@ -182,7 +182,10 @@ else if (!cmd.hasOption("i") || cmd.hasOption("h")) { // If required, we also output the symbol table of the *decorated* AST this.createAndExportDecoratedSymbolTable(decorated, cmd.getOptionValue("sd")); } - }, asts); + if (cmd.hasOption("pp")) { + this.prettyPrint(decorated, Paths.get(cmd.getOptionValue("pp")).toString()); + } + }, asts, cmd.hasOption("o")); } } catch (ParseException e) { @@ -224,7 +227,7 @@ public void initializeDecConf(GlobalExtensionManagement glex, DecoratorConfig de public void decorateAndGenerate(GlobalExtensionManagement glex, Consumer initializeDecConf, GeneratorSetup setup, Runnable initDecoratedGlobalScope, Consumer postDecorate, - Collection asts) { + Collection asts, boolean doGenerate) { glex.setGlobalValue("cdPrinter", new CdUtilsPrinter()); glex.setGlobalValue("mcTypeFacade", MCTypeFacade.getInstance()); // TODO: Remove from templates glex.setGlobalValue("cdGenService", new CDGenService()); @@ -274,7 +277,9 @@ public void decorateAndGenerate(GlobalExtensionManagement glex, topTransformer.addToTraverser(t); decorated.get().accept(t); - generator.generate(decorated.get()); + if (doGenerate) { + generator.generate(decorated.get()); + } Log.enableFailQuick(qf); // reset quick-fail } } @@ -377,6 +382,9 @@ public Options addAdditionalOptions(Options options) { .argName("file").hasArg().desc( "Serializes the decorated symbol table of the given artifact.").build()); + options.addOption(org.apache.commons.cli.Option.builder("pp").longOpt("prettyprint").argName( + "file").hasArg().desc("Pretty prints the decorated AST to the given file.").build()); + return options; } diff --git a/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractDecoratorTest.java b/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractDecoratorTest.java index 38780d128..39fda45fa 100644 --- a/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractDecoratorTest.java +++ b/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractDecoratorTest.java @@ -95,7 +95,9 @@ public TestResult doTest(ASTCDCompilationUnit cd) { tool.completeSymbolTable(decorated); results.add(new TestResult(decorated, decoratedScope)); - }, List.of(cd)); + }, List.of(cd), + + true); System.out.println("Wrote CDGenTest results to " + outputDir.getAbsolutePath()); diff --git a/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractMethodDecoratorTest.java b/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractMethodDecoratorTest.java new file mode 100644 index 000000000..a33a7bfda --- /dev/null +++ b/cdlang/src/test/java/de/monticore/cd/cdgen/AbstractMethodDecoratorTest.java @@ -0,0 +1,74 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cd.cdgen; + +import de.monticore.cd.codegen.DecoratorConfig; +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.generating.GeneratorSetup; +import de.monticore.generating.templateengine.GlobalExtensionManagement; +import de.monticore.io.paths.MCPath; +import de.monticore.runtime.junit.MCAssertions; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Optional; + +public class AbstractMethodDecoratorTest extends AbstractDecoratorTest { + + @Override + protected Optional getHandWrittenPath() { + // The path should point to the root of the handwritten source folder, + // not to the package folder itself. + return Optional.of(new MCPath("src/test/resources/de/monticore/cd/codegen/hwc")); + } + + @Test + public void testNotTopped() throws IOException { + var opt = CD4CodeMill.parser().parse_String(""" + classdiagram TestAbstractMethodDecoratorNotTopped { + abstract class Asset { + void process(); + } + + class Task extends Asset { + void process(); + } + } + """); + Assertions.assertTrue(opt.isPresent()); + + super.doTest(opt.get()); + + MCAssertions.assertHasFinding(f -> f.getMsg().contains("0xC0FFEE00")); + Log.clearFindings(); + } + + @Test + public void testTopped() throws Exception { + var opt = CD4CodeMill.parser().parse_String(""" + classdiagram TestAbstractMethodDecoratorTopped { + abstract class Asset { + void process(); + } + + class Task extends Asset { + void process(); + } + } + """); + Assertions.assertTrue(opt.isPresent()); + + super.doTest(opt.get()); + + MCAssertions.assertNoFindings(); + } + + @Override + public void initializeDecConf(GlobalExtensionManagement glex, DecoratorConfig config, + GeneratorSetup setup) { + config.withCopyCreator().defaultApply(); + config.withAbstractMethodSignatures().defaultApply(); + } + +} diff --git a/cdlang/src/test/java/de/monticore/cd/cdgen/DefaultCD2PojoDecoratorTest.java b/cdlang/src/test/java/de/monticore/cd/cdgen/DefaultCD2PojoDecoratorTest.java index 0f81a741c..af31957d0 100644 --- a/cdlang/src/test/java/de/monticore/cd/cdgen/DefaultCD2PojoDecoratorTest.java +++ b/cdlang/src/test/java/de/monticore/cd/cdgen/DefaultCD2PojoDecoratorTest.java @@ -33,7 +33,8 @@ public void testAll() throws Exception { super.doTest(opt.get()); - MCAssertions.assertNoFindings(); + //because the class diagram contains methods which are not topped by the TOP-Mechanism, we still get errors here + MCAssertions.assertHasFindingsStartingWith("0xC0FFEE00"); } @Override diff --git a/cdlang/src/test/resources/de/monticore/cd/codegen/hwc/TestAbstractMethodDecoratorTopped/Task.java b/cdlang/src/test/resources/de/monticore/cd/codegen/hwc/TestAbstractMethodDecoratorTopped/Task.java new file mode 100644 index 000000000..a378b1dfb --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cd/codegen/hwc/TestAbstractMethodDecoratorTopped/Task.java @@ -0,0 +1,8 @@ +package TestAbstractMethodDecoratorTopped; + +public class Task extends TaskTOP { + @Override + public void process() { + // Handwritten implementation + } +} diff --git a/cdtool/cdgradle/src/test/resources/cdgradle-it/src/main/java/MyCD/AbstractClassIml.java b/cdtool/cdgradle/src/test/resources/cdgradle-it/src/main/java/MyCD/AbstractClassIml.java new file mode 100644 index 000000000..1a795c4d6 --- /dev/null +++ b/cdtool/cdgradle/src/test/resources/cdgradle-it/src/main/java/MyCD/AbstractClassIml.java @@ -0,0 +1,16 @@ +/* (c) https://github.com/MontiCore/monticore */ +package MyCD; + +public class AbstractClassIml extends AbstractClassImlTOP{ + + @Override + public void doY() { + System.out.println("Y"); + } + + @Override + public void doX() { + System.out.println("X"); + } + +} \ No newline at end of file diff --git a/doc/MyLife.cd b/doc/MyLife.cd deleted file mode 100644 index 4aa254925..000000000 --- a/doc/MyLife.cd +++ /dev/null @@ -1,27 +0,0 @@ -/* (c) https://github.com/MontiCore/monticore */ -// package monticore; - -import MyAddress.Address; -import java.lang.String; -import java.util.List; -import java.util.Date; - -classdiagram MyLife { - abstract class Person { - int age; - Date birthday; - List nickNames; - } - class PhoneNumber; - package uni { - class Student extends Person { - StudentStatus status; - -> Address [1..*] {ordered}; - } - class Grade; - enum StudentStatus { ENROLLED, FINISHED; } - composition uni.Student -> uni.Grade [*]; - association phonebook uni.Student [java.lang.String] -> PhoneNumber; - } - association [0..1] Person (parent) <-> (child) Person [*]; -} diff --git a/doc/MyLife.svg b/doc/MyLife.svg deleted file mode 100644 index 6c32e8f31..000000000 --- a/doc/MyLife.svg +++ /dev/null @@ -1,61 +0,0 @@ -monticore.MyLife CDmonticoreuniPersonint agejava.util.Date birthdayjava.util.List<java.lang.String> nickNamesPhoneNumberStudentStudentStatus statusGradeStudentStatusENROLLEDFINISHEDEnum ConstantsPhoneNumberAddressphonebookphonebookphonebookstudentgradestudentaddressparentchildgenerated with MontiCore using PlantUML diff --git a/doc/Adapter.cd b/docs/Adapter.cd similarity index 100% rename from doc/Adapter.cd rename to docs/Adapter.cd diff --git a/doc/BankingCon.cd b/docs/BankingCon.cd similarity index 100% rename from doc/BankingCon.cd rename to docs/BankingCon.cd diff --git a/doc/BankingRef.cd b/docs/BankingRef.cd similarity index 100% rename from doc/BankingRef.cd rename to docs/BankingRef.cd diff --git a/doc/BuilderRef.cd b/docs/BuilderRef.cd similarity index 100% rename from doc/BuilderRef.cd rename to docs/BuilderRef.cd diff --git a/doc/CDGen.md b/docs/CDGen.md similarity index 100% rename from doc/CDGen.md rename to docs/CDGen.md diff --git a/doc/DataModelCon.cd b/docs/DataModelCon.cd similarity index 100% rename from doc/DataModelCon.cd rename to docs/DataModelCon.cd diff --git a/doc/DigitalTwin1.cd b/docs/DigitalTwin1.cd similarity index 100% rename from doc/DigitalTwin1.cd rename to docs/DigitalTwin1.cd diff --git a/doc/DigitalTwin2.cd b/docs/DigitalTwin2.cd similarity index 100% rename from doc/DigitalTwin2.cd rename to docs/DigitalTwin2.cd diff --git a/doc/DigitalTwin3.cd b/docs/DigitalTwin3.cd similarity index 100% rename from doc/DigitalTwin3.cd rename to docs/DigitalTwin3.cd diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md new file mode 100644 index 000000000..47f003af2 --- /dev/null +++ b/docs/GettingStarted.md @@ -0,0 +1,645 @@ + +This page is under construction. + +# Getting Started with the CD4Code Generator + +This page describes the technical installation and usage of the CD4Code Generator for +language developers. This page inspects a simple example class diagram and the +Java classes and other artifacts that are generated from the decorating CD generator. +After installing the CD4Code Generator, as described on this page, it can be used to +automatically generate Java code with additional functionality as described in the +later chapters. + +The decorating CD generator is available as a command line interface (CLI) tool, as a library, and +can easily be used with Gradle. The Gradle integration enables developers to easily employ +the generator in commonly used integrated developer environments (IDEs), such as +Eclipse and IntelliJ IDEA. This page contains information about an example +class diagram and the generated files and depending on the selected decorators. +(It also shortly explains some key features of the generator.) + +(Detailed information about all configuration options that can be used in the Cd4Code +Generator can be found in the [Configuration](CDGen.md) page.) + +## Prerequisites: Installing the Java Development Kit (JDK) + +We start with the JDK: Please perform the following steps to install the +Java Development Kit (JDK) and validate that the installation was successful: + +- Install a JDK with at least version 21 provided by Oracle or OpenJDK. +- Make sure the environment variable `JAVA_HOME` points to the installed JDK, and + *not* to the JRE, e.g., the following would be good: + - `/user/lib/jvm/java-21-openjdk` on UNIX or + - `C:\Program Files\Java\jdk-21.*` on Windows. + You will need this to run the Java compiler for compiling + the generated Java source files. +- Also, make sure that the system variable is set such that the Java + compiler can be used from any directory. JDK installations on UNIX + systems do this automatically. On Windows systems, the `bin` + directory of the JDK installation needs to be appended to the `PATH` + variable, e.g. `%PATH%;%JAVA_HOME%`. +- Test whether the setup was successful. Open a command line shell in + any directory. Execute the command `javac -version`. If this command + is recognized and the shell displays the version of the installed + JDK (e.g., `javac 21.0.10`), then the setup was successful. +- *(Optional)* Install [Gradle](https://gradle.org/install/) version 8.14.4. + +Now we have the prerequisites to run the CD4Code generator from the command line (CLI) +or alternatively using Gradle. + +### Installation + +For installing the CD4Code generator for either the CLI or Gradle usage, +select the suitable tab below and perform the following steps: + +=== "CLI" + A ready to use version of the tool can be downloaded in the form of an + executable JAR file. + You can use [**this download link**][ToolDownload] for downloading the tool. + Alternatively, the `wget` command can be used to download the latest version + into your working directory: + ```shell + wget "https://monticore.de/download/MCCD.jar" -O MCCD.jar + ``` +=== "Gradle" + By adding the `de.rwth.se.cdgen` Gradle plugin to your project, + all class diagrams in the _cds_ source-directory-set (e.g., _src/main/cds_, _src/test/cds_) are generated to Java code. + + ```groovy + // build.gradle + plugins { + id 'java-library' + id 'de.rwth.se.cdgen' version '7.9.0-SNAPSHOT' + } + + repositories { + maven { url 'https://nexus.se.rwth-aachen.de/content/groups/public' } + mavenCentral() + } + + // settings.gradle + pluginManagement { + repositories { + maven { + url "https://nexus.se.rwth-aachen.de/content/groups/public" + } + } + } + ``` + For the main source set, all `.cd` files within the `src/main/cds` directory will be processed. + +## Inspect the class diagram + +The CD4Code generator helps to generate Java code from class diagrams. It supports easy +integration within Gradle projects, but also as a one-shot generation tool. The CD4Code +generator processes class diagrams that are stored in files. The CD4Code generator will +process all `.cd` files in these directories and generate Java code based on the +class diagrams defined in these files. Each CD contains packages, classes, attributes, +methods, and associations. The CD4Code generator will generate Java code based on the +structure of the class diagram and the applied decorators. + +It is a *key feature* of the CD4Code generator that the generated Java code can be +extended with the addition of decorators. These decorators dictate what artifacts +are generated from the class diagram, or which should not be generated at all. + +```cd4code +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; + +classdiagram MyOrganizer { + + enum Status { PROCESSING, DONE, OPEN; } + + abstract class Asset { + void process(); + } + + class Task extends Asset { + Status taskStatus; + void process(); + } + + class Project extends Asset { + public String projectName; + private Optional deadline; + protected double budget; + void process(); + } + + class Day { + Date date; + } + + association [1] Day (day) -> (tasks) Task [*]; + association [*] Task (tasks) <-> (project) Project [1]; +} +``` +
Listing 2.1: The MyOrganizer class diagram
+ +As usual in model-based software engineering, the core of the file is the diagram definition itself. +It begins with the `classdiagram` keyword, followed by the name of the diagram, +which must match the filename. In our example, the diagram is named `MyOrganizer` and +its body is enclosed in curly braces `{ }`. + +Class diagrams can have import statements to integrate external types. +Every import is of the form `import` *QualifiedName*. For instance, the `MyOrganizer` class diagram +uses `import java.util.Date;` and `import java.util.Optional;` to make the standard Java `Date` and `Optional` classes available within the model. + +Inside the class diagram, various object-oriented constructs can be defined, such as enumerations, +classes, interfaces, and their relationships. The `MyOrganizer` diagram introduces the enumeration `Status` using +the `enum` keyword, defining the constants `PROCESSING`, `DONE`, and `OPEN`. +It also defines several classes, such as `Asset`, `Task`, `Project`, and `Day`. The `abstract` +keyword can be applied to classes, as seen with `abstract class Asset;`. +Furthermore, the `extends` keyword is used to establish inheritance. In our example, +`Task` extends `Asset`, and `Project` extends `Asset`. Equally, interfaces can be defined +as well, using the `interface` keyword, and classes can implement interfaces using the `implements` keyword. + +Classes typically contain attributes, which consist of a type and a name. The CD4Code +generator supports standard Java primitive types (like `double budget;`) , imported external types (like `Date date` and `Optional deadline`), +and custom types like enums (`Status taskStatus`). Classes and interfaces can also define methods, such as `void process();`. You can also define access modifiers like `public`, `private` and `protected`. + +Finally, the class diagram defines how these entities relate to one another using associations and compositions. +While associations define relationships between two entities that simply know about each other, +compositions define a strong ownership relationship between two entities. +Standalone relationships use keywords like `association` or `composition`, followed by +cardinalities (e.g., `[1]`, `[1..*]`, `[*]`), the participating classes, +and navigation arrows (`<->` for bidirectional, `->` for directional, +or `--` for unspecified). Relationships can also specify role names in parentheses to clarify the relationship's context, +such as `(project)` and `(tasks)`. + +## Decorators +At the very start of the CD4Code generator, the generator parses the class diagram DSL into the *CD4C Abstract Syntax Tree (AST)* +which represents the class diagram as an object tree. +Then the CD4Code generator applies the decorators to the AST. + +Decorators are classes that can modify the AST by adding, removing, or changing elements and by adding template +hooks to objects of the AST. While the modifications on the AST can be visualized and seen directly, the template +hooks are only processed when the actual code generation takes place. + +The CD4Code generator comes with a set of prewritten decorators which can be applied to the AST. In This chapter, we will +discuss the decorators in detail. + +The Basis of all Decorators is the `CopyDecorator` which is responsible for copying the original `AST` and doing +some basic transformations on it. After this the CD4Code generator will apply the remaining decorators to the AST. +As some Decorators dependent on other Decorators, all Decorators implement the +interface `IDecorator` which contains the method `getDependencies()` which returns a list of Decorators that must +be run before itself. For example, the VisitorDecorator depends on the GetterDecorator, +so the `getDependencies()` method returns `Collections.singletonList(GetterDecorator.class)`. Therefore, the +CD4Code generator checks the dependencies of the selected Decorators and runs them in the correct order. +If there is a circular dependency, the CD4Code generator throws an error and does not generate any code. + +```java +/** Extend {@link AbstractDecorator} for shared */ +public interface IDecorator extends IVisitor { + + /** + * Add your decorator-visitor to the given traverser + * + * @param traverser the traverser + */ + void addToTraverser(CD4CodeTraverser traverser); + + void init(DecoratorData util, Optional glexOpt); + + /** @return the list of decorators which MUST traverse the AST before */ + @SuppressWarnings("rawtypes") + default Iterable> getMustRunAfter() { + return Collections.singletonList(ICreator.class); + } + +} +``` +By default, the [CD2Pojo.ftl](../cdlang/src/main/resources/cd2java/init/CD2Pojo.ftl) template is being used by the CD4Code generator. It defines which decorators are applied to the AST. +It uses the `decConfig` variable which is an instance of the `DecoratorConfig` class. To apply a decorator to the AST, +we simply need to add the decorator to the `decConfig` variable. For example, to apply the `GetterDecorator` to the AST, we can add the following line to the `CD2Pojo.ftl` template: + +```ftl +<#-- Apply the GetterDecorator to the AST --> +${decConfig.withGetters().applyOnName("getter").ignoreOnName("noGetter").defaultApply()} +``` + +The `.applyOnName("getter")` defines that the `GetterDecorator` should only be applied to classes or attributes which have the stereotype `<>`. +The `.ignoreOnName("noGetter")` defines that the `GetterDecorator` should not be applied to classes or attributes which have the stereotype `<>`. +The `.defaultApply()` defines that the `GetterDecorator` should be applied to all classes or attributes which do not have the stereotype `<>` or `<>`. +The [CD2Pojo.ftl](../cdlang/src/main/resources/cd2java/init/CD2Pojo.ftl) includes decorators with the following configurations: + + +| Decorator | Description | To Enable | To Disable | +|--------------------------------------------------------------------------|--------------------------------------------------------------------|--------------------------|-------------------------------------------| +| [CopyDecorator](decorators/CopyDecorator.md) | Include all elements of the original CD in the output | always | - | +| [GetterDecorator](decorators/GetterDecorator.md) | Add Getter Methods | 🟩 `<>` | `<>` | +| [SetterDecorator](decorators/SetterDecorator.md) | Add Setter Methods | 🟩 `<>` | `<>` | +| [CardinalityDefaultDecorator](decorators/CardinalityDefaultDecorator.md) | Optional and list attributes are initialized with an empty default | 🟩 | `<>` | +| [NavigableSetterDecorator](decorators/NavigableSetterDecorator.md) | Setters of bidirectional associations are also bidirectional | 🟩 `<>` | `<>` | +| [AbstractMethodDecorator](decorators/AbstractMethodDecorator.md) | Defined methods are made abstract | 🟩 `<>` | `<>` | +| [BuilderDecorator](decorators/BuilderDecorator.md) | Add a builder class | 🟨 `<>` | `<>` | +| [ObserverDecorator](decorators/ObserverDecorator.md) | Turn the class observable | 🟨 `<>` | `<>` | +| [VisitorDecorator](decorators/VisitorDecorator.md) | Include a visitor | 🟨 `<>` | `<>` or `<>` | + +In the default configuration, +🟩 means the decorator is applied unless disabled. +🟨 means the decorator is not applied unless enabled. +You can find a more detailed description of the decorators by clicking on the corresponding name. + +This means by default that the CD4Code generator will generate getters and setters for all attributes. +Furthermore, it will initialize the cardinality of all optional attributes with an empty default value. Finally, the bidirectional associations between +`Project` and `Task` will be navigable in both directions, meaning that the generated setter methods +will also set the opposite side of the association by default. + +## Designing a custom Decorator + +To design a new decorator, we need to first implement the new Decorator itself, and then add it to the `DecoratorConfig` class. + +### Implementing a new Decorator +The different Decorator classes are located in the `cdlang/src/main/java/de/monticore/cd/codegen/decorators/` folder. + +As mentioned above, all Decorators extend the `AbstractDecorator` class. This class provides some basic functionality +for all Decorators, such as the `init()` method which is called by the CD4Code generator before the actual code generation takes place. +Furthermore, it provides the `addToTraverser()` method which is used to include the decorator in the traverser. + +Let’s imagine we want to create a `ToStringDecorator` which will add a `toString()` method to all classes. +As our new decorator does not specify any additional data for other decorators, we can set the generic +attribute of the `AbstractDecorator` class to `NoData`. Keep in mind that we still need to implement the `addToTraverser()` and `getMustRunAfter()` methods. + +To later add functionality to the `ToStringDecorator`, we need to implement the visitor methods for the AST elements +we want to modify. To add them, the class needs to implement the `CDBasisVisitor2` interface. This interface contains +visitor methods for all AST elements of the class diagram. + +```java +public class ToStringDecorator extends AbstractDecorator implements CDBasisVisitor2 { + + @Override + public void addToTraverser(CD4CodeTraverser traverser) { + traverser.add4CDBasis(this); // 4CDBasis means this decorator will be applied to all elements of the class diagram AST + } + + @Override + public Iterable> getMustRunAfter() { + return Iterables.concat(super.getMustRunAfter(), Collections.singletonList( + GetterDecorator.class)); + // GetterDecorator is a Decorator that must be run before this decorator + } + + //... + +} +``` + +Now we can add functionality by using the generated visitor pattern. Let’s assume we want to add a `toString()` method to all classes. +We can do this by implementing the `visit(ASTCDClass node)` method of the `CDBasisVisitor2` interface which we need to add as well. + +```java + @Override + public void visit(ASTCDClass node) { + if (decoratorData.shouldDecorate(this.getClass(), node)) { + addToStringMethod(node); + } + } + + private void addToStringMethod(ASTCDClass node) { + if (node.getCDMethodList().stream().noneMatch(m -> m.getName().equals("toString"))) { + // add the toString method signature to the AST + ASTCDMethod toStringMethod = CD4CodeMill.cDMethodBuilder() + .setName("toString") + .setModifier(CDModifier.PUBLIC.build()) + .setMCReturnType((CD4CodeMill.mCReturnTypeBuilder() + .setMCType(MCTypeFacade.getInstance().createStringType()) + .build())) + .build(); + node.addCDMember(toStringMethod); + + // create the toString method body + StringBuilder body = new StringBuilder(); + body.append("return \"").append(node.getName()).append("{\" +\n"); + for (ASTCDAttribute attribute : node.getCDAttributeList()) { + body.append(" \"").append(attribute.getMCType().printType()).append("=\" + ").append(attribute.getName()).append(" +\n"); + } + body.append(" '}';"); + + // as the class diagram language does not support method bodies, we need to add the body as a hook to the method signature + glexOpt.ifPresent(glex -> glex.replaceTemplate(EMPTY_BODY, toStringMethod, new StringHookPoint(body.toString()))); + } + } +``` + +### Adding the new Decorator to the DecoratorConfig +To add the new decorator to the CD4Code generator, we need to add it to the `DecoratorConfig` class. +This class is responsible for configuring the decorators that are applied to the class diagram. +To do this, we simply need to add the method `withToString` to the `DecoratorConfig` class. + +```java +public class DecoratorConfig { + + //... + + public ChainableGenSetup withToString() { + return this.withDecorator(new ToStringDecorator()); + } + + //... +} +``` + +Then we can modify the template used for configuration our decorators. +As we want to generate the `toString()` method for all classes, we add the following line to the `CD2Pojo.ftl` template: +```injectedfreemarker +<#-- ... --> + +${decConfig.withToString().applyOnName("toString").ignoreOnName("noToString").defaultApply()} + +<#-- ... --> +``` +to apply to all classes by default. + +### Add tests +To test the new decorator, we can add a test class to the `cdlang/src/test/java/de/monticore/cd/cdgen/` folder. +This test class should extend the `AbstractDecoratorTest` class which provides some basic functionality for testing decorators. +For more information about test infrastructure, please look into the already existing test classes. + +Finally, we just run the CD4Code generator and the `toString()` method will be generated for all classes in our class diagram. + +## The TOP Mechanism: Integrating Handwritten Code + +A fundamental principle of the CD4Code Generator—and model-driven software engineering in general—is that generated code +should never be modified manually. If we edit a generated file in the `build/` directory, your changes will be permanently +overwritten the next time the generator runs. + +Class diagrams are structural models. They do not define behavior logic or method bodies. To solve the conflict between +preserving generated code and implementing custom behavior, the CD4Code Generator uses the TOP Mechanism. + +We need to create the handwritten class files *before* running the generator for the first time. +This prevents the generator from creating a conflicting file and ensures a smooth integration. + +To use the TOP mechanism, we need to tell the generator where to look for handwritten code. This is done by specifying +the `-hcp` option when running the generator. If we want to change the behavior of a class `X` in the generated code, +we can create a file in the same directory as the generated file, named `X.java`. The generator checks for each class +if it already has a corresponding file in the handwritten code directory. If it finds one, it will rename the generated +class to `XTOP` and use the handwritten code instead. This way, the generated code can still be used by extending it, +while the handwritten code can modify specific behavior without complete class rewrites. + +### Use Cases for the TOP Mechanism +The TOP mechanism allows developers to seamlessly inject handwritten code into the generated architecture. It is primarily used for: + +- **Implementing Method Bodies**: We can write the logic for methods defined in the class diagram without modifying generated files. +- **Adding complex Business Logic**: Business logic not directly related to the class diagram can be added to the generated code. +- **Connecting to non-generated code**: We can integrate with external libraries or frameworks that are not part of the generated architecture. +- **Overriding Generated Code**: Intercepting and modifying the generated code. +- **Adding Non-Modeled State**: Adding attributes or methods that are not part of the class diagram. + +### Fixing the Issues +If we look back at our class diagram, we see that the abstract class `Asset` has the method `process()`. +This method is inherited by both `Project` and `Task`. However, the implementation of this method is not +defined in the class diagram. This poses a problem for the CD4Code generator, as method bodies cannot be +generated from the class diagram. But if we do not add the method body before generating the code, the +generated code will not compile. Which is against the design principle of the CD4Code generator. + +To safely implement the `process()` methods, we use the TOP mechanism by telling the generator where our handwritten +code lives using the Handwritten Code Path (`-hcp`). +To implement the behavior for `Project` and `Task`, we create the following files in the `src/` directory: + +```src/ +├── main/ +│ └── java/ +│ └── MyOrganization/ +│ ├── Project.java +│ └── Task.java +``` + +In `Project.java`, we implement the `process()` method for the `Project` class: + +```java +package MyOrganization; + +class Project extends ProjectTOP { + + @Override + public void process() { + // Custom logic for Project processing + } +} +``` + +and in `Task.java`, we implement the `process()` method for the `Task` class: + +```java +package MyOrganization; + +class Task extends TaskTOP { + + @Override + public void process() { + // Custom logic for Task processing + } +} +``` +By following this approach, we can safely implement the behavior for both `Project` and `Task` without modifying +any generated files. The CD4Code generator will generate the necessary structure and method signatures based on +the class diagram, while our handwritten code will provide the specific logic for the `process()` methods. This allows +us to maintain a clear separation between generated code and custom behavior, adhering to the principles of model-driven +software engineering. + +The CD4Code generator will also now generate the compiling code, where the classes `Project` and `Task` are not +abstract anymore. Therefore, the generated Builder and Observer classes will produce compilable code. + +### Configuring the CD4Code Generator + +While the default `CD2Pojo` configuration is a great starting point, manually adding stereotypes +(like `<>` or `<>`) directly to every element in a `.cd` file can become +tedious and clutter the model. To solve this, the CD4Code Generator allows you to configure +decorators externally. + +Configuration can be applied at two different levels: + +1. **Element-Level Configuration (Tagging):** You can target specific elements inside your class diagram + (such as a specific class, enum, or attribute) to explicitly enable or disable a decorator. + This uses a targeting syntax of `.:`. For example, targeting + `MyOrganizer.Day:noSetter` will prevent the generator from creating setter methods specifically for + the `Day` class. +2. **Global-Level Configuration (Templates):** If you need to fundamentally change the default behavior or + apply your own custom decorators across the entire build, you can supply a custom configuration template + (e.g., a custom `.ftl` file) to replace the default `CD2Pojo` template. + +### Applying Configurations + +Depending on how you are running the CD4Code Generator, you can pass these configurations via the command line, +your Gradle build script, or directly through the Java API. Select your environment below: + +=== "CLI" +When running the CD4Code generator from the command line, you can pass element-level tags using the `-cliconfig` +parameter. Multiple configurations can be applied by repeating the argument. + + For example, to disable getters and setters specifically for the `Day` class inside the `MyOrganizer` + diagram, use the following command: + + ```shell + java -jar MCCD.jar -i src/MyOrganizer.cd -cliconfig "MyOrganizer.Day:noGetter" -cliconfig "MyOrganizer.Day:noSetter" + ``` + + To apply a global configuration template, use the `-ct` (config template) argument to specify the + template name, and `-fp` (file path) to specify the directory where the custom `.ftl` file is located: + + ```shell + java -jar MCCD.jar -i src/MyOrganizer.cd -ct CD2OwnDecorator -fp src/main/configTemplate + ``` + +=== "Gradle" +When using Gradle, element-level configurations can be added directly to the `options` list of the +`generateClassDiagrams` task. + + ```groovy + // build.gradle + tasks.named("generateClassDiagrams") { + // Element-level configuration targeting the Day class + options.add("MyOrganizer.Day:noGetter") + options.add("MyOrganizer.Day:noSetter") + + // Global-level configuration: Change the config template used by the generator + // getConfigTemplate().set("CD2OwnDecorator") + + // Additional optional configurations: + // getClass2MC().set(true) + // getCoCos().set(false) // (Not encouraged!) + // getOriginalSymbolOutput().set(...) + // getDecoratedSymbolOutput().set(...) + // getOutputDir().set(...) + } + ``` + +## Running the CD4Code Generator +The execution of the CD4Code Generator follows a structured pipeline. +First parsing and validating the model, then managing its symbols, and finally transforming the diagram +into executable Java source code. + +### 1. Loading, CoCo-Checking, and Symbol Table Creation +The first phase of execution focuses on frontend processing. The generator loads the .cd file, parses its +contents, creates an internal symbol table to resolve types, and runs Context Conditions (CoCos) to +ensure the diagram adheres to all semantic rules of the language. + +=== "CLI" + To parse and validate a class diagram model without generating any code artifacts, pass the input file using the `-i` flag to specify the input file path. By default, basic validation occurs, but you can explicitly enforce full CoCo checks or enable Java type resolution. + + ```shell + # Basic parse, symbol table creation, and check + java -jar MCCD.jar -i src/MyOrganizer.cd + + # Explicitly check all CD4C Context Conditions (CoCos) + java -jar MCCD.jar -i src/MyOrganizer.cd --checkcocos + + # Enable resolution of standard Java classes (e.g., java.util.List) within the model + java -jar MCCD.jar -i src/MyOrganizer.cd --class2mc + ``` + +=== "Gradle" + In a standard Gradle setup, the plugin automatically configures these phases as part of its default task execution pipeline. However, you can control CoCo behavior and type resolution directly within the task configuration block. + + ```groovy + // build.gradle + tasks.named("generateClassDiagrams") { + // Enables resolving standard Java classes used inside the CD diagram + getClass2MC().set(true) + + // Controls whether CoCo checks are executed (enabled by default) + getCoCos().set(true) + } + ``` + +### 2. Storing and Exporting Symbols +In a large-scale project, comprehensibility suffers when a single file contains all artifacts of our class +diagram. To address this issue, the CD4Code Generator can serialize its symbol table into a standalone +symbol file, which can then be exported or loaded as a dependency by other models. + +=== "CLI" + Use the `-s` or `--symboltable` flag to specify where the serialized symbol table file should be saved. If your diagram depends on external symbols, use the -path flag to point to the directory containing those symbol files. + + ```shell + # Export the symbol table to a specific file + java -jar MCCD.jar -i src/MyOrganizer.cd -s out/symbols/MyOrganizer.cdsym + + # Load external dependencies/symbols while processing a diagram + java -jar MCCD.jar -i src/MyOrganizer.cd -path dependencies/symbols/ + ``` + +=== "Gradle" + The Gradle plugin manages symbol storage and tracking automatically, storing original and decorated symbols in separate build directories. You can customize these locations if your build pipeline requires a non-standard layout. + + ```groovy + // build.gradle + tasks.named("generateClassDiagrams") { + // Customize the output directory for the original symbol table + getOriginalSymbolOutput().set(file("build/custom-symbols/original")) + + // Customize the output directory for the decorated symbol table + getDecoratedSymbolOutput().set(file("build/custom-symbols/decorated")) + } + ``` + +### 3. Generating Java Code +Once the model is fully validated and its symbols are resolved, the generator can proceed to execute +the decorators and generate the actual Java source files. + +=== "CLI" + To trigger code generation, you must explicitly include the `--gen` flag. You can combine this with the `-o` flag to specify the target directory for the generated code, and `--fieldfromrole` to control how associations are translated into actual class fields. + + ``` + # Generate Java files into a dedicated output directory + java -jar MCCD.jar -i src/MyOrganizer.cd --gen -o out/generated-sources + + # Generate code while explicitly mapping navigable association roles to Java fields + java -jar MCCD.jar -i src/MyOrganizer.cd --gen -o out/generated-sources --fieldfromrole navigable + ``` + + If your class diagram contains associations (e.g., `association [*] Task (tasks) <-> (project) Project [1];`), + the basic `--gen` command will not automatically generate the corresponding Java fields to link these objects. + Instead, you must explicitly tell the generator to map these association roles to fields using the + `--fieldfromrole` flag. + + In our example, the `Project` class has a role named `project` in its association with `Task`. + This means the generator will create an `project` field inside the generated `Task` Java class to represent + the relationship. To generate these fields, use the following command: + + ```shell + java -jar MCCD.jar -i src/MyOrganizer.cd -o out --gen --fieldfromrole navigable + ``` + +=== "Gradle" + Code generation is fully integrated into the standard Gradle lifecycle. Executing the `build` task or the specific `generateClassDiagrams` task automatically processes all source sets and places the output in the configured directory. + + ```groovy + // build.gradle + tasks.named("generateClassDiagrams") { + // Set the target directory for the generated Java files + getOutputDir().set(file("build/generated/sources/cdgen/main/java")) + } + ``` + +Running the CD4Code generator tooled into a Gradle build is as simple as executing the Gradle build task. + +### Inspecting the Generated Code +The generated code should now be located in the specified directory. Let's take a look at the generated code. +As by default, we use the `CD2Pojo` template, for configuring the Decorators, we are applying the `GetterDecorator`, +`SetterDecorator`, `CardinalityDecorator`, `NavigableSetterDecorator`, and `AbstactMethodDecorator` Decorators. +Therefore, we also expect the respecitive code artifacts to be generated. In our example, we should find the following files: + +```text +my-project/ +├── build/ +│ ├── cdgensymbols/ +│ ├── classes/ +│ ├── generated/ +│ └── generated-sources/ +│ └── cdgen/ +│ └── sourcecode/ +│ └── MyOrganizer/ +│ ├── Asset.java +│ ├── Day.java +│ ├── Project.java +│ ├── Status.java +│ └── Task.java +├── configTemplate/ +│ └── CD2OwnDecorator.ftl +└── build.gradle + README.md +``` + +The generated Java files should contain the expected getters, setters, and other methods as defined by the applied decorators. +You can now integrate this generated code into your Java project, further customize it, or use it as a base for additional development using the TOP-Mechanism. diff --git a/doc/GraphAdapter.cd b/docs/GraphAdapter.cd similarity index 100% rename from doc/GraphAdapter.cd rename to docs/GraphAdapter.cd diff --git a/doc/IOAdapter.cd b/docs/IOAdapter.cd similarity index 100% rename from doc/IOAdapter.cd rename to docs/IOAdapter.cd diff --git a/doc/Management.cd b/docs/Management.cd similarity index 100% rename from doc/Management.cd rename to docs/Management.cd diff --git a/doc/MyAddress.cd b/docs/MyAddress.cd similarity index 100% rename from doc/MyAddress.cd rename to docs/MyAddress.cd diff --git a/doc/MyCompany.cd b/docs/MyCompany.cd similarity index 100% rename from doc/MyCompany.cd rename to docs/MyCompany.cd diff --git a/doc/MyEmployees1.cd b/docs/MyEmployees1.cd similarity index 100% rename from doc/MyEmployees1.cd rename to docs/MyEmployees1.cd diff --git a/doc/MyEmployees2.cd b/docs/MyEmployees2.cd similarity index 100% rename from doc/MyEmployees2.cd rename to docs/MyEmployees2.cd diff --git a/doc/MyExample.cd b/docs/MyExample.cd similarity index 100% rename from doc/MyExample.cd rename to docs/MyExample.cd diff --git a/doc/MyWorkplace.cd b/docs/MyWorkplace.cd similarity index 100% rename from doc/MyWorkplace.cd rename to docs/MyWorkplace.cd diff --git a/doc/Teaching.cd b/docs/Teaching.cd similarity index 100% rename from doc/Teaching.cd rename to docs/Teaching.cd diff --git a/doc/cwdiff_DT3_DT2_module.als b/docs/cwdiff_DT3_DT2_module.als similarity index 100% rename from doc/cwdiff_DT3_DT2_module.als rename to docs/cwdiff_DT3_DT2_module.als diff --git a/docs/decorators/AbstractMethodDecorator.md b/docs/decorators/AbstractMethodDecorator.md new file mode 100644 index 000000000..f89dab891 --- /dev/null +++ b/docs/decorators/AbstractMethodDecorator.md @@ -0,0 +1,36 @@ +# AbstractMethodDecorator + +The `AbstractMethodDecorator` is responsible for adding `abstract` modifiers to all methods defined in the classes +and interfaces of the class diagram, or changing their implementation into abstract methods. This is particularly +useful when generating a base structural framework where the concrete implementation of business logic is expected +to be provided by developers extending these generated base classes. + +## The Core Mechanism + +When the generator runs, this decorator traverses every method (`ASTCDMethod`) in the class diagram and applies a transformation: + +1. **Guard Clauses:** It verifies if the method and its enclosing class/interface should be decorated. +2. **Modifier Update:** It updates the modifiers of the method to include `abstract`. +3. **Body Removal:** It removes any existing method body, as abstract methods cannot have bodies. +4. **Class Modifier Update:** If a class contains abstract methods, the class itself must be declared abstract. The decorator ensures the enclosing class's modifiers are updated accordingly. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the AbstractMethodDecorator](../../myOrganizer/img/MyOrganizerOnlyWithAbstractMethodSignatures.svg) +
Figure 1.2 The original class diagram after applying the AbstractMethodDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies + +In the `MyOrganizer` example, if we look at the `Asset` and `Task` classes, they both define a `void process();` +method. After applying the `AbstractMethodDecorator`, these methods are transformed into abstract methods +within the generated Java code, forcing any concrete subclass to provide an implementation. + +```java +// Generated by AbstractMethodDecorator +public abstract void process(); +``` diff --git a/docs/decorators/BuilderDecorator.md b/docs/decorators/BuilderDecorator.md new file mode 100644 index 000000000..cfffc7bea --- /dev/null +++ b/docs/decorators/BuilderDecorator.md @@ -0,0 +1,62 @@ +# BuilderDecorator + +The `BuilderDecorator` implements the Builder Design Pattern by automatically generating a dedicated Builder class for every instantiable class in your class diagram. This is especially useful for classes with numerous attributes, providing a fluent and readable API for object creation instead of relying on massive constructors or numerous setter calls. + +## The Core Mechanism + +When the generator runs, this decorator processes every class (`ASTCDClass`) in the diagram: + +1. **Target Selection:** It identifies classes that require a builder (typically non-abstract classes, or those specifically tagged). +2. **Builder Class Generation:** For a class named `X`, it creates a new class `XBuilder`. +3. **Attribute Duplication:** It duplicates all attributes from the target class into the builder class to hold the intermediate state. +4. **Fluent Setter Generation:** It generates "set" methods (e.g., `setProjectName(String)`) that return `this.realBuilder` for method chaining, ensuring compatibility with inheritance. +5. **Validation Method Generation:** It generates a `isValid()` method that checks if all required attributes have been set. +6. **Build Method:** It generates a `build()` method that validates the state, constructs, and returns an instance of the target class using the accumulated properties. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the BuilderDecorator](../../myOrganizer/img/MyOrganizerOnlyBuilders.svg) +
Figure 1.2 The original class diagram after applying the BuilderDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies + +Looking at the `Project` class, which has multiple attributes (`projectName`, `deadline`, `budget`, `tasks`), creating an instance manually might be cumbersome. The `BuilderDecorator` simplifies this by generating a `ProjectBuilder`. + +```java +// Example of generated fluent setters in ProjectBuilder +public ProjectBuilder setProjectName(String projectName) { + this.projectName = projectName; + return this.realBuilder; +} + +public ProjectBuilder setBudget(double budget) { + this.budget = budget; + return this.realBuilder; +} + +// Example of the final build method +public Project build() { + if(!isValid()){ + throw new IllegalStateException("build called on an incomplete object of type Project."); + } + var v = new Project(); + v.setProjectName(this.projectName); + v.setBudget(this.budget); + // ... set other attributes + return v; +} +``` + +This allows developers to create instances of `Project` with a more concise syntax: + +```java +Project project = new ProjectBuilder() + .setProjectName("New Website") + .setBudget(10000.0) + .build(); +``` diff --git a/docs/decorators/CardinalityDefaultDecorator.md b/docs/decorators/CardinalityDefaultDecorator.md new file mode 100644 index 000000000..13a1ae95d --- /dev/null +++ b/docs/decorators/CardinalityDefaultDecorator.md @@ -0,0 +1,40 @@ +# CardinalityDefaultDecorator + +The `CardinalityDefaultDecorator` initiates Lists, Sets, and Optional attributes of the class diagram with an +empty List, empty Set, or an empty Optional respectively. As the `CopyDecorator` always runs early in the +generation process and adds the attributes defined in the associations and compositions to the respective +classes, the `CardinalityDefaultDecorator` ensures these attributes are safely initialized, preventing +`NullPointerException`s at runtime. + +As raw initialization syntax (like `new ArrayList<>()`) is not natively represented in the class diagram language, +the `CardinalityDefaultDecorator` instead injects this initialization via template hooks directly into the +class's constructors. + +## Core Mechanism +When the generator runs, this decorator traverses every attribute in a class diagram (`ASTCDAttribute`) and applies a targeted transformation: + +1. **Type Routing:** It checks if the attribute is a List, Set, or Optional. Everything else is ignored. +2. **Constructor Generation:** It calls `getOrCreateDecConstructors`. If the decorated class does not already have a constructor, the decorator creates a default, no-argument constructor for it. +3. **Template Injection:** It injects specific Java instantiation code into the body of the constructor. + +--- + +## Real-World Breakdown: Generation Strategies +Looking directly at the generated Java code for the `Project` class, we can see exactly how the `CardinalityDefaultDecorator` handled the different attribute types. + +Standard attributes like `projectName` and `budget` are ignored, but the collections and optionals are handled securely within the generated default constructor: + +```java +public Project() { + /* INJECTED BY CardinalityDefaultDecorator */ + /* generated by template methods.InstantiationEmptyOptional*/ + this.deadline = java.util.Optional.empty(); + + /* INJECTED BY CardinalityDefaultDecorator */ + /* generated by template methods.Instantiation*/ + this.tasks = new java.util.LinkedHashSet(); +} +``` + +Optional attributes like `deadline` are initialized with `java.util.Optional.empty()`, while sets like `tasks` +are initialized with `new java.util.LinkedHashSet()`. Lists are initialized with `new java.util.ArrayList()`. diff --git a/docs/decorators/CopyDecorator.md b/docs/decorators/CopyDecorator.md new file mode 100644 index 000000000..2f0c01aef --- /dev/null +++ b/docs/decorators/CopyDecorator.md @@ -0,0 +1,56 @@ +# CopyDecorator + +The `CopyDecorator` is a mandatory, foundational step in the CD4Code generation pipeline. Unlike other decorators +that add optional features, the `CopyDecorator`'s primary role is to create a clean, unified, and complete +Abstract Syntax Tree (AST) from the initial parsed class diagram. It normalizes the diagram's structure, +resolves associations, and sets sensible defaults, preparing the AST for all subsequent decorators. + +## The Core Mechanism + +The `CopyDecorator` is always the first to run. It takes the raw AST from the parser and performs several crucial transformations: + +1. **AST Unification:** It creates a deep copy of the original AST. This ensures that all subsequent modifications by other decorators do not affect the original, parsed model. +2. **Package Creation:** If the class diagram is not explicitly defined within a package, the decorator creates a default package to encapsulate all the elements. +3. **Association Materialization:** It translates abstract `association` and `composition` relationships into concrete class attributes. Based on the defined cardinality, it adds fields to the corresponding classes: + * `[*]` or `[0..*]` becomes a `Set`. + * `[1..*]` becomes a `List`. + * `[1]` or `[1..1]` becomes a direct `Type` reference. + * `[0..1]` becomes an `Optional`. +4. **Default Visibility:** It enforces a "public by default" policy. Any class, attribute, or method without an explicit visibility modifier (`public`, `protected`, `private`) is automatically set to `public`. + +--- + +![Figure 1.1 The original class diagram](../../myOrganizer/img/MyOrganizer.svg) +
Figure 1.1 The original class diagram as defined in the `.cd` file
+ +![Figure 1.2 The class diagram after applying the CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.2 The unified class diagram after the `CopyDecorator` has run
+ +--- + +## Real-World Breakdown: Generation Strategies + +The `CopyDecorator`'s impact is best seen by comparing the user-defined class diagram with the resulting class +diagram after the `CopyDecorator` modiefied it. + +### Association to Attributes + +The original diagram defines a bidirectional association: +`association [*] Task (tasks) <-> (project) Project [1];` + +The `CopyDecorator` dissolves this relationship and injects corresponding attributes into the classes: +* The `Project` class gets a `private Set tasks;` attribute. +* The `Task` class gets a `private Project project;` attribute. + +### Default Visibility + +In the original diagram, the `Task` class and its `taskStatus` attribute have no explicit visibility. + +```cd4code +class Task extends Asset { + Status taskStatus; + void process(); +} +``` + +The `CopyDecorator` automatically promotes them to `public`, making them accessible by default. This unified AST is then passed to other decorators like `GetterDecorator` and `SetterDecorator`, which will later reduce the visibility of the fields to `protected` to enforce encapsulation. diff --git a/docs/decorators/GetterDecorator.md b/docs/decorators/GetterDecorator.md new file mode 100644 index 000000000..96d1f6349 --- /dev/null +++ b/docs/decorators/GetterDecorator.md @@ -0,0 +1,87 @@ +# GetterDecorator + +The `GetterDecorator` is responsible for automatically creating accessor (getter) methods for class attributes. +Rather than generating a basic, one-size-fits-all `get()` method, it intelligently analyzes the multiplicity +(Mandatory, Optional, Set/List) and type (e.g., Booleans vs. Standard Objects) of each attribute to generate +a method for reading attribute values. + +## The Core Mechanism + +When the generator runs, this decorator traverses every attribute in a class diagram (`ASTCDAttribute`) and applies a multi-step transformation: + +1. **Guard Clauses:** It verifies if the attribute should be decorated, ensuring it only processes valid targets. +2. **Multiplicity Routing:** It checks the attribute's definition and routes it to specific generator methods (`decorateMandatory`, `decorateOptional`, `decorateList`, `decorateSet`). +3. **Template Injection:** It creates the method signature and injects templates to fill in the method body. +4. **Encapsulation Enforcement:** At the very end (`updateModifier`), it alters the visibility of the underlying field itself to `protected`. This enforces encapsulation, ensuring that external Java code must route through the newly generated getter methods. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the SetterDecorator](../../myOrganizer/img/MyOrganizerOnlyGetter.svg) +
Figure 1.2 The original class diagram after applying the GetterDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies + +Looking at the `Project` class, we can see exactly how the decorator handled Mandatory, Optional, and Collection +data types to create a highly usable Java class. + +### 1. Mandatory Attributes (`[1]`) +For standard attributes like `String projectName` and `double budget`, the decorator generates a standard, direct +accessor. In the resulting class diafram, we see `getProjectName` and `getBudget`. + +```java +// Generated by GetterDecorator -> decorateMandatory +public String getProjectName() { + return this.projectName; +} +``` + +### 2. Optional Attributes (`[0..1]`) +For the `Optional deadline`, simply returning the `Optional` object isn't always good practice. +Instead, the decorator generates two methods (seen in the class diagram as `isPresentDeadline` and `getDeadline`). +The first method `isPresentDeadline()` checks if the value is present, while the second method `getDeadline()` +retrieves the value if it exists, or throws an exception if it doesn't. + +```java +// Generated by GetterDecorator -> decorateOptionalIsPresent +private boolean isPresentDeadline() { + return this.deadline.isPresent(); +} + +// Generated by GetterDecorator -> decorateOptional +private Date getDeadline() { + if (isPresentDeadline()) { + return this.deadline.get(); + } + Log.error("0xA7003x70140 get for Deadline can't return a value. Attribute is empty."); + throw new IllegalStateException(); +} +``` + +### 3. Sets and Lists (`[*]`) +Because `Project` holds multiple `Task`s via an association, the decorator generates the main `getTasks()` method. +However, to follow encapsulation best practices, it also generates a massive suite of delegation functions. + +These methods allow external code to query the collection directly, which results in a much more intuitive code. + +```java +// Generated by GetterDecorator -> decorateSet +public Set getTasks() { + return this.tasks; +} + +// Generated by GetterDecorator -> decorateWithAssocFunctions +// Automatically delegates to the underlying collection +public boolean containsTasks(Object element) { + return this.getTasks().contains(element); +} + +public java.util.stream.Stream streamTasks() { + return this.getTasks().stream(); +} +// ... and many more (iteratorTasks, toArrayTasks, equalsTasks, etc.) +``` diff --git a/docs/decorators/NavigableSetterDecorator.md b/docs/decorators/NavigableSetterDecorator.md new file mode 100644 index 000000000..db4dcbf9c --- /dev/null +++ b/docs/decorators/NavigableSetterDecorator.md @@ -0,0 +1,94 @@ +# NavigableSetterDecorator + +The `NaviagableSetterDecorator` depends on the `SetterDecorator` and shares the same enabler and disabler as +the `SetterDecorator` by default. This means that the `NaviagableSetterDecorator` is precisely then applied, +when the `SetterDecorator` is applied is always applied. Furthermore, as the `NaviagableSetterDecorator` +depends on the SetterDecorator, it is applied after the SetterDecorator. + +The `NavigableSetterDecorator` is responsible for automatically managing **bidirectional associations** (`<->`). +When an association is navigable from both sides, this decorator injects the boilerplate code necessary to keep +both ends of the relationship perfectly in sync, preventing dangling references and infinite recursion. + +## The Core Mechanism + +To safely link two objects without causing an infinite loop (where Object A updates Object B, which triggers Object B to update Object A, and so on), the decorator employs a two-part strategy: + +1. **The "Local" Setter:** It generates a new, hidden method (e.g., `set*Local`) that silently updates the underlying field *without* notifying the other side. +2. **The Hook Injection:** It injects a template hook into the standard, public setter. This hook automatically calls the other object's "Local" method. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the SetterDecorator and NavigableSetterDecorator](../../myOrganizer/img/MyOrganizerOnlyNavigableSetter.svg) +
Figure 1.2 The original class diagram after applying the SetterDecorator and the NavigableSetterDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies + +To see this in action, we look at the bidirectional association between `Task` and `Project` in our class diagram: +`association [*] Task (tasks) <-> (project) Project [1];` + +Because both sides are navigable, the `NavigableSetterDecorator` modifies the generated Java code for both classes +to ensure they stay perfectly synchronized. + +### 1. Generating the "Local" Setter +To prevent infinite recursion during updates, the decorator generates safe, localized mutators for both the `[1]` +side (Task) and the `[*]` side (Project). These methods handle the internal assignment but purposefully do not +attempt to update the other side. + +```java +// Inside Task class: Generated by NavigableSetterDecorator +public void setProjectLocal(MyOrganizer.Project project) { + /* Hookpoint: Setter:Before */ + this.project = project; + /* Hookpoint: Setter:After */ +} + +// Inside Project class: Generated by NavigableSetterDecorator +public boolean addTasksLocal(MyOrganizer.Task tasks) { + /* Hookpoint: Setter:Before */ + var __ret = this.tasks.add(tasks); + /* Hookpoint: Setter:After */ + return __ret; +} +``` + +### 2. Hook Injection +Once the local setters exist, the decorator takes advantage of the empty `Setter:After` hook points left behind +by the standard `SetterDecorator`. It injects a call to the opposite class's `*Local` method. + +If a developer calls `setProject` on a Task, the injected hook immediately ensures the `Project` adds that `Task` +to its internal set. + +```java +// Inside Task class: Modified standard setter +public void setProject(MyOrganizer.Project project) { + /* Hookpoint: Setter:Before */ + this.project = project; + /* Hookpoint: Setter:After */ + + /* INJECTED BY NavigableSetterDecorator */ + this.project.addTasksLocal((Task)this); +} +``` + +Conversely, if a developer calls `addTasks` on a `Project`, the injected hook assigns that `Project` to the +`Task`'s internal set. + +```java +// Inside Project class: Modified standard setter +public boolean addTasks(MyOrganizer.Task tasks) { + /* Hookpoint: Setter:Before */ + var __ret = this.tasks.add(tasks); + /* Hookpoint: Setter:After */ + + /* INJECTED BY NavigableSetterDecorator */ + tasks.setProjectLocal((Project)this); + return __ret; +} +``` + +This ensures that both sides of the relationship are perfectly in sync without manual state management. diff --git a/docs/decorators/ObserverDecorator.md b/docs/decorators/ObserverDecorator.md new file mode 100644 index 000000000..bd305c0cf --- /dev/null +++ b/docs/decorators/ObserverDecorator.md @@ -0,0 +1,94 @@ +# ObserverDecorator + +The `ObserverDecorator` implements the Observer Design Pattern by automatically turning the classes in your class +diagram into observable entities. It generates the necessary infrastructure allowing other components of your +system to subscribe to and react to state changes within these generated objects. + +## The Core Mechanism + +When the generator runs, this decorator traverses the AST and enriches it with the Observer pattern: + +1. **Listener Interface Generation:** For a class `X`, it creates a listener/observer interface (e.g., `IXObserver`). +2. **Subscription Management:** It adds fields to the target class to manage a collection of registered listeners. +3. **Registration Methods:** It generates methods in the target class to `addObserver()` and `removeObserver()`. +4. **Notification Hooks:** It injects code into state-changing methods (like setters) to notify all registered observers whenever a value is updated, providing both generic and attribute-specific notifications. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the ObserverDecorator](../../myOrganizer/img/MyOrganizerOnlyObservers.svg) +
Figure 1.2 The original class diagram after applying the ObserverDecorator
+ + +--- + +## Real-World Breakdown: Generation Strategies +If we look at the `Task` class, we might want UI components or project managers to be notified when the +`taskStatus` changes. By enabling the `ObserverDecorator`, the class becomes observable. + +For the `Task` class the decorator generates the interface `ITaskObserver.java` with default empty methods for both generic updates and attribute-specific updates: + +```java +package MyOrganizer; + +public interface ITaskObserver extends de.monticore.cd.ICDObserver { + + default public void notifyUpdate(Task clazz) { + // empty body + } + + default public void notifyUpdateSetTaskStatus(Task clazz, Status ov) { + // empty body + } + + default public void notifyUpdateSetProject(Task clazz, MyOrganizer.Project ov) { + // empty body + } +} +``` + +And add management and notification methods directly to the `Task` class: +```java +public class Task extends Asset implements de.monticore.cd.ICDObservable { + + protected List observerList = new ArrayList<>(); + + public void addObserver(MyOrganizer.ITaskObserver observer) { + this.observerList.add(observer); + } + + public void removeObserver(MyOrganizer.ITaskObserver observer) { + this.observerList.remove(observer); + } + + protected void notifyObservers() { + for(MyOrganizer.ITaskObserver observer : this.observerList) { + observer.notifyUpdate(this); + } + } + + protected void notifyObserversSetTaskStatus(Status ov) { + for(ITaskObserver observer : this.observerList) { + observer.notifyUpdateSetTaskStatus(this, ov); + } + } + + // ... other attribute-specific notification methods +} +``` +It is strongly recommended to use the `ObserverDecorator` together with the `SetterDecorator` to automatically +generate the necessary hooks for attribute-specific notifications. The generated setters will then automatically trigger these specific notifications like `notifyObserversSetProject(oldValue)`. + +```java +public void setProjectLocal (MyOrganizer.Project project) { + /* Hookpoint: Setter:Before */ + var _oldValue = this.project; + + this.project = project; + /* Hookpoint: Setter:After */ + this.notifyObserversSetProject(_oldValue ); + this.notifyObservers(); +} +``` diff --git a/docs/decorators/SetterDecorator.md b/docs/decorators/SetterDecorator.md new file mode 100644 index 000000000..c1e02772f --- /dev/null +++ b/docs/decorators/SetterDecorator.md @@ -0,0 +1,98 @@ +# SetterDecorator + +The `SetterDecorator` is responsible for automatically creating setter methods for class attributes. +Rather than generating a basic, one-size-fits-all `set()` method, it intelligently +analyzes the **multiplicity** (Mandatory, Optional, Set/List) and **properties** (Ordered vs. Unordered) of each +attribute to generate a method for modifying attribute values. + +## The Core Mechanism + +When the generator runs, this decorator traverses every attribute in a class diagram (`ASTCDAttribute`) and applies a multi-step transformation: + +1. **Guard Clauses:** It immediately skips any attributes marked as `derived`, `readonly`, or `final`, because these logically should not have public mutators. +2. **Multiplicity Routing:** It checks the attribute's definition and routes it to specific generator methods (`decorateMandatory`, `decorateOptionalAbsent`, `decorateAddUnordered`, etc.). +3. **Template Injection:** It creates the method signature and injects templates to fill in the method body. +4. **Encapsulation Enforcement:** At the very end (`updateModifier`), it alters the visibility of the underlying field itself to `protected`. This enforces encapsulation, ensuring that external Java code must route through the newly generated setter methods. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the SetterDecorator](../../myOrganizer/img/MyOrganizerOnlySetter.svg) +
Figure 1.2 The original class diagram after applying the SetterDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies +The generated setters are highly customizable through hook points. Additional to the basic assignment logic, +the decorator also leaves empty template hook points (`Setter:Before` and `Setter:After`) that you can use to +inject custom logic. This is used in the `NavigableSetterDecorator` to automatically manage bidirectional +associations, but you can also use it for your own custom logic. + +Depending on how an attribute is defined in your class diagram, the `SetterDecorator` will produce radically different +Java code. The `Project` class is a perfect example, as it contains all three primary multiplicity types: Mandatory, +Optional, and Set. + +Here is how the decorator handles each scenario: +### 1. Mandatory Attributes (`[1]`) +For standard, required attributes like `String projectName` and `double budget`, the decorator generates a straightforward mutator. +* +```java +// Generated by SetterDecorator -> decorateMandatory +public void setProjectName(String projectName) { + /* Hookpoint: Setter:Before */ + this.projectName = projectName; + /* Hookpoint: Setter:After */ +} + +public void setBudget(double budget) { + /* Hookpoint: Setter:Before */ + this.budget = budget; + /* Hookpoint: Setter:After */ +} +``` + +### 2. Optional Attributes (`[0..1]`) +Because deadline is explicitly modeled as an `Optional`, the CD4Code generator needs to provide a safe way to +update or clear this value. For this the decorator generates two distinct methods. +The first one is `setDeadline(Date deadline)` that updates the value if it is not null. +The second one is `setDeadlineAbsent()` that clears the value. + +```java +// Generated by SetterDecorator -> decorateOptSet +private void setDeadline(Date deadline) { + /* Hookpoint: Setter:Before */ + this.deadline = Optional.ofNullable(deadline); + /* Hookpoint: Setter:After */ +} + +// Generated by SetterDecorator -> decorateOptionalAbsent +private void setDeadlineAbsent() { + /* Hookpoint: Setter:Before */ + this.deadline = Optional.empty(); + /* Hookpoint: Setter:After */ +} +``` + +### 3. Sets and Lists (`[*]`) +The bidirectional association creates a `Set tasks` in the Project class. Instead of overwriting the whole +set with a basic `setTasks()`, the decorator generates muliple collection management methods (addTasks and removeTasks). + +```java +// Generated by SetterDecorator -> decorateAddUnordered +public boolean addTasks(MyOrganizer.Task tasks) { + /* Hookpoint: Setter:Before */ + var __ret = this.tasks.add(tasks); + /* Hookpoint: Setter:After */ + return __ret; +} + +// Generated by SetterDecorator -> decorateRemoveUnordered +public boolean removeTasks (MyOrganizer.Task tasks) { + /* Hookpoint: Setter:Before */ + var __ret = this.tasks.remove(tasks); + /* Hookpoint: Setter:After */ + return __ret; +} +``` diff --git a/docs/decorators/VisitorDecorator.md b/docs/decorators/VisitorDecorator.md new file mode 100644 index 000000000..f6f7cd826 --- /dev/null +++ b/docs/decorators/VisitorDecorator.md @@ -0,0 +1,76 @@ +# VisitorDecorator + +The `VisitorDecorator` implements the Visitor Design Pattern by generating the necessary infrastructure to +traverse and process the object graph formed by the generated classes. This pattern is essential for separating +algorithms from the object structure on which they operate, making it easy to add new operations without +modifying the generated classes themselves. + +## The Core Mechanism + +The `VisitorDecorator` operates globally across the class diagram to set up the visitor pattern: + +1. **Visitor Interface Generation:** It generates a single, central visitor interface for the entire class diagram. For a diagram named `MyOrganizer`, this interface will be named `IMyOrganizerVisitor`. +2. **`visit` Methods:** For every class `X` in the diagram, it adds a corresponding abstract `visit(X node)` method to the `IMyOrganizerVisitor` interface. This ensures that a concrete visitor implementation must provide logic for handling each specific type. +3. **`accept` Method Injection:** It injects a public `accept(IMyOrganizerVisitor visitor)` method into every generated class (`Task`, `Project`, etc.). +4. **Delegation:** The implementation of the generated `accept` method is a single call that delegates control to the visitor, e.g., `visitor.visit((Task) this)`. This is the core of the "double dispatch" mechanism in the Visitor pattern. + +--- + +![Figure 1.1 The original class diagramm after applying the mandatory CopyDecorator](../../myOrganizer/img/MyOrganizerNoDecorators.svg) +
Figure 1.1 The class diagram after the mandatory CopyDecorator
+ +![Figure 1.2 The original class diagram after applying the VisitorDecorator](../../myOrganizer/img/MyOrganizerOnlyVisitors.svg) +
Figure 1.2 The original class diagram after applying the VisitorDecorator
+ +--- + +## Real-World Breakdown: Generation Strategies + +In the `MyOrganizer` example, you have a `Project` that contains `Task`s. If you want to write a custom tool to perform an operation on this structure (e.g., validate all tasks), you can use the generated visitor infrastructure. + +### Generated `IMyOrganizerVisitor` Interface +The decorator first creates the central interface with a `visit` method for each class: + +```java +// Generated IMyOrganizerVisitor.java +package MyOrganizer; + +public interface IMyOrganizerVisitor { + void visit(Asset node); + void visit(Task node); + void visit(Project node); + void visit(Day node); + // ... and so on for all other types +} +``` + +### Generated `accept` Method +The decorator injects a `accept` method into every generated class For the `Task` class, it would look like this: + +```java +// Injected into Task.java +public void accept(MyOrganizer.IMyOrganizerVisitor visitor) { + visitor.visit((Task) this); +} +``` + +## Usage +A developer can now implement the IMyOrganizerVisitor interface (or extend the generated default traversal class +`MyOrganizerVisitorImplementation`) to create a visitor. This visitor can then be used to traverse the object +graph and perform operations on each node. This separates the logic of the operation from the data structure of +the generated classes, allowing for clean and maintainable code. + +```java +// A custom implementation to validate tasks +public class TaskValidator implements IMyOrganizerVisitor { + @Override + public void visit(Task task) { + // Add validation logic for Task objects here + if (task.getTaskStatus() == Status.OPEN) { + System.out.println("Task is still open!"); + } + } + + // Abstract methods for other nodes would need to be implemented or ignored if utilizing the interface directly +} +``` diff --git a/doc/mrg-param.json b/docs/mrg-param.json similarity index 100% rename from doc/mrg-param.json rename to docs/mrg-param.json diff --git a/docs/myOrganizer/cds/MyOrganizer.cd b/docs/myOrganizer/cds/MyOrganizer.cd new file mode 100644 index 000000000..c25aa5d61 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizer.cd @@ -0,0 +1,31 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; + +classdiagram MyOrganizer { + + enum Status { PROCESSING, DONE, OPEN; } + + abstract class Asset { + void process(); + } + + class Task extends Asset { + Status taskStatus; + void process(); + } + + class Project extends Asset { + public String projectName; + private Optional deadline; + protected double budget; + void process(); + } + + class Day { + Date date; + } + + association [1] Day (day) -> (tasks) Task [*]; + association [*] Task (tasks) <-> (project) Project [1]; +} \ No newline at end of file diff --git a/docs/myOrganizer/cds/MyOrganizerNoDecorators.cd b/docs/myOrganizer/cds/MyOrganizerNoDecorators.cd new file mode 100644 index 000000000..b4962613b --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerNoDecorators.cd @@ -0,0 +1,39 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + public Status taskStatus; + public void process(); + public MyOrganizer.Project project; + + } + public class Project extends Asset { + public String projectName; + private Optionaldeadline; + protected double budget; + public void process(); + public Settasks; + + } + public class Day { + public Date date; + public Settasks; + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyBuilders.cd b/docs/myOrganizer/cds/MyOrganizerOnlyBuilders.cd new file mode 100644 index 000000000..eba5d4c79 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyBuilders.cd @@ -0,0 +1,90 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + public Status taskStatus; + public void process(); + public MyOrganizer.Project project; + + } + public class Project extends Asset { + public String projectName; + private Optionaldeadline; + protected double budget; + public void process(); + public Settasks; + + } + public class Day { + public Date date; + public Settasks; + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + public abstract class AssetBuilder { + protected AssetBuilder realBuilder; + public AssetBuilder(); + private boolean isValid(); + public Asset build(); + public Asset unsafeBuild(); + + } + public class TaskBuilder { + protected TaskBuilder realBuilder; + public TaskBuilder(); + private boolean isValid(); + public Task build(); + public Task unsafeBuild(); + protected Status taskStatus; + protected MyOrganizer.Project project; + public TaskBuilder setTaskStatus(Status taskStatus); + public TaskBuilder setProject(MyOrganizer.Project project); + + } + public class ProjectBuilder { + protected ProjectBuilder realBuilder; + public ProjectBuilder(); + private boolean isValid(); + public Project build(); + public Project unsafeBuild(); + protected String projectName; + protected Optionaldeadline; + protected double budget; + protected Settasks; + public ProjectBuilder setProjectName(String projectName); + public ProjectBuilder setDeadline(Date deadline); + public ProjectBuilder setBudget(double budget); + public ProjectBuilder setTasks(Settasks); + public ProjectBuilder setDeadlineAbsent(); + public ProjectBuilder setTasksAbsent(); + + } + public class DayBuilder { + protected DayBuilder realBuilder; + public DayBuilder(); + private boolean isValid(); + public Day build(); + public Day unsafeBuild(); + protected Date date; + protected Settasks; + public DayBuilder setDate(Date date); + public DayBuilder setTasks(Settasks); + public DayBuilder setTasksAbsent(); + + } + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyDefaultsForCardinalityAttrs.cd b/docs/myOrganizer/cds/MyOrganizerOnlyDefaultsForCardinalityAttrs.cd new file mode 100644 index 000000000..bbc074111 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyDefaultsForCardinalityAttrs.cd @@ -0,0 +1,41 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + public Status taskStatus; + public void process(); + public MyOrganizer.Project project; + + } + public class Project extends Asset { + public String projectName; + private Optionaldeadline; + protected double budget; + public void process(); + public Settasks; + public Project(); + + } + public class Day { + public Date date; + public Settasks; + public Day(); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyGetter.cd b/docs/myOrganizer/cds/MyOrganizerOnlyGetter.cd new file mode 100644 index 000000000..2d8d01f06 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyGetter.cd @@ -0,0 +1,72 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + protected Status taskStatus; + public void process(); + protected MyOrganizer.Project project; + public Status getTaskStatus(); + public MyOrganizer.Project getProject(); + + } + public class Project extends Asset { + protected String projectName; + protected Optionaldeadline; + protected double budget; + public void process(); + protected Settasks; + public String getProjectName(); + private Date getDeadline(); + private boolean isPresentDeadline(); + protected double getBudget(); + public SetgetTasks(); + public boolean containsTasks(Object element); + public boolean containsAllTasks(java.util.Collectioncollection); + public boolean isEmptyTasks(); + public java.util.IteratoriteratorTasks(); + public int sizeTasks(); + public MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + public Object []toArrayTasks(); + public java.util.SpliteratorspliteratorTasks(); + public java.util.stream.StreamstreamTasks(); + public java.util.stream.StreamparallelStreamTasks(); + public boolean equalsTasks(Object o); + public int hashCodeTasks(); + + } + public class Day { + protected Date date; + protected Settasks; + public Date getDate(); + public SetgetTasks(); + public boolean containsTasks(Object element); + public boolean containsAllTasks(java.util.Collectioncollection); + public boolean isEmptyTasks(); + public java.util.IteratoriteratorTasks(); + public int sizeTasks(); + public MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + public Object []toArrayTasks(); + public java.util.SpliteratorspliteratorTasks(); + public java.util.stream.StreamstreamTasks(); + public java.util.stream.StreamparallelStreamTasks(); + public boolean equalsTasks(Object o); + public int hashCodeTasks(); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyNavigableSetter.cd b/docs/myOrganizer/cds/MyOrganizerOnlyNavigableSetter.cd new file mode 100644 index 000000000..6f161db39 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyNavigableSetter.cd @@ -0,0 +1,53 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + protected Status taskStatus; + public void process(); + protected MyOrganizer.Project project; + public void setTaskStatus(Status taskStatus); + public void setProject(MyOrganizer.Project project); + public void setProjectLocal(MyOrganizer.Project project); + + } + public class Project extends Asset { + protected String projectName; + protected Optionaldeadline; + protected double budget; + public void process(); + protected Settasks; + public void setProjectName(String projectName); + private void setDeadlineAbsent(); + private void setDeadline(Date deadline); + protected void setBudget(double budget); + public boolean addTasks(MyOrganizer.Task tasks); + public boolean removeTasks(MyOrganizer.Task tasks); + public boolean addTasksLocal(MyOrganizer.Task tasks); + public boolean removeTasksLocal(MyOrganizer.Task tasks); + + } + public class Day { + protected Date date; + protected Settasks; + public void setDate(Date date); + public boolean addTasks(MyOrganizer.Task tasks); + public boolean removeTasks(MyOrganizer.Task tasks); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyObservers.cd b/docs/myOrganizer/cds/MyOrganizerOnlyObservers.cd new file mode 100644 index 000000000..dc340f718 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyObservers.cd @@ -0,0 +1,91 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset implements de.monticore.cd.ICDObservable{ + public void process(); + protected ListobserverList; + public void addObserver(MyOrganizer.IAssetObserver observer); + public void removeObserver(MyOrganizer.IAssetObserver observer); + protected void notifyObservers(); + + } + public class Task extends Asset implements de.monticore.cd.ICDObservable{ + public Status taskStatus; + public void process(); + public MyOrganizer.Project project; + protected ListobserverList; + public void addObserver(MyOrganizer.ITaskObserver observer); + public void removeObserver(MyOrganizer.ITaskObserver observer); + protected void notifyObservers(); + protected void notifyObserversSetTaskStatus(Status ov); + protected void notifyObserversSetProject(MyOrganizer.Project ov); + + } + public class Project extends Asset implements de.monticore.cd.ICDObservable{ + public String projectName; + private Optionaldeadline; + protected double budget; + public void process(); + public Settasks; + protected ListobserverList; + public void addObserver(MyOrganizer.IProjectObserver observer); + public void removeObserver(MyOrganizer.IProjectObserver observer); + protected void notifyObservers(); + protected void notifyObserversSetProjectName(String ov); + protected void notifyObserversSetDeadline(Optionalov); + protected void notifyObserversSetBudget(double ov); + protected void notifyObserversAddTasks(MyOrganizer.Task newElem); + protected void notifyObserversRemoveTasks(MyOrganizer.Task elem); + + } + public class Day implements de.monticore.cd.ICDObservable{ + public Date date; + public Settasks; + protected ListobserverList; + public void addObserver(MyOrganizer.IDayObserver observer); + public void removeObserver(MyOrganizer.IDayObserver observer); + protected void notifyObservers(); + protected void notifyObserversSetDate(Date ov); + protected void notifyObserversAddTasks(MyOrganizer.Task newElem); + protected void notifyObserversRemoveTasks(MyOrganizer.Task elem); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + public interface IAssetObserver extends de.monticore.cd.ICDObserver{ + public void notifyUpdate(Asset clazz); + + } + public interface ITaskObserver extends de.monticore.cd.ICDObserver{ + public void notifyUpdate(Task clazz); + public void notifyUpdateSetTaskStatus(Task clazz,Status ov); + public void notifyUpdateSetProject(Task clazz,MyOrganizer.Project ov); + + } + public interface IProjectObserver extends de.monticore.cd.ICDObserver{ + public void notifyUpdate(Project clazz); + public void notifyUpdateSetProjectName(Project clazz,String ov); + public void notifyUpdateSetDeadline(Project clazz,Optionalov); + public void notifyUpdateSetBudget(Project clazz,double ov); + public void notifyUpdateAddTasks(Project clazz,MyOrganizer.Task newElem); + public void notifyUpdateRemoveTasks(Project clazz,MyOrganizer.Task elem); + + } + public interface IDayObserver extends de.monticore.cd.ICDObserver{ + public void notifyUpdate(Day clazz); + public void notifyUpdateSetDate(Day clazz,Date ov); + public void notifyUpdateAddTasks(Day clazz,MyOrganizer.Task newElem); + public void notifyUpdateRemoveTasks(Day clazz,MyOrganizer.Task elem); + + } + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlySetter.cd b/docs/myOrganizer/cds/MyOrganizerOnlySetter.cd new file mode 100644 index 000000000..53d88494e --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlySetter.cd @@ -0,0 +1,50 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + + } + public class Task extends Asset { + protected Status taskStatus; + public void process(); + protected MyOrganizer.Project project; + public void setTaskStatus(Status taskStatus); + public void setProject(MyOrganizer.Project project); + + } + public class Project extends Asset { + protected String projectName; + protected Optionaldeadline; + protected double budget; + public void process(); + protected Settasks; + public void setProjectName(String projectName); + private void setDeadlineAbsent(); + private void setDeadline(Date deadline); + protected void setBudget(double budget); + public boolean addTasks(MyOrganizer.Task tasks); + public boolean removeTasks(MyOrganizer.Task tasks); + + } + public class Day { + protected Date date; + protected Settasks; + public void setDate(Date date); + public boolean addTasks(MyOrganizer.Task tasks); + public boolean removeTasks(MyOrganizer.Task tasks); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyVisitors.cd b/docs/myOrganizer/cds/MyOrganizerOnlyVisitors.cd new file mode 100644 index 000000000..bfa748ab7 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyVisitors.cd @@ -0,0 +1,91 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public void process(); + public void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + } + public class Task extends Asset { + protected Status taskStatus; + public void process(); + protected MyOrganizer.Project project; + public Status getTaskStatus(); + public MyOrganizer.Project getProject(); + public void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + } + public class Project extends Asset { + protected String projectName; + protected Optionaldeadline; + protected double budget; + public void process(); + protected Settasks; + public String getProjectName(); + private Date getDeadline(); + private boolean isPresentDeadline(); + protected double getBudget(); + public SetgetTasks(); + public boolean containsTasks(Object element); + public boolean containsAllTasks(java.util.Collectioncollection); + public boolean isEmptyTasks(); + public java.util.IteratoriteratorTasks(); + public int sizeTasks(); + public MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + public Object []toArrayTasks(); + public java.util.SpliteratorspliteratorTasks(); + public java.util.stream.StreamstreamTasks(); + public java.util.stream.StreamparallelStreamTasks(); + public boolean equalsTasks(Object o); + public int hashCodeTasks(); + public void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + } + public class Day { + protected Date date; + protected Settasks; + public Date getDate(); + public SetgetTasks(); + public boolean containsTasks(Object element); + public boolean containsAllTasks(java.util.Collectioncollection); + public boolean isEmptyTasks(); + public java.util.IteratoriteratorTasks(); + public int sizeTasks(); + public MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + public Object []toArrayTasks(); + public java.util.SpliteratorspliteratorTasks(); + public java.util.stream.StreamstreamTasks(); + public java.util.stream.StreamparallelStreamTasks(); + public boolean equalsTasks(Object o); + public int hashCodeTasks(); + public void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + public interface IMyOrganizerVisitor { + public abstract void visit(Asset node); + public abstract void visit(Task node); + public abstract void visit(Project node); + public abstract void visit(Day node); + + } + public class MyOrganizerVisitorImplementation implements IMyOrganizerVisitor { + protected CollectiontraversedElements; + public void visit(Asset node); + public void visit(Task node); + public void visit(Project node); + public void visit(Day node); + + } + + } + +} diff --git a/docs/myOrganizer/cds/MyOrganizerOnlyWithAbstractMethodSignatures.cd b/docs/myOrganizer/cds/MyOrganizerOnlyWithAbstractMethodSignatures.cd new file mode 100644 index 000000000..d67992861 --- /dev/null +++ b/docs/myOrganizer/cds/MyOrganizerOnlyWithAbstractMethodSignatures.cd @@ -0,0 +1,39 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.util.Date; +import java.util.Optional; +import java.util.*; +public classdiagram MyOrganizer { + package MyOrganizer { + public enum Status { + PROCESSING,DONE,OPEN; + + } + public abstract class Asset { + public abstract void process(); + + } + public abstract class Task extends Asset { + public Status taskStatus; + public abstract void process(); + public MyOrganizer.Project project; + + } + public abstract class Project extends Asset { + public String projectName; + private Optionaldeadline; + protected double budget; + public abstract void process(); + public Settasks; + + } + public class Day { + public Date date; + public Settasks; + + } + public association public [1]Day(day)->(tasks)Task[*]public; + public association public[*]Task(tasks)<->(project)Project [1]public; + + } + +} diff --git a/docs/myOrganizer/img/MyOrganizer.svg b/docs/myOrganizer/img/MyOrganizer.svg new file mode 100644 index 000000000..fb8526dcc --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizer.svg @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + + + «abstract» + Asset + + + + + void process(); + + + + + + + + + Task + + + Status taskStatus; + + + + + void process(); + + + + + + + + + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + + + void process(); + + + + + + + + + Day + + + Date date; + + + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerNoDecorators.svg b/docs/myOrganizer/img/MyOrganizerNoDecorators.svg new file mode 100644 index 000000000..827abdb85 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerNoDecorators.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + + Status taskStatus; + + + MyOrganizer.Project project; + + + + + + void process(); + + + + + + MyOrganizer + + + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + + + + MyOrganizer + + + Day + + + + Date date; + + + Set<MyOrganizer.Task>tasks; + + + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyBuilders.svg b/docs/myOrganizer/img/MyOrganizerOnlyBuilders.svg new file mode 100644 index 000000000..b14f07255 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyBuilders.svg @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + + Status taskStatus; + + + MyOrganizer.Project project; + + + + + + void process(); + + + + + + MyOrganizer + + + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + + + + MyOrganizer + + + Day + + + + Date date; + + + Set<MyOrganizer.Task>tasks; + + + + + + + + MyOrganizer + + «abstract» + AssetBuilder + + + # AssetBuilder realBuilder; + + + + + - boolean isValid(); + + + Asset build(); + + + Asset unsafeBuild(); + + + + + + MyOrganizer + + + TaskBuilder + + + # TaskBuilder realBuilder; + + # Status taskStatus; + + # MyOrganizer.Project project; + + + + + - boolean isValid(); + + + Task build(); + + + Task unsafeBuild(); + + + TaskBuilder setTaskStatus(Status taskStatus); + + + TaskBuilder setProject(MyOrganizer.Project project); + + + + + + MyOrganizer + + + ProjectBuilder + + + # ProjectBuilder realBuilder; + + # String projectName; + + # Optional<Date>deadline; + + # double budget; + + # Set<MyOrganizer.Task>tasks; + + + + + - boolean isValid(); + + + Project build(); + + + Project unsafeBuild(); + + + ProjectBuilder setProjectName(String projectName); + + + ProjectBuilder setDeadline(Date deadline); + + + ProjectBuilder setBudget(double budget); + + + ProjectBuilder setTasks(Set<MyOrganizer.Task>tasks); + + + ProjectBuilder setDeadlineAbsent(); + + + ProjectBuilder setTasksAbsent(); + + + + + + MyOrganizer + + + DayBuilder + + + # DayBuilder realBuilder; + + # Date date; + + # Set<MyOrganizer.Task>tasks; + + + + + - boolean isValid(); + + + Day build(); + + + Day unsafeBuild(); + + + DayBuilder setDate(Date date); + + + DayBuilder setTasks(Set<MyOrganizer.Task>tasks); + + + DayBuilder setTasksAbsent(); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyDefaultsForCardinalityAttrs.svg b/docs/myOrganizer/img/MyOrganizerOnlyDefaultsForCardinalityAttrs.svg new file mode 100644 index 000000000..827abdb85 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyDefaultsForCardinalityAttrs.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + + Status taskStatus; + + + MyOrganizer.Project project; + + + + + + void process(); + + + + + + MyOrganizer + + + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + + + + MyOrganizer + + + Day + + + + Date date; + + + Set<MyOrganizer.Task>tasks; + + + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyGetter.svg b/docs/myOrganizer/img/MyOrganizerOnlyGetter.svg new file mode 100644 index 000000000..559ce353e --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyGetter.svg @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + # Status taskStatus; + + # MyOrganizer.Project project; + + + + + + void process(); + + + Status getTaskStatus(); + + + MyOrganizer.Project getProject(); + + + + + + MyOrganizer + + + Project + + + # String projectName; + + # Optional<Date>deadline; + + # double budget; + + # Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + String getProjectName(); + + - Date getDeadline(); + + - boolean isPresentDeadline(); + + # double getBudget(); + + + Set<MyOrganizer.Task>getTasks(); + + + boolean containsTasks(Object element); + + + boolean containsAllTasks(java.util.Collection<?>collection); + + + boolean isEmptyTasks(); + + + java.util.Iterator<MyOrganizer.Task>iteratorTasks(); + + + int sizeTasks(); + + + MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + + + Object []toArrayTasks(); + + + java.util.Spliterator<MyOrganizer.Task>spliteratorTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>streamTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>parallelStreamTasks(); + + + boolean equalsTasks(Object o); + + + int hashCodeTasks(); + + + + + + MyOrganizer + + + Day + + + # Date date; + + # Set<MyOrganizer.Task>tasks; + + + + + + Date getDate(); + + + Set<MyOrganizer.Task>getTasks(); + + + boolean containsTasks(Object element); + + + boolean containsAllTasks(java.util.Collection<?>collection); + + + boolean isEmptyTasks(); + + + java.util.Iterator<MyOrganizer.Task>iteratorTasks(); + + + int sizeTasks(); + + + MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + + + Object []toArrayTasks(); + + + java.util.Spliterator<MyOrganizer.Task>spliteratorTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>streamTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>parallelStreamTasks(); + + + boolean equalsTasks(Object o); + + + int hashCodeTasks(); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyNavigableSetter.svg b/docs/myOrganizer/img/MyOrganizerOnlyNavigableSetter.svg new file mode 100644 index 000000000..8456b0927 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyNavigableSetter.svg @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + # Status taskStatus; + + # MyOrganizer.Project project; + + + + + + void process(); + + + void setTaskStatus(Status taskStatus); + + + void setProject(MyOrganizer.Project project); + + + void setProjectLocal(MyOrganizer.Project project); + + + + + + MyOrganizer + + + Project + + + # String projectName; + + # Optional<Date>deadline; + + # double budget; + + # Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + void setProjectName(String projectName); + + - void setDeadlineAbsent(); + + - void setDeadline(Date deadline); + + # void setBudget(double budget); + + + boolean addTasks(MyOrganizer.Task tasks); + + + boolean removeTasks(MyOrganizer.Task tasks); + + + boolean addTasksLocal(MyOrganizer.Task tasks); + + + boolean removeTasksLocal(MyOrganizer.Task tasks); + + + + + + MyOrganizer + + + Day + + + # Date date; + + # Set<MyOrganizer.Task>tasks; + + + + + + void setDate(Date date); + + + boolean addTasks(MyOrganizer.Task tasks); + + + boolean removeTasks(MyOrganizer.Task tasks); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyObservers.svg b/docs/myOrganizer/img/MyOrganizerOnlyObservers.svg new file mode 100644 index 000000000..a31d739e9 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyObservers.svg @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + # List<MyOrganizer.IAssetObserver>observerList; + + + + + + void process(); + + + void addObserver(MyOrganizer.IAssetObserver observer); + + + void removeObserver(MyOrganizer.IAssetObserver observer); + + # void notifyObservers(); + + + + + + MyOrganizer + + + Task + + + + Status taskStatus; + + + MyOrganizer.Project project; + + # List<MyOrganizer.ITaskObserver>observerList; + + + + + + void process(); + + + void addObserver(MyOrganizer.ITaskObserver observer); + + + void removeObserver(MyOrganizer.ITaskObserver observer); + + # void notifyObservers(); + + # void notifyObserversSetTaskStatus(Status ov); + + # void notifyObserversSetProject(MyOrganizer.Project ov); + + + + + + MyOrganizer + + + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + Set<MyOrganizer.Task>tasks; + + # List<MyOrganizer.IProjectObserver>observerList; + + + + + + void process(); + + + void addObserver(MyOrganizer.IProjectObserver observer); + + + void removeObserver(MyOrganizer.IProjectObserver observer); + + # void notifyObservers(); + + # void notifyObserversSetProjectName(String ov); + + # void notifyObserversSetDeadline(Optional<Date>ov); + + # void notifyObserversSetBudget(double ov); + + # void notifyObserversAddTasks(MyOrganizer.Task newElem); + + # void notifyObserversRemoveTasks(MyOrganizer.Task elem); + + + + + + MyOrganizer + + + Day + + + + Date date; + + + Set<MyOrganizer.Task>tasks; + + # List<MyOrganizer.IDayObserver>observerList; + + + + + + void addObserver(MyOrganizer.IDayObserver observer); + + + void removeObserver(MyOrganizer.IDayObserver observer); + + # void notifyObservers(); + + # void notifyObserversSetDate(Date ov); + + # void notifyObserversAddTasks(MyOrganizer.Task newElem); + + # void notifyObserversRemoveTasks(MyOrganizer.Task elem); + + + + + + MyOrganizer + + «interface» + IAssetObserver + + + + + + void notifyUpdate(Asset clazz); + + + + + + MyOrganizer + + «interface» + ITaskObserver + + + + + + void notifyUpdate(Task clazz); + + + void notifyUpdateSetTaskStatus(Task clazz,Status ov); + + + void notifyUpdateSetProject(Task clazz,MyOrganizer.Project ov); + + + + + + MyOrganizer + + «interface» + IProjectObserver + + + + + + void notifyUpdate(Project clazz); + + + void notifyUpdateSetProjectName(Project clazz,String ov); + + + void notifyUpdateSetDeadline(Project clazz,Optional<Date>ov); + + + void notifyUpdateSetBudget(Project clazz,double ov); + + + void notifyUpdateAddTasks(Project clazz,MyOrganizer.Task newElem); + + + void notifyUpdateRemoveTasks(Project clazz,MyOrganizer.Task elem); + + + + + + MyOrganizer + + «interface» + IDayObserver + + + + + + void notifyUpdate(Day clazz); + + + void notifyUpdateSetDate(Day clazz,Date ov); + + + void notifyUpdateAddTasks(Day clazz,MyOrganizer.Task newElem); + + + void notifyUpdateRemoveTasks(Day clazz,MyOrganizer.Task elem); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlySetter.svg b/docs/myOrganizer/img/MyOrganizerOnlySetter.svg new file mode 100644 index 000000000..8cadc8480 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlySetter.svg @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + + + + MyOrganizer + + + Task + + + # Status taskStatus; + + # MyOrganizer.Project project; + + + + + + void process(); + + + void setTaskStatus(Status taskStatus); + + + void setProject(MyOrganizer.Project project); + + + + + + MyOrganizer + + + Project + + + # String projectName; + + # Optional<Date>deadline; + + # double budget; + + # Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + void setProjectName(String projectName); + + - void setDeadlineAbsent(); + + - void setDeadline(Date deadline); + + # void setBudget(double budget); + + + boolean addTasks(MyOrganizer.Task tasks); + + + boolean removeTasks(MyOrganizer.Task tasks); + + + + + + MyOrganizer + + + Day + + + # Date date; + + # Set<MyOrganizer.Task>tasks; + + + + + + void setDate(Date date); + + + boolean addTasks(MyOrganizer.Task tasks); + + + boolean removeTasks(MyOrganizer.Task tasks); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyVisitors.svg b/docs/myOrganizer/img/MyOrganizerOnlyVisitors.svg new file mode 100644 index 000000000..e4b9628c9 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyVisitors.svg @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + void process(); + + + void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + + + + + MyOrganizer + + + Task + + + # Status taskStatus; + + # MyOrganizer.Project project; + + + + + + void process(); + + + Status getTaskStatus(); + + + MyOrganizer.Project getProject(); + + + void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + + + + + MyOrganizer + + + Project + + + # String projectName; + + # Optional<Date>deadline; + + # double budget; + + # Set<MyOrganizer.Task>tasks; + + + + + + void process(); + + + String getProjectName(); + + - Date getDeadline(); + + - boolean isPresentDeadline(); + + # double getBudget(); + + + Set<MyOrganizer.Task>getTasks(); + + + boolean containsTasks(Object element); + + + boolean containsAllTasks(java.util.Collection<?>collection); + + + boolean isEmptyTasks(); + + + java.util.Iterator<MyOrganizer.Task>iteratorTasks(); + + + int sizeTasks(); + + + MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + + + Object []toArrayTasks(); + + + java.util.Spliterator<MyOrganizer.Task>spliteratorTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>streamTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>parallelStreamTasks(); + + + boolean equalsTasks(Object o); + + + int hashCodeTasks(); + + + void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + + + + + MyOrganizer + + + Day + + + # Date date; + + # Set<MyOrganizer.Task>tasks; + + + + + + Date getDate(); + + + Set<MyOrganizer.Task>getTasks(); + + + boolean containsTasks(Object element); + + + boolean containsAllTasks(java.util.Collection<?>collection); + + + boolean isEmptyTasks(); + + + java.util.Iterator<MyOrganizer.Task>iteratorTasks(); + + + int sizeTasks(); + + + MyOrganizer.Task []toArrayTasks(MyOrganizer.Task []array); + + + Object []toArrayTasks(); + + + java.util.Spliterator<MyOrganizer.Task>spliteratorTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>streamTasks(); + + + java.util.stream.Stream<MyOrganizer.Task>parallelStreamTasks(); + + + boolean equalsTasks(Object o); + + + int hashCodeTasks(); + + + void accept(MyOrganizer.IMyOrganizerVisitor visitor); + + + + + + MyOrganizer + + «interface» + IMyOrganizerVisitor + + + + + + abstract void visit(Asset node); + + + abstract void visit(Task node); + + + abstract void visit(Project node); + + + abstract void visit(Day node); + + + + + + MyOrganizer + + + MyOrganizerVisitorImplementation + + + # Collection<Object>traversedElements; + + + + + + void visit(Asset node); + + + void visit(Task node); + + + void visit(Project node); + + + void visit(Day node); + + + + + + CD + + + diff --git a/docs/myOrganizer/img/MyOrganizerOnlyWithAbstractMethodSignatures.svg b/docs/myOrganizer/img/MyOrganizerOnlyWithAbstractMethodSignatures.svg new file mode 100644 index 000000000..884f00356 --- /dev/null +++ b/docs/myOrganizer/img/MyOrganizerOnlyWithAbstractMethodSignatures.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + day + 1 + + + + + + + + day + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + + + + + + + tasks + * + + + + + + + + tasks + + + + + + + project + 1 + + + + + + + + project + + + + + + MyOrganizer + + «enum» + Status + + + PROCESSING + DONE + OPEN + + + + + + + + + MyOrganizer + + «abstract» + Asset + + + + + + abstract void process(); + + + + + + MyOrganizer + + «abstract» + Task + + + + Status taskStatus; + + + MyOrganizer.Project project; + + + + + + abstract void process(); + + + + + + MyOrganizer + + «abstract» + Project + + + + String projectName; + + - Optional<Date>deadline; + + # double budget; + + + Set<MyOrganizer.Task>tasks; + + + + + + abstract void process(); + + + + + + MyOrganizer + + + Day + + + + Date date; + + + Set<MyOrganizer.Task>tasks; + + + + + + + + CD + + + diff --git a/docs/overrides/extensions/docsnippet.py b/docs/overrides/extensions/docsnippet.py new file mode 100644 index 000000000..84b74b3ac --- /dev/null +++ b/docs/overrides/extensions/docsnippet.py @@ -0,0 +1,294 @@ +from pymdownx import snippets + +""" +Snippet ---8<---. + +pymdownx.snippet +Inject snippets + +------ +Extended pymdownx.snippet: We add some additional replacements: +# - .java relative link -> GitHub source + JavaDoc link (if found) +# - .mc4 relative link -> GitHub source +Original source is located: +https://github.com/facelessuser/pymdown-extensions/blob/f64422f87c05031a8c8d62b1988bf76e8f65f27f/pymdownx/snippets.py +------- + +MIT license. + +Copyright (c) 2017 Isaac Muse + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +class SnippetExtension(snippets.SnippetExtension): + + def extendMarkdown(self, md): + """Register the extension.""" + self.md = md + md.registerExtension(self) + config = self.getConfigs() + snippet = MCSnippetPreprocessor(config, md) + md.preprocessors.register(snippet, "snippet", 32) + +# add this snippet extension here +def on_config(config, **kwargs): + config.markdown_extensions.append(SnippetExtension()) + + +# We have to copy the parse_snippets method to add our own hook point +class MCSnippetPreprocessor(snippets.SnippetPreprocessor): + + # A processing step to modify links + def process_snippet(self, s_lines, snippet): + import re + from pathlib import Path + base_path = Path(snippet).parent # directory in which the snippet resides + cwd = Path().resolve() + + # Pattern to match anchors to relative files [anchor]: url + file_pattern = r'(?!https?:\/\/)([^)]+\.(java|mc4))' + pattern_anchor = re.compile(r'\[([a-zA-Z0-9_]+)\]: ' + file_pattern) + # collect all used link-targets + relative_anchors = {m[0]: m for line in s_lines for m in pattern_anchor.findall(line)} + + # Pattern to match relative markdown links: [text](url) + pattern_rel_link = re.compile(r'\[([^\]]+)\]\(' + file_pattern + r'(#\S+)?\)') + # Pattern to match relative markdown links: [text][anchor] + pattern_anchor_link = re.compile(r'\[([^\]]+)\]\[([a-zA-Z0-9_]+)\]') + + # Replace functions + def anchor_replacer(match): # [text][anchor] + text = match.group(1) # the text within the link + a_name = match.group(2) # the name of the anchor + if a_name not in relative_anchors: + return match.group(0) # keep original + anchor = relative_anchors[a_name] + return replace_link(text, anchor[1], anchor[2]) + + def link_replacer(match): # [text](url) links + text = match.group(1) # the text within the link + url = match.group(2) # the url including everything + file_ext = match.group(3) # the url including everything + anchor = match.group(4) or "" # an optional #anchor + return replace_link(text, url, file_ext, anchor) + + def replace_link(text, url, file_ext, anchor=""): + # which file does the url point to? + resolved_path = (base_path / url).resolve().relative_to(cwd) + # step 1: construct a GitHub link + github_link = f"https://github.com/MontiCore/cd4analysis/blob/dev/{resolved_path} \"View file on GitHub\"" + github_icon = f"[:material-github:{{ .nonhighlight}}]({github_link})" + # step 2: construct a JavaDocs link + parts = resolved_path.parts + project = parts[0] # the project, i.e. cdlang, cd2plantuml, cd2smt, cddiff, cdmerge, cdtool, language-server, symtabdefinitiontoll, cd-runtime + source_set = parts[2] # main or testFixtures + # we have links to non main/testFixtures java files -> no javadoc link + link = github_link + if source_set in ['main', 'testFixtures'] and project in ['cdlang', + 'cd2plantuml', + 'cd2smt', + 'cddiff', + 'cdmerge', + 'cdtool', + 'language-server', + 'symtabdefinitiontool', + 'cd-runtime'] and file_ext == 'java': + # find the correct file location + javadoc_task = 'javadoc' if source_set == 'main' else 'testFixturesJavadoc' + back_to_root = '../' * (len(base_path.relative_to(cwd).parts)) + file = '/'.join(parts[4:])[:-len(".java")] + javadoc_link = f"{back_to_root}{project}/{javadoc_task}/{file}.html{anchor} \"View JavaDoc\"" + javadoc_icon = f"[:material-file-document:{{ .nonhighlight }}]({javadoc_link})" + link = javadoc_link + else: + javadoc_icon = '' + return f"[{text}]({link}) {github_icon} {javadoc_icon}" + + # Apply replacement + return [pattern_anchor_link.sub(anchor_replacer, pattern_rel_link.sub(link_replacer, md_text)) for md_text in + s_lines] + + + # the following code is copied 1-to-1 (just with the addition of one hook) + """Handle snippets in Markdown content. - a copy of """ + def parse_snippets(self, lines, file_name=None, is_url=False, is_section=False): + """Parse snippets snippet.""" + + if file_name: + # Track this file. + self.seen.add(file_name) + + new_lines = [] + inline = False + block = False + for line in lines: + # Check for snippets on line + inline = False + m = self.RE_ALL_SNIPPETS.match(line) + if m: + if m.group('escape'): + # The snippet has been escaped, replace first `;` and continue. + new_lines.append(line.replace(';', '', 1)) + continue + + if block and m.group('inline_marker'): + # Don't use inline notation directly under a block. + # It's okay if inline is used again in sub file though. + continue + + elif m.group('inline_marker'): + # Inline + inline = True + + else: + # Block + block = not block + continue + + elif not block: + if not is_section: + # Check for section line, if present remove, if escaped, reformat it + m2 = self.RE_SNIPPET_SECTION.match(line) + if m2 and m2.group('escape'): + line = ( + m2.group('pre') + m2.group('escape').replace(';', '', 1) + m2.group('inline_marker') + + m2.group('section') + m2.group('post') + ) + m2 = None + + # Found a section that must be removed + if m2 is not None: + continue + + # Not in snippet, and we didn't find an inline, + # so just a normal line + new_lines.append(line) + continue + + if block and not inline: + # We are in a block and we didn't just find a nested inline + # So check if a block path + m = self.RE_SNIPPET.match(line) + + if m: + # Get spaces and snippet path. Remove quotes if inline. + space = m.group('space').expandtabs(self.tab_length) + path = m.group('snippet')[1:-1].strip() if inline else m.group('snippet').strip() + + if not inline: + # Block path handling + if not path: + # Empty path line, insert a blank line + new_lines.append('') + continue + + # Ignore commented out lines + if path.startswith(';'): + continue + + # Get line numbers (if specified) + end = [] + start = [] + section = None + m = self.RE_SNIPPET_FILE.match(path) + path = '' if m is None else m.group(1).strip() + # Looks like we have an empty file and only lines specified + if not path: + if self.check_paths: + raise snippets.SnippetMissingError(f"Snippet at path '{path}' could not be found") + else: + continue + if m.group(2): + for nums in m.group(2)[1:].split(','): + span = nums.split(':') + st = int(span[0]) if span[0] else None + start.append(st if st is None or st < 0 else max(0, st - 1)) + en = int(span[1]) if len(span) > 1 and span[1] else None + end.append(en) + elif m.group(3): + section = m.group(3)[1:] + + # Ignore path links if we are in external, downloaded content + is_link = path.lower().startswith(('https://', 'http://')) + if is_url and not is_link: + continue + + # If this is a link, and we are allowing URLs, set `url` to true. + # Make sure we don't process `path` as a local file reference. + url = self.url_download and is_link + snippet = self.get_snippet_path(path) if not url else path + + if snippet: + + # This is in the stack and we don't want an infinite loop! + if snippet in self.seen: + continue + + if not url: + # Read file content + with open(snippet, 'r', encoding=self.encoding) as f: + last = False + s_lines = [] + for l in f: + last = l.endswith(('\r', '\n')) + s_lines.append(l.strip('\r\n')) + if last: + s_lines.append('') + else: + # Read URL content + try: + s_lines = self.download(snippet) + except snippets.SnippetMissingError: + if self.check_paths: + raise + s_lines = [] + + if s_lines: + total = len(s_lines) + if start and end: + final_lines = [] + for sel in zip(start, end): + s_start = snippets.util.clamp(total + sel[0], 0, total) if sel[0] and sel[0] < 0 else sel[0] + s_end = snippets.util.clamp(total + 1 + sel[1], 0, total) if sel[1] and sel[1] < 0 else sel[1] + final_lines.extend(s_lines[slice(s_start, s_end, None)]) + s_lines = self.dedent(final_lines) if self.dedent_subsections else final_lines + elif section: + s_lines = self.extract_section(section, s_lines) + + # BEGIN MODIFICATION: Call hook point + if s_lines: + s_lines = self.process_snippet(s_lines, snippet) + # END MODIFICATION + + # Process lines looking for more snippets + new_lines.extend( + [ + space + l2 for l2 in self.parse_snippets( + s_lines, + snippet, + is_url=url, + is_section=section is not None + ) + ] + ) + + elif self.check_paths: + raise snippets.SnippetMissingError(f"Snippet at path '{path}' could not be found") + + # Pop the current file name out of the cache + if file_name: + self.seen.remove(file_name) + + return new_lines diff --git a/doc/owdiff_DT3_DT2_module.als b/docs/owdiff_DT3_DT2_module.als similarity index 100% rename from doc/owdiff_DT3_DT2_module.als rename to docs/owdiff_DT3_DT2_module.als diff --git a/docs/scripts/preprocessing.sh b/docs/scripts/preprocessing.sh new file mode 100644 index 000000000..924ff2a3b --- /dev/null +++ b/docs/scripts/preprocessing.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# (c) https://github.com/MontiCore/monticore +# script for all preprocessing steps of the pages job +# is used to have uniform bases for both gitlab and github pages +# +# remove all occurrences of '[[_TOC_]]' in markdown files +# because mkdocs already renders its own toc +case " $* " in + *" inplace "*) + for file in $(find ./docs/docs -type f -name "*.md") + do + sed -i 's/\[\[_TOC_\]\]//' $file + perl -pi -e 's/\[([^\[\]\(\)]*)\]\([^\[\]\(\)]*git.rwth-aachen.de[^\[\]\(\)]*?\)/$1/g' $file + done + echo "[INFO] Removed all occurrences of '[[_TOC_]]' in *.md files" + echo "[INFO] Removed all links to https://git.rwth-aachen.de in *.md files" + ;; +esac +# move all directories that contain *.md files to the docs folder +# because mkdocs can only find *.md files there +rm -r docs_wd || true + +case " $* " in + *" symlink "*) + # use symlinks to track updates + mkdir docs_wd + ln -s ../docs/overrides docs_wd/ + ln -s ../docs/stylesheets docs_wd/ + ln -s ../docs/scripts docs_wd/ + ln -s ../docs/img docs_wd/ + echo "[INFO] Using symlinks for live editing" + ;; + *) + cp -r docs docs_wd + rm docs_wd/*.md + cp README.md docs_wd/README.md + # all images referenced in the root-Readme must be handled specially :( + # mkdir -p docs_wd/docs/img + # cp docs/img/MC_Symp_Banner.png docs_wd/docs/img/MC_Symp_Banner.png + # echo "[INFO] Copied site design" + # Copy the javadoc directories for cd2plantuml, cd2smt, cd-runtime, cddiff, cdlang, cdmerge, cdtool, language-server, symtabdefinitiontool + mkdir -p docs_wd/cd2plantuml + cp -r cd2plantuml/target/docs/javadoc docs_wd/cd2plantuml/javadoc + cp -r cd2plantuml/target/docs/testFixturesJavadoc docs_wd/cd2plantuml/testFixturesJavadoc + mkdir -p docs_wd/cd2smt + cp -r cd2smt/target/docs/javadoc docs_wd/cd2smt/javadoc + cp -r cd2smt/target/docs/testFixturesJavadoc docs_wd/cd2smt/testFixturesJavadoc + mkdir -p docs_wd/cd-runtime + cp -r cd-runtime/target/docs/javadoc docs_wd/cd-runtime/javadoc + cp -r cd-runtime/target/docs/testFixturesJavadoc docs_wd/cd-runtime/testFixturesJavadoc + mkdir -p docs_wd/cddiff + cp -r cddiff/target/docs/javadoc docs_wd/cddiff/javadoc + cp -r cddiff/target/docs/testFixturesJavadoc docs_wd/cddiff/testFixturesJavadoc + mkdir -p docs_wd/cdlang + cp -r cdlang/target/docs/javadoc docs_wd/cdlang/javadoc + cp -r cdlang/target/docs/testFixturesJavadoc docs_wd/cdlang/testFixturesJavadoc + mkdir -p docs_wd/cdmerge + cp -r cdmerge/target/docs/javadoc docs_wd/cdmerge/javadoc + cp -r cdmerge/target/docs/testFixturesJavadoc docs_wd/cdmerge/testFixturesJavadoc + mkdir -p docs_wd/cdtool + cp -r cdtool/target/docs/javadoc docs_wd/cdtool/javadoc + cp -r cdtool/target/docs/testFixturesJavadoc docs_wd/cdtool/testFixturesJavadoc + mkdir -p docs_wd/language-server + cp -r language-server/target/docs/javadoc docs_wd/language-server/javadoc + cp -r language-server/target/docs/testFixturesJavadoc docs_wd/language-server/testFixturesJavadoc + mkdir -p docs_wd/symtabdefinitiontool + cp -r symtabdefinitiontool/target/docs/javadoc docs_wd/symtabdefinitiontool/javadoc + cp -r symtabdefinitiontool/target/docs/testFixturesJavadoc docs_wd/symtabdefinitiontool/testFixturesJavadoc + echo "[INFO] Copied JavaDocs" + ;; +esac + + +for SOURCE_DIR in "docs" "cd2plantuml/src" "cd2smt/src" "cd-runtime/src" "cddiff/src" "cdlang/src" "cdmerge/src" "cdtool/src" "language-server/src" "symtabdefinitiontool/src"; do + # We link to java & mc4 files in our md files - which is why we have to redirect them too + find "$SOURCE_DIR" -type f \( -name "*.md" \) | while read -r filepath; do + target_file="docs_wd/$filepath" + mkdir -p "$(dirname "$target_file")" + # use snippets to include the original files content + if [ ! -f "$target_file" ]; then + echo "--8<-- \"$filepath\"" > "$target_file" + fi + done +done +echo "[INFO] Created snippet files" + +# the landing page snippet has to be removed again +# rm docs_wd/docs/README.md diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 000000000..f3796f0b9 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,25 @@ +/* (c) https://github.com/MontiCore/monticore */ +.md-header { + background-color: #006BA5; +} +:root { + --md-primary-fg-color: #006BA5; +} +.tip { + border: 2px solid grey; + border-radius: 5px; + padding: 10px; + margin-bottom: 5px; +} +.tip-header { + font-size: larger; + border-bottom: 2px solid grey; +} +.bibliography { + empty-cells: hide; +} + +/* Reduce icon intensity*/ +.nonhighlight { + color: color-mix(in srgb, currentColor 50%, transparent); +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..5a441638e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,78 @@ +site_name: CD4Analysis +theme: + name: 'material' + favicon: 'img/favicon.ico' + custom_dir: docs/overrides + hide: + - navigation + features: + - navigation.tabs + - toc.integrate + - toc.follow + - content.tabs.link + - content.code.copy + - navigation.instant # search index survives navigation + icon: + logo: 'fontawesome/solid/desktop' + +site_url: https://monticore.github.io/cd4analysis/ +repo_url: https://github.com/MontiCore/cd4analysis/ +edit_uri: "" + +extra_css: + - 'stylesheets/extra.css' + +copyright: '(c) https://github.com/MontiCore/monticore' + +markdown_extensions: + - admonition + - attr_list + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.details + - pymdownx.superfences + # pymdownx.snippets is added by the docsnippet hook! + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +hooks: + - docs/overrides/extensions/docsnippet.py + +plugins: + - search + +nav: + - Home: 'README.md' + - 'Getting Started': 'docs/GettingStarted.md' +# - Core Grammars: +# - 'Overview': 'monticore-grammar/src/main/grammars/de/monticore/Grammars.md' +# - 'Expressions': 'monticore-grammar/src/main/grammars/de/monticore/expressions/Expressions.md' +# - 'Literals': 'monticore-grammar/src/main/grammars/de/monticore/literals/Literals.md' +# - 'Types': 'monticore-grammar/src/main/grammars/de/monticore/types/Types.md' +# - Languages: +# - 'Languages and Language Components': 'docs/DevelopedLanguages.md' +# - 'List of Languages': 'docs/Languages.md' +# - 'Best Practices': 'docs/BestPractices.md' +# - Changelog: '00.org/Explanations/CHANGELOG.md' +# - Downloads: 'docs/Download.md' +# - Publications: 'docs/Publications.md' +# - License: '00.org/Licenses/LICENSE-MONTICORE-3-LEVEL.md' + +# run `docs/scripts/preprocessing.sh symlink` to create this directory locally +docs_dir: docs_wd + +watch: + - docs + - cd2plantuml/src + - cd2smt/src + - cd-runtime/src + - cddiff/src + - cdlang/src + - cdmerge/src + - cdtool/src + - language-server/src + - symtabdefinitiontool/src + - README.md \ No newline at end of file