Compare commits

..

25 Commits
dev ... main

  1. 451
      README.md
  2. BIN
      doc/images/image1.png
  3. BIN
      doc/images/image2.jpg
  4. BIN
      doc/images/image3.jpg
  5. BIN
      doc/images/image4.jpg
  6. 316
      mvnw
  7. 188
      mvnw.cmd
  8. 89
      pom.xml
  9. 13
      src/main/java/org/chulk/codegen/CodeGenApp.java
  10. 160
      src/main/java/org/chulk/codegen/CodeGenerator.java
  11. 25
      src/main/java/org/chulk/codegen/Command.java
  12. 59
      src/main/java/org/chulk/codegen/JavaCodeMapper.java
  13. 13
      src/main/java/org/chulk/codegen/MapperBuilder.java
  14. 9
      src/main/java/org/chulk/codegen/command/construct/ModelValue.java
  15. 19
      src/main/java/org/chulk/codegen/command/construct/PayloadElement.java
  16. 9
      src/main/java/org/chulk/codegen/command/construct/StringLiteral.java
  17. 22
      src/main/java/org/chulk/codegen/file/FileCreator.java
  18. 31
      src/main/java/org/chulk/codegen/file/GeneratedFile.java
  19. 46
      src/test/java/lorem/ipsum/javatester/CodeGenTest.java
  20. 14
      src/test/java/lorem/ipsum/javatester/domain/CodeModel.java
  21. 24
      src/test/java/lorem/ipsum/javatester/domain/Person.java
  22. 39
      src/test/resource/greeting-template.txt
  23. 44
      src/test/resource/test-template-transformed.txt
  24. 29
      src/test/resource/test-template.txt
  25. 29
      src/test/resource/test-template2.txt
  26. 39
      src/test/resource/test-template3.txt

451
README.md

