Question

I'm attempting to make a system similar to https://github.com/ElgarL/TownyChat/blob/master/src/com/palmergames/bukkit/TownyChat/TownyChatFormatter.java

replacer.registerFormatReplacement(Pattern.quote("{worldname}"), new TownyChatReplacerCallable() {
        @Override
        public String call(String match, LocalTownyChatEvent event) throws Exception {
            return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName());
        }
    });
    replacer.registerFormatReplacement(Pattern.quote("{town}"), new TownyChatReplacerCallable() {
        @Override
        public String call(String match, LocalTownyChatEvent event) throws Exception {
            return event.getResident().hasTown() ? event.getResident().getTown().getName() : "";
        }
    });

and more.

Is there a way to use annotations to cut down on the amount of repeated code, avoiding reflection to call the call method, and only using it during registration, if at all?

I'm not adverse to the idea of creating an annotation pre processor as I was already planning on doing this to enable automatically generating documentation.

Was it helpful?

Solution

Let's assume you write a small Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface PatternHandler {
    String value();
}

And create a class like

class Callables {

    @PatternHandler("foo")
    public static final TownyChatReplacerCallable FOO = new TownyChatReplacerCallable() {
        @Override
        public String call(String match, String event) {
            return "This is foo handler called with " + match + "," + event;
        }
    };

    @PatternHandler("bar")
    public static final TownyChatReplacerCallable BAR = new TownyChatReplacerCallable() {
        @Override
        public String call(String match, String event) {
            return "This is foo handler called with " + match + "," + event;
        }
    };
}

Now you can take the whole class or even multiple classes that contain those static fields and pass it to some registry method that iterates reflectively over each field in that class and if it's an annotated callable registers that.

class AnnotationRegistry {
    public static void register(String pattern, TownyChatReplacerCallable handler) {}

    public static void register(Class<?> clazz) {
        // only fields declared by this class, not inherited ones (static fields can't be inherited)
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // must have that annotation
            PatternHandler annotation = field.getAnnotation(PatternHandler.class);
            if (annotation != null) {
                // must be static
                if (!Modifier.isStatic(field.getModifiers())) {
                    System.out.println("Field must be static:" + field.getName());
                    continue;
                }
                // get content of that field
                try {
                    Object object = field.get(null);
                    // must be != null and a callable
                    if (object instanceof TownyChatReplacerCallable) {
                        register(annotation.value(), (TownyChatReplacerCallable) object);
                    } else {
                        System.out.println("Field must be instanceof TownyChatReplacerCallable:"  + field.getName());
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

That would save you a bit code and would have no speed disadvantage at runtime since there is no need to use reflection to call those callables.

Full example here: http://ideone.com/m3PPcY

Besides using static fields, you can also use non static ones if you pass an instance of a class to the registry which would then be used like Object object = field.get(instance); instead of the null.

Furthermore, instead of fields the same approach would work with methods which would be less code to write:

@PatternHandler("foo")
public static String fooMethod(String match, String event) {
    return "This is foo handler called with " + match + "," + event;
}

Registry would then look for all Methods. Then for example wrap them in

class MethodAdapter implements TownyChatReplacerCallable {
    private final Method method;
    public MethodAdapter(Method m) {
        method = m;
    }
    @Override
    public String call(String match, String event) {
        try {
            return (String) method.invoke(null, match, event);
        } catch (Exception e) {
            e.printStackTrace();
            return "OMGZ";
        }
    }
}

and continue as usual. But beware: invoking a method reflectively is potentially slower than calling it directly via code - few percent only, nothing to worry about

Full example for methods: http://ideone.com/lMJsrl

OTHER TIPS

You can try new Java 8 Lambda Expressions instead (http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html).

replacer.registerFormatReplacement(Pattern.quote("{worldname}"), new TownyChatReplacerCallable() {
        @Override
        public String call(String match, LocalTownyChatEvent event) throws Exception {
            return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName());
        }
    });

Can be written as :

replacer.registerFormatReplacement(
  Pattern.quote("{worldname}"), 
  (match, event) -> { return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName()); } 
});

You can also push it further with another interface, method, ... that wrap it

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top