fogus (owner)

Revisions

  • b5c66c Fri Jan 23 09:14:05 -0800 2009
gist: 51089 Download_button fork
public
Public Clone URL: git://gist.github.com/51089.git
Embed All Files: show embed
Multimethods.java #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Totally stolen from http://logand.com/sw/jmultimethod/
 
// Multi.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Multi {
 
    public String value();
}
 
// V.java
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface V {
 
    public String value();
}
 
 
// Multimethod.java
public class Multimethod {
 
    protected String name;
    protected final ArrayList methods = new ArrayList();
    protected final MethodComparator methodComparator = new MethodComparator();
 
    public Multimethod(String name, Class... classes) {
        this.name = name;
        for(Class c: classes) {
            add(c);
        }
    }
 
    public void add(Class c) {
        for(Method m: c.getMethods()) {
            for(Annotation ma: m.getAnnotations()) {
                if(ma instanceof Multi) {
                    Multi g = (Multi) ma;
                    if(this.name.equals(g.value())) {
                        methods.add(m);
                    }
                }
            }
        }
        sort();
    }
 
    protected void sort() {
        Method[] a = new Method[methods.size()];
        methods.toArray(a);
        Arrays.sort(a, methodComparator);
        methods.clear();
        for(Method m: a) {
            methods.add(m);
        }
    }
 
    protected class MethodComparator implements Comparator {
        @Override
        public int compare(Method l, Method r) {
            // most specific methods first
            Class[] lc = l.getParameterTypes();
            Class[] rc = r.getParameterTypes();
            for(int i = 0; i < lc.length; i++) {
                String lv = value(l, i);
                String rv = value(r, i);
                if(lv == null) {
                    if(rv != null) {
                        return 1;
                    }
                }
                if(lc[i].isAssignableFrom(rc[i])) {
                    return 1;
                }
            }
            return -1;
        }
    }
 
    protected String value(Method method, int arg) {
        Annotation[] a = method.getParameterAnnotations()[arg];
        for(Annotation p: a) {
            if(p instanceof V) {
                V v = (V) p;
                return v.value();
            }
        }
        return null;
    }
 
    protected boolean isApplicable(Method method, Object... args) {
        Class[] c = method.getParameterTypes();
        for(int i = 0; i < c.length; i++) {
            // must be instanceof and equal to annotated value if present
            if(c[i].isInstance(args[i])) {
                String v = value(method, i);
                if(v != null && !v.equals(args[i])) {
                    return false;
                }
            } else {
                if(args[i] != null || !Object.class.equals(c[i])) {
                    return false;
                }
            }
        }
        return true;
    }
 
    public Object invoke(Object self, Object... args) {
        Method m = null; // first applicable method (most specific)
        for(Method method: methods) {
            if(isApplicable(method, args)) {
                m = method;
                break;
            }
        }
        if(m == null) {
            throw new RuntimeException("No applicable method '" + name + "'.");
        }
        try {
            return m.invoke(self, args);
        } catch (Exception e) {
            throw new RuntimeException("Method invocation failed '" + name + "'.");
        }
    }
}
 
/*
1. Annotate methods with the multimethod name, e.g.
 
@Multi("myMultimethod")
 
The name of the annotated methods can be anything Java is happy with. It is most likely going to be different from the multimethod name because some methods can have prototype similar enough to cause name clashes for the Java compiler (and maybe because the compiler could have problems with the null value). Also, the method should be visible (e.g. public) to the Multimethod class.
2. Process the annotations by creating the Multimethod object, e.g.
 
protected Multimethod mm = new Multimethod("myMultimethod", getClass());
 
3. Define multimethod "entry point" method with parameters as general as necessary. This method dispatches using the Multimethod object created above, e.g.
 
public void myMultimethod(Object X, Object Y) {
mm.invoke(this, X, Y);
}
 
4. And then, the multimethod can be called as any normal Java method, e.g.
 
myMultimethod(1, null);
*/