自动生成替换方法
Auto generate replace methods
在为我正在制作的应用程序创建语言文件时,我运行正在处理大量样板代码。我目前有一个 class,其中包含所有语言字符串,然后我使用反射将这些字符串写入文件。
我 运行 经常遇到的问题是我的字符串中有某些我想替换的占位符,例如我可能有这样的字符串:
public static String USER_INFO = "Username: %name% money: %balance%";
我想实现的是生成一些基于注释的方法,比如我可以生成 getters/setters 和其他使用 lombok 的方法。基于上面的字符串,我会有一个名为 Arguments
的注释(正确地应该命名为 Replacers 或更有意义的东西),如下所示:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Arguments {
String[] value();
}
我想做的是添加这样的注释:
@Arguments(
value = {"%balance%", "%name%"}
)
public static String USER_INFO = "Username: %name% - money: %balance%";
并自动生成以下替换方法:
public static String USER_INFONameReplacement(String name) {
return USER_INFO.replace("%name%", name);
}
public static String USER_INFOAllReplacement(String name, String balance) {
return USER_INFO.replace("%name%", name).replace("%balance%", balance);
}
public static String USER_INFOBalanceReplacement(String balance) {
return USER_INFO.replace("%balance%", balance);
}
经过一些搜索后,我最终尝试在 class 中实现 AbstractProcessor,如下所示:
@SupportedAnnotationTypes(
{"io.github.freakyville.configHelper.annotations.Arguments"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class SuggestProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
}
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
for (TypeElement annoation : annoations) {
Set<? extends Element> annotatedElements = env.getElementsAnnotatedWith(annoation);
Map<Boolean, List<Element>> annotatedFields = annotatedElements.stream().collect(
Collectors.partitioningBy(element ->
((ArrayType) element.asType()).getComponentType().getClass().equals(PrimitiveType.class)));
List<Element> setters = annotatedFields.get(true);
if (setters.isEmpty()) {
continue;
}
String className = ((TypeElement) setters.get(0)
.getEnclosingElement()).getQualifiedName().toString();
Map<String, List<String>> setterMap = setters.stream().collect(Collectors.toMap(
setter -> setter.getSimpleName().toString(),
setter -> Arrays.asList(setter.getAnnotation(Arguments.class).value()))
);
try {
writeBuilderFile(className, setterMap);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void writeBuilderFile(
String className, Map<String, List<String>> setterMap)
throws IOException {
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String builderSimpleClassName = className
.substring(lastDot + 1);
JavaFileObject builderFile = processingEnv.getFiler()
.createSourceFile(className);
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
if (packageName != null) {
out.print("package ");
out.print(packageName);
out.println(";");
out.println();
}
out.print("public class ");
out.print(builderSimpleClassName);
out.println(" {");
out.println();
setterMap.forEach((key, orgArgNames) -> {
for (int i = 0; i < orgArgNames.size(); i++) {
List<String> subList = orgArgNames.subList(0, i + 1);
List<String> argNames = subList.stream().map(v -> v.replace("%", "") + "Replacement").collect(Collectors.toList());
List<String> argsWithTypes = argNames.stream().map(v -> "String " + v).collect(Collectors.toList());
String argumentList = "(" + String.join("", argsWithTypes).substring(0, argsWithTypes.size() - 3) + ")";
String methodName;
if (orgArgNames.size() <= 1) {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "")).collect(Collectors.joining(""));
} else {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "").substring(0, 1).toUpperCase() + v.substring(1)).collect(Collectors.joining(""));
}
out.print(" public static ");
out.print(methodName);
out.print(argumentList);
out.println("{");
StringBuilder replaceSB = new StringBuilder();
replaceSB.append(key);
for (int i1 = 0; i1 < subList.size(); i1++) {
replaceSB
.append(".replace(")
.append("\"")
.append(subList.get(i))
.append("\"")
.append(",")
.append(argNames.get(i))
.append(")");
}
String replace = replaceSB.toString();
out.println("return " + replace + ";");
out.println("}");
out.println("");
}
});
out.println("}");
}
}
}
但我似乎无法注册它?
所以我的第一个问题是,如果我想实现这个目标,AbstractProcessor 是可行的方法吗?如果不是那怎么办?如果是,那为什么不注册?我正在使用 IntelliJ 并进入设置 -> 构建 -> 编译器并将注释处理器更改为启用并将处理器路径设置为我的 SuggestProcessor
Java 注释处理 (APT) 插件旨在根据其他 classes 生成代码。这些 classes 最终会出现在生成的源文件夹中,该文件夹随后也会被编译。这些 APT 插件是从 class 路径/构建工具配置和 运行 中被 IntelliJ 编译器发现的。请记住:APT 是用于生成源代码的,而不是用于替换现有的 classes。 Lombok 仍然能够这样做的唯一原因是因为他们非常深入地侵入了编译器,并且通过这种方式能够在编译下操纵 classes 的 AST。
因为这种方法在 Java 的未来版本中存在很大争议并且容易出错,所以极不可能有人会尝试构建基于 APT 的 class 替代框架或能够做到这一点的 Lombok 扩展(不是因为 Lombok 是唯一可以被视为此类 APT 使用的 "framework" 的工具,而 Lombok 本身根本不是构建在可扩展的方式)。
结论:APT 可能是可行的方法,但您的处理器必须创建一个新的 class 而不是尝试修改现有的。
有关如何创建注释处理器的示例,您可以查看以下存储库:https://github.com/galberola/java-apt-simple-example
我不确定为什么您当前的注释处理器没有与您的编译器正确关联。如果您使用的是 Maven,则可以尝试在本地为您的处理器安装工件,并将其作为编译依赖项添加到您的其他项目中。不要忘记也将 class 注册为编译器的注解处理器,我在此处引用的示例项目就是这样做的:https://github.com/galberola/java-apt-simple-example/blob/master/example/pom.xml#L29-L31。相同的配置也可以应用于其他构建系统。
在 Java 中没有真正的方法在编译下修改 classes,所以如果你真的必须在同一个 class 中有这个方法,那么不幸的是,这意味着做不到。
您可以像 Lombok 一样修改抽象语法树 (AST),而不是实际创建文件并写入文件。不推荐这样做,不同的编译器以不同的方式实现 AST,但您可以从 github (https://github.com/rzwitserloot/lombok) 扩展 Lombok 源代码,并根据需要创建注释处理程序。但是,它有点难,因此请确保您确实需要它。
抱歉,我没有正确阅读你的问题。要注册它,您需要在使用注释和注释处理器的项目中创建一个 META-INF\services 目录。在该目录中,创建一个名为 "javax.annotation.processing.Processor" 的 txt 文件,其中包含处理器的名称,例如 mypackage.SuggestProcessor。如果你决定使用java9,你也可以在module-info文件中声明处理器。处理器的模块必须包含 "provides javax.annotation.processing.Processor with something.SuggestProcessor" 并且使用注解的模块必须包含 "uses javax.annotation.processing.Processor." 这就是 javac 注册注解处理器的方式。
在为我正在制作的应用程序创建语言文件时,我运行正在处理大量样板代码。我目前有一个 class,其中包含所有语言字符串,然后我使用反射将这些字符串写入文件。
我 运行 经常遇到的问题是我的字符串中有某些我想替换的占位符,例如我可能有这样的字符串:
public static String USER_INFO = "Username: %name% money: %balance%";
我想实现的是生成一些基于注释的方法,比如我可以生成 getters/setters 和其他使用 lombok 的方法。基于上面的字符串,我会有一个名为 Arguments
的注释(正确地应该命名为 Replacers 或更有意义的东西),如下所示:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Arguments {
String[] value();
}
我想做的是添加这样的注释:
@Arguments(
value = {"%balance%", "%name%"}
)
public static String USER_INFO = "Username: %name% - money: %balance%";
并自动生成以下替换方法:
public static String USER_INFONameReplacement(String name) {
return USER_INFO.replace("%name%", name);
}
public static String USER_INFOAllReplacement(String name, String balance) {
return USER_INFO.replace("%name%", name).replace("%balance%", balance);
}
public static String USER_INFOBalanceReplacement(String balance) {
return USER_INFO.replace("%balance%", balance);
}
经过一些搜索后,我最终尝试在 class 中实现 AbstractProcessor,如下所示:
@SupportedAnnotationTypes(
{"io.github.freakyville.configHelper.annotations.Arguments"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class SuggestProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
}
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
for (TypeElement annoation : annoations) {
Set<? extends Element> annotatedElements = env.getElementsAnnotatedWith(annoation);
Map<Boolean, List<Element>> annotatedFields = annotatedElements.stream().collect(
Collectors.partitioningBy(element ->
((ArrayType) element.asType()).getComponentType().getClass().equals(PrimitiveType.class)));
List<Element> setters = annotatedFields.get(true);
if (setters.isEmpty()) {
continue;
}
String className = ((TypeElement) setters.get(0)
.getEnclosingElement()).getQualifiedName().toString();
Map<String, List<String>> setterMap = setters.stream().collect(Collectors.toMap(
setter -> setter.getSimpleName().toString(),
setter -> Arrays.asList(setter.getAnnotation(Arguments.class).value()))
);
try {
writeBuilderFile(className, setterMap);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void writeBuilderFile(
String className, Map<String, List<String>> setterMap)
throws IOException {
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String builderSimpleClassName = className
.substring(lastDot + 1);
JavaFileObject builderFile = processingEnv.getFiler()
.createSourceFile(className);
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
if (packageName != null) {
out.print("package ");
out.print(packageName);
out.println(";");
out.println();
}
out.print("public class ");
out.print(builderSimpleClassName);
out.println(" {");
out.println();
setterMap.forEach((key, orgArgNames) -> {
for (int i = 0; i < orgArgNames.size(); i++) {
List<String> subList = orgArgNames.subList(0, i + 1);
List<String> argNames = subList.stream().map(v -> v.replace("%", "") + "Replacement").collect(Collectors.toList());
List<String> argsWithTypes = argNames.stream().map(v -> "String " + v).collect(Collectors.toList());
String argumentList = "(" + String.join("", argsWithTypes).substring(0, argsWithTypes.size() - 3) + ")";
String methodName;
if (orgArgNames.size() <= 1) {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "")).collect(Collectors.joining(""));
} else {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "").substring(0, 1).toUpperCase() + v.substring(1)).collect(Collectors.joining(""));
}
out.print(" public static ");
out.print(methodName);
out.print(argumentList);
out.println("{");
StringBuilder replaceSB = new StringBuilder();
replaceSB.append(key);
for (int i1 = 0; i1 < subList.size(); i1++) {
replaceSB
.append(".replace(")
.append("\"")
.append(subList.get(i))
.append("\"")
.append(",")
.append(argNames.get(i))
.append(")");
}
String replace = replaceSB.toString();
out.println("return " + replace + ";");
out.println("}");
out.println("");
}
});
out.println("}");
}
}
}
但我似乎无法注册它?
所以我的第一个问题是,如果我想实现这个目标,AbstractProcessor 是可行的方法吗?如果不是那怎么办?如果是,那为什么不注册?我正在使用 IntelliJ 并进入设置 -> 构建 -> 编译器并将注释处理器更改为启用并将处理器路径设置为我的 SuggestProcessor
Java 注释处理 (APT) 插件旨在根据其他 classes 生成代码。这些 classes 最终会出现在生成的源文件夹中,该文件夹随后也会被编译。这些 APT 插件是从 class 路径/构建工具配置和 运行 中被 IntelliJ 编译器发现的。请记住:APT 是用于生成源代码的,而不是用于替换现有的 classes。 Lombok 仍然能够这样做的唯一原因是因为他们非常深入地侵入了编译器,并且通过这种方式能够在编译下操纵 classes 的 AST。
因为这种方法在 Java 的未来版本中存在很大争议并且容易出错,所以极不可能有人会尝试构建基于 APT 的 class 替代框架或能够做到这一点的 Lombok 扩展(不是因为 Lombok 是唯一可以被视为此类 APT 使用的 "framework" 的工具,而 Lombok 本身根本不是构建在可扩展的方式)。
结论:APT 可能是可行的方法,但您的处理器必须创建一个新的 class 而不是尝试修改现有的。
有关如何创建注释处理器的示例,您可以查看以下存储库:https://github.com/galberola/java-apt-simple-example
我不确定为什么您当前的注释处理器没有与您的编译器正确关联。如果您使用的是 Maven,则可以尝试在本地为您的处理器安装工件,并将其作为编译依赖项添加到您的其他项目中。不要忘记也将 class 注册为编译器的注解处理器,我在此处引用的示例项目就是这样做的:https://github.com/galberola/java-apt-simple-example/blob/master/example/pom.xml#L29-L31。相同的配置也可以应用于其他构建系统。
在 Java 中没有真正的方法在编译下修改 classes,所以如果你真的必须在同一个 class 中有这个方法,那么不幸的是,这意味着做不到。
您可以像 Lombok 一样修改抽象语法树 (AST),而不是实际创建文件并写入文件。不推荐这样做,不同的编译器以不同的方式实现 AST,但您可以从 github (https://github.com/rzwitserloot/lombok) 扩展 Lombok 源代码,并根据需要创建注释处理程序。但是,它有点难,因此请确保您确实需要它。
抱歉,我没有正确阅读你的问题。要注册它,您需要在使用注释和注释处理器的项目中创建一个 META-INF\services 目录。在该目录中,创建一个名为 "javax.annotation.processing.Processor" 的 txt 文件,其中包含处理器的名称,例如 mypackage.SuggestProcessor。如果你决定使用java9,你也可以在module-info文件中声明处理器。处理器的模块必须包含 "provides javax.annotation.processing.Processor with something.SuggestProcessor" 并且使用注解的模块必须包含 "uses javax.annotation.processing.Processor." 这就是 javac 注册注解处理器的方式。