~hudson-ubuntu/+junk/hudson-jexl

« back to all changes in this revision

Viewing changes to src/java/org/apache/commons/jexl/util/introspection/MethodMap.java

  • Committer: James Page
  • Date: 2011-01-18 18:03:16 UTC
  • Revision ID: james.page@canonical.com-20110118180316-zl0h9qww7s8box16
InitialĀ releaseĀ 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
 
3
 * contributor license agreements.  See the NOTICE file distributed with
 
4
 * this work for additional information regarding copyright ownership.
 
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
 
6
 * (the "License"); you may not use this file except in compliance with
 
7
 * the License.  You may obtain a copy of the License at
 
8
 *
 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
 
10
 *
 
11
 * Unless required by applicable law or agreed to in writing, software
 
12
 * distributed under the License is distributed on an "AS IS" BASIS,
 
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
14
 * See the License for the specific language governing permissions and
 
15
 * limitations under the License.
 
16
 */
 
17
 
 
18
package org.apache.commons.jexl.util.introspection;
 
19
 
 
20
import java.lang.reflect.Method;
 
21
import java.util.ArrayList;
 
22
import java.util.Hashtable;
 
23
import java.util.Iterator;
 
24
import java.util.LinkedList;
 
25
import java.util.List;
 
26
import java.util.Map;
 
27
 
 
28
/**
 
29
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 
30
 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
 
31
 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
 
32
 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 
33
 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
 
34
 * @version $Id: MethodMap.java 584046 2007-10-12 05:14:37Z proyal $
 
35
 * @since 1.0
 
36
 */
 
