几天前,我写了有关Java :: Geci架构,代码生成原理以及生成Java源代码的可能不同方式的文章。
在本文中,我将讨论在Java :: Geci中创建生成器有多么简单。
您好,Wold生成器
HelloWorld1
最简单的生成器是Hello, World!
发电机。 这将生成一个打印Hello, World!
的方法Hello, World!
到标准输出。 要创建此生成器,Java类必须实现Generator
接口。 生成器的整个代码为:
package javax0.geci.tutorials.hello;
import javax0.geci.api.GeciException;
import javax0.geci.api.Generator;
import javax0.geci.api.Source;
public class HelloWorldGenerator1 implements Generator {
public void process(Source source) {
try {
final var segment = source.open("hello");
segment.write_r("public static void hello(){");
segment.write("System.out.println(\"Hello, World\");");
segment.write_l("}");
} catch (Exception e) {
throw new GeciException(e);
}
}
}
这确实是整个生成器类。 没有简化或删除的行。 当框架找到需要方法hello()
的文件时,它将调用process()
。
方法process ()
查询名为“ hello”的段。 这是指线
//<editor-fold id="hello">
//</editor-fold>
在源代码中。 segment
对象可用于将行写入代码。 write()
方法write()
一行。 方法write_r()
也会写一行,但是它也表示必须缩进该行之后的行。 相反的是write_l()
,该信号指示已经将此行和连续的行重新制表回到先前的位置。
要使用生成器,我们应该有一个需要它的类。 这是
package javax0.geci.tutorials.hello;
public class HelloWorld1 {
//<editor-fold id="hello">
//</editor-fold>
}
我们还需要一个测试,该测试将在每次编译代码并运行单元测试时运行代码生成:
package javax0.geci.tutorials.hello;
import javax0.geci.engine.Geci;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static javax0.geci.api.Source.maven;
public class TestHelloWorld1 {
@Test
@DisplayName("Start code generator for HelloWorld1")
void testGenerateCode() throws Exception {
Assertions.assertFalse(new Geci()
.only("^.*/HelloWorld1.java$")
.register(new HelloWorldGenerator1()).generate(), Geci.FAILED);
}
}
执行代码后,将修改HelloWorld1.java
文件,并将在编辑器折叠之间插入以下行:
package javax0.geci.tutorials.hello;
public class HelloWorld1 {
//<editor-fold id="hello">
public static void hello(){
System.out.println("Hello, World");
}
//</editor-fold>
}
这是一个非常简单的示例,我们可以进一步发展。
HelloWorld2
该示例中低于标准的一件事是,生成器的范围在调用only()
方法的测试中受到限制。 更好的做法是让框架扫描所有文件并选择本身以某种方式表明它们需要生成器服务的源文件。 在“你好,世界!”的情况下 生成器,它可以是hello
段的存在,作为源代码中的编辑器折叠。 如果存在,则代码需要方法hello()
,否则不需要。 我们可以通过这种方式实现生成器的第二个版本。 我们还修改了实现,而不仅是实现接口Generator
还扩展了抽象类AbstractGeneratorEx
。 名称中的后缀Ex
表示该类为我们处理异常。 这个抽象类实现方法process()
并调用要定义的processEx()
,该签名具有与process()
相同的签名,但允许抛出异常。 如果发生这种情况,则将其封装在GeciException
,就像我们在第一个示例中所做的那样。
该代码将如下所示:
package javax0.geci.tutorials.hello;
import javax0.geci.api.Source;
import javax0.geci.tools.AbstractGeneratorEx;
import java.io.IOException;
public class HelloWorldGenerator2 extends AbstractGeneratorEx {
public void processEx(Source source) throws IOException {
final var segment = source.open("hello");
if (segment != null) {
segment.write_r("public static void hello(){");
segment.write("System.out.println(\"Hello, World\");");
segment.write_l("}");
}
}
}
尽管它正在检查段的存在,但它甚至比第一个简单。 当代码调用source.open("hello")
,如果源代码中没有名为hello
段,则该方法将返回null
。 使用第二个生成器的实际代码与第一个相同。 当我们在代码库中运行两个测试时,它们都会生成代码,所幸的是相同的。
调用第二个生成器的测试是
package javax0.geci.tutorials.hello;
import javax0.geci.engine.Geci;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static javax0.geci.api.Source.maven;
public class TestHelloWorld2 {
@Test
@DisplayName("Start code generator for HelloWorld2")
void testGenerateCode() throws Exception {
Assertions.assertFalse(new Geci()
.register(new HelloWorldGenerator2())
.generate(), Geci.FAILED);
}
}
请注意,这一次我们不需要限制调用only()
方法的代码扫描。 同样, only(RegEx x)
方法的文档only(RegEx x)
说,这是在生成器生成器的API中的最后选择。
HelloWorld3
生成器的第一个版本和第二个版本正在处理文本文件,并且不使用我们修改的代码实际上是Java的事实。 生成器的第三个版本将依赖于这一事实,这样就可以创建一个生成器,可以在需要代码生成的类中对其进行配置。
为此,我们可以扩展抽象类AbstractJavaGenerator
。 这个抽象类将找到与源代码相对应的类,并且还将读取该类的注释中编码的配置,我们将看到。 仅当源代码是Java文件,已经编译过的类(抱歉,编译器,我们现在可以修改源代码processEx()
, processEx()
的抽象类实现才调用process(Source source, Class klass, CompoundParams global)
可能需要重新编译),并且对该类进行了适当的注释。
生成器代码如下:
package javax0.geci.tutorials.hello;
import javax0.geci.api.Source;
import javax0.geci.tools.AbstractJavaGenerator;
import javax0.geci.tools.CompoundParams;
import java.io.IOException;
public class HelloWorldGenerator3 extends AbstractJavaGenerator {
public void process(Source source, Class<?> klass, CompoundParams global)
throws IOException {
final var segment = source.open(global.get("id"));
final var methodName = global.get("methodName", "hello");
segment.write_r("public static void %s(){", methodName);
segment.write("System.out.println(\"Hello, World\");");
segment.write_l("}");
}
public String mnemonic() {
return "HelloWorld3";
}
}
方法process()
(接口中定义的方法的重载版本)获取三个参数。 第一个是与第一个示例中非常相同的Source
对象。 第二个是从我们正在处理的Java源文件创建的Class
。 第三个是框架从类注释读取的配置。 这也需要方法mnemonic()
的支持。 这标识了生成器的名称。 它是在配置中用作引用的字符串。 它必须是唯一的。
需要使用生成器进行修改的Java类必须使用Geci
注释进行注释。 Geci
注释在库javax0.geci.annotations.Geci
定义。 用生成的代码扩展的源代码将如下所示:
package javax0.geci.tutorials.hello;
import javax0.geci.annotations.Geci;
@Geci("HelloWorld3 id='hallo' methodName='hiya'")
public class HelloWorld3 {
//<editor-fold id="hallo">
//</editor-fold>
}
这里有点麻烦。 Java :: Geci是一个测试阶段工具,对其的所有依赖项都是测试依赖项。 注释库是一个例外。 该库必须是正常的依赖项,因为使用代码生成的类都带有此注释,因此JVM将在运行时查找该注释类,即使在运行时该注释没有作用。 因为JVM测试执行只是运行时,所以没有区别。
要克服此Java :: Geci,您可以使用任何注释,只要注释接口的名称为Geci
且其value
是String
。 这样,我们可以通过以下方式使用第三个hello world生成器:
package javax0.geci.tutorials.hello;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@HelloWorld3a.Geci(value = "HelloWorld3 id='hallo'", methodName = "hiyaHuya")
public class HelloWorld3a {
//<editor-fold id="hallo">
//</editor-fold>
@Retention(RetentionPolicy.RUNTIME)
@interface Geci {
String value();
String methodName() default "hello";
}
}
请注意,在前面的示例中,参数id
和methodName
是在value
字符串内定义的(如果未在注释中定义任何其他参数,则这是默认参数)。 在这种情况下,很容易将参数拼写错误,并且IDE不会仅仅因为IDE对配置Java :: Geci的字符串格式一无所知就不会为您提供任何支持。 另一方面,如果您有自己的注释,则可以自由定义任何命名参数。 在此示例中,我们在接口中定义了方法methodName
。 Java :: Geci正在读取注释的参数以及解析参数的value
字符串。 这样,某些生成器可以使用自己的注释,这些注释可以帮助用户定义定义为注释参数的参数。
我们的第三个“ Hello,World!”的最后一个版本 应用程序可能是最简单的:
package javax0.geci.tutorials.hello;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class HelloWorld3b {
//<editor-fold id="HelloWorld3" methodName = "hiyaNyunad">
//</editor-fold>
}
类上没有注释,也没有看起来像注释的注释。 唯一存在id
HelloWorld3
的editor-fold
段是生成器的助记符。 如果存在,则AbstractJavaGenerator
意识到这一点并从那里读取参数。 (顺便说一句:即使存在注释,它也会读取注释中不存在的其他参数。)不仅读取参数,还调用具体的实现,因此生成了代码。 这种方法最简单,可用于仅需要一个段即可将代码生成到其中的代码生成器,以及当它们不需要类中方法和字段的单独配置选项时使用。
摘要
在本文中,我描述了如何编写自己的生成器,并且还深入研究了如何使用注释来配置需要生成代码的类。 请注意,本文中讨论的某些功能可能不在发行版中,但是您可以从https://github.com/verhas/javageci下载并构建(b)领先版本。
翻译自: https://www.javacodegeeks.com/2019/05/creating-javageci-generator.html
所有评论(0)