I am not sure if this is possible or not, but I would basically like to be able to easily add new items to a list, just by adding a class with a special annotation. The only example I can think of is kind of what I am currently working on. There are a bunch of "challenges" that a user can complete, currently I have a package in my app for the "challenges" and I would like to be able to just create a new class in that package, give it an annotation with some values, like...
@Challenge(key="new_challenge")
public class NewChallenge extends Achievement {... }
That key would be different for each challenge, it will be the challenge name, then in my code I would like to be able to add them all to a HashMap with that "key" as the hashmap key, so it would be map.add(annotationkey, class); It sounds like it should be possible and doable. I am wondering, if it's possible, if it's the right way to go about doing it, and how can it be done?
EDIT1 I was able to get the Reflections implemented with gradle, but it is always coming up as empty. There are no annotations found at all. Here is how I have it..
Challenge.java
package com.test.annotationtest.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Challenge {
public String key();
}
Then for one of my challenges I have... TestChallenge.java
package com.test.annotationtest.challenges
import com.test.annotationtest.R;
import com.test.annotationtest.annotation.Challenge;
@Challenge(key="test_challenge")
public class TestChallenge extends Achievement {
public String name = "Test Challenge";
public TestChallenge() {}
}
Then in my main activity I have... MainActivity.java
package com.test.annotationtest;
import...
public class MainActivity extends AppCompatActivity {
public Reflections reflections = new Reflections("com.test"); // have tried "com.test.annotationtest" and "com.test.annotationtest.challenges"
public static Map<String, Achievement> achievements;
public static List<String> names = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
achievements = getClasses();
//setup recycler view and all that to show achievements
}
public Map<String, Achievement> getClasses() {
Set<Class<?>> challengeClasses = reflections.getTypesAnnotatedWith(Challenge.class);
Log.d("Main Activity", "The Challenges");
Log.d("MAin Activity", "How many are there? " + Integer.toString(challengeClasses.size());
//more processing on them;
}
}
but every time I run the application on my emulator it shows in the logcat.. Main Activity: The Challenges MAin Activity: How many are there? 0
I have 9 classes in the challenges package all annotated with the @Challenge(key="name") in them, but none of them are showing up with the reflections getTypesAnnotatedWith return. The "name" is different for each one. Any help would be appreciated. Thank you.
You can create your own annotation:
@Retention(RetentionPolicy.RUNTIME)
public @interface Challenge {
public String key() default "";
}
Then you may use Reflections to get all annotated classes.
Set<Class<?>> challengeClasses = reflections.getTypesAnnotatedWith(Challenge.class);
Then you can extract your key by:
String key = challengeClass.getAnnotation(Challenge.class).key();
But be advised that reflections should be used carefully and only if really necessary. You are losing control of your code correctness by using it.
Example:
com.test.webapp.Main
package com.test.testapp;
import com.test.testapp.annotations.Challenge;
import org.reflections.Reflections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Reflections reflections = new Reflections("com.test.testapp");
Set<Class<?>> challengeClasses = reflections.getTypesAnnotatedWith(Challenge.class);
Map challengeClassesMap = challengeClasses.stream().collect(
Collectors.toMap(
challengeClass -> challengeClass.getAnnotation(Challenge.class).key(),
Main::createNewInstanceOfClass
)
);
challengeClassesMap.forEach(
(key, challengeClass) -> System.out.println(key + " = " + challengeClass.toString())
);
}
private static <T> T createNewInstanceOfClass(Class<T> someClass) {
try {
return someClass.newInstance();
} catch (Exception e) {
return null; //Bad idea but now it's waste of time
}
}
}
com.test.testapp.annotations.Challenge
package com.test.testapp.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Challenge {
public String key();
}
com.test.testapp.challenges.SomeChallenge
package com.test.testapp.challenges;
import com.test.testapp.annotations.Challenge;
@Challenge(key = "some_challenge")
public class SomeChallenge {
public String name = "Some Challenge";
@Override
public String toString() {
return "SomeChallenge{" +
"name='" + name + '\'' +
'}';
}
}
com.test.testapp.challenges.AnotherChallenge
package com.test.testapp.challenges;
import com.test.testapp.annotations.Challenge;
@Challenge(key = "another_challenge")
public class AnotherChallenge {
public String name = "Another Challenge";
@Override
public String toString() {
return "AnotherChallenge{" +
"name='" + name + '\'' +
'}';
}
}
pom.xml dependency (I'm using Maven)
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
Output
some_challenge = SomeChallenge{name='Some Challenge'}
another_challenge = AnotherChallenge{name='Another Challenge'}
I have no more code. Directories are same as packages.