@ -1,2 +1,451 @@
# codegen
# Lazy Coding Day - Code/Text Generation
#### 17 Feb 2022
- [Goals](#goals)
- [Proof of Concept Walkthrough](#proof-of-concept-walkthrough)
- [Implementation Walkthrough](#implementation-walkthrough)
- [Seeing everything in action](#seeing-everything-in-action)
- [Tea Break](#tea-break)
### Goals
Generating text is a common coding task, and one popular approach is to use a template engine. While code and text might seem like two different things, they're pretty much the same when it comes to creating them.
I'm playing around with a template-based solution that meets two goals:
1. Keep it simple for Java developers who only need a quick tutorial to understand it.
2. To generate **<span style="text-decoration:underline;">multiple files</span>** from a single template file - this is a pet peeve of mine.
1. [https://stackoverflow.com/questions/55025283/how-to-create-multiple-files-using-one-template-in-maven-custom-archetype-apach](https://stackoverflow.com/questions/55025283/how-to-create-multiple-files-using-one-template-in-maven-custom-archetype-apach)
### Warning!
Code generation is a well trodden field. There are better alternatives than doing DIY code generation from scratch. The whole exercise I present here is really about exploring ”mentally unburdened” code generation within the confines of a single afternoon.
### Serious Code Generation
I've used a few different code generation stacks in the past, and they're all good for different purposes. These are all better alternatives to what I demonstrate here:
1. Full blown DSL + Code generation (Antlr, JCup + JFlex)
2. Template engine (Apache Velocity)
3. Visual programming language driven code generation/VM. E.g., Scratch and Marama [https://tinyurl.com/y9no2u5p](https://tinyurl.com/y9no2u5p)
4. Macro (supported by any LISP like language)
## Proof of Concept Walkthrough
### Step 1
Intending to create something that is both lightweight and easy to use, I've decided on mixing Java and non-Java bits. By doing this, I will be able to get the full power of Java within my reach and delegate commonly occurring text output tasks to a few non-Java bits - from this point on, the non-Java bits are called generative functions.
Let’s mock-up template structures:
<p align="center">
<img src="/code-dev/codegen/media/branch/main/doc/images/image4.jpg" alt="Template Mock-up 1" width="500">
<img src="/code-dev/codegen/media/branch/main/doc/images/image3.jpg" alt="Template Mock-up 2" width="500">
</p>
The first part is to create models that drive text output; the second half involves file and text related things such as file creation and text output.
Consider the following template that I created for POC:
```java
package lorem.ipsum.javatester.file;
import java.util.UUID;
import java.io.Serializable;
import lorem.ipsum.javatester.domain.CodeModel;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
/*
Comment
*/
<<:begin>>
<<:method generate CodeModel model>>
<<:file x ./test {UUID.randomUUID().toString()}.txt>>
if (model.getName().equals("Lorem Ipsum")) {
<<:out x Hello, Lorem Ipsum!\n>>
<<:out x ---{UUID.randomUUID().toString()}---\n>>
} else {
<<:file y ./ something-else-{UUID.randomUUID().toString()}.txt>>
<<:out y Hello, {model.getName().toUpperCase()}!\n>>
}
<<:end>>
<<:end>>
```
`<<:function-symbol arg0 arg1 … argN>>` expressions that appear in the above template are generative functions. Each generative function is mapped into Java code when a template is loaded for code generation.
What the template does here is self-explanatory:
1. Creates a file at ./text whose name is a concatenation of a freshly generated UUID and ".txt".
2. If the name of the model object is "Lorem Ipsum" then writes the literal string and evaluated values as specified in Line X - Y.
3. If the name of the model object is not "Lorem Ipsum" then
1. Creates a file at ./ whose name is a concatenation of "something-else", a freshly generated UUID and ".txt".
2. Writes "Hello," and an uppercased name of the model object.
### Step 2
The next step, which happens during run-time, is to load a template file. The loaded template file is turned into Java code by mapping generative functions into Java code.
<p align="center">
<img src="/code-dev/codegen/media/branch/main/doc/images/image2.jpg" alt="Template loading" width="650">
</p>
The following Java code has been generated based on the template from the previous step.
```java
package lorem.ipsum.javatester.file;
import java.util.UUID;
import java.io.Serializable;
import lorem.ipsum.javatester.domain.CodeModel;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
/*
Comment
*/
public class Generator {
public void generate(CodeModel model) {
final GeneratedFile x = FileCreator.create("./test", "" + UUID.randomUUID().toString() + ".txt");
if (model.getName().equals("Lorem Ipsum")) {
x.out("Hello, Lorem Ipsum!\n");
x.out("---" + UUID.randomUUID().toString() + "---\n");
} else {
final GeneratedFile y = FileCreator.create("./", "something-else-" + UUID.randomUUID().toString() + ".txt");
y.out("Hello, " + model.getName().toUpperCase() + "!\n");
}
}
}
```
### Step 3
The user feeds a domain model to the template-driven Java code and generates text output.
```java
final CodeModel model = new CodeModel();
model.setName("Lorem Ipsum");
final CodeGenerator codeGen = CodeGenerator.create(Charsets.UTF_8);
codeGen.generateCode(codeGen.writeGeneratorCode(Resources.getResource("test-template.txt").getPath()), model);
```
Text Output (./test/6c6d0e84-7c1a-4ad4-8d3d-f28c713a6dee.txt)
```
Hello, Lorem Ipsum!
---e9eb85c7-547e-417b-a34b-dbe8964d9cec---
```
```java
final CodeModel model = new CodeModel();
model.setName("Not Lorem Ipsum");
final CodeGenerator codeGen = CodeGenerator.create(Charsets.UTF_8);
codeGen.generateCode(codeGen.writeGeneratorCode(Resources.getResource("test-template.txt").getPath()), model);
```
Text Output (./something-else-8ed3c1fa-43b8-4cd3-8072-ea36c7327939.txt)
```
Hello, NOT LOREM IPSUM!
```
## Implementation Walkthrough
### Processing Template
Each line of a template file can only be either Java code or generative function.
```java
Template Line := Java Code | Generative-Function
```
The template loader can easily detect a generative function line by looking for generative function start and end markers; “&lt;<: and >>”. When the template loader gets a generative function line then the loader invokes a corresponding Java code mapper for the function symbol of the generative function.
Generative Functions:
* `begin`
* `end`
* `file`
* `creates a new file.`
* `method`
* `a unary generative method that takes a model as an argument; a template needs to have one generative method.`
* `out (a workhorse function that directs the text to file)`
When we're done with this simple process, there is Java code that does what the generative functions intend to do.
### A Little Enhancement
The initial design was limited so that a single generative function could not take over more than one line. After a few tries, it became apparent the limitation had to go - so changes were made!
out - The workhorse function has been modified so that it can spread over multiple lines as shown below:
```java
<<:out x
Line 1 \n
Line 2 \n
Line 3 \n
>>
```
Not
```java
<<:out x Line 1 \n>>
<<:out x Line 2 \n>>
<<:out x Line 3 \n>>
```
So the line detection - primitive lexer - of the template loader had to change to deal with the multi-line out function. A simple solution was when a “&lt;<:out line does not end with >>” then “delayed evaluation” kicks in until we have the whole out file function.
### Code Generation - Text Output
At the end of the line detection and code mapping, we get a String value which is Java code. We hand over a collection of String values to a reflection facility, turning the text input into live Java code at runtime - the whole process has been completed!
FYI: I am using jOOR to build Java code during runtime: [https://www.jooq.org/products/jOOR/javadoc/latest/org.jooq.joor/org/joor/Reflect.html](https://www.jooq.org/products/jOOR/javadoc/latest/org.jooq.joor/org/joor/Reflect.html)
## Seeing everything in action
Let's create a template so that you can personalise your greeting message for each person.
### Goal - Timezone aware greeting text
Given a list of people, for each one of them we want to generate a timezone appropriate greeting text output.
Assuming it is 3:30 pm CEST and we want to generate a greeting for someone in NY, we would print out “Good morning, Mary.” If the person lives in London, we would print out “Good afternoon, Mary.” And if the person lives in Perth, we would print out “Good evening, Mary.” This can be done by using a simple function that takes in the current time and the person's location and outputs the appropriate greeting.
By doing this, we can make sure that everyone gets the appropriate greetings no matter what time it is where they are.
Domain Class - Person
- Name
- ZoneID
[https://docs.oracle.com/middleware/12212/wcs/tag-ref/MISC/TimeZones.html](https://docs.oracle.com/middleware/12212/wcs/tag-ref/MISC/TimeZones.html)
```java
public class Person {
private final String name;
private final ZoneId zoneId;
public Person(String name, ZoneId zoneId) {
this.name = name;
this.zoneId = zoneId;
}
public String getName() {
return name;
}
public ZoneId getZoneId() {
return zoneId;
}
}
```
Now that I have a domain class to feed to a template. Let’s sketch out a template.
Firstly, we need to create a list of "Person" objects. The list is fed to CodeGenerator, and the ZoneId of the Person object is used for each Person to find out what greeting to use.
```java
<<:begin>>
<<:method generate List<Person> model>>
for (Person person : model) {
ZonedDateTime zonedTime = Instant.now().atZone(person.getZoneId());
int hour = zonedTime.getHour();
String greeting = "";
if (hour >=0 && hour < 5) {
greeting = "Good night";
} else if (hour >= 5 && hour <= 12 ) {
greeting = "Good morning";
} else if (hour > 12 && hour <= 17) {
greeting = "Good afternoon";
} else if (hour > 17 && hour <= 21) {
greeting = "Good evening";
} else if (hour > 21) {
greeting = "Good night";
}
}
<<:end>>
<<:end>>
```
Secondly, a new file with the text content is created for each Person.
```java
<<:begin>>
<<:method generate List<Person> model>>
for (Person person : model) {
<<:file x ./test greeting-{UUID.randomUUID()}-{person.getName()}.txt>>
ZonedDateTime zonedTime = Instant.now().atZone(person.getZoneId());
int hour = zonedTime.getHour();
String greeting = "";
if (hour >=0 && hour < 5) {
greeting = "Good night";
} else if (hour >= 5 && hour <= 12 ) {
greeting = "Good morning";
} else if (hour > 12 && hour <= 17) {
greeting = "Good afternoon";
} else if (hour > 17 && hour <= 21) {
greeting = "Good evening";
} else if (hour > 21) {
greeting = "Good night";
}
}
<<:end>>
<<:end>>
```
Lastly, we append the appropriate greeting for each Person to the Person's name.
```java
<<:out x {greeting}, {person.getName()}.\n>>
```
After filling out import statements and few extra generative functions, We now have a complete template to achieve the goal.
```java
package lorem.ipsum.javatester.test;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.chulk.codegen.CodeGenerator;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
import lorem.ipsum.javatester.domain.Person;
<<:begin>>
<<:method generate List<Person> model>>
for (Person person : model) {
<<:file x ./test greeting-{UUID.randomUUID()}-{person.getName()}.txt>>
ZonedDateTime zonedTime = Instant.now().atZone(person.getZoneId());
int hour = zonedTime.getHour();
String greeting = "";
if (hour >=0 && hour < 5) {
greeting = "Good night";
} else if (hour >= 5 && hour <= 12 ) {
greeting = "Good morning";
} else if (hour > 12 && hour <= 17) {
greeting = "Good afternoon";
} else if (hour > 17 && hour <= 21) {
greeting = "Good evening";
} else if (hour > 21) {
greeting = "Good night";
}
<<:out x {greeting}, {person.getName()}.\n>>
}
<<:end>>
<<:end>>
```
Let’s try out the template as shown below:
```java
Person mary = new Person("Mary", ZoneId.of("America/New_York"));
Person bob = new Person("Bob", ZoneId.of("Pacific/Auckland"));
List<Person> ppl = new ArrayList<>();
ppl.add(mary);
ppl.add(bob);
final CodeGenerator codeGen = CodeGenerator.create(Charsets.UTF_8);
codeGen.generateCode("lorem.ipsum.javatester.test.Generator", codeGen.writeGeneratorCode(Resources.getResource("greeting-template.txt").getPath()), ppl);
```
We got new files!
![New files](/doc/images/image1.png "new files")
The test code was run at 11:26 on Sunday, 3 April 2022 (CEST). And indeed the text outputs did look good.
Mary
```
Good afternoon, Mary.
```
Bob
```
Good morning, Bob.
```
## Tea Break
I'm off for a cuppa. Cheers!

BIN
doc/images/image1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
doc/images/image2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
doc/images/image3.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
doc/images/image4.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

316
mvnw vendored

@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored

@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

89
pom.xml

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.chulk</groupId>
<artifactId>codegen</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>codegen</name>
<description>POC Codegen</description>
<properties>
<java.version>11</java.version>
<start-class>org.chulk.codegen.CodeGenApp</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor</artifactId>
<version>0.9.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-test-resource</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/src/test/resource</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

13
src/main/java/org/chulk/codegen/CodeGenApp.java

@ -0,0 +1,13 @@
package org.chulk.codegen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CodeGenApp {
public void main(String[] args) {
SpringApplication.run(CodeGenApp.class, args);
}
}

160
src/main/java/org/chulk/codegen/CodeGenerator.java

@ -0,0 +1,160 @@
package org.chulk.codegen;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.chulk.codegen.command.construct.ModelValue;
import org.chulk.codegen.command.construct.PayloadElement;
import org.chulk.codegen.command.construct.StringLiteral;
import org.joor.Reflect;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import lombok.extern.java.Log;
@Log
public class CodeGenerator {
private final Charset charset;
private CodeGenerator(final Charset charset) {
this.charset = charset;
}
public static CodeGenerator create(final Charset charset) {
return new CodeGenerator(charset);
}
/**
* Write generator code.
*
* <p>
* Takes the template file at the templatePath and outputs Java generator code
*
* @param templatePath the template path
* @return the string
* @throws IOException Signals that an I/O exception has occurred.
*/
public String writeGeneratorCode(final String templatePath) throws IOException {
try (final BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(templatePath), this.charset))) {
String lineToProcess = "";
StringBuilder strBuilder = new StringBuilder();
while (reader.ready()) {
lineToProcess += reader.readLine();
if (lineToProcess.trim().matches("<<:(out|method)\\s.*>>")) { //unary function
buildJavaCode(lineToProcess, strBuilder, 2);
lineToProcess = "";
} else if (lineToProcess.trim().matches("<<:(file)\\s.*>>")) { //binary function
buildJavaCode(lineToProcess, strBuilder, 3);
lineToProcess = "";
} else if (lineToProcess.trim().matches("<<:(begin|end).*>>")) {
String lineContent = lineToProcess.trim().replaceFirst("^<<:", "").replaceFirst(">>$", "");
strBuilder.append(templateCommandToJava(lineContent, null, null));
lineToProcess = "";
} else if (lineToProcess.trim().startsWith("<<:out")) { // non-terminal out function
log.info("Non-terminal out command");
lineToProcess += " \\n";
} else {
strBuilder.append(lineToProcess + "\n");
lineToProcess = "";
}
}
return strBuilder.toString();
}
}
private void buildJavaCode(String lineToProcess, StringBuilder strBuilder, int payloadIndex) {
String lineContent = lineToProcess.trim().replaceFirst("^<<:", "").replaceFirst(">>$", "");
List<String> contentTokens = Splitter.on(" ").trimResults().splitToList(lineContent);
String payload = Joiner.on(" ").join(contentTokens.subList(payloadIndex, contentTokens.size()));
strBuilder.append(templateCommandToJava(contentTokens.get(0), collectArgs(contentTokens, payloadIndex), evalPayload(payload))
+ "\n");
}
private List<String> collectArgs(final List<String> contentTokens, final int argEndIndex) {
List<String> args = Lists.newArrayList();
for (int n = 1; n < argEndIndex; n++) {
args.add(contentTokens.get(n));
}
return args;
}
private String templateCommandToJava(String fnName, List<String> args, final Queue<PayloadElement> queue) {
Preconditions.checkArgument(Command.valueOf(fnName) != null, "Non-supported command");
return Command.valueOf(fnName).getMapper().map(args, queue);
}
private Queue<PayloadElement> evalPayload(final String payload) {
final char[] c = payload.toCharArray();
final Queue<PayloadElement> payloadQueue = new LinkedList<>();
boolean isModelValue = false;
String tmpToken = "";
for (int n = 0; n < c.length; n++) {
if (c[n] == '{') {
addPayloadElement(payloadQueue, isModelValue, tmpToken);
isModelValue = true;
tmpToken = "";
} else if (c[n] == '}') {
addPayloadElement(payloadQueue, isModelValue, tmpToken);
isModelValue = false;
tmpToken = "";
} else {
tmpToken += c[n];
}
}
if (!tmpToken.isEmpty()) {
addPayloadElement(payloadQueue, isModelValue, tmpToken);
}
return payloadQueue;
}
private void addPayloadElement(final Queue<PayloadElement> payloadQueue, final boolean isModelValue,
final String tmpToken) {
PayloadElement payloadElement = isModelValue ? new ModelValue(tmpToken) : new StringLiteral(tmpToken);
payloadQueue.add(payloadElement);
}
/**
* Generate code.
*
* @param <T> the generic type
* @param packagePath the package path
* @param content the content
* @param model the model
* @throws NoSuchMethodException the no such method exception
* @throws IllegalAccessException the illegal access exception
* @throws InvocationTargetException the invocation target exception
*/
public <T> void generateCode(final String packagePath, final String content, final T model) //Add type.. as Model class can be of a super class
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Object genObj = Reflect.compile(packagePath, content).create().get();
final Method genMethod = genObj.getClass().getDeclaredMethods()[0];
genMethod.invoke(genObj, model);
}
}

25
src/main/java/org/chulk/codegen/Command.java

@ -0,0 +1,25 @@
package org.chulk.codegen;
public enum Command {
file(MapperBuilder.build()),
out(MapperBuilder.build()),
begin(MapperBuilder.build()),
end(MapperBuilder.build()),
method(MapperBuilder.build());
private JavaCodeMapper mapper;
Command(MapperBuilder builder) {
this.mapper = builder.createMapper(this);
}
public JavaCodeMapper getMapper() {
return this.mapper;
}
}

59
src/main/java/org/chulk/codegen/JavaCodeMapper.java

@ -0,0 +1,59 @@
package org.chulk.codegen;
import java.util.List;
import java.util.Queue;
import org.chulk.codegen.command.construct.ModelValue;
import org.chulk.codegen.command.construct.PayloadElement;
import org.chulk.codegen.command.construct.StringLiteral;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
public class JavaCodeMapper {
private final Command command;
public JavaCodeMapper(Command command) {
this.command = command;
}
public String map(List<String> args, Queue<PayloadElement> payloadElements) {
if (command.equals(Command.file)) {
return String.format("final %s %s = FileCreator.create(\"%s\", %s);", "GeneratedFile", args.get(0), args.get(1) ,buildFileCommandJavaCode(payloadElements));
}
if (command.equals(Command.out)) {
return String.format("%s.out(%s);", args.get(0), buildFileCommandJavaCode(payloadElements));
}
if (command.equals(Command.begin)) {
return "public class Generator {\n"; //TODO add resource management code..
}
if (command.equals(Command.end)) {
return "}\n";
}
if (command.equals(Command.method)) {
return String.format("public void generate(%s) {\n", payloadElements.iterator().next().getValue());
}
throw new RuntimeException("Non-supported command type.");
}
private String buildFileCommandJavaCode(Queue<PayloadElement> payloadElements) {
List<String> strs = Lists.newArrayList();
for (PayloadElement element : payloadElements) {
if (element instanceof StringLiteral) {
strs.add("\"" + element.getValue() + "\"");
} else if (element instanceof ModelValue) {
strs.add(element.getValue());
}
}
return Joiner.on("+").join(strs);
}
}

13
src/main/java/org/chulk/codegen/MapperBuilder.java

@ -0,0 +1,13 @@
package org.chulk.codegen;
public class MapperBuilder {
public static MapperBuilder build() {
return new MapperBuilder();
}
public JavaCodeMapper createMapper(Command command) {
return new JavaCodeMapper(command);
}
}

9
src/main/java/org/chulk/codegen/command/construct/ModelValue.java

@ -0,0 +1,9 @@
package org.chulk.codegen.command.construct;
public class ModelValue extends PayloadElement {
public ModelValue(String value) {
super(value);
}
}

19
src/main/java/org/chulk/codegen/command/construct/PayloadElement.java

@ -0,0 +1,19 @@
package org.chulk.codegen.command.construct;
public class PayloadElement {
public PayloadElement(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
private String value;
}

9
src/main/java/org/chulk/codegen/command/construct/StringLiteral.java

@ -0,0 +1,9 @@
package org.chulk.codegen.command.construct;
public class StringLiteral extends PayloadElement {
public StringLiteral(String value) {
super(value);
}
}

22
src/main/java/org/chulk/codegen/file/FileCreator.java

@ -0,0 +1,22 @@
package org.chulk.codegen.file;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class FileCreator {
public static GeneratedFile create(String inputPath, String name) {
Path newFilePath = Paths.get(inputPath + "/" + name);
try {
Files.createDirectories(Paths.get(inputPath));
Path path = Files.createFile(newFilePath);
return new GeneratedFile(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

31
src/main/java/org/chulk/codegen/file/GeneratedFile.java

@ -0,0 +1,31 @@
package org.chulk.codegen.file;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
public class GeneratedFile {
private final Path path;
public GeneratedFile(final Path path) {
this.path = path;
}
public Path getPath() {
return path;
}
public void out(final String output) {
Objects.requireNonNull(output);
try {
Files.writeString(path, output, StandardOpenOption.APPEND);
} catch (IOException e) {
new RuntimeException(e);
}
}
}

46
src/test/java/lorem/ipsum/javatester/CodeGenTest.java

@ -0,0 +1,46 @@
package lorem.ipsum.javatester;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import org.chulk.codegen.CodeGenerator;
import org.junit.jupiter.api.Test;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import lorem.ipsum.javatester.domain.CodeModel;
import lorem.ipsum.javatester.domain.Person;
public class CodeGenTest {
@Test
public void testCodeGenerator()
throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final CodeModel model = new CodeModel();
model.setName("Lorem Ipsum");
final CodeGenerator codeGen = CodeGenerator.create(Charsets.UTF_8);
codeGen.generateCode("lorem.ipsum.javatester.file.Generator",
codeGen.writeGeneratorCode(Resources.getResource("test-template.txt").getPath()), model);
}
@Test
public void testGreetingGenerator()
throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Person mary = new Person("Mary", ZoneId.of("America/New_York"));
Person bob = new Person("Bob", ZoneId.of("Pacific/Auckland"));
List<Person> ppl = new ArrayList<>();
ppl.add(mary);
ppl.add(bob);
final CodeGenerator codeGen = CodeGenerator.create(Charsets.UTF_8);
codeGen.generateCode("lorem.ipsum.javatester.test.Generator",
codeGen.writeGeneratorCode(Resources.getResource("greeting-template.txt").getPath()), ppl);
}
}

14
src/test/java/lorem/ipsum/javatester/domain/CodeModel.java

@ -0,0 +1,14 @@
package lorem.ipsum.javatester.domain;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
public class CodeModel {
private String name;
}

24
src/test/java/lorem/ipsum/javatester/domain/Person.java

@ -0,0 +1,24 @@
package lorem.ipsum.javatester.domain;
import java.time.ZoneId;
public class Person {
private final String name;
private final ZoneId zoneId;
public Person(String name, ZoneId zoneId) {
this.name = name;
this.zoneId = zoneId;
}
public String getName() {
return name;
}
public ZoneId getZoneId() {
return zoneId;
}
}

39
src/test/resource/greeting-template.txt

@ -0,0 +1,39 @@
package lorem.ipsum.javatester.test;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.chulk.codegen.CodeGenerator;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
import lorem.ipsum.javatester.domain.Person;
<<:begin>>
<<:method generate List<Person> model>>
for (Person person : model) {
<<:file x ./test greeting-{UUID.randomUUID()}-{person.getName()}.txt>>
ZonedDateTime zonedTime = Instant.now().atZone(person.getZoneId());
int hour = zonedTime.getHour();
String greeting = "";
if (hour >=0 && hour < 5) {
greeting = "Good night";
} else if (hour >= 5 && hour <= 12 ) {
greeting = "Good morning";
} else if (hour > 12 && hour <= 17) {
greeting = "Good afternoon";
} else if (hour > 17 && hour <= 21) {
greeting = "Good evening";
} else if (hour > 21) {
greeting = "Good night";
}
<<:out x {greeting}, {person.getName()}.\n>>
}
<<:end>>
<<:end>>

44
src/test/resource/test-template-transformed.txt

@ -0,0 +1,44 @@
import java.util.UUID;
import lorem.ipsum.javatester.domain.CodeModel;
//By convention, the first template command line is a model creation command
//and the second template command line is a file creation command
<<:model CodeModel model>>
<<:file x {UUID.randomUUID().toString()}.txt>>
if (model.getName().equals("Lorem Ipsum")) {
<<:out x Hello, Lorem!\r\n>>
} else if (model.getName() == null) {
<<:out x Null???\r\n>>
} else {
<<:file y hello-{UUID.randomUUID().toString()}>>
<<:out y Hello, {model.getName().toUpperCase()} !\r\n>>
}
//Phase 2
import java.util.UUID;
import lorem.ipsum.javatester.domain.CodeModel;
import lorem.ipsum.javatester.file.*;
//By convention. ...
//and the second line has to have a model creation command
class GenerativeFunXYZ {
public static void generate(CodeModel inputModel, String outputPath) {
GeneratedFile x = GeneratesFiles.create(inputPath, ""+UUID.randomUUID().toString()+".txt");
CodeModel model = inputModel;
if (model.getName().equals("Lorem Ipsum")) {
x.out("Hello, Lorem!\r\n");
} else if (model.getName() == null) {
x.out("Null???\r\n");
} else {
GeneratedFile y = GeneratesFiles.create(inputPath, "hello-"+UUID.randomUUID().toString()+"\r\n");
y.out("Hello, "+model.getName().toUpperCase()+" !\r\n");
}
}
}

29
src/test/resource/test-template.txt

@ -0,0 +1,29 @@
package lorem.ipsum.javatester.file;
import java.util.UUID;
import java.io.Serializable;
import lorem.ipsum.javatester.domain.CodeModel;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
/*
Comment
*/
<<:begin>>
<<:method generate CodeModel model>>
<<:file x ./test {UUID.randomUUID().toString()}.txt>>
if (model.getName().equals("Lorem Ipsum")) {
<<:out x Hello, Lorem Ipsum!\n
Line 1 \n
Line 2 \n
Line 3 \n
>>
<<:out x ---{UUID.randomUUID().toString()}---\n>>
} else {
<<:file y ./ something-else-{UUID.randomUUID().toString()}.txt>>
<<:out y Hello, {model.getName().toUpperCase()}!\n>>
}
<<:end>>
<<:end>>

29
src/test/resource/test-template2.txt

@ -0,0 +1,29 @@
package lorem.ipsum.javatester
import java.util.UUID;
import lorem.ipsum.javatester.domain.CodeModel;
import lorem.ipsum.javatester.file.*;
//By convention. ...
//and the second line has to have a model creation command
<<:begin>>
<<:method generate CodeModel model, String inputPath>>
<<:file x {UUID.randomUUID().toString()}.txt>>
<<:model CodeModel model>>
if (model.getName().equals("Lorem Ipsum")) {
<<:out x Hello, Lorem!\r\n>>
} else if (model.getName() == null) {
<<:out x Null???\r\n>>
} else {
<<:file y hello-{UUID.randomUUID().toString()}\r\n>>
<<:out y Hello, {model.getName().toUpperCase()} !\r\n>>
}
for (Attribute attr : model.getAttributes()) {
<<:file z hello-z-{UUID.randomUUID().toString()}\r\n>>
<<:out z Hello, {attr.getId().toUpperCase()} !\r\n>>
}
<<:end>>
<<:end>>

39
src/test/resource/test-template3.txt

@ -0,0 +1,39 @@
package lorem.ipsum.javatester.file;
import java.util.UUID;
import java.io.Serializable;
import lorem.ipsum.javatester.domain.CodeModel;
import org.chulk.codegen.file.GeneratedFile;
import org.chulk.codegen.file.FileCreator;
/*
Block Comment Rimu-Dev Expositor
*/
<<:begin>>
<<:method generate CodeModel model>>
//make file to take a path argument
<<:file x ./test {UUID.randomUUID().toString()}.txt>>
if (model.getName().equals("Lorem Ipsum")) {
<<:out x Hello, Lorem!\n>>
<<:out x ------------------------{UUID.randomUUID().toString()} -----------------------\n>>
//allow a block out command
<<:out x hello this is a block\n>>
<<:out x
hello this is a block
What this is new.
Out block ends here.
>>
for (String s : model.getAttributeList()) {
<<:file x2 ./test-attributes hello-{UUID.randomUUID().toString()}.txt>>
<<:out x2 {s}>>
}
} else if (model.getName() == null) {
<<:out x Null???\r\n>>
} else {
<<:file y ./ hello-{UUID.randomUUID().toString()}.txt>>
<<:out y Hello, {model.getName().toUpperCase()} !\r\n>>
}
<<:end>>
<<:end>>
Loading…
Cancel
Save