test(annotations): New @RequireAnnotation

New annotation @RequireAnnotation can be used on annotations so that all
classes with that specific annotation must also have another annotation.
If this is not true, test will not pass. This annotation takes array of
annotations.
This commit is contained in:
Jakub Žitník 2025-03-16 20:37:24 +01:00
parent e1190f7e6a
commit 53a3ae43f3
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
15 changed files with 72 additions and 42 deletions

5
.idea/misc.xml generated
View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<writeAnnotations>
<writeAnnotation name="cz.jzitnik.game.annotations.AutoTransient" />
</writeAnnotations>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">

View File

@ -7,6 +7,7 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(BlockRegistry.class)
public @interface BlockDropPercentage {
int value();
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RequireAnnotation(BlockRegistry.class)
public @interface BreakableByWater {
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(BlockRegistry.class)
public @interface BreaksByPlace {
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(BlockRegistry.class)
public @interface BreaksFalling {
}

View File

@ -5,6 +5,7 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(CustomDrops.class)
@RequireAnnotation(BlockRegistry.class)
public @interface CustomDrop {
String tool();
String drops();

View File

@ -7,6 +7,7 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(ItemRegistry.class)
public @interface Fuel {
double value();
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RequireAnnotation(BlockRegistry.class)
public @interface PlaceOnSolid {
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RequireAnnotation(BlockRegistry.class)
public @interface PlaceOnSolidNoHandler {
}

View File

@ -0,0 +1,9 @@
package cz.jzitnik.game.annotations;
import java.lang.annotation.*;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAnnotation {
Class<? extends Annotation>[] value();
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RequireAnnotation(BlockRegistry.class)
public @interface ResetDataOnMine {
}

View File

@ -7,5 +7,6 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(BlockRegistry.class)
public @interface Sapling {
}

View File

@ -7,6 +7,7 @@ import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RequireAnnotation(ItemRegistry.class)
public @interface Smeltable {
String value();
}

View File

@ -4,7 +4,7 @@ import cz.jzitnik.game.annotations.CraftingRecipeRegistry;
@CraftingRecipeRegistry(
recipe = {
"Allium", "_", "_",
"allium", "_", "_",
"_", "_", "_",
"_", "_", "_"
},

View File

@ -1,14 +1,12 @@
package cz.jzitnik.game.handlers.place;
import cz.jzitnik.game.annotations.BlockDropPercentage;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.annotations.PlaceOnSolid;
import cz.jzitnik.game.annotations.ResetDataOnMine;
import cz.jzitnik.game.annotations.*;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.reflections.Reflections;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;
@ -27,7 +25,6 @@ class PlaceHandlerTest {
}
}
@Test
@DisplayName("All blocks in annotation @PlaceHandler must exist")
void allBlocksMustExist() {
@ -43,47 +40,55 @@ class PlaceHandlerTest {
}
}
@Test
@DisplayName("All blocks annotated with @PlaceOnSolid must be also annotated with @BlockRegistry")
void placeOnSolidTest() {
Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
Set<Class<?>> handlerClasses = reflections
.getTypesAnnotatedWith(PlaceOnSolid.class);
/*@Test
@DisplayName("All blocks must be annotated with @BlockRegistry if they are annotated with certain other annotations")
void checkAnnotationsWithBlockRegistry() {
// Define annotations and their names
Object[][] annotationsToCheck = {
{PlaceOnSolid.class, "@PlaceOnSolid"},
{ResetDataOnMine.class, "@ResetDataOnMine"},
{BlockDropPercentage.class, "@BlockDropPercentage"},
{BreakableByWater.class, "@BreakableByWater"},
{BreaksByPlace.class, "@BreaksByPlace"},
{BreaksFalling.class, "@BreaksFalling"},
{Fuel.class, "@Fuel"},
{Smeltable.class, "@Smeltable"},
};
for (Class<?> clazz : handlerClasses) {
boolean hasAnnotation = clazz.isAnnotationPresent(BlockRegistry.class);
assertTrue(hasAnnotation, "Class " + clazz.getName() + " is annotated with @PlaceOnSolid but does not have annotation @BlockRegistry");
// Loop through each annotation and check for @BlockRegistry
for (Object[] annotationData : annotationsToCheck) {
Class<?> annotationClass = (Class<?>) annotationData[0];
String annotationName = (String) annotationData[1];
checkBlockRegistryAnnotationForClassesWithAnnotation(annotationClass, annotationName);
}
}*/
@Test
void checkAnnotationValidity() {
Reflections reflections = new Reflections("cz.jzitnik.game");
Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(RequireAnnotation.class);
for (Class<?> annotation : annotations) {
if (!annotation.isAnnotation()) {
continue;
}
Set<Class<?>> classes = reflections.getTypesAnnotatedWith((Class<? extends Annotation>) annotation);
var anot = annotation.getAnnotation(RequireAnnotation.class);
for (Class<?> clazz : classes) {
for (Class<? extends Annotation> annotationRequired : anot.value()) {
if (!clazz.isAnnotationPresent(annotationRequired)) {
fail("All classes annotated with @" + annotation.getSimpleName() + " must be also annotated with @" + annotationRequired.getSimpleName() + " but class " + clazz.getName() + " isn't.");
}
}
}
}
}
@Test
@DisplayName("All blocks annotated with @ResetDataOnMine must be also annotated with @BlockRegistry")
void resetDataOnMineTest() {
Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
Set<Class<?>> handlerClasses = reflections
.getTypesAnnotatedWith(ResetDataOnMine.class);
for (Class<?> clazz : handlerClasses) {
boolean hasAnnotation = clazz.isAnnotationPresent(BlockRegistry.class);
assertTrue(hasAnnotation, "Class " + clazz.getName() + " is annotated with @ResetDataOnMine but does not have annotation @BlockRegistry");
}
}
@Test
@DisplayName("All blocks annotated with @BlockDropPercentage must be also annotated with @BlockRegistry")
void blockDropPercentageTest() {
Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
Set<Class<?>> handlerClasses = reflections
.getTypesAnnotatedWith(BlockDropPercentage.class);
for (Class<?> clazz : handlerClasses) {
boolean hasAnnotation = clazz.isAnnotationPresent(BlockRegistry.class);
assertTrue(hasAnnotation, "Class " + clazz.getName() + " is annotated with @BlockDropPercentage but does not have annotation @BlockRegistry");
}
}
@Test
@DisplayName("All blocks annotated with @ResetDataOnMine must have data class with default constructor")
@DisplayName("All blocks annotated with @ResetDataOnMine must have data class.")
void dataOnMineHasDataTest() {
Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
Set<Class<?>> handlerClasses = reflections