.
@biziclop, @bayou.io @Aasmund Eldhuset 3 , , . , , OP (). , , .
@Radiodef, , , , , maven. , maven , , Apache Maven . , maven, 2.
, , , , :
maven: org.apache.velocity: speed: 1.7: jar.
, . , POM.
4 POMs:
- RootProject
- ActualProject
- AnnotationProcessors
, RootProject - , , pom :
<modules>
<module>ActualProject</module>
<module>Annotations</module>
<module>AnnotationProcessors</module>
</modules>
<!— Global dependencies can be configured here as well —>
, , , AnnotationProcessors. AnnotationProcessors Annotation, maven:
- AnnotationProcessors
- ActualProject
, , . , -proc:none:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<compilerArgs>
<arg>-proc:none</arg>
</compilerArgs>
</configuration>
</plugin>
maven-processor-plugin build-helper-maven:
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processors>
<processor>my.annotations.processors.MessageListProcessor</processor>
</processors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
, , , String , .
, , , , String getKey() String[] getParams(). () :
@MessageList("my.config.file.wrapper.type")
public enum Messages implements MessageInfo {
NO_PERMISSION("no_permission"),
YOU_DIED("you_died", "score"),
PLAYER_LEFT("player_left", "player_name", "server_name");
private String key;
private String[] params;
Messages(String key, String… params) {
this.key = key;
this.params = params;
@Override
public String getKey() { return key; }
@Override
public String[] getParams() { return params; }
}
, AnnotationProcessor. , AbstractProcessor , , @ . @SupportedAnnotationTypes("my.annotation.type"). . , , , , , foreach. , one @MessageList . , , , . , , .
( , .)
for (Element e : roundEnv.getElementsAnnotatedWith(MessageList.class)) {
if (!(e.getKind() == ElementKind.ENUM)) {
raiseErrorAt(e, "Can only annotate enum types");
continue;
} ... }
, . : . MessageInfo :
Class<MessageInfo> messageInfoClass = (Class<MessageInfo>) Class.forName("my.annotations.MessageInfo");
, , , ClassCastException. , . , musnt , . , .properties. , , , .
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
new File("ActualProject/" + convertToPath(element.getQualifiedName().toString())));
String classpath = getCurrentClasspath(false) +
new File("Annotations/target/Annotations-version.jar").getAbsolutePath();
File outputDir = new File("ActualProject/target/classes/");
Iterable<String> arguments = Arrays.asList("-proc:none",
"-d", outputDir.getAbsolutePath(),
"-classpath", classpath);
boolean success = compiler.getTask(null, fileManager, null, arguments, null, compilationUnits).call();
fileManager.close();
, , , false.
getCurrentClassPath:
private String getCurrentClasspath(boolean trim) {
StringBuilder builder = new StringBuilder();
for (URL url : ((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs()) {
builder.append(new File(url.getPath()));
builder.append(System.getProperty("path.separator"));
}
String classpath = builder.toString();
return trim ? classpath.substring(0, classpath.length() - 1) : classpath;
}
, , :
URL classesURL = new URL("file://" + outputDir.getAbsolutePath() + "/");
URLClassLoader customCL = URLClassLoader.newInstance(new URL[]{classesURL}, classLoader);
Class<?> annotatedClass = customCL.loadClass(element.getQualifiedName().toString());
, , :
if (!Arrays.asList(annotatedClass.getInterfaces()).contains(messageInfoClass)) {
raiseErrorAt(element, "Can only annotate subclasses of MessageInfo");
continue;
}
, :
MessageList annotation = element.getAnnotation(MessageList.class);
String locals = annotation.value();
Element enclosingElement = element;
while (!((enclosingElement = enclosingElement.getEnclosingElement()) instanceof PackageElement)) ;
String packageName = ((PackageElement) enclosingElement).getQualifiedName().toString();
ArrayList<Message> messages = new ArrayList<>();
for (Field field : annotatedClass.getDeclaredFields()) {
if (!field.isEnumConstant()) continue;
Object value = field.get(null);
MessageInfo messageInfo = messageInfoClass.cast(value);
messages.add(new Message(field.getName(), messageInfo.getKey(), messageInfo.getParams()));
}
Message - getter. , , .
! Velocity . - .
, 3 , , ...
#set ($doubleq = '"')
#set ($opencb = "{")
#set ($closecb = "}")
package $package;
foreach:
/**
* This class was generated by the Annotation Processor for the project ActualProject.
*/
public abstract class Message {
#foreach ($message in $messages)
#set ($args = "")
#set ($replaces = "")
#foreach ($param in $message.params)
#set ($args = "${args}String $param, ")
#set ($replaces = "${replaces}.replace($doubleq$opencb$param$closecb$doubleq, $param)")
#end
#set ($endIndex = $args.length() - 2)
#if ($endIndex < 0)
#set ($endIndex = 0)
#end
#set ($args = $args.substring(0, $endIndex))
public static final String ${message.name}($args) {
return locals.getMessage("$message.key")$replaces;
}
#end
private static final $locals locals = ${locals}.getInstance();
}
Velocity , . , , . , ? . :
- String, args
- :
- "String" args.
- ".replace(" {} ",)" params.
- args. ( , endIndex . , endIndex 0.)
- , 2 3.
Locals. , , , , . , .
, raiseErrorAt (Element, String), , , , processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element);
, . . , , . .
- , .