1

I have a String property that relates to calling a particular method.

I have this DTO, with an icon property

public class MyDto
{
    String icon; // "first", "second", "third" etc

    public String getIcon()
    { 
         return icon;
    }
}

In my interface I have these methods: (GWT ClientBundle)

public interface MyClientBundle extends ClientBundle
{
    @Source("icon_first.jpg")
    ImageResource icon_first();

    @Source("logo_second.jpg")
    ImageResource icon_second();

    @Source("icon_third.jpg")
    ImageResource icon_third();
}

I'm currently using an inefficient lookup with selection statements, but want to select the right method by building a string instead:

public ImageResource getValue(MyDto object)
{
    return getIconFromCode(object.getIcon());
}

private ImageResource getIconFromCode(String code)
{
    if(code.equalsIgnoreCase("first"))
    {
        return resources.icon_first();
    }
    else if(code.equalsIgnoreCase("second"))
    {
        return resources.icon_second();
    }
    else if(code.equalsIgnoreCase("third"))
    {
        return resources.icon_third();
    }
    else
    {
        return resources.icon_default();
    }
}

I want to build a string to select the right method instead, something like "icon_" + object.getIcon()+ "()"

Having done some research I understand I need to use reflection? How would this be accomplished?

slugmandrew
  • 1,776
  • 2
  • 25
  • 45
  • What are you using this for? Reflection is somewhat complicated and probably not what you want, I'm guessing. You could use an array, ArrayList, or HashMap for quick access. An array and ArrayList will give you look up via int index, and HashMap will let you index via any key, including an int index. Then, inside MyClientBundle, you could make one getter method that takes a key/index parameter and return the associated ImageResource. – stoooops Oct 28 '13 at 18:23
  • The ClientBundle is a GWT interface that creates a concrete class where each method maps to a particular image. I want to render the right image each time but I don't want a switch (or if-elses) with 20 or so elements in it. – slugmandrew Oct 28 '13 at 18:27
  • I don't think I can add a method to MyClientBundle as it's an interface and the actual class is created by a code generator. I'm being a dumbass anyway as reflection is not supported by the GWT emulator. Any suggestions welcome :) – slugmandrew Oct 28 '13 at 18:30
  • Why do you not want any if-elses? You could use enum/int keys and a switch table if you're concerned about efficiency, but generally its unnecessary to optimize at that level. – stoooops Oct 28 '13 at 18:37
  • I just thought there would be a more elegant solution than 20 if-elses, that's all. – slugmandrew Oct 28 '13 at 18:38
  • 1
    If you have 20, then you should be using a collection class like an array, ArrayList, or HashMap. Otherwise, a 20-line switch statement is about as clean as it will get. Reflection would get complicated. And you need to define all the methods before hand, so you would need to write 20 full methods. – stoooops Oct 28 '13 at 18:41
  • I guess I'll stick with how it is then. Thanks – slugmandrew Oct 28 '13 at 18:47

2 Answers2

4

The interface MyClientBundle should extends ClientBundleWithLookup instead of ClientBundle.

The ClientBundleWithLookup has a getResource(String name) method that lets you retrieve the resource from the resource name (method name).

Fedy2
  • 3,147
  • 4
  • 26
  • 44
2

Cache the getter methods by the icon name from the annotation and use that cache to call the getters.

The code below only gets the annotations from interfaces one level up. If you need to go higher for some reason, just recursively call the cacheGettersFor() method.

There is no check here to make sure the icon getter methods have the right signature (take no arguments, return ImageResource), you'll want to add that if you use any of this code.

public class IconGetterCache {
    class IconGetterCacheForBundleType {
        private Map<String, Method> _iconGetters = new HashMap<>();
        private Class<?> _runtimeClass;

        private void cacheGettersFor(Class<?> forClazz) {
            for (Method method : forClazz.getMethods()) {
                Source iconSourceAnnotation = method.getAnnotation(Source.class);
                if (iconSourceAnnotation != null) {
                    _iconGetters.put(iconSourceAnnotation.value(), method);
                }
            }
        }

        IconGetterCacheForBundleType(final Class<?> runtimeClass) {
            _runtimeClass = runtimeClass;
            for (Class<?> iface : _runtimeClass.getInterfaces()) {
                cacheGettersFor(iface);
            }
            cacheGettersFor(_runtimeClass);
        }

        public ImageResource getIconFromBundle(ClientBundle bundle, String iconName) {

            if (!_runtimeClass.isAssignableFrom(bundle.getClass())) {
                throw new IllegalArgumentException("Mismatched bundle type");
            }

            Method getter = _iconGetters.get(iconName);
            if (getter == null) { return null; }
            try {
                return (ImageResource) getter.invoke(bundle);
            }
            catch (Throwable t) {
                throw new RuntimeException("Could not get icon", t);
            }
        }
    }

    private Map<Class<?>, IconGetterCacheForBundleType> _getterCaches = new HashMap<>();

    //main entry point, use this to get your icons
    public ImageResource getIconFromBundle(ClientBundle bundle, String iconName) {
        final Class<? extends ClientBundle> getterClass = bundle.getClass();
        IconGetterCacheForBundleType getterCache = _getterCaches.get(getterClass);
        if (getterCache == null) {
            _getterCaches.put(getterClass, getterCache = new IconGetterCacheForBundleType(getterClass));
        }
        return getterCache.getIconFromBundle(bundle, iconName);
    }
}


//Here's how I tested
IconGetterCache gc = new IconGetterCache();
MyClientBundle concreteClientBundle = new MyClientBundle() {
    @Override
    public ImageResource icon_first() {
       //return correct icon
    }

    @Override
    public ImageResource icon_second() {
        //return correct icon
    }

    @Override
    public ImageResource icon_third() {
        //return correct icon
    }
};
gc.getIconFromBundle(concreteClientBundle, "icon_first.jpg");
Wes Cumberland
  • 1,318
  • 8
  • 12