Bug 13027 fixed: There was no autowrap into array in JIMS
[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.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      * To find the "correct" method we proceed as follow:
185      * i) We calculate the distance between the Class of the arguments.
186      *    For a Class B which derivates from A (A.isAssignableFrom(B) == true), the distance between A and B is the number of
187      *    derivations of A to obtain B. For example, if we have C extends B extends A, dist(C,A)=2.
188      *    So the square of the distance between two Class array is defined as the sum of the square of the distance between
189      *    Class elements.
190      *    In the particular case where a int.class is required and the argument is double.class, if the double value is an integer
191      *    the argument is converted into an int. In this case, dist(int.class, double.class) is equal to 2048. This value has been
192      *    choosed to be "sure" that another method with double.class will be considered better than the previous one.
193      *    If the method has variable arguments foo(A a, B b, Object...), the Java reflection only shows that Class arguments are
194      *    A.class, B.class and Object[].class. So such a method will give a very high distance (more than 2^40).
195      *    When a method is not correct, the distance will be Long.MIN_VALUE.
196      *
197      * ii) For each Method we calculate the distance and we try to find the method with the minimal distance.
198      * iii) If a method has variable arguments, then we transform the arguments and return the new ones in the second element of
199      *      the returned array.
200      */
201
202     /**
203      * Find a valid method. An argument can be null (thanks to Fabien Viale).
204      * @param name the method name
205      * @param functions the set of the valid functions in the class
206      * @param argsClass the class of the arguments
207      * @param args the arguments
208      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
209      */
210     public static final Object[] findMethod(String name, MethodDescriptor[] descriptors, Class[] argsClass, Object[] args) throws NoSuchMethodException {
211         String internName = name.intern();
212         Method better = null;
213         boolean mustConv = false;
214         boolean isVarArgs = false;
215         long sqd = Long.MAX_VALUE;
216         boolean[] refBools = new boolean[1];
217         Map<Integer, Converter> toConvert = null;
218         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
219         for (MethodDescriptor desc : descriptors) {
220             if (desc.getName() == internName) {
221                 Method f = desc.getMethod();
222                 Class[] types = f.getParameterTypes();
223                 refBools[0] = false;
224                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
225                 if (d != Long.MIN_VALUE && d < sqd) {
226                     // The method is valid and the distance is lesser than the previous found one.
227                     sqd = d;
228                     better = f;
229                     isVarArgs = refBools[0];
230                     toConvert = toConv;
231                     toConv = new HashMap<Integer, Converter>();
232                 } else {
233                     toConv.clear();
234                 }
235
236                 if (d == 0) {
237                     break;
238                 }
239             }
240         }
241
242         if (better != null) {
243             if (toConvert != null && !toConvert.isEmpty()) {
244                 // Contains int.class arguments and we passed double.class args
245                 Class[] types = better.getParameterTypes();
246                 Class base = types[types.length - 1].getComponentType();
247                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
248                     int i = entry.getKey();
249                     if (i >= 0) {
250                         argsClass[i] = types[i];
251                         args[i] = entry.getValue().convert(args[i], types[i]);
252                     } else {
253                         i = -i - 1;
254                         args[i] = entry.getValue().convert(args[i], base);
255                     }
256                 }
257             }
258
259             if (isVarArgs) {
260                 // Variable arguments
261                 Class[] types = better.getParameterTypes();
262                 Class base = types[types.length - 1].getComponentType();
263                 Object o = Array.newInstance(base, args.length - types.length + 1);
264
265                 // Don't use System.arraycopy since it does not handle unboxing
266                 for (int i = 0; i < args.length - types.length + 1; i++) {
267                     Array.set(o, i, args[i + types.length - 1]);
268                 }
269
270                 Object[] newArgs = new Object[types.length];
271                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
272                 newArgs[types.length - 1] = o;
273
274                 return new Object[] {better, newArgs};
275             }
276
277             return new Object[] {better};
278         }
279
280         throw new NoSuchMethodException("");
281     }
282
283     /**
284      * Find a valid method. An argument can be null (thanks to Fabien Viale).
285      * @param functions the set of the valid constructors in the class
286      * @param argsClass the class of the arguments
287      * @param args the arguments
288      * @return a valid method wrapped in an array or a method and the modified args (variable args) wrapped in an array
289      */
290     public static final Object[] findConstructor(Constructor[] functions, Class[] argsClass, Object[] args) throws NoSuchMethodException {
291         // Essentially a clone of findMethod.
292         Constructor better = null;
293         boolean mustConv = false;
294         boolean isVarArgs = false;
295         long sqd = Long.MAX_VALUE;
296         boolean[] refBools = new boolean[1];
297         Map<Integer, Converter> toConvert = null;
298         Map<Integer, Converter> toConv = new HashMap<Integer, Converter>();
299         for (Constructor f : functions) {
300             if (Modifier.isPublic(f.getModifiers())) {
301                 Class[] types = f.getParameterTypes();
302                 refBools[0] = false;
303                 long d = compareClassArgs(types, argsClass, args, refBools, toConv);
304                 if (d != Long.MIN_VALUE && d < sqd) {
305                     // The constructor is valid and the distance is lesser than the previous found one.
306                     sqd = d;
307                     better = f;
308                     isVarArgs = refBools[0];
309                     toConvert = toConv;
310                     toConv = new HashMap<Integer, Converter>();
311                 } else {
312                     toConv.clear();
313                 }
314
315                 if (d == 0) {
316                     break;
317                 }
318             }
319         }
320
321         if (better != null) {
322             if (toConvert != null && !toConvert.isEmpty()) {
323                 // Contains int.class arguments and we passed double.class args
324                 Class[] types = better.getParameterTypes();
325                 Class base = types[types.length - 1].getComponentType();
326                 for (Map.Entry<Integer, Converter> entry : toConvert.entrySet()) {
327                     int i = entry.getKey();
328                     if (i >= 0) {
329                         argsClass[i] = types[i];
330                         args[i] = entry.getValue().convert(args[i], types[i]);
331                     } else {
332                         i = -i - 1;
333                         args[i] = entry.getValue().convert(args[i], base);
334                     }
335                 }
336             }
337             if (isVarArgs) {
338                 // Variable arguments
339                 Class[] types = better.getParameterTypes();
340                 Class base = types[types.length - 1].getComponentType();
341                 Object o = Array.newInstance(base, args.length - types.length + 1);
342
343                 // Don't use System.arraycopy since it does not handle unboxing
344                 for (int i = 0; i < args.length - types.length + 1; i++) {
345                     Array.set(o, i, args[i + types.length - 1]);
346                 }
347
348                 Object[] newArgs = new Object[types.length];
349                 System.arraycopy(args, 0, newArgs, 0, types.length - 1);
350                 newArgs[types.length - 1] = o;
351
352                 return new Object[] {better, newArgs};
353             }
354
355             return new Object[] {better};
356         }
357
358         throw new NoSuchMethodException("");
359     }
360
361     /**
362      * This function calculates the distance between thes method signatures
363      * @param A the method signature
364      * @param B the class of the passed arguments
365      * @param arr array of arguments (used to transform double in int)
366      * @param bools references on boolean
367      * @return the distance
368      */
369     private static final long compareClassArgs(Class[] A, Class[] B, Object[] arr, boolean[] bools, Map<Integer, Converter> toConvert) {
370         if (A.length > B.length) {
371             return Long.MIN_VALUE;
372         }
373
374         long s = 0;
375         int end = A.length;
376         if (A.length > 0 && A[A.length - 1].isArray() && (A.length < B.length || (A.length == 1 && B.length == 1 && !B[0].isArray()))) {
377             Class base = A[A.length - 1].getComponentType();
378             // this is a variable arguments method
379             bools[0] = true;
380             end--;
381             s = 1 << 40;
382
383             for (int i = A.length - 1; i < B.length; i++) {
384                 long d = dist(base, B[i]);
385                 if (d == -1) {
386                     for (Converter converter : converters) {
387                         if (converter.canConvert(B[i], base)) {
388                             d = 2048;
389                             toConvert.put(-i - 1, converter);
390                             break;
391                         }
392                     }
393
394                     if (d != 2048) {
395                         return Long.MIN_VALUE;
396                     }
397                 }
398                 // s is the sum of the square of the distance
399                 s += d * d;
400             }
401         } else if (A.length < B.length) {
402             return Long.MIN_VALUE;
403         }
404
405         for (int i = 0; i < end; i++) {
406             long d = dist(A[i], B[i]);
407             if (d == -1) {
408                 for (Converter converter : converters) {
409                     if (converter.canConvert(B[i], A[i])) {
410                         d = 2048;
411                         toConvert.put(i, converter);
412                         break;
413                     }
414                 }
415
416                 if (d != 2048) {
417                     return Long.MIN_VALUE;
418                 }
419             }
420             // s is the sum of the square of the distance
421             s += d * d;
422         }
423
424         return s;
425     }
426
427     /**
428      * Calculate the distance between two classes.
429      * If B derivate from A, then the distance is the number of derivations.
430      * @param A a class
431      * @param B another class
432      * @return the distance
433      */
434     private static final long dist(Class<?> A, Class<?> B) {
435         if (B == null) {
436             return 0;
437         }
438
439         if (!A.isPrimitive() && B.isPrimitive()) {
440             // Autounboxing
441             B = ScilabJavaObject.primTypes.get(B);
442         }
443
444         if (A == B) {
445             return 0;
446         }
447
448         if (A.isAssignableFrom(B)) {
449             long i = 0;
450             do {
451                 i++;
452                 B = B.getSuperclass();
453             } while (B != null && A.isAssignableFrom(B));
454
455             return i;
456         }
457
458         return -1;
459     }
460 }