8d4706d023a247f432b6d7cb21ce96831f9805b5
[scilab.git] / scilab / modules / external_objects_java / src / java / org / scilab / modules / external_objects_java / FunctionArguments.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - 2011 - Calixte DENIZET <calixte@contrib.scilab.org>
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.external_objects_java;
14
15 import java.beans.MethodDescriptor;
16 import java.lang.reflect.Constructor;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.Modifier;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 /**
25  * A constructor wrapper
26  *
27  * @author Calixte DENIZET
28  */
29 public final class FunctionArguments {
30
31     private static final List<Converter> converters = new ArrayList<Converter>();
32
33     static {
34         // Converter to convert a number to an int
35         registerConverter(new Converter() {
36             @Override
37             public Object convert(Object original, Class<?> to) {
38                 return ((Number) original).intValue();
39             }
40
41             @Override
42             public boolean canConvert(Class<?> from, Class<?> to) {
43                 return (to == int.class || to == Integer.class) && (Number.class.isAssignableFrom(from) || ScilabJavaObject.primTypes.containsKey(from));
44             }
45         });
46
47         // Converter to convert a double to a float
48         registerConverter(new Converter() {
49             @Override
50             public Object convert(Object original, Class<?> to) {
51                 return ((Number) original).floatValue();
52             }
53
54             @Override
55             public boolean canConvert(Class<?> from, Class<?> to) {
56                 return (to == float.class || to == Float.class) && (from == double.class || from == Double.class);
57             }
58         });
59
60         // Converter to convert a double[] to a Double[] (or an other primitive type)
61         registerConverter(new Converter() {
62             @Override
63             public Object convert(Object original, Class<?> to) {
64                 return ScilabJavaArray.fromPrimitive(original);
65             }
66
67             @Override
68             public boolean canConvert(Class<?> from, Class<?> to) {
69                 if (from.isArray() && to.isArray()) {
70                     final Class _from = ScilabJavaArray.getArrayBaseType(from);
71                     final Class _to = ScilabJavaArray.getArrayBaseType(to);
72                     return ScilabJavaObject.primTypes.get(_from) == _to;
73                 }
74                 return false;
75             }
76         });
77
78         // Converter to convert a double[] to a ArrayList<Double> (or an other primitive type)
79         registerConverter(new Converter() {
80             @Override
81             public Object convert(Object original, Class<?> to) {
82                 return ScilabJavaArray.toList(original);
83             }
84
85             @Override
86             public boolean canConvert(Class<?> from, Class<?> to) {
87                 return from.isArray() && to.isAssignableFrom(ArrayList.class);
88             }
89         });
90
91         // Converter to convert a String to a Enum
92         registerConverter(new Converter() {
93             @Override
94             public Object convert(Object original, Class<?> to) {
95                 return Enum.valueOf((Class) to, (String) original);
96             }
97
98             @Override
99             public boolean canConvert(Class<?> from, Class<?> to) {
100                 return String.class.isAssignableFrom(from) && to.isEnum();
101             }
102         });
103     }
104
105     /**
106      * Register a converter
107      * @param converter the converter to register
108      */
109     public static final void registerConverter(Converter converter) {
110         int index = converters.indexOf(converter);
111         if (index == -1) {
112             converters.add(converter);
113         } else {
114             converters.remove(index);
115             converters.add(converter);
116         }
117     }
118
119     /**
120      * Unregister a converter
121      * @param converter the converter to unregister
122      */
123     public static final void unregisterConverter(Converter converter) {
124         int index = converters.indexOf(converter);
125         if (index != -1) {
126             converters.remove(index);
127         }
128     }
129
130     /**
131      * To find the "correct" method we proceed as follow:
132      * i) We calculate the distance between the Class of the arguments.
133      *    For a Class B which derivates from A (A.isAssignableFrom(B) == true), the distance between A and B is the number of
134      *    derivations of A to obtain B. For example, if we have C extends B extends A, dist(C,A)=2.
135      *    So the square of the distance between two Class array is defined as the sum of the square of the distance between
136      *    Class elements.
137      *    In the particular case where a int.class is required and the argument is double.class, if the double value is an integer
138      *    the argument is converted into an int. In this case, dist(int.class, double.class) is equal to 2048. This value has been
139      *    choosed to be "sure" that another method with double.class will be considered better than the previous one.
140      *    If the method has variable arguments foo(A a, B b, Object...), the Java reflection only shows that Class arguments are
141      *    A.class, B.class and Object[].class. So such a method will give a very high distance (more than 2^40).
142      *    When a method is not correct, the distance will be Long.MIN_VALUE.
143      *
144      * ii) For each Method we calculate the distance and we try to find the method with the minimal distance.
145      * iii) If a method has variable arguments, then we transform the arguments and return the new ones in the second element of
146      *      the returned array.
147      */
148
149     /**
150      * Find a valid method. An argument can be null (thanks to Fabien Viale).
151      * @param name the method name
152      * @param functions the set of the valid functions in the class
153      * @param argsClass the class of the arguments
154      * @param args the arguments
155      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
156      */
157     public static final Object[] findMethod(String name, MethodDescriptor[] descriptors, Class[] argsClass, Object[] args) throws NoSuchMethodException {
158         String internName = name.intern();
159         Method better = null;
160         boolean mustConv = false;
161         boolean isVarArgs = false;
162         long sqd = Long.MAX_VALUE;
163         boolean[] refBools = new boolean[1];
164         Map<Integer, Converter> toConvert = null;
165         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
166         for (MethodDescriptor desc : descriptors) {
167             if (desc.getName() == internName) {
168                 Method f = desc.getMethod();
169                 Class[] types = f.getParameterTypes();
170                 refBools[0] = false;
171                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
172                 if (d != Long.MIN_VALUE && d < sqd) {
173                     // The method is valid and the distance is lesser than the previous found one.
174                     sqd = d;
175                     better = f;
176                     isVarArgs = refBools[0];
177                     toConvert = toConv;
178                     toConv = new HashMap<Integer, Converter>();
179                 } else {
180                     toConv.clear();
181                 }
182
183                 if (d == 0) {
184                     break;
185                 }
186             }
187         }
188
189         if (better != null) {
190             if (toConvert != null && !toConvert.isEmpty()) {
191                 // Contains int.class arguments and we passed double.class args
192                 Class[] types = better.getParameterTypes();
193                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
194                     int i = entry.getKey();
195                     argsClass[i] = types[i];
196                     args[i] = entry.getValue().convert(args[i], types[i]);
197                 }
198             }
199
200             if (isVarArgs) {
201                 // Variable arguments
202                 Class[] types = better.getParameterTypes();
203                 Object[] o = new Object[args.length - types.length + 1];
204                 System.arraycopy(args, types.length - 1, o, 0, args.length - types.length + 1);
205
206                 Object[] newArgs = new Object[types.length];
207                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
208                 newArgs[types.length - 1] = o;
209
210                 return new Object[] {better, newArgs};
211             }
212
213             return new Object[] {better};
214         }
215
216         throw new NoSuchMethodException("");
217     }
218
219     /**
220      * Find a valid method. An argument can be null (thanks to Fabien Viale).
221      * @param functions the set of the valid constructors in the class
222      * @param argsClass the class of the arguments
223      * @param args the arguments
224      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
225      */
226     public static final Object[] findConstructor(Constructor[] functions, Class[] argsClass, Object[] args) throws NoSuchMethodException {
227         // Essentially a clone of findMethod.
228         Constructor better = null;
229         boolean mustConv = false;
230         boolean isVarArgs = false;
231         long sqd = Long.MAX_VALUE;
232         boolean[] refBools = new boolean[1];
233         Map<Integer, Converter> toConvert = null;
234         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
235         for (Constructor f : functions) {
236             if (Modifier.isPublic(f.getModifiers())) {
237                 Class[] types = f.getParameterTypes();
238                 refBools[0] = false;
239                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
240                 if (d != Long.MIN_VALUE && d < sqd) {
241                     // The constructor is valid and the distance is lesser than the previous found one.
242                     sqd = d;
243                     better = f;
244                     isVarArgs = refBools[0];
245                     toConvert = toConv;
246                     toConv = new HashMap<Integer, Converter>();
247                 } else {
248                     toConv.clear();
249                 }
250
251                 if (d == 0) {
252                     break;
253                 }
254             }
255         }
256
257         if (better != null) {
258             if (toConvert != null && !toConvert.isEmpty()) {
259                 // Contains int.class arguments and we passed double.class args
260                 Class[] types = better.getParameterTypes();
261                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
262                     int i = entry.getKey();
263                     argsClass[i] = types[i];
264                     args[i] = entry.getValue().convert(args[i], types[i]);
265                 }
266             }
267             if (isVarArgs) {
268                 // Variable arguments
269                 Class[] types = better.getParameterTypes();
270                 Object[] o = new Object[args.length - types.length + 1];
271                 System.arraycopy(args, types.length - 1, o, 0, args.length - types.length + 1);
272
273                 Object[] newArgs = new Object[types.length];
274                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
275                 newArgs[types.length - 1] = o;
276
277                 return new Object[] {better, newArgs};
278             }
279
280             return new Object[] {better};
281         }
282
283         throw new NoSuchMethodException("");
284     }
285
286     /**
287      * This function calculates the distance between thes method signatures
288      * @param A the method signature
289      * @param B the class of the passed arguments
290      * @param arr array of arguments (used to transform double in int)
291      * @param bools references on boolean
292      * @return the distance
293      */
294     private static final long compareClassArgs(Class[] A, Class[] B, Object[] arr, boolean[] bools, Map<Integer, Converter> toConvert) {
295         if (A.length > B.length) {
296             return Long.MIN_VALUE;
297         }
298
299         long s = 0;
300         int end = A.length;
301         if (A.length > 0 && ((A.length < B.length && A[A.length - 1] == Object[].class) || (A.length == B.length && A.length == 1 && A[0] == Object[].class && B[0] != Object[].class))) {
302             // this is a variable arguments method
303             bools[0] = true;
304             end--;
305             s = 1 << 40;
306         } else if (A.length < B.length) {
307             return Long.MIN_VALUE;
308         }
309
310         for (int i = 0; i < end; i++) {
311             long d = dist(A[i], B[i]);
312             if (d == -1) {
313                 for (Converter converter : converters) {
314                     if (converter.canConvert(B[i], A[i])) {
315                         d = 2048;
316                         toConvert.put(i, converter);
317                         break;
318                     }
319                 }
320
321                 if (d != 2048) {
322                     return Long.MIN_VALUE;
323                 }
324             }
325             // s is the sum of the square of the distance
326             s += d * d;
327         }
328
329         return s;
330     }
331
332     /**
333      * Calculate the distance between two classes.
334      * If B derivate from A, then the distance is the number of derivations.
335      * @param A a class
336      * @param B another class
337      * @return the distance
338      */
339     private static final long dist(Class<?> A, Class<?> B) {
340         if (B == null) {
341             return 0;
342         }
343
344         if (!A.isPrimitive() && B.isPrimitive()) {
345             // Autounboxing
346             B = ScilabJavaObject.primTypes.get(B);
347         }
348
349         if (A == B) {
350             return 0;
351         }
352
353         if (A.isAssignableFrom(B)) {
354             long i = 0;
355             do {
356                 i++;
357                 B = B.getSuperclass();
358             } while (B != null && A.isAssignableFrom(B));
359
360             return i;
361         }
362
363         return -1;
364     }
365 }