1

The idea I'm going for is that I have a bunch of actions/functions that happen in our program. They're all predefined and separated into categories. So there might be a category for admin actions that then defines a bunch of static codes for actions that are in the admin actions category.

Since the categories and actions are fixed, they're all in static classes.

These static category classes all implement an interface, ICategory:

public static interface ICategory{
    int getCateogory();
    String getCategoryName();
    String getFunctionName(int function);
}

Each of these static classes is added to a static Map:

private static Map<Integer, Class<? extends ICategory>> catMap = new HashMap<Integer, Class<? extends ICategory>>();

Basically there's an integer code associated with each category. What I'm trying to do is just made a human readable string that I can print out when I receive the category and action codes. What I would like to do is something like

ICategory whatever = catMap.get(catNumber);
System.out.println(whatever.getCategoryName());
System.out.println(whatever.getFunctionName(actionCode));

So catMap.get(catNumber) will actually return the proper static class, but I then don't know how I can use that returned class to access these static methods. I can do it with regular instances of a class, just fine, but doing it with static classes has got me puzzled.

Clarification of Problem:

Some Clarification of The problem I'm trying to solve in case you guys have suggestions of better / more intuitive ways to do this:

Basically I'm interpreting commands from some piece of custom hardware at my company. It's a little data collection gizmo that has a bunch of predefined messages/functions that I have to interpret.

These functions are split into various categories: Display, Keypad, Acquisition, etc.

So basically I have a mapping like this:

  Display Category: 128
      ShowGraph: 01
      ShowText: 02
  Keypad Category: 129
      F1: 01
      F2: 02
      MenuKey: 03

I'm making a little stream display that prints the stream of commands out in human readable format. So I'd just print out a big list of something like

Got Category Display, Function ShowGraph
Got Category Keypad, Function MenuKey

Normally I'd use a map for this, but what I want is to also use the functions in each category as constants because I'll have to reference them in if-statements and often times send those same categories back to the little gizmo.

For Instance:

sendMessage(Categories.DisplayCategory.getCategoryInt(), Categories.DisplayCategory.SHOW_GRAPH);

More Code as requested:

public class Functions {

    public static interface ICategory{
        int getCateogory();
        String getCategoryName();
        String getFunctionName(int function);
    }

    private static Map<Integer, Class<? extends ICategory>> catMap = new HashMap<Integer, Class<? extends ICategory>>();

    public static String getCategoryString(int category) {
        Class<? extends ICategory> clazz = catMap.get(category);
        System.out.println(catMap.toString());

        if(clazz != null){
            try{
                Method m = clazz.getMethod("getCategoryName", Integer.class);
                return (String) m.invoke(0, category);
            }catch (Exception e){
                return null;
            }

        }else{
            System.out.println("clazz was null");
            return null;
        }
    }

    public static class SystemKey implements ICategory{
        public static int CATEGORY = 134;
        private static Map<Integer, String> fmap = new HashMap<Integer, String>();
        @Override
        public int getCateogory() {
            return CATEGORY;
        }

        @Override
        public String getCategoryName() {
            return "SystemKey";
        }

        @Override
        public String getFunctionName(int function) {
            return fmap.get(function);
        }
    }

    public static class SystemCat implements ICategory{
        public static int CATEGORY = 128;
        private static Map<Integer, String> fmap = new HashMap<Integer, String>();

        public static final int POWER_UP = 0x01;
        public static final int END_OF_TRANSMIT = 0x02;
        public static final int CLEAR_TO_SEND = 0x03;
        public static final int NET_TEST = 0x05; /*Fom station to ctrlr*/ 
        public static final int NET_OK = 0x06; /*Response to controller*/
        public static final int MAIN_MENU = 0x07;

