diff --git a/README.md b/README.md index 3bc9d860..9febf434 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,20 @@ [![javadoc](https://javadoc.io/badge2/com.helger/jcodemodel/javadoc.svg)](https://javadoc.io/doc/com.helger/jcodemodel) -A fork of the com.sun.codemodel 2.7-SNAPSHOT. +This project provides modeling and exporting java source code at java runtime, either in a pre-processing phase or to access them using a dynamic Classloader. + +It is a fork of the com.sun.codemodel 2.7-SNAPSHOT. + The classes in this project use a different package name `com.helger.jcodemodel` to avoid conflicts with other `com.sun.codemodel` instances that might be floating around in the classpath. That of course implies, that this artefact cannot directly be used with JAXB, since the configuration of this would be very tricky. -A site with the links to the [API docs](http://phax.github.io/jcodemodel/) etc. is available. +## Links and doc + + - A site with the links to the [API docs](http://phax.github.io/jcodemodel/) etc. is available. + - This project has a dedicated [documentation folder](./docs) maintained by helpers. Don't hesitate [to help](./docs/contributing.md) ! + ## Maven usage @@ -200,28 +207,6 @@ v2.6.4 - 2014-04-10 2013-09-23 * Changes from https://github.com/UnquietCode/JCodeModel have been incorporated. -## Contribution - -Pull requests must follow my personal [Coding Styleguide](https://github.com/phax/meta/blob/master/CodingStyleguide.md) - -### Tabs vs spaces - -This project uses double-space for indentation. If you want to use tabs, you can ask git to modify the files when commiting them and when pulling them. A [specific script](sh/tabspaces) makes that chnage, run it from the root project. - - -What this script does : - - - create the file .git/info/attributes with `*.java filter=tabspace` . This will tell git to apply the script tabspace on the *.java files - - run `git config filter.tabspace.clean 'expand --tabs=2 --initial'` to ask git to replace tabs with two spaces on commit of *.java files. - - run `git config filter.tabspace.smudge 'unexpand --tabs=2 --first-only'` to request git to replace double spaces with two tabs on checking a *.java file out. - - -### Eclipse - -For eclipse, a formatter xml and a cleanup xml are present in the meta/formatter/eclipse/ directory. You can load them from the "project properties > java code style" settings. Check "Enable project specific settings", then load them. - -NOTE : you also need to change the save actions to make them meet the clean up actions. Save actions are done even when they are not present in the clean up. - --- diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..b01d060e --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,80 @@ +## Contributing + +The target audience is people who use JCM already + +If you use JCM, you may come across bugs, have ideas to implement, request features that could help you, or help implement or resolve issues. + +The ideas and requests should go to the [git's discussion](https://github.com/phax/jcodemodel/discussions) . They can then be converted to issues. + +Any bug should go in the [git's issues](https://github.com/phax/jcodemodel/issues) . They are then open for solving. Please provide a minimal working example, so that we can reproduce and check that the verification passes ; also mention what you are working on to avoid multiple person doing the same work. + +To help resolve an issue, you can [clone](https://github.com/phax/jcodemodel/fork) the repository, create a new branch, commit your changes, then submit a pull request, explaining what are the choices you made for that issue. + +### Formatting + +Pull requests must follow my personal [Coding Styleguide](https://github.com/phax/meta/blob/master/CodingStyleguide.md) + +#### Tabs vs spaces + +This project uses double-space for indentation. If you want to use tabs, you can ask git to modify the files when commiting them. A [specific script](../sh/cfg/tabspaces) makes that change, run it from the root project. + + +What this script does : + + - create the file .git/info/attributes with `*.java filter=tabspace` . This will tell git to apply the filter `tabspace` on the *.java files ; if the file already exists it is deleted. + - append the line `*.xml filter=tabspace` to this same file. This will tell git to apply the filter `tabspace` on the *.xml files. This is because `pom.xml` files are also supposed to be space-started. + - run `git config filter.tabspace.clean 'expand --tabs=2 --initial'` : Before committing (*clean*) files applied the *filter*`tabspace`, *git* must run the [expand](https://man7.org/linux/man-pages/man1/expand.1.html) command, replacing tabs with *2* spaces at the beginning of each line (*--initial*). This setting is repository-specific and can be removed by deleting the corresponding line in `.git/config` + +#### Eclipse + +For eclipse, a formatter xml and a cleanup xml are present in the meta/formatter/eclipse/ directory. You can load them from the "project properties > java code style" settings. Check "Enable project specific settings", then load them. + +NOTE : you also need to change the save actions to make them meet the clean up actions. Save actions are done even when they are not present in the clean up. + + +### Testing + +New features, as well as bug fixes, are expected to test the minimum amount of use cases to ensure their main usage remains correct. +For example, if you find a bug in a specific case, then that specific case must be tested against in the PR fixing it. +This is important both for validating your PR, but also to ensure new modifications won't break the existing features + +Disabling an existing test must be explained (typically no more relevant), at least in the commit or in the PR. + +There are three main ways to add tests : + - checking your own class behavior and the generated **file** content, + - checking the generated **class** content and behavior, + - checking your plugin generator behavior. + +#### In the main module + +You can add usual unit tests in the [main module's test dir](../jcodemodel/src/test/java). + +Those are useful to check the behavior of specific parts of the projects, as well as the expected file content for a constructed JCM , typically using [test utils](../jcodemodel/src/test/java/com/helger/jcodemodel/util/CodeModelTestsHelper.java) + +Implementation of parsers, validations, a well as code generation that does not persist at runtime (like javadocs, formatting, etc.) are expected to use this method. + +Note that the helper class allows to compile a JCM in memory, however using the generated class can be cumbersome since you need to use reflect, unless you can cast it to a known interface. +The next method allows easier manipulation, plus it permits to visually check the generated class files since they are exported and put in git. Therefore any later change in generated files can be tracked down to its commit. + +#### In the jcodemodeltests module + +[This module](../jcodemodeltests) uses a specific architecture : + +1. generating classes should be annotated with `@TestJCM` and contain public methods that have a JCM and/or a JPackage parameter(s), or produce their own JCM. The convention is to end such a class with `TestGen` and place them in their own feature package. The method can be static; if not, a new instance is generated for each generating method. +2. those classes are parsed during the generate-test phase and the resulting (or requested) JCM is then exported in the `src/generated/javatest` dir. You can run the [GenerateTestFiles](../jcodemodeltests/src/main/java/com/helger/jcodemodel/compile/annotation/GenerateTestFiles.java) in your IDE to generate them manually. +3. You can then add test classes in the usual `src/test/java` dir, that rely on those generated classes to check their behaviour and content. The convention is to place the test clas in the same package and with same start as the generating one, ending with `Test`. + +General convention is as such, for feature Feat : generat**ing** is `jcodemodel/tests/feat/FeatTestGen.java` ; generat**ed** should be named eg `jcodemodel/tests/feat/FeatExample1.java` ; **testing** class should be `jcodemodel/tests/feat/FeatTest.java` + +#### Testing your plugin's generator + +A generator generates a JCM that the plugin will export when requested. + + - The generator module should be in the [plugin's generators](../plugin/generators/pom.xml) submodule, with a module name starting with `GEN ` (in its pom) ; + - The testing module should be in the [plugins examples](../examples/plugins/pom.xml) submodule, with a module name starting with `XPL Generator ` . + +For example, see the [HelloWorld generator](../plugin/generators/helloworld/pom.xml) and its [HelloWorld example](../examples/plugins/helloworld/pom.xml) modules. +The former is *named* `GEN Helloworld`, the later `XPL Generator Helloworld`. + +The testing module should not rely on internet data, as this can be an issue when remote host is down. +With [correct configuration](../examples/plugins/helloworld/pom.xml) the plugin will apply the generator and produce the classes in `src/generated/java` , allowing the usual unit tests in that module. \ No newline at end of file diff --git a/docs/extending.md b/docs/extending.md new file mode 100644 index 00000000..0f024231 --- /dev/null +++ b/docs/extending.md @@ -0,0 +1,60 @@ +## Extending JCM with plugin generator + +### JCodeModel maven plugin + +The plugin is part of the project. However it does not generate data by itself, it needs to be specified a generator. For example, the [helloworld plugin example](../examples/plugins/helloworld/pom.xml) extensively configures that plugin in various ways. + +The [plugin](../plugin/plugin/src/main/java/com/helger/jcodemodel/plugin/maven/GenerateSourceMojo.java) does much of the leg-work to load a configured resource, load the configured generator - or default one if none is specified but present as dependency of the plugin - then call that generator and export the produced JCodeModel into a target directory. + +It contains several generic settings that can be used when generating the data. For example, the root package of all classes, the target directory, the source to load. +Any more genrator-specific configuration can be passed as the `params` plugin configuration. For example, in the HelloWorld example, + +```xml + + com.helger.jcodemodel.plugin.generators.helloworld.HelloWorldGenerator2 + src/generated/java2 + + Hello2 + world2 + + +``` + +forces a specific generator (instead of the default one provided by the HelloWorld Generator) ; then sets the output to be specific one (instead of default `src/generated/java`) ; then transmits the map `name=Hello2, value=world2` to the generator when the plugin is called. + +### Creating a generator + +To create a new Generator, you must +1. create a new project in the the `plugin/generators` directory +2. add it to the [generators pom](../plugin/generators/pom.xml) modules list +3. create a class that implements `ICodeModelBuilder` and is annotated with `@JCMGen` +4. implement your generator logic in the `void build (JCodeModel model, @Nullable InputStream source) throws JCodeModelException` method. +5. if needed, specify how it's configured by overriding the `void configure (@NonNull final Map params)` method + +The [annotation processor](../plugin/plugin/src/main/java/com/helger/jcodemodel/plugin/maven/generators/JCMGenProcessor.java) will automatically generate the resources required by the plugin on the maven install phase. + +You still need to test your generator, to be sure nothing changes too much + +### Testing a generator + +Once the generator module is done, you create a new project in the `examples/plugins` module, add it to the [plugin examples](../examples/plugins/pom.xml) modules list, and add your invocation of the plugin, with the generator as its dependency. + +This would be something like + +```xml + + + + + com.helger.jcodemodel + jcodemodel-maven-plugin + + + com.helger.jcodemodel.plugin.generators + mygenerator + ${project.version} + + +``` + +several executions can be configured, with various generators and/or configurations. \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000..5d167423 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,34 @@ +## Project overview + +### Maven architecture + +The project is split in + + - The [root pom project](../pom.xml) + - The [core project](../jcodemodel/pom.xml) lib to generate classes and resources programmatically. + - The [test project](../jcodemodeltests/pom.xml) validates the *generated* classes behavior. + - The [plugin](../plugin/plugin/pom.xml) to generate java classes in maven + - The various [plugin generators](../plugin/generators/pom.xml) to load a JCodeModel in the plugin + - The [examples](../examples/pom.xml) that showcase how to use JCodeModel, the plugin, the generators. + +### Core classes + +#### JCodeModel + +This is the main class. It contains the definition of the classes and resources to generate. + +Its main usage is to add classes using the various `_class` methods. Those classes can then be added methods, fields, or other classes. + +#### JCMWriter + +This allows to export a `JCodeModel`. Typically to a directory. + +### Scripts + +The scripts present in the [sh directory](../sh) are run using a linux shell. For windows, you rather install git for windows which comes with a `bash` shell. + + - [cleaninstall](../sh/cleaninstall) runs the maven up-to install phases with parrallel execution and no output of the transfer to reduce logs, with a call to the clean phase to delete all intermediate products. It's only useful when you need to ensure other branches don't interfere with the result, like before a release. + - [install](../sh/install) does it without a clean. That's the main call to typically test changes. + - [mergeupstream](../sh/mergeupstream) adds an `upstream` repository to you local git, if needed, then checks its `master` commits out in the current branch. This allows to have your local branch up-to-date, for example before submitting a PR. + - [upgrades](../sh/upgrades) lists the possible dependencies/plugins upgrades. This is purely informative. + - [voidtest](../sh/voidtest) compiles and run the project, up to the integration-test phase, on a fresh (empty, temporary) maven repository. This will force re-downloading of **all** the libraries and plugins. This is only used when we suspect compiled libraries are generating issues. \ No newline at end of file diff --git a/docs/starting.md b/docs/starting.md new file mode 100644 index 00000000..9cc94c34 --- /dev/null +++ b/docs/starting.md @@ -0,0 +1,172 @@ +## Starting with JCodeModel (JCM) + +The target audience is developers who intend to use JCM as a part of their production. + +JCM at its core generates java source code, with the ability to do so at runtime and load the generated code, by a running java program. + +It can be used to generate data structures used to parse files or resources, create database entities, load runtime information and validate against meta data. + +### Requirements + +Starting requirements are : a Java Development Kit (JDK) at least version 17 ; maven v3 minimum installed. We strongly recommend an IDE ; and git is always a good idea to manage your code base. + +Note that Git for windows also embeds a Bash which is required for running the linux scripts on windows. However, if you don't intend to either use the scripts nor manage your codebase then this is not mandatory. For example, quick testing does not require git. + +#### Windows + +Several links to help you get them : + + - [Java](https://www.java.com/download/manual.jsp) + - [Maven](https://maven.apache.org/download.cgi) + - [NetBeans IDE](https://netbeans.apache.org/front/main/download/) + - [Git](https://git-scm.com/install/windows) + +#### Linux + +Those are pretty standard. For example Ubuntu can install them with `sudo apt install default-jdk maven netbeans git-all` + +### Maven project setup + +Once you have a java project loaded in your IDE, you need to add a dependency to JCM. This is done typically with the following lines in your root pom.xml : + +```xml + + + + com.helger + jcodemodel + ${jcodemodel.version} + +``` + +You then need to replace  `${jcodemodel.version}` by the version you intend to use ; +OR set it as a property (better) typically with + +```xml + + + x.y.z +``` + +OR even (recommended) you remove the version line completely and instead import the JCM pom with + + +```xml + + + + + com.helger + jcodemodel-parent-pom + x.y.z + import + pom + + + + + com.helger + jcodemodel + +``` + +### First program + +Let's first make a simple program that generates a new, empty class. With [java 25](https://openjdk.org/jeps/512) you can create a file `JCMFirstProgram.java` containing only + +```java +void main() throws JCodeModelException, IOException { + var jcm = new JCodeModel(); + jcm._class("JCMFirstClass"); + File outFile = new File("src/generated/java/"); + outFile.mkdirs(); + new JCMWriter(jcm).build(outFile); +} +``` + +Then ask your IDE to resolve the names for you. This should add imports at the top of the file. + +If you are using java <25 then you need to use a full class. We made [one already](../jcodemodel/src/test/java/JCMFirstProgram.java) that contains those lines. + +Let's explain the instructions + + - `var jcm = new JCodeModel();` We need to create a model to represent the classes we want to export. JCodeModel is the class for that model. + - `jcm._class("JCMFirstClass");` We add a new class in that model. This is a public class by default. + - `File outFile = new File("src/generated/java/");` we need a directory to export the model. We Choose `src/generated/java` as source files and generated files should be in distinct directories. + - `outFile.mkdirs();` create the directory if it does not exist. + - `new JCMWriter(jcm)` we need a writer to write down the model we just created. This allows us to provide exporting options. + - `.build(outFile);` write the model we created to the specified directory. + +### Generating code for a library + +Now that you have a main class that produces code, you may want to embed that code in your program. However, the maven building is made in specific steps, and you can't easily compile the same source class and execute it. Instead, you need to have two maven modules, with the first one generating classes, and the second one, **core**, having the first one as dependency and calling its main class. + +It's actually possible to have maven do two passes of compiling but this is a bad habit as it blurs the visibility of what is generated, embedded, and can lead to issues with non-deterministic approaches. See [stackoverflow](https://stackoverflow.com/questions/21342342/run-maven-compilation-twice) + +#### Reorganising the maven project + +1. You need to change your **root** module to a `pom` *packaging* (in your root `pom.xml`). This means, your root pom becomes an aggregation of other modules. This is the case, for example, for our [root module](../pom.xml) . Note that the root module can't anymore export java code by itself. Regardless, this is a good habit for maven projects, with your root module defining all the dependencies, properties, plugins, etc. . +2. You need one **generator** sub module in that root pom to have the class generation. This module will have the java class that we made previously. +3. Your **core** module will have the generating module as a dependency, with scope `optional` ; and a configuration of the `exec-maven-plugin` to call your java main class +4. You also need to tell maven that the `src/generated/java` dir of the **core** module is a source directory + +Your **core** pom.xml should now contain : + + +```xml + + + + my.project + CodeGenerator + ${project.version} + true + +… + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-classes + generate-sources + + java + + + + + JCMFirstProgram + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + src/generated/java + + + + + +``` + +Now, since your **generator** module is not configured to use its `src/generated/java` dir, you can run your main class in your IDE to check the result - but the generated class won't be visible in your **core**, until maven is used to rebuild the project. + +### Using the JCM plugin with an existing generator + +You can use the JCM plugin, by specifying its generator as dependency and the generator configuration. A generator is basically a parser of a resource into a JCM ; the JCM plugin [when called](../plugin/plugin/src/main/java/com/helger/jcodemodel/plugin/maven/GenerateSourceMojo.java#L105) selects the generator, configures it, generates the JCM, and exports it. + +For example, the CSV generator is used [in our examples](../examples/plugins/csv/pom.xml) to generate java source files from either a local file, and/or a complete configuration in the plugin. There are several configurations because there are several features to tests, you don't need that many. + +Note that it's possible to use an internet resource for the generator, but doing so should not be bound to a maven phase as a failure with the distant server could lead to building errors. diff --git a/jcodemodel/.gitignore b/jcodemodel/.gitignore new file mode 100644 index 00000000..21dd9913 --- /dev/null +++ b/jcodemodel/.gitignore @@ -0,0 +1 @@ +src/generated/java/JCMFirstClass.java diff --git a/jcodemodel/src/test/java/JCMFirstProgram.java b/jcodemodel/src/test/java/JCMFirstProgram.java new file mode 100644 index 00000000..c6146d80 --- /dev/null +++ b/jcodemodel/src/test/java/JCMFirstProgram.java @@ -0,0 +1,18 @@ +import java.io.File; +import java.io.IOException; + +import com.helger.jcodemodel.JCodeModel; +import com.helger.jcodemodel.exceptions.JCodeModelException; +import com.helger.jcodemodel.writer.JCMWriter; + +public class JCMFirstProgram { + + public static void main(String... args) throws JCodeModelException, IOException { + var jcm = new JCodeModel(); + jcm._class("JCMFirstClass"); + File outFile = new File("src/generated/java/"); + outFile.mkdirs(); + new JCMWriter(jcm).build(outFile); + } + +}