37
public class MethodMap {
 
38
    /**
 
39
     * whether a method is more specific than a previously compared one.
 
40
     */
 
41
    private static final int MORE_SPECIFIC = 0;
 
42
    /**
 
43
     * whether a method is less specific than a previously compared one.
 
44
     */
 
45
    private static final int LESS_SPECIFIC = 1;
 
46
    /**
 
47
     * A method doesn't match a previously compared one.
 
48
     */
 
49
    private static final int INCOMPARABLE = 2;
 
50
 
 
51
    /**
 
52
     * Keep track of all methods with the same name.
 
53
     */
 
54
    Map methodByNameMap = new Hashtable();
 
55
 
 
56
    /**
 
57
     * Add a method to a list of methods by name. For a particular class we are
 
58
     * keeping track of all the methods with the same name.
 
59
     *
 
60
     * @param method the method.
 
61
     */
 
62
    public void add(Method method) {
 
63
        String methodName = method.getName();
 
64
 
 
65
        List l = get(methodName);
 
66
 
 
67
        if (l == null) {
 
68
            l = new ArrayList();
 
69
            methodByNameMap.put(methodName, l);
 
70
        }
 
71
 
 
72
        l.add(method);
 
73
    }
 
74
 
 
75
    /**
 
76
     * Return a list of methods with the same name.
 
77
     *
 
78
     * @param key
 
79
     * @return List list of methods
 
80
     */
 
81
    public List get(String key) {
 
82
        return (List) methodByNameMap.get(key);
 
83
    }
 
84
 
 
85
    /**
 
86
     * <p>
 
87
     * Find a method.  Attempts to find the
 
88
     * most specific applicable method using the
 
89
     * algorithm described in the JLS section
 
90
     * 15.12.2 (with the exception that it can't
 
91
     * distinguish a primitive type argument from
 
92
     * an object type argument, since in reflection
 
93
     * primitive type arguments are represented by
 
94
     * their object counterparts, so for an argument of
 
95
     * type (say) java.lang.Integer, it will not be able
 
96
     * to decide between a method that takes int and a
 
97
     * method that takes java.lang.Integer as a parameter.
 
98
     * </p>
 
99
     *
 
100
     * <p>
 
101
     * This turns out to be a relatively rare case
 
102
     * where this is needed - however, functionality
 
103
     * like this is needed.
 
104
     * </p>
 
105
     *
 
106
     * @param methodName name of method
 
107
     * @param args       the actual arguments with which the method is called
 
108
     * @return the most specific applicable method, or null if no
 
109
     *         method is applicable.
 
110
     * @throws AmbiguousException if there is more than one maximally
 
111
     *                            specific applicable method
 
112
     */
 
113
    public Method find(String methodName, Object[] args)
 
114
            throws AmbiguousException {
 
115
        List methodList = get(methodName);
 
116
 
 
117
        if (methodList == null) {
 
118
            return null;
 
119
        }
 
120
 
 
121
        int l = args.length;
 
122
        Class[] classes = new Class[l];
 
123
 
 
124
        for (int i = 0; i < l; ++i) {
 
125
            Object arg = args[i];
 
126
 
 
127
            /*
 
128
             * if we are careful down below, a null argument goes in there
 
129
             * so we can know that the null was passed to the method
 
130
             */
 
131
            classes[i] =
 
132
                    arg == null ? null : arg.getClass();
 
133
        }
 
134
 
 
135
        return getMostSpecific(methodList, classes);
 
136
    }
 
137
 
 
138
    /**
 
139
     * Simple distinguishable exception, used when
 
140
     * we run across ambiguous overloading.  Caught
 
141
     * by the introspector.
 
142
     */
 
143
    public static class AmbiguousException extends RuntimeException {
 
144
        /**
 
145
         * Version Id for serializable
 
146
         */
 
147
        private static final long serialVersionUID = -2314636505414551663L;
 
148
    }
 
149
 
 
150
 
 
151
    private static Method getMostSpecific(List methods, Class[] classes)
 
152
            throws AmbiguousException {
 
153
        LinkedList applicables = getApplicables(methods, classes);
 
154
 
 
155
        if (applicables.isEmpty()) {
 
156
            return null;
 
157
        }
 
158
 
 
159
        if (applicables.size() == 1) {
 
160
            return (Method) applicables.getFirst();
 
161
        }
 
162
 
 
163
        /*
 
164
         * This list will contain the maximally specific methods. Hopefully at
 
165
         * the end of the below loop, the list will contain exactly one method,
 
166
         * (the most specific method) otherwise we have ambiguity.
 
167
         */
 
168
 
 
169
        LinkedList maximals = new LinkedList();
 
170
 
 
171
        for (Iterator applicable = applicables.iterator();
 
172
             applicable.hasNext();) {
 
173
            Method app = (Method) applicable.next();
 
174
            Class[] appArgs = app.getParameterTypes();
 
175
            boolean lessSpecific = false;
 
176
 
 
177
            for (Iterator maximal = maximals.iterator();
 
178
                 !lessSpecific && maximal.hasNext();) {
 
179
                Method max = (Method) maximal.next();
 
180
 
 
181
                switch (moreSpecific(appArgs, max.getParameterTypes())) {
 
182
                    case MORE_SPECIFIC: {
 
183
                        /*
 
184
                         * This method is more specific than the previously
 
185
                         * known maximally specific, so remove the old maximum.
 
186
                         */
 
187
 
 
188
                        maximal.remove();
 
189
                        break;
 
190
                    }
 
191
 
 
192
                    case LESS_SPECIFIC: {
 
193
                        /*
 
194
                         * This method is less specific than some of the
 
195
                         * currently known maximally specific methods, so we
 
196
                         * won't add it into the set of maximally specific
 
197
                         * methods
 
198
                         */
 
199
 
 
200
                        lessSpecific = true;
 
201
                        break;
 
202
                    }
 
203
                }
 
204
            }
 
205
 
 
206
            if (!lessSpecific) {
 
207
                maximals.addLast(app);
 
208
            }
 
209
        }
 
210
 
 
211
        if (maximals.size() > 1) {
 
212
            // We have more than one maximally specific method
 
213
            throw new AmbiguousException();
 
214
        }
 
215
 
 
216
        return (Method) maximals.getFirst();
 
217
    }
 
218
 
 
219
    /**
 
220
     * Determines which method signature (represented by a class array) is more
 
221
     * specific. This defines a partial ordering on the method signatures.
 
222
     *
 
223
     * @param c1 first signature to compare
 
224
     * @param c2 second signature to compare
 
225
     * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
 
226
     *         c1 is less specific than c2, INCOMPARABLE if they are incomparable.
 
227
     */
 
228
    private static int moreSpecific(Class[] c1, Class[] c2) {
 
229
        boolean c1MoreSpecific = false;
 
230
        boolean c2MoreSpecific = false;
 
231
 
 
232
        // compare lengths to handle comparisons where the size of the arrays
 
233
        // doesn't match, but the methods are both applicable due to the fact
 
234
        // that one is a varargs method
 
235
        if (c1.length > c2.length) {
 
236
            return MORE_SPECIFIC;
 
237
        }
 
238
        if (c2.length > c1.length) {
 
239
            return LESS_SPECIFIC;
 
240
        }
 
241
 
 
242
        // ok, move on and compare those of equal lengths
 
243
        for (int i = 0; i < c1.length; ++i) {
 
244
            if (c1[i] != c2[i]) {
 
245
                boolean last = (i == c1.length - 1);
 
246
                c1MoreSpecific =
 
247
                        c1MoreSpecific ||
 
248
                                isStrictConvertible(c2[i], c1[i], last);
 
249
                c2MoreSpecific =
 
250
                        c2MoreSpecific ||
 
251
                                isStrictConvertible(c1[i], c2[i], last);
 
252
            }
 
253
        }
 
254
 
 
255
        if (c1MoreSpecific) {
 
256
            if (c2MoreSpecific) {
 
257
                /*
 
258
                 *  Incomparable due to cross-assignable arguments (i.e.
 
259
                 * foo(String, Object) vs. foo(Object, String))
 
260
                 */
 
261
 
 
262
                return INCOMPARABLE;
 
263
            }
 
264
 
 
265
            return MORE_SPECIFIC;
 
266
        }
 
267
 
 
268
        if (c2MoreSpecific) {
 
269
            return LESS_SPECIFIC;
 
270
        }
 
271
 
 
272
        /*
 
273
         * Incomparable due to non-related arguments (i.e.
 
274
         * foo(Runnable) vs. foo(Serializable))
 
275
         */
 
276
 
 
277
        return INCOMPARABLE;
 
278
    }
 
279
 
 
280
    /**
 
281
     * Returns all methods that are applicable to actual argument types.
 
282
     *
 
283
     * @param methods list of all candidate methods
 
284
     * @param classes the actual types of the arguments
 
285
     * @return a list that contains only applicable methods (number of
 
286
     *         formal and actual arguments matches, and argument types are assignable
 
287
     *         to formal types through a method invocation conversion).
 
288
     */
 
289
    private static LinkedList getApplicables(List methods, Class[] classes) {
 
290
        LinkedList list = new LinkedList();
 
291
 
 
292
        for (Iterator imethod = methods.iterator(); imethod.hasNext();) {
 
293
            Method method = (Method) imethod.next();
 
294
 
 
295
            if (isApplicable(method, classes)) {
 
296
                list.add(method);
 
297
            }
 
298
 
 
299
        }
 
300
        return list;
 
301
    }
 
302
 
 
303
    /**
 
304
     * Returns true if the supplied method is applicable to actual
 
305
     * argument types.
 
306
     *
 
307
     * @param method  method that will be called
 
308
     * @param classes arguments to method
 
309
     * @return true if method is applicable to arguments
 
310
     */
 
311
    private static boolean isApplicable(Method method, Class[] classes) {
 
312
        Class[] methodArgs = method.getParameterTypes();
 
313
 
 
314
        if (methodArgs.length > classes.length) {
 
315
            // if there's just one more methodArg than class arg
 
316
            // and the last methodArg is an array, then treat it as a vararg
 
317
            return methodArgs.length == classes.length + 1 &&
 
318
                    methodArgs[methodArgs.length - 1].isArray();
 
319
        }
 
320
 
 
321
        if (methodArgs.length == classes.length) {
 
322
            // this will properly match when the last methodArg
 
323
            // is an array/varargs and the last class is the type of array
 
324
            // (e.g. String when the method is expecting String...)
 
325
            for (int i = 0; i < classes.length; ++i) {
 
326
                if (!isConvertible(methodArgs[i], classes[i], false)) {
 
327
                    // if we're on the last arg and the method expects an array
 
328
                    if (i == classes.length - 1 && methodArgs[i].isArray()) {
 
329
                        // check to see if the last arg is convertible
 
330
                        // to the array's component type
 
331
                        return isConvertible(methodArgs[i], classes[i], true);
 
332
                    }
 
333
                    return false;
 
334
                }
 
335
            }
 
336
            return true;
 
337
        }
 
338
 
 
339
        if (methodArgs.length > 0) {// more arguments given than the method accepts; check for varargs
 
340
            // check that the last methodArg is an array
 
341
            Class lastarg = methodArgs[methodArgs.length - 1];
 
342
            if (!lastarg.isArray()) {
 
343
                return false;
 
344
            }
 
345
 
 
346
            // check that they all match up to the last method arg
 
347
            for (int i = 0; i < methodArgs.length - 1; ++i) {
 
348
                if (!isConvertible(methodArgs[i], classes[i], false)) {
 
349
                    return false;
 
350
                }
 
351
            }
 
352
 
 
353
            // check that all remaining arguments are convertible to the vararg type
 
354
            Class vararg = lastarg.getComponentType();
 
355
            for (int i = methodArgs.length - 1; i < classes.length; ++i) {
 
356
                if (!isConvertible(vararg, classes[i], false)) {
 
357
                    return false;
 
358
                }
 
359
            }
 
360
 
 
361
            return true;
 
362
        }
 
363
 
 
364
        return false;
 
365
    }
 
366
 
 
367
    private static boolean isConvertible(Class formal, Class actual,
 
368
                                         boolean possibleVarArg) {
 
369
        return IntrospectionUtils.
 
370
                isMethodInvocationConvertible(formal, actual, possibleVarArg);
 
371
    }
 
372
 
 
373
    private static boolean isStrictConvertible(Class formal, Class actual,
 
374
                                               boolean possibleVarArg) {
 
375
        return IntrospectionUtils.
 
376
                isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
 
377
    }
 
378
}