671dd1679c42114ed5954d7913129ecf8000c12a
[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.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.external_objects_java;
14
15 import java.beans.MethodDescriptor;
16 import java.lang.reflect.Array;
17 import java.lang.reflect.Constructor;
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 /**
26  * A constructor wrapper
27  *
28  * @author Calixte DENIZET
29  */
30 public final class FunctionArguments {
31
32     private static final List<Converter> converters = new ArrayList<Converter>();
33
34     static {
35         // Converter to convert a number to an int
36         registerConverter(new Converter() {
37             @Override
38             public Object convert(Object original, Class<?> to) {
39                 return ((Number) original).intValue();
40             }
41
42             @Override
43             public boolean canConvert(Class<?> from, Class<?> to) {
44                 return (to == int.class || to == Integer.class) && (Number.class.isAssignableFrom(from) || ScilabJavaObject.primTypes.containsKey(from));
45             }
46         });
47
48         // Converter to convert a double to a float
49         registerConverter(new Converter() {
50             @Override
51             public Object convert(Object original, Class<?> to) {
52                 return ((Number) original).floatValue();
53             }
54
55             @Override
56             public boolean canConvert(Class<?> from, Class<?> to) {
57                 return (to == float.class || to == Float.class) && (from == double.class || from == Double.class);
58             }
59         });
60
61         // Converter to convert a double[] to an int[]
62         registerConverter(new Converter() {
63             @Override
64             public Object convert(Object original, Class<?> to) {
65                 return ScilabJavaArray.toIntArray((double[]) original);
66             }
67
68             @Override
69             public boolean canConvert(Class<?> from, Class<?> to) {
70                 return (to == int[].class) && (from == double[].class);
71             }
72         });
73
74         // Converter to convert a double[] to a Double[] (or an other primitive type)
75         registerConverter(new Converter() {
76             @Override
77             public Object convert(Object original, Class<?> to) {
78                 return ScilabJavaArray.fromPrimitive(original);
79             }
80
81             @Override
82             public boolean canConvert(Class<?> from, Class<?> to) {
83                 if (from.isArray() && to.isArray()) {
84                     final Class _from = ScilabJavaArray.getArrayBaseType(from);
85                     final Class _to = ScilabJavaArray.getArrayBaseType(to);
86                     return ScilabJavaObject.primTypes.get(_from) == _to;
87                 }
88                 return false;
89             }
90         });
91
92         // Converter to convert a double[] to a ArrayList<Double> (or an other primitive type)
93         registerConverter(new Converter() {
94             @Override
95             public Object convert(Object original, Class<?> to) {
96                 return ScilabJavaArray.toList(original);
97             }
98
99             @Override
100             public boolean canConvert(Class<?> from, Class<?> to) {
101                 return from.isArray() && to.isAssignableFrom(ArrayList.class);
102             }
103         });
104
105         // Converter to convert a String to a Enum
106         registerConverter(new Converter() {
107             @Override
108             public Object convert(Object original, Class<?> to) {
109                 return Enum.valueOf((Class) to, (String) original);
110             }
111
112             @Override
113             public boolean canConvert(Class<?> from, Class<?> to) {
114                 return String.class.isAssignableFrom(from) && to.isEnum();
115             }
116         });
117
118         // Converter to convert double[][] to double[]
119         registerConverter(new Converter() {
120             @Override
121             public Object convert(Object original, Class<?> to) {
122                 return ScilabJavaArray.toOneDim(original);
123             }
124
125             @Override
126             public boolean canConvert(Class<?> from, Class<?> to) {
127                 return to.isArray() && from.isArray() && from.getComponentType().isArray() && to.getComponentType() == from.getComponentType().getComponentType();
128             }
129         });
130
131         // Converter to convert double[] to double[][]
132         registerConverter(new Converter() {
133             @Override
134             public Object convert(Object original, Class<?> to) {
135                 return ScilabJavaArray.toBiDim(original);
136             }
137
138             @Override
139             public boolean canConvert(Class<?> from, Class<?> to) {
140                 return to.isArray() && from.isArray() && to.getComponentType().isArray() && from.getComponentType() == to.getComponentType().getComponentType();
141             }
142         });
143
144         // Converter to convert double to double[]
145         registerConverter(new Converter() {
146             @Override
147             public Object convert(Object original, Class<?> to) {
148                 return ScilabJavaArray.singleToOneDim(to.getComponentType(), original);
149             }
150
151             @Override
152             public boolean canConvert(Class<?> from, Class<?> to) {
153                 return to.isArray() && from == to.getComponentType();
154             }
155         });
156     }
157
158     /**
159      * Register a converter
160      * @param converter the converter to register
161      */
162     public static final void registerConverter(Converter converter) {
163         int index = converters.indexOf(converter);
164         if (index == -1) {
165             converters.add(converter);
166         } else {
167             converters.remove(index);
168             converters.add(converter);
169         }
170     }
171
172     /**
173      * Unregister a converter
174      * @param converter the converter to unregister
175      */
176     public static final void unregisterConverter(Converter converter) {
177         int index = converters.indexOf(converter);
178         if (index != -1) {
179             converters.remove(index);
180         }
181     }
182
183     /**
184      * Convert an object x into another one according to base Class
185      * @param x the object to convert
186      * @param base the base Class
187      * @return the converted object
188      */
189     public static final Object convert(Object x, Class base) {
190         if (x == null) {
191             return null;
192         }
193         final Class clazz = x.getClass();
194         if (base.isAssignableFrom(clazz)) {
195             return x;
196         }
197         for (Converter converter : converters) {
198             if (converter.canConvert(clazz, base)) {
199                 return converter.convert(x, base);
200             }
201         }
202
203         return x;
204     }
205
206     /**
207      * To find the "correct" method we proceed as follow:
208      * i) We calculate the distance between the Class of the arguments.
209      *    For a Class B which derivates from A (A.isAssignableFrom(B) == true), the distance between A and B is the number of
210      *    derivations of A to obtain B. For example, if we have C extends B extends A, dist(C,A)=2.
211      *    So the square of the distance between two Class array is defined as the sum of the square of the distance between
212      *    Class elements.
213      *    In the particular case where a int.class is required and the argument is double.class, if the double value is an integer
214      *    the argument is converted into an int. In this case, dist(int.class, double.class) is equal to 2048. This value has been
215      *    choosed to be "sure" that another method with double.class will be considered better than the previous one.
216      *    If the method has variable arguments foo(A a, B b, Object...), the Java reflection only shows that Class arguments are
217      *    A.class, B.class and Object[].class. So such a method will give a very high distance (more than 2^40).
218      *    When a method is not correct, the distance will be Long.MIN_VALUE.
219      *
220      * ii) For each Method we calculate the distance and we try to find the method with the minimal distance.
221      * iii) If a method has variable arguments, then we transform the arguments and return the new ones in the second element of
222      *      the returned array.
223      */
224
225     /**
226      * Find a valid method. An argument can be null (thanks to Fabien Viale).
227      * @param name the method name
228      * @param functions the set of the valid functions in the class
229      * @param argsClass the class of the arguments
230      * @param args the arguments
231      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
232      */
233     public static final Object[] findMethod(String name, MethodDescriptor[] descriptors, Class[] argsClass, Object[] args) throws NoSuchMethodException {
234         String internName = name.intern();
235         Method better = null;
236         boolean mustConv = false;
237         boolean isVarArgs = false;
238         long sqd = Long.MAX_VALUE;
239         boolean[] refBools = new boolean[1];
240         Map<Integer, Converter> toConvert = null;
241         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
242         for (MethodDescriptor desc : descriptors) {
243             if (desc.getName() == internName) {
244                 Method f = desc.getMethod();
245                 Class[] types = f.getParameterTypes();
246                 refBools[0] = false;
247                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
248                 if (d != Long.MIN_VALUE && d < sqd) {
249                     // The method is valid and the distance is lesser than the previous found one.
250                     sqd = d;
251                     better = f;
252                     isVarArgs = refBools[0];
253                     toConvert = toConv;
254                     if (d == 0) {
255                         break;
256                     }
257
258                     toConv = new HashMap<Integer, Converter>();
259                 } else {
260                     toConv.clear();
261                 }
262             }
263         }
264
265         if (better != null) {
266             if (!toConvert.isEmpty()) {
267                 // Contains int.class arguments and we passed double.class args
268                 Class[] types = better.getParameterTypes();
269                 Class base = types[types.length - 1].getComponentType();
270                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
271                     int i = entry.getKey();
272                     if (i >= 0) {
273                         argsClass[i] = types[i];
274                         args[i] = entry.getValue().convert(args[i], types[i]);
275                     } else {
276                         i = -i - 1;
277                         args[i] = entry.getValue().convert(args[i], base);
278                     }
279                 }
280             }
281
282             if (isVarArgs) {
283                 // Variable arguments
284                 Class[] types = better.getParameterTypes();
285                 Object o;
286                 if (args.length == 1 && types.length == 1 && args[0] == null) {
287                     o = null;
288                 } else {
289                     Class base = types[types.length - 1].getComponentType();
290                     o = Array.newInstance(base, args.length - types.length + 1);
291
292                     // Don't use System.arraycopy since it does not handle unboxing
293                     for (int i = 0; i < args.length - types.length + 1; i++) {
294                         Array.set(o, i, args[i + types.length - 1]);
295                     }
296                 }
297
298                 Object[] newArgs = new Object[types.length];
299                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
300                 newArgs[types.length - 1] = o;
301
302                 return new Object[] {better, newArgs};
303             }
304
305             return new Object[] {better};
306         }
307
308         throw new NoSuchMethodException("");
309     }
310
311     /**
312      * Find a valid method. An argument can be null (thanks to Fabien Viale).
313      * @param functions the set of the valid constructors in the class
314      * @param argsClass the class of the arguments
315      * @param args the arguments
316      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
317      */
318     public static final Object[] findConstructor(Constructor[] functions, Class[] argsClass, Object[] args) throws NoSuchMethodException {
319         // Essentially a clone of findMethod.
320         Constructor better = null;
321         boolean mustConv = false;
322         boolean isVarArgs = false;
323         long sqd = Long.MAX_VALUE;
324         boolean[] refBools = new boolean[1];
325         Map<Integer, Converter> toConvert = null;
326         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
327         for (Constructor f : functions) {
328             if (Modifier.isPublic(f.getModifiers())) {
329                 Class[] types = f.getParameterTypes();
330                 refBools[0] = false;
331                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
332                 if (d != Long.MIN_VALUE && d < sqd) {
333                     // The constructor is valid and the distance is lesser than the previous found one.
334                     sqd = d;
335                     better = f;
336                     isVarArgs = refBools[0];
337                     toConvert = toConv;
338                     if (d == 0) {
339                         break;
340                     }
341
342                     toConv = new HashMap<Integer, Converter>();
343                 } else {
344                     toConv.clear();
345                 }
346             }
347         }
348
349         if (better != null) {
350             if (!toConvert.isEmpty()) {
351                 // Contains int.class arguments and we passed double.class args
352                 Class[] types = better.getParameterTypes();
353                 Class base = types[types.length - 1].getComponentType();
354                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
355                     int i = entry.getKey();
356                     if (i >= 0) {
357                         argsClass[i] = types[i];
358                         args[i] = entry.getValue().convert(args[i], types[i]);
359                     } else {
360                         i = -i - 1;
361                         args[i] = entry.getValue().convert(args[i], base);
362                     }
363                 }
364             }
365             if (isVarArgs) {
366                 // Variable arguments
367                 Class[] types = better.getParameterTypes();
368                 Object o;
369                 if (args.length == 1 && types.length == 1 && args[0] == null) {
370                     o = null;
371                 } else {
372                     Class base = types[types.length - 1].getComponentType();
373                     o = Array.newInstance(base, args.length - types.length + 1);
374                     // Don't use System.arraycopy since it does not handle unboxing
375                     for (int i = 0; i < args.length - types.length + 1; i++) {
376                         Array.set(o, i, args[i + types.length - 1]);
377                     }
378                 }
379
380                 Object[] newArgs = new Object[types.length];
381                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
382                 newArgs[types.length - 1] = o;
383
384                 return new Object[] {better, newArgs};
385             }
386
387             return new Object[] {better};
388         }
389
390         throw new NoSuchMethodException("");
391     }
392
393     /**
394      * This function calculates the distance between the method signatures
395      * @param A the method signature
396      * @param B the class of the passed arguments
397      * @param arr array of arguments (used to transform double in int)
398      * @param bools references on boolean
399      * @return the distance
400      */
401     private static final long compareClassArgs(Class[] A, Class[] B, Object[] arr, boolean[] bools, Map<Integer, Converter> toConvert) {
402         if (A.length > B.length) {
403             return Long.MIN_VALUE;
404         }
405
406         long s = 0;
407         int end = A.length;
408         if (A.length > 0 && A[A.length - 1].isArray() && (A.length < B.length || (A.length == 1 && B.length == 1 && (B[0] == null || !B[0].isArray())))) {
409             Class base = A[A.length - 1].getComponentType();
410             // this is a variable arguments method
411             bools[0] = true;
412             end--;
413             s = 1 << 40;
414
415             for (int i = A.length - 1; i < B.length; i++) {
416                 long d = dist(base, B[i]);
417                 if (d == -1) {
418                     for (Converter converter : converters) {
419                         if (converter.canConvert(B[i], base)) {
420                             d = 2048;
421                             toConvert.put(-i - 1, converter);
422                             break;
423                         }
424                     }
425
426                     if (d != 2048) {
427                         return Long.MIN_VALUE;
428                     }
429                 }
430                 // s is the sum of the square of the distance
431                 s += d * d;
432             }
433         } else if (A.length < B.length) {
434             return Long.MIN_VALUE;
435         }
436
437         for (int i = 0; i < end; i++) {
438             long d = dist(A[i], B[i]);
439             if (d == -1) {
440                 for (Converter converter : converters) {
441                     if (converter.canConvert(B[i], A[i])) {
442                         d = 2048;
443                         toConvert.put(i, converter);
444                         break;
445                     }
446                 }
447
448                 if (d != 2048) {
449                     return Long.MIN_VALUE;
450                 }
451             }
452             // s is the sum of the square of the distance
453             s += d * d;
454         }
455
456         return s;
457     }
458
459     /**
460      * Calculate the distance between two classes.
461      * If B derivate from A, then the distance is the number of derivations.
462      * @param A a class
463      * @param B another class
464      * @return the distance
465      */
466     private static final long dist(Class<?> A, Class<?> B) {
467         if (B == null) {
468             return 0;
469         }
470
471         if (!A.isPrimitive() && B.isPrimitive()) {
472             // Autounboxing
473             B = ScilabJavaObject.primTypes.get(B);
474         }
475
476         if (A == B) {
477             return 0;
478         }
479
480         if (A.isAssignableFrom(B)) {
481             long i = 0;
482             do {
483                 i++;
484                 B = B.getSuperclass();
485             } while (B != null && A.isAssignableFrom(B));
486
487             return i;
488         }
489
490         return -1;
491     }
492 }