Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add annotations module #116

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions annotation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
`java-library`
}

group = "io.rpg"
version = "1.0.0"

repositories {
mavenCentral()
}

dependencies {
implementation("com.google.auto.service:auto-service:1.0.1")
annotationProcessor("com.google.auto.service:auto-service:1.0.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks.getByName<Test>("test") {
useJUnitPlatform()
}
108 changes: 108 additions & 0 deletions annotation/src/main/java/io/rpg/annotation/BuilderProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.rpg.annotation;

import com.google.auto.service.AutoService;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Requires parameterless constructor.
*/
@SupportedAnnotationTypes("io.rpg.annotation.BuilderProperty")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {
private final int INDENTATION = 2;
private final String INDENT = " ".repeat(INDENTATION);

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "PROCESSING ANNOTATION");
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);

// Annotated method with one parameter && name wit "set" prefix is recognized as setter
Map<Boolean, List<Element>> annotatedMethods = annotatedElements.stream().collect(Collectors.partitioningBy(element ->
((ExecutableType) element.asType()).getParameterTypes().size() == 1
&& element.getSimpleName().toString().startsWith("set"))
);

List<Element> annotatedSetters = annotatedMethods.get(true);
List<Element> otherMethods = annotatedMethods.get(false);

otherMethods.forEach(element -> {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@BuilderProperty annotation must be" +
" applied to a method with name starting with \"set\" prefix with exactly one parameter", element);
});

if (annotatedElements.isEmpty()) {
continue;
}

String className = ((TypeElement) annotatedSetters.get(0).getEnclosingElement()).getQualifiedName().toString();

Map<String, String> setterMap = annotatedSetters.stream().collect(Collectors.toMap(
setter -> setter.getSimpleName().toString(),
setter -> ((ExecutableType) setter.asType())
.getParameterTypes().get(0).toString()
));

String packageName = null;
int lastDotIndex = className.lastIndexOf('.');

if (lastDotIndex > 0) {
packageName = className.substring(0, lastDotIndex);
}

String simpleClassName = className.substring(lastDotIndex + 1);
String builderClassName = className + "Builder";
String builderSimpleClassName = builderClassName.substring(lastDotIndex + 1);

try {
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName);
try (PrintWriter writer = new PrintWriter(builderFile.openWriter())) {
if (packageName != null) {
writer.println("package " + packageName + ';' + '\n');
}

// begin class definition
writer.println("public class " + builderSimpleClassName + " {");

writer.println(INDENT + "private " + simpleClassName + " object = new " + simpleClassName + "();");

writer.println(INDENT + "public " + simpleClassName + " build() {");
writer.println(INDENT.repeat(2) + "return object;");
writer.println(INDENT + "}");

// generate setters for builder
setterMap.forEach((methodName, argumentType) -> {
writer.println(INDENT + " public " + builderSimpleClassName + " " + methodName + "(" + argumentType + " value) {");
writer.println(INDENT.repeat(2) + "object." + methodName + "(value);");
writer.println(INDENT.repeat(2) + "return this;");
writer.println(INDENT + '}');
});

// end class definition
writer.println('}');

}
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "IOException while creating builder file");
e.printStackTrace();
}
}

return true;
}
}
11 changes: 11 additions & 0 deletions annotation/src/main/java/io/rpg/annotation/BuilderProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.rpg.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface BuilderProperty {
}
6 changes: 6 additions & 0 deletions annotation/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module io.rpg.annotation {
requires java.compiler;
requires com.google.auto.service;

exports io.rpg.annotation;
}
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,27 @@ dependencies {

implementation("com.kkafara.rt:result-type:1.0.3")

implementation(project(":annotation"))

annotationProcessor(project(":annotation"))

testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${junitVersion}")
testImplementation("org.mockito:mockito-core:4.5.1")

}

test {
useJUnitPlatform()
}

tasks.withType(JavaCompile) {
doFirst {
println "AnnotationProcessorPath for $name is ${options.getAnnotationProcessorPath().getFiles()}"
}
}

compileJava {
options.compilerArgs += ["--add-exports=javafx.graphics/com.sun.javafx.scene=io.rpg"]
}
Expand Down
4 changes: 3 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
rootProject.name = "rpg"
rootProject.name = "rpg"
include 'annotation'

8 changes: 7 additions & 1 deletion src/main/java/io/rpg/model/location/LocationModel.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.rpg.model.location;

import com.kkafara.rt.Result;
import io.rpg.annotation.BuilderProperty;
import io.rpg.model.actions.ActionConsumer;
import io.rpg.model.actions.BaseActionEmitter;
import io.rpg.model.actions.LocationChangeAction;
Expand Down Expand Up @@ -36,12 +37,17 @@ public LocationModel(@NotNull String tag, @NotNull List<GameObject> gameObjects)
this.gameObjects = gameObjects;
}

private LocationModel() {
public LocationModel() {
this.positionListeners = new HashMap<>();
this.positionGameObjectMap = new HashMap<>();
this.directionToLocationMap = new HashMap<>();
}

@BuilderProperty
public void setTag(String tag) {
this.tag = tag;
}

public String getTag() {
return tag;
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
requires org.apache.logging.log4j.core;
requires org.apache.commons.io;
requires result.type;
requires io.rpg.annotation;

opens io.rpg.model.location to com.google.gson;
opens io.rpg.model.object to com.google.gson;
Expand Down