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