Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
code-dev | 9cf6b075c9 | 1 year ago |
jkzuri | 0e40543fee | 2 years ago |
jkzuri | 3bc0bf2a15 | 2 years ago |
code-dev | 9e4bd480ab | 3 years ago |
code-dev | 9cc336f479 | 3 years ago |
code-dev | b22b78b4a6 | 3 years ago |
code-dev | 7763d08e98 | 3 years ago |
code-dev | af46d104bc | 3 years ago |
code-dev | af5b12f2f5 | 3 years ago |
code-dev | 8a1d9c5eab | 3 years ago |
code-dev | d11cee3aa1 | 3 years ago |
code-dev | acf7ff3784 | 3 years ago |
code-dev | 17f5bfe03a | 3 years ago |
code-dev | f4203a0e22 | 3 years ago |
notme | 4548f940bc | 3 years ago |
notme | 9d3d6fa6a1 | 3 years ago |
notme | 3092c0c2a5 | 3 years ago |
notme | 12918e51a9 | 3 years ago |
notme | 4051dd88b2 | 3 years ago |
notme | 7200476873 | 3 years ago |
notme | 531e033c6f | 3 years ago |
notme | 0ce8549dc0 | 3 years ago |
notme | f1adff7597 | 3 years ago |
notme | e9c458dfc3 | 3 years ago |
notme | 1ed916c966 | 3 years ago |
26 changed files with 1667 additions and 1 deletions
@ -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; “<<:” 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 “<<: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! |
||||
|
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 246 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 213 KiB |
@ -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 "$@" |
@ -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% |
@ -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> |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,9 @@
|
||||
package org.chulk.codegen.command.construct; |
||||
|
||||
public class ModelValue extends PayloadElement { |
||||
|
||||
public ModelValue(String value) { |
||||
super(value); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
} |
@ -0,0 +1,9 @@
|
||||
package org.chulk.codegen.command.construct; |
||||
|
||||
public class StringLiteral extends PayloadElement { |
||||
|
||||
public StringLiteral(String value) { |
||||
super(value); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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>> |
@ -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"); |
||||
} |
||||
} |
||||
} |
@ -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>> |
@ -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>> |
@ -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…
Reference in new issue