        static{
            catMap.put(CATEGORY, SystemCat.class);

            fmap.put(POWER_UP, "POWER_UP"); 
            fmap.put(END_OF_TRANSMIT, "END_OF_TRANSMIT"); 
            fmap.put(CLEAR_TO_SEND, "CLEAR_TO_SEND");
            fmap.put(NET_TEST, "NET_TEST"); 
            fmap.put(NET_OK, "NET_OK"); 
            fmap.put(MAIN_MENU, "MAIN_MENU");
        }


        @Override
        public int getCateogory() {
            return CATEGORY;
        }

        @Override
        public String getCategoryName() {
            return "System";
        }

        @Override
        public String getFunctionName(int function) {
            return fmap.get(function);
        }
    }




    public static class SoftKey implements ICategory{
        public static int CATEGORY = 129;

        private static Map<Integer, String> fmap = new HashMap<Integer, String>();

        public static final int F1 = 0x20;
        public static final int F2 = 0x21;
        public static final int F3 = 0x22;
        public static final int F4 = 0x23;
        public static final int F5 = 0x24;

        static{
            catMap.put(CATEGORY, SoftKey.class);

            fmap.put(F1, "F1");
            fmap.put(F2, "F2");
            fmap.put(F3, "F3");
            fmap.put(F4, "F4");
            fmap.put(F5, "F5");

        @Override
        public int getCateogory() {
            return CATEGORY;
        }

        @Override
        public String getCategoryName() {
            return "SoftKey";
        }

        @Override
        public String getFunctionName(int function) {
            return fmap.get(function);
        }

    }


    public static void main (String[] args) throws Exception{


         System.out.println(Functions.getCategoryString(128));
    }

}
MechaMarinara
  • 620
  • 1
  • 7
  • 22
  • 1
    There's no reason why these things need to be `static`. Just attach them to instances and have those be the contents of the `Map`. Generally, in Java, avoid making things `static` unless they're pure utility functions. In your case clearly you have alternate implementations of the same concept. – millimoose May 14 '13 at 00:26
  • The code you want to write will work because those methods are instance methods. The keyword static on the interface declaration doesn't mean the methods inside are then static... – cambecc May 14 '13 at 00:35

2 Answers2

1

Update

As I suspected, the solution is quite simple. There are different ways to do this, here is one, I seem to remember calling it Registry, back in the days when Patterns were known as Idioms. You are almost there, what you need is following changes:

  1. Change catMap type from Map<String,Class<? extends ICategory> to Map<Integer, ICategory>.

  2. In the static initializers create an object and put it in the map, e.g.

    public static class SoftKey implements ICategory{ ....

    static{
        catMap.put(CATEGORY, new SoftKey());
    
  3. In getCategoryString use the ICategory object in the registry:

    ICategory categ = catMap.get(category); return categ.getCategoyString()


I might have misunderstood the question, but part of it are confusing:

So catMap.get(catNumber) will actually return the proper static class,

By static class I assume you mean that the interfaces are nested inside some class/interface. There is no such thing as a top-level static class in Java. get returns an Object of a static class, not a class.

but I then don't know how I can use that returned class to access these static methods.

The methods you have declared are not static, they are instance methods

I can do it with regular instances of a class, just fine, but doing it with static classes has got me puzzled.

I am puzzled too. You can call instance methods on objects of static class. Can you post a complete code sample?

Miserable Variable
  • 28,432
  • 15
  • 72
  • 133
  • I've updated with some clarification. I thought about doing this with enums, but I can't think of a way to do the categories. I want that reverse mapping with it. I also can't think of a way to nest the category enums into a larger one that maps each category. – MechaMarinara May 14 '13 at 01:29
  • I am pretty sure you are misunderstanding the problem and it will all "just work". You posted problem clarification and while I still haven't understood the problem what you need to post is more code, so we can show you that it works. – Miserable Variable May 14 '13 at 02:00
  • Okay this solution works. I had thought about doing it that way before by adding instances of the objects to the map, but for some reason I got it in my head that I couldn't access the static members from the instance object and discounted the whole approach. – MechaMarinara May 14 '13 at 12:47
  • Gad it worked for you, it is perfectly ok with enough justification to use static members from instance members, the other way is not possible. – Miserable Variable May 14 '13 at 16:38
1

Assuming you know all the codes in advance, and there aren't 1000s of function values, this would work. The non-uniqueness of the function value codes isn't a problem as long as you don't mind looking through a container to find them (as opposed to a Map).

You could do away with the static maps completely if you don't mind looping through all the enum values all the time. This could be perfectly acceptable if you don't do lookups very often.

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


public enum FunctionCategory {

    DISPLAY(128, "Display"),
    KEYPAD(129, "KeyPad");
    // more categories here...
    private final int code;
    private final String name;

    private static Map<Integer, FunctionCategory> categoryMap = new HashMap<>();

    static {
        for( FunctionCategory c : FunctionCategory.values() ) {
            categoryMap.put(c.getCode(), c);
        }
    }

    // For looking up a category from its code
    public static FunctionCategory fromCode( int code ) {
        return categoryMap.get(code);
    }

    private FunctionCategory(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return code;
    }

    public String getName() {
        return name;
    }


    public static enum FunctionValue {

        // DISPLAY
        DISPLAY_GRAPH(DISPLAY, 1, "Graph"),
        DISPLAY_TEXT(DISPLAY, 2, "ShowText"),
        //KEYPAD
        KEYPAD_MENU(KEYPAD, 1, "MenuKey"),
        KEYPAD_ENTER(KEYPAD, 2, "EnterKey");
            // TODO, others
        private static Map<FunctionCategory, Set<FunctionValue>> codeMapping = new EnumMap<>( FunctionCategory.class );

        static {
            for( FunctionValue fv : FunctionValue.values() ) {
                Set<FunctionValue> values = codeMapping.get(fv.getCategory());
                if( values == null ) {
                    values = EnumSet.of(fv);
                }
                else {
                    values.add(fv);
                }
                codeMapping.put(fv.getCategory(), values);
            }
        }

            // First we look up the category, then we just loop over all the values
            // within that category. Unless you have lots of values, or really need
            // to optimize the lookups, there is no need to do something more complex
        public static FunctionValue getFromCodes( int categoryCode, int valueCode ) {
            FunctionCategory c = FunctionCategory.fromCode(categoryCode);
            if( c != null ) {
                Set<FunctionValue> valueSet = codeMapping.get(c);
                if( valueSet != null ) {
                    // Just spin through them, there aren't that many
                    for( FunctionValue v : valueSet ) {
                        if( v.getCode() == valueCode ) {
                            return v;
                        }
                    }
                }
            }
            return null;
        }

        private final FunctionCategory category;
        private final int code;
        private final String name;
        private FunctionValue(FunctionCategory category, int code, String name) {
            this.category = category;
            this.code = code;
            this.name = name;
        }
        public FunctionCategory getCategory() {
            return category;
        }
        public int getCode() {
            return code;
        }
        public String getName() {
            return name;
        }       
    }
}
wolfcastle
  • 5,850
  • 3
  • 33
  • 46
  • I've been trying to think of a way to use an enum for it, but your example isn't quite what I'm going for. Think of it like it's nested. I have Function Categories, each which have a name and an integer that defines them. In each of those function categories are functions, which have a integer and string name associated with them. Function values are not unique across all categories; they're only unique within each category. So I could have Display:128{Graph:01,ShowText:02} and KeyPad:129{MenuKey:01, EnterKey:02} where the text is the same as the category and function names in this case – MechaMarinara May 14 '13 at 02:40
  • @CryptDemon okay, then can't you have 2 enums? One for `FunctionCategory` that just has an int and String, then a `FunctionValue` that has a parent `FunctionCategory` as well as an int and String. You would need a bit more complex function to look one up based on an numeric code, but you could definitely do it. – wolfcastle May 14 '13 at 05:08