io.ultreia.java4all.i18n.spi.builder.TranslateEnumerationProcessor Maven / Gradle / Ivy
package io.ultreia.java4all.i18n.spi.builder;
/*-
* #%L
* I18n :: Spi Builder
* %%
* Copyright (C) 2018 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.auto.service.AutoService;
import io.ultreia.java4all.i18n.spi.enumeration.TranslateEnumeration;
import io.ultreia.java4all.i18n.spi.enumeration.TranslateEnumerations;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
/**
* To generate translate enumeration helper.
*
* Created on 06/09/2021.
*
* @author Tony Chemit - [email protected]
* @since 3.0.0
*/
@SupportedAnnotationTypes({"io.ultreia.java4all.i18n.spi.enumeration.TranslateEnumeration", "io.ultreia.java4all.i18n.spi.enumeration.TranslateEnumerations"})
@AutoService(Processor.class)
@SupportedOptions({"debug"})
public class TranslateEnumerationProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
I18nModule i18nModule;
I18nKeySet i18nKeysFile;
try {
Path configurationPath = Path.of(processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", "fake")
.toUri().toURL().getFile()).getParent().getParent().getParent().resolve(".mvn").resolve("i18n-configuration.properties");
if (!Files.exists(configurationPath)) {
throw new IllegalStateException("Can't find i18n module configuration at " + configurationPath + ", please launch once i18n:init mojo.");
}
String i18nModuleConfiguration = Files.readString(configurationPath);
Properties properties = new Properties();
properties.put(I18nModuleConfiguration.I18N_MODULE_CONFIGURATION, i18nModuleConfiguration);
i18nModule = I18nModule.forGetter(properties);
i18nKeysFile = i18nModule.getModuleKeySet("java-enumeration");
} catch (Exception e) {
throw new RuntimeException("Can't load I18n module", e);
}
TypeMirror translateEnumerationType = processingEnv.getElementUtils().getTypeElement(TranslateEnumeration.class.getName()).asType();
TypeMirror translateEnumerationsType = processingEnv.getElementUtils().getTypeElement(TranslateEnumerations.class.getName()).asType();
for (TypeElement annotation : annotations) {
Set extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element annotatedElement : annotatedElements) {
EnumerationDefinition definition = new EnumerationDefinition(annotatedElement);
List extends AnnotationMirror> annotationMirrors = processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedElement);
TypeMirror annotationType = annotation.asType();
AnnotationMirror annotationMirror = annotationMirrors.stream().filter(a -> a.getAnnotationType().equals(annotationType)).findFirst().orElseThrow();
if (translateEnumerationType.equals(annotationType)) {
// single translation
definition.addTranslation(annotationMirror);
} else if (translateEnumerationsType.equals(annotationType)) {
// multiple translations
AnnotationValue value = annotationMirror.getElementValues().entrySet().iterator().next().getValue();
List> value1 = (List>) value.getValue();
for (Object o : value1) {
definition.addTranslation((AnnotationMirror) o);
}
}
processEnumeration(definition, i18nKeysFile);
}
}
if (i18nKeysFile.isNotEmpty()) {
try {
i18nModule.storeModuleKeySet(i18nKeysFile);
} catch (IOException e) {
throw new RuntimeException("Can't store I18n getters", e);
}
}
return true;
}
private void processEnumeration(EnumerationDefinition definition, I18nKeySet i18nKeysFile) {
String generatedSimpleClassName = definition.enumerationName + "I18n";
String generatedClassName = definition.packageName + "." + generatedSimpleClassName;
try {
FileObject resource = processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, definition.packageName, generatedSimpleClassName + ".java");
Path javaFile = Path.of(resource.toUri().toURL().getFile());
if (Files.exists(javaFile)) {
// Already done
logWarning(String.format("Skip already processed class: %s", generatedClassName));
return;
}
} catch (IOException e) {
// file not found, can safely execute it
}
logDebug(String.format("Detect enumeration to process: %s", generatedClassName));
logInfo(String.format("Generate enumeration I18n helper: %s", generatedClassName));
try {
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(generatedClassName);
try (BufferedWriter out = new BufferedWriter(new PrintWriter(builderFile.openWriter()))) {
generateDefinitionFile(definition, i18nKeysFile, generatedSimpleClassName, out);
out.flush();
}
} catch (Exception e) {
throw new RuntimeException(String.format("Can't generate enumeration I18n file for: %s", generatedClassName), e);
}
}
private void generateDefinitionFile(EnumerationDefinition definition,
I18nKeySet i18nKeysFile,
String generatedClassName,
BufferedWriter writer) throws IOException {
String packageName = definition.packageName;
String enumerationName = definition.enumerationName;
String anEnumType = packageName + "." + enumerationName;
writer.write("package " + packageName + ";\n");
writer.write("\n");
writer.write("import javax.annotation.Generated;\n");
writer.write("import java.util.Locale;\n");
writer.newLine();
writer.write("import static io.ultreia.java4all.i18n.I18n.l;\n");
writer.write("import static io.ultreia.java4all.i18n.I18n.t;\n");
writer.newLine();
writer.newLine();
writer.write("@Generated(value = \"Generated by " + getClass().getName() + "\",date = \"" + new Date() + "\")\n");
writer.write("public class " + generatedClassName + " {\n");
writer.newLine();
for (Map.Entry entry : definition.translations.entrySet()) {
String name = entry.getKey();
String pattern = entry.getValue();
int ordinal = 0;
for (String enumName : definition.enumerationValues) {
i18nKeysFile.addKey(definition.transformToKey(pattern, anEnumType, enumerationName, enumName, (ordinal++) + ""));
}
String methodName = "get" + StringUtils.capitalize(name);
writer.write(" protected static String " + methodName + "Key(" + enumerationName + " e) {\n");
writer.write(" return " + definition.transformToMessage(packageName, enumerationName, pattern) + ";\n");
writer.write(" }\n");
writer.newLine();
writer.write(" public static String " + methodName + "(" + enumerationName + " e) {\n");
writer.write(" return t(" + methodName + "Key(e));\n");
writer.write(" }\n");
writer.newLine();
writer.write(" public static String " + methodName + "(Locale locale, " + enumerationName + " e) {\n");
writer.write(" return l(locale, " + methodName + "Key(e));\n");
writer.write(" }\n");
writer.newLine();
}
writer.write("}\n");
}
private void logDebug(String msg) {
if (processingEnv.getOptions().containsKey("debug")) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
}
}
private void logInfo(String msg) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
}
private void logWarning(String msg) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg);
}
class EnumerationDefinition {
final String packageName;
final String enumerationName;
final Map translations;
final Set enumerationValues;
public EnumerationDefinition(Element annotatedElement) {
TypeElement classElement = (TypeElement) annotatedElement;
this.packageName = processingEnv.getElementUtils().getPackageOf(classElement).toString();
this.enumerationName = classElement.getSimpleName().toString();
this.translations = new LinkedHashMap<>();
this.enumerationValues = new LinkedHashSet<>();
processingEnv.getElementUtils().getAllMembers(classElement).stream().filter(m -> m.asType().equals(annotatedElement.asType())).forEach(m -> enumerationValues.add(m.getSimpleName().toString()));
}
void addTranslation(AnnotationMirror annotationMirror) {
Map extends ExecutableElement, ? extends AnnotationValue> map = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
String name = null;
String pattern = null;
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : map.entrySet()) {
String key = entry.getKey().getSimpleName().toString();
switch (key) {
case "name":
name = entry.getValue().getValue().toString();
break;
case "pattern":
pattern = entry.getValue().getValue().toString();
break;
}
}
translations.put(name, pattern);
}
String transformToMessage(String packageName, String enumerationName, String pattern) {
StringBuilder builder = new StringBuilder();
StringTokenizer stringTokenizer = new StringTokenizer(pattern, "@");
while (stringTokenizer.hasMoreTokens()) {
if (builder.length() > 0) {
builder.append(" + ");
}
String token = stringTokenizer.nextToken();
switch (token) {
case "CLASS_SIMPLE_NAME":
builder.append(String.format("\"%s\"", enumerationName));
break;
case "CLASS_NAME":
builder.append(String.format("\"%s.%s\"", packageName, enumerationName));
break;
case "NAME":
builder.append("e.name()");
break;
case "ORDINAL":
builder.append("e.ordinal()");
break;
default:
builder.append(String.format("\"%s\"", token));
}
}
String result = builder.toString().trim();
if (result.endsWith("+")) {
result = result.substring(0, builder.length() - 1);
}
return result.trim().replaceAll("\" \\+ \"", "");
}
String transformToKey(String pattern, String className, String simpleName, String name, String ordinal) {
String result = pattern;
result = result.replace("@CLASS_NAME@", className);
result = result.replace("@CLASS_SIMPLE_NAME@", simpleName);
result = result.replace("@NAME@", name);
result = result.replace("@ORDINAL@", ordinal);
return result;
}
}
}