Search code examples
javaandroidhashmapannotations

Get all classes with an annotation and add them to a hashMap in android


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.


Solution

  • 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.