|
| 1 | +/* |
| 2 | + * Copyright (c) 2024 unknowIfGuestInDream. |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * Redistribution and use in source and binary forms, with or without |
| 6 | + * modification, are permitted provided that the following conditions are met: |
| 7 | + * * Redistributions of source code must retain the above copyright |
| 8 | + * notice, this list of conditions and the following disclaimer. |
| 9 | + * * Redistributions in binary form must reproduce the above copyright |
| 10 | + * notice, this list of conditions and the following disclaimer in the |
| 11 | + * documentation and/or other materials provided with the distribution. |
| 12 | + * * Neither the name of unknowIfGuestInDream, any associated website, nor the |
| 13 | + * names of its contributors may be used to endorse or promote products |
| 14 | + * derived from this software without specific prior written permission. |
| 15 | + * |
| 16 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 17 | + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 18 | + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 19 | + * DISCLAIMED. IN NO EVENT SHALL UNKNOWIFGUESTINDREAM BE LIABLE FOR ANY |
| 20 | + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 21 | + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 22 | + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 23 | + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 24 | + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 25 | + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | + */ |
| 27 | + |
| 28 | +package com.tlcsdm.core.javapoet; |
| 29 | + |
| 30 | +import com.palantir.javapoet.ClassName; |
| 31 | +import com.palantir.javapoet.JavaFile; |
| 32 | +import com.palantir.javapoet.MethodSpec; |
| 33 | +import com.palantir.javapoet.ParameterizedTypeName; |
| 34 | +import com.palantir.javapoet.TypeName; |
| 35 | +import com.palantir.javapoet.TypeSpec; |
| 36 | +import org.junit.jupiter.api.Test; |
| 37 | + |
| 38 | +import javax.lang.model.element.Modifier; |
| 39 | +import java.io.IOException; |
| 40 | +import java.util.Collections; |
| 41 | +import java.util.Date; |
| 42 | + |
| 43 | +/** |
| 44 | + * <a href="https://github.com/palantir/javapoet">官网实例</a> |
| 45 | + * |
| 46 | + * @author unknowIfGuestInDream |
| 47 | + */ |
| 48 | +public class JavapoetTest { |
| 49 | + |
| 50 | + /** |
| 51 | + * 为了声明 main 方法,我们创建了一个 MethodSpec “main”,它配置了修饰符、返回类型、 |
| 52 | + * 参数和代码语句。我们将 main 方法添加到 HelloWorld 类中,然后将其添加到 HelloWorld.java 文件中。 |
| 53 | + * <p> |
| 54 | + * 在本例中,我们将文件写入 System.out,但我们也可以将其作为字符串 (JavaFile.toString()) 或 |
| 55 | + * 将其写入文件系统 (JavaFile.writeTo())。 |
| 56 | + */ |
| 57 | + @Test |
| 58 | + void testMain() throws IOException { |
| 59 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 60 | + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) |
| 61 | + .returns(void.class) |
| 62 | + .addParameter(String[].class, "args") |
| 63 | + .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") |
| 64 | + .build(); |
| 65 | + |
| 66 | + print(main); |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * 不对方法和构造函数的主体进行建模。没有表达式类,没有语句类或语法树节点。 |
| 71 | + * 相反,JavaPoet 使用字符串作为代码块: |
| 72 | + * |
| 73 | + * @throws IOException |
| 74 | + */ |
| 75 | + @Test |
| 76 | + void codeFlow1() throws IOException { |
| 77 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 78 | + .addCode("" |
| 79 | + + "int total = 0;\n" |
| 80 | + + "for (int i = 0; i < 10; i++) {\n" |
| 81 | + + " total += i;\n" |
| 82 | + + "}\n") |
| 83 | + .build(); |
| 84 | + |
| 85 | + print(main); |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * 手动分号、换行和缩进很繁琐,因此 JavaPoet 提供了 API 来简化它。 |
| 90 | + * 有 addStatement() 来处理分号和换行符, |
| 91 | + * 和 beginControlFlow() + endControlFlow() 一起用于大括号、换行符和缩进: |
| 92 | + */ |
| 93 | + @Test |
| 94 | + void codeFlow2() throws IOException { |
| 95 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 96 | + .addStatement("int total = 0") |
| 97 | + .beginControlFlow("for (int i = 0; i < 10; i++)") |
| 98 | + .addStatement("total += i") |
| 99 | + .endControlFlow() |
| 100 | + .build(); |
| 101 | + |
| 102 | + print(main); |
| 103 | + } |
| 104 | + |
| 105 | + /** |
| 106 | + * 假设我们不是简单地将 0 添加到 10,而是希望使操作和范围可配置。下面是一个生成方法的方法: |
| 107 | + * |
| 108 | + * @throws IOException |
| 109 | + */ |
| 110 | + @Test |
| 111 | + void codeFlow3() throws IOException { |
| 112 | + MethodSpec main = MethodSpec.methodBuilder("multiply10to20") |
| 113 | + .returns(int.class) |
| 114 | + .addStatement("int result = 1") |
| 115 | + .beginControlFlow("for (int i = " + "10" + "; i < " + "20" + "; i++)") |
| 116 | + .addStatement("result = result " + "*" + " i") |
| 117 | + .endControlFlow() |
| 118 | + .addStatement("return result") |
| 119 | + .build(); |
| 120 | + |
| 121 | + print(main); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * 某些控制流语句(例如 if/else)可以具有无限的控制流可能性。 |
| 126 | + * 您可以使用 nextControlFlow() 处理这些选项: |
| 127 | + * |
| 128 | + * @throws IOException |
| 129 | + */ |
| 130 | + @Test |
| 131 | + void codeFlow4() throws IOException { |
| 132 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 133 | + .addStatement("long now = $T.currentTimeMillis()", System.class) |
| 134 | + .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class) |
| 135 | + .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!") |
| 136 | + .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class) |
| 137 | + .addStatement("$T.out.println($S)", System.class, "Time stood still!") |
| 138 | + .nextControlFlow("else") |
| 139 | + .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward") |
| 140 | + .endControlFlow() |
| 141 | + .build(); |
| 142 | + |
| 143 | + print(main); |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * 使用 try/catch 捕获异常也是 nextControlFlow() 的一个用例: |
| 148 | + */ |
| 149 | + @Test |
| 150 | + void codeFlow5() throws IOException { |
| 151 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 152 | + .beginControlFlow("try") |
| 153 | + .addStatement("throw new Exception($S)", "Failed") |
| 154 | + .nextControlFlow("catch ($T e)", Exception.class) |
| 155 | + .addStatement("throw new $T(e)", RuntimeException.class) |
| 156 | + .endControlFlow() |
| 157 | + .build(); |
| 158 | + |
| 159 | + print(main); |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * 对 beginControlFlow() 和 addStatement 的调用中的字符串连接会分散注意力。 |
| 164 | + * 运算符太多。为了解决这个问题,JavaPoet 提供了一种受 String.format() 启发但不兼容的语法。 |
| 165 | + * 它接受 $L 在输出中发出 Literal 值。这就像 Formatter 的 %s 一样: |
| 166 | + * <p> |
| 167 | + * 文本直接发出到输出代码,无需转义。文本的参数可以是字符串, |
| 168 | + * 原语和一些 JavaPoet 类型。 |
| 169 | + * |
| 170 | + * @throws IOException |
| 171 | + */ |
| 172 | + @Test |
| 173 | + void literals() throws IOException { |
| 174 | + MethodSpec main = MethodSpec.methodBuilder("main") |
| 175 | + .returns(int.class) |
| 176 | + .addStatement("int result = 0") |
| 177 | + .beginControlFlow("for (int i = $L; i < $L; i++)", 1, 10) |
| 178 | + .addStatement("result = result $L i", "*") |
| 179 | + .endControlFlow() |
| 180 | + .addStatement("return result") |
| 181 | + .build(); |
| 182 | + print(main); |
| 183 | + } |
| 184 | + |
| 185 | + /** |
| 186 | + * 当发出包含字符串文字的代码时,我们可以使用 $S 来发出一个字符串,并带有换行引号 |
| 187 | + * 标记和转义。这是一个发出 3 个方法的程序,每个方法都返回自己的名称: |
| 188 | + * |
| 189 | + * @throws IOException |
| 190 | + */ |
| 191 | + @Test |
| 192 | + void strings1() throws IOException { |
| 193 | + TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") |
| 194 | + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) |
| 195 | + .addMethod(whatsMyName("slimShady")) |
| 196 | + .addMethod(whatsMyName("eminem")) |
| 197 | + .addMethod(whatsMyName("marshallMathers")) |
| 198 | + .build(); |
| 199 | + |
| 200 | + JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) |
| 201 | + .build(); |
| 202 | + |
| 203 | + javaFile.writeTo(System.out); |
| 204 | + } |
| 205 | + |
| 206 | + private MethodSpec whatsMyName(String name) { |
| 207 | + return MethodSpec.methodBuilder(name) |
| 208 | + .returns(String.class) |
| 209 | + .addStatement("return $S", name) |
| 210 | + .build(); |
| 211 | + } |
| 212 | + |
| 213 | + /** |
| 214 | + * 我们 Java 程序员喜欢我们的类型:它们使我们的代码更容易理解。JavaPoet 也加入了进来。 |
| 215 | + * 它内置了丰富的类型支持,包括自动生成 import 语句。只需使用 $T 来引用类型: |
| 216 | + * |
| 217 | + * @throws IOException |
| 218 | + */ |
| 219 | + @Test |
| 220 | + void types() throws IOException { |
| 221 | + MethodSpec today = MethodSpec.methodBuilder("today") |
| 222 | + .returns(Date.class) |
| 223 | + .addStatement("return new $T()", Date.class) |
| 224 | + .build(); |
| 225 | + |
| 226 | + TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") |
| 227 | + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) |
| 228 | + .addMethod(today) |
| 229 | + .build(); |
| 230 | + |
| 231 | + JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) |
| 232 | + .build(); |
| 233 | + |
| 234 | + javaFile.writeTo(System.out); |
| 235 | + } |
| 236 | + |
| 237 | + /** |
| 238 | + * 我们传递了 Date.class 来引用一个类,该类恰好在我们生成代码时可用。 |
| 239 | + * 情况并非如此。下面是一个类似的示例,但此示例引用了一个尚不存在的类: |
| 240 | + * |
| 241 | + * @throws IOException |
| 242 | + */ |
| 243 | + @Test |
| 244 | + void types1() throws IOException { |
| 245 | + ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); |
| 246 | + |
| 247 | + MethodSpec today = MethodSpec.methodBuilder("tomorrow") |
| 248 | + .returns(hoverboard) |
| 249 | + .addStatement("return new $T()", hoverboard) |
| 250 | + .build(); |
| 251 | + |
| 252 | + print(today); |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * ClassName 类型非常重要,在使用 JavaPoet 时,您将经常需要它。 |
| 257 | + * 它可以识别任何声明的类。声明类型只是 Java 丰富类型系统的开始: |
| 258 | + * 我们还有数组、参数化类型、通配符类型和类型变量。JavaPoet 具有用于构建以下每个类的类: |
| 259 | + * |
| 260 | + * @throws IOException |
| 261 | + */ |
| 262 | + @Test |
| 263 | + void types2() throws IOException { |
| 264 | + ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); |
| 265 | + ClassName list = ClassName.get("java.util", "List"); |
| 266 | + ClassName arrayList = ClassName.get("java.util", "ArrayList"); |
| 267 | + TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard); |
| 268 | + |
| 269 | + MethodSpec beyond = MethodSpec.methodBuilder("beyond") |
| 270 | + .returns(listOfHoverboards) |
| 271 | + .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList) |
| 272 | + .addStatement("result.add(new $T())", hoverboard) |
| 273 | + .addStatement("result.add(new $T())", hoverboard) |
| 274 | + .addStatement("result.add(new $T())", hoverboard) |
| 275 | + .addStatement("return result") |
| 276 | + .build(); |
| 277 | + |
| 278 | + print(beyond); |
| 279 | + } |
| 280 | + |
| 281 | + /** |
| 282 | + * JavaPoet 支持 import static。它通过显式收集类型成员名称来实现这一点。 |
| 283 | + * 让我们用一些静态糖来增强前面的例子: |
| 284 | + * |
| 285 | + * @throws IOException |
| 286 | + */ |
| 287 | + @Test |
| 288 | + void types3() throws IOException { |
| 289 | + ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards"); |
| 290 | + ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); |
| 291 | + |
| 292 | + MethodSpec main = MethodSpec.methodBuilder("tomorrow") |
| 293 | + .returns(hoverboard) |
| 294 | + .addStatement("return new $T()", hoverboard) |
| 295 | + .build(); |
| 296 | + |
| 297 | + TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") |
| 298 | + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) |
| 299 | + .addMethod(main) |
| 300 | + .build(); |
| 301 | + |
| 302 | + JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) |
| 303 | + .addStaticImport(hoverboard, "createNimbus") |
| 304 | + .addStaticImport(namedBoards, "*") |
| 305 | + .addStaticImport(Collections.class, "*") |
| 306 | + .build(); |
| 307 | + javaFile.writeTo(System.out); |
| 308 | + } |
| 309 | + |
| 310 | + /** |
| 311 | + * 生成的代码通常是自引用的。使用 $N 按名称引用另一个生成的声明。 |
| 312 | + * 这是一个调用另一个方法的方法: |
| 313 | + * |
| 314 | + * @throws IOException |
| 315 | + */ |
| 316 | + @Test |
| 317 | + void names() throws IOException { |
| 318 | + MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit") |
| 319 | + .addParameter(int.class, "i") |
| 320 | + .returns(char.class) |
| 321 | + .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')") |
| 322 | + .build(); |
| 323 | + |
| 324 | + MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex") |
| 325 | + .addParameter(int.class, "b") |
| 326 | + .returns(String.class) |
| 327 | + .addStatement("char[] result = new char[2]") |
| 328 | + .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit) |
| 329 | + .addStatement("result[1] = $N(b & 0xf)", hexDigit) |
| 330 | + .addStatement("return new String(result)") |
| 331 | + .build(); |
| 332 | + |
| 333 | + print(byteToHex); |
| 334 | + } |
| 335 | + |
| 336 | + private void print(MethodSpec main) throws IOException { |
| 337 | + TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") |
| 338 | + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) |
| 339 | + .addMethod(main) |
| 340 | + .build(); |
| 341 | + |
| 342 | + JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) |
| 343 | + .build(); |
| 344 | + |
| 345 | + javaFile.writeTo(System.out); |
| 346 | + } |
| 347 | +} |
0 commit comments