001    package org.bridj;
002    import java.lang.annotation.Annotation;
003    import static org.bridj.NativeConstants.*;
004    import static org.bridj.dyncall.DyncallLibrary.*;
005    import org.bridj.ann.Constructor;
006    //import org.bridj.cpp.CPPObject;
007    
008    import java.lang.reflect.*;
009    import java.util.ArrayList;
010    import java.util.Arrays;
011    import java.util.List;
012    import java.util.NoSuchElementException;
013    import org.bridj.ann.Convention;
014    import org.bridj.ann.DisableDirect;
015    import org.bridj.ann.Ptr;
016    import org.bridj.ann.Virtual;
017    import org.bridj.util.Utils;
018    import static org.bridj.util.AnnotationUtils.*;
019    /**
020     * Internal class that encapsulate all the knowledge about a native method call : signatures (ASM, dyncall and Java), calling convention, context...
021     * @author Olivier
022     */
023    public class MethodCallInfo {
024    
025        /*public static class GenericMethodInfo {
026            Type returnType, paramsTypes[];
027        }
028        GenericMethodInfo genericInfo = new GenericMethodInfo();*/
029            List<CallIO> callIOs;
030            private Class<?> declaringClass;
031            long nativeClass;
032        int returnValueType, paramsValueTypes[];
033            Method method;//, definition;
034            String methodName, symbolName;
035            private long forwardedPointer;
036        String dcSignature;
037            String javaSignature;
038            String asmSignature;
039            Object javaCallback;
040            boolean isGenericCallback;
041            boolean isObjCBlock;
042            int virtualIndex = -1;
043            int virtualTableOffset = 0;
044        private int dcCallingConvention = DC_CALL_C_DEFAULT;
045    
046            boolean isVarArgs;
047            boolean isStatic;
048        boolean isCPlusPlus;
049            boolean direct;
050            boolean startsWithThis;
051            boolean bNeedsThisPointer;
052            boolean bThrowLastError;
053    
054        public MethodCallInfo(Method method) {
055            this(method, method);
056        }
057        static boolean derivesFrom(Class<?> c, String className) {
058                    while (c != null) {
059                            if (c.getName().equals(className))
060                                    return true;
061                            c = c.getSuperclass();  
062                    }
063                    return false;
064        }
065        public MethodCallInfo(Type genericReturnType, Type[] parameterTypes, boolean prependJNIPointers) {
066                    this(genericReturnType, new Annotation[0], parameterTypes, new Annotation[parameterTypes.length][], prependJNIPointers);
067        }
068            public MethodCallInfo(Type genericReturnType, Annotation[] returnAnnotations, Type[] parameterTypes, Annotation[][] paramsAnnotations, boolean prependJNIPointers) {
069            init(null, Utils.getClass(genericReturnType), genericReturnType, returnAnnotations, Utils.getClasses(parameterTypes), parameterTypes, paramsAnnotations, prependJNIPointers, false, true);
070        }
071        public MethodCallInfo(Method method, Method definition) {
072            this.setMethod(method);
073            //this.setDefinition(definition);
074                    this.setDeclaringClass(method.getDeclaringClass());
075                    symbolName = methodName;
076            
077            int modifiers = method.getModifiers();
078            isStatic = Modifier.isStatic(modifiers);
079            isVarArgs = method.isVarArgs();
080            boolean isNative = Modifier.isNative(modifiers);
081            boolean isVirtual = isAnnotationPresent(Virtual.class, definition);
082            boolean isDirectModeAllowed = 
083                getInheritableAnnotation(DisableDirect.class, definition) == null &&
084                BridJ.isDirectModeEnabled();
085            
086            isCPlusPlus = !isStatic && derivesFrom(method.getDeclaringClass(), "org.bridj.cpp.CPPObject");
087            isObjCBlock = !isStatic && derivesFrom(method.getDeclaringClass(), "org.bridj.objc.ObjCBlock");
088            
089            init(
090                method, 
091                method.getReturnType(), method.getGenericReturnType(), method.getAnnotations(), 
092                method.getParameterTypes(), method.getGenericParameterTypes(), method.getParameterAnnotations(), 
093                isNative, 
094                isVirtual, 
095                isDirectModeAllowed
096            );
097            
098            Convention cc = getInheritableAnnotation(Convention.class, definition);
099            if (cc != null) {
100                setCallingConvention(cc.value());
101            }
102            List<Class<?>> exceptionTypes = Arrays.asList(definition.getExceptionTypes());
103            if (!exceptionTypes.isEmpty()) {
104                this.direct = false; // there is no crash / exception protection for direct raw calls
105                if (exceptionTypes.contains(LastError.class))
106                    this.bThrowLastError = true;
107            }
108            
109        }
110        protected void init(AnnotatedElement annotatedElement, Class returnType, Type genericReturnType, Annotation[] returnAnnotations, Class[] parameterTypes, Type[] genericParameterTypes, Annotation[][] paramsAnnotations, boolean prependJNIPointers, boolean isVirtual, boolean isDirectModeAllowed) {
111            assert returnType != null;
112            assert genericReturnType != null;
113            assert parameterTypes != null;
114            assert genericParameterTypes != null;
115            assert returnAnnotations != null;
116            assert parameterTypes.length == genericParameterTypes.length;
117            assert paramsAnnotations.length == genericParameterTypes.length;
118            
119            int nParams = genericParameterTypes.length;
120            paramsValueTypes = new int[nParams];
121    
122            direct = isDirectModeAllowed; // TODO on native side : test number of parameters (on 64 bits win : must be <= 4)
123            
124            StringBuilder 
125                javaSig = new StringBuilder(64), 
126                asmSig = new StringBuilder(64), 
127                dcSig = new StringBuilder(16);
128            javaSig.append('(');
129            asmSig.append('(');
130            if (prependJNIPointers)//!isCPlusPlus)
131                    dcSig.append(DC_SIGCHAR_POINTER).append(DC_SIGCHAR_POINTER); // JNIEnv*, jobject: always present in native-bound functions
132    
133                    if (BridJ.debug)
134                            BridJ.info("Analyzing " + (declaringClass == null ? "anonymous method" : declaringClass.getName() + "." + methodName));
135            
136            if (isObjCBlock)
137                appendToSignature(0, ValueType.ePointerValue, Pointer.class, Pointer.class, null, dcSig, null);
138            
139            for (int iParam = 0; iParam < nParams; iParam++) {
140    //            Options paramOptions = paramsOptions[iParam] = new Options();
141                Type genericParameterType = genericParameterTypes[iParam];
142                Class<?> parameterType = parameterTypes[iParam];
143    
144                ValueType paramValueType = getValueType(iParam, nParams, parameterType, genericParameterType, null, paramsAnnotations[iParam]);
145                if (BridJ.veryVerbose)
146                                    BridJ.info("\tparam " + paramValueType);
147                    paramsValueTypes[iParam] = paramValueType.ordinal();
148    
149                appendToSignature(iParam, paramValueType, parameterType, genericParameterType, javaSig, dcSig, asmSig);
150            }
151            javaSig.append(')');
152            asmSig.append(')');
153            dcSig.append(')');
154    
155            ValueType retType = getValueType(-1, nParams, returnType, genericReturnType, annotatedElement, returnAnnotations);
156            if (BridJ.veryVerbose)
157                            BridJ.info("\treturns " + retType);
158                    appendToSignature(-1, retType, returnType, genericReturnType, javaSig, dcSig, asmSig);
159            returnValueType = retType.ordinal();
160    
161            javaSignature = javaSig.toString();
162            asmSignature = asmSig.toString();
163            dcSignature = dcSig.toString();
164            
165            isCPlusPlus = isCPlusPlus || isVirtual;
166            
167            if (isCPlusPlus && !isStatic) {
168                    if (!startsWithThis)
169                            direct = false;
170                    bNeedsThisPointer = true;
171                            if (Platform.isWindows()) {
172                                    if (!Platform.is64Bits())
173                                            setDcCallingConvention(DC_CALL_C_X86_WIN32_THIS_MS);
174                            } else {
175                                    //if (!Platform.is64Bits())
176                                    //      setDcCallingConvention(DC_CALL_C_X86_WIN32_THIS_GNU);
177                            }
178            }
179    
180            if (nParams > Platform.getMaxDirectMappingArgCount())
181                this.direct = false;
182    
183            if (BridJ.veryVerbose) {
184                            BridJ.info("\t-> direct " + direct);
185                            BridJ.info("\t-> javaSignature " + javaSignature);
186                            BridJ.info("\t-> callIOs " + callIOs);
187                            BridJ.info("\t-> asmSignature " + asmSignature);
188                            BridJ.info("\t-> dcSignature " + dcSignature);
189                    }
190                    
191            if (BridJ.veryVerbose)
192                    BridJ.info((direct ? "[mappable as direct] " : "[not mappable as direct] ") + method);
193        }
194            
195            boolean hasCC;
196            public boolean hasCallingConvention() {
197                    return hasCC;
198            }
199            public void setCallingConvention(Convention.Style style) {
200            if (style == null)
201                return;
202        
203            if (!Platform.isWindows() || Platform.is64Bits())
204                return;
205            
206                    switch (style) {
207                    case FastCall:
208                            this.direct = false;
209                            setDcCallingConvention(Platform.isWindows() ? DC_CALL_C_X86_WIN32_FAST_MS : DC_CALL_C_DEFAULT); // TODO allow GCC-compiled C++ libs on windows
210                            break;
211                    case Pascal:
212                    case StdCall:
213                            this.direct = false;
214                            setDcCallingConvention(DC_CALL_C_X86_WIN32_STD);
215                            break;
216                    case ThisCall:
217                            this.direct = false;
218                            setDcCallingConvention(Platform.isWindows() ? DC_CALL_C_X86_WIN32_THIS_MS : DC_CALL_C_DEFAULT);
219                    }
220                    if (BridJ.veryVerbose)
221                            BridJ.info("Setting CC " + style + " (-> " + dcCallingConvention + ") for " + methodName);
222                    
223            }
224            void addCallIO(CallIO handler) {
225                    if (callIOs == null)
226                            callIOs = new ArrayList<CallIO>();
227                    callIOs.add(handler);
228            }
229            public CallIO[] getCallIOs() {
230                    if (callIOs == null)
231                            return new CallIO[0];
232                    return callIOs.toArray(new CallIO[callIOs.size()]);
233            }
234    
235            public void prependCallbackCC() {
236                    char cc = getDcCallbackConvention(getDcCallingConvention());
237                    if (cc == 0)
238                            return;
239                    
240                    dcSignature = String.valueOf(DC_SIGCHAR_CC_PREFIX) + String.valueOf(cc) + dcSignature;
241            }
242            public String getDcSignature() {
243                    return dcSignature;
244            }
245            public String getJavaSignature() {
246                    return javaSignature;
247            }
248        public String getASMSignature() {
249                    return asmSignature;
250            }
251        boolean getBoolAnnotation(Class<? extends Annotation> ac, AnnotatedElement element, Annotation... directAnnotations) {
252            Annotation ann = getAnnotation(ac, element, directAnnotations);
253            return ann != null;
254        }
255        public ValueType getValueType(int iParam, int nParams, Class<?> c, Type t, AnnotatedElement element, Annotation... directAnnotations) {
256            boolean isPtr = isAnnotationPresent(Ptr.class, element, directAnnotations);
257            boolean isCLong = isAnnotationPresent(org.bridj.ann.CLong.class, element, directAnnotations);
258            Constructor cons = getAnnotation(Constructor.class, element, directAnnotations);
259            
260            if (isPtr || cons != null || isCLong) {
261                    if (!(c == Long.class || c == Long.TYPE))
262                            throw new RuntimeException("Annotation should only be used on a long parameter, not on a " + c.getName());
263                    
264                    if (isPtr) {
265                    if (!Platform.is64Bits())
266                        direct = false;
267                } else if (isCLong) {
268                    if (Platform.CLONG_SIZE != 8)
269                        direct = false;
270                } else if (cons != null) {
271                    isCPlusPlus = true;
272                                    startsWithThis = true;
273                                    if (iParam != 0)
274                                            throw new RuntimeException("Annotation " + Constructor.class.getName() + " cannot have more than one (long) argument");
275                }
276                return ValueType.eSizeTValue;
277            }
278            if (c == null || c.equals(Void.TYPE))
279                return ValueType.eVoidValue;
280            if (c == Integer.class || c == Integer.TYPE)
281                return ValueType.eIntValue;
282            if (c == Long.class || c == Long.TYPE) {
283                    return !isPtr || Platform.is64Bits() ? ValueType.eLongValue : ValueType.eIntValue;
284            }
285            if (c == Short.class || c == Short.TYPE)
286                return ValueType.eShortValue;
287            if (c == Byte.class || c == Byte.TYPE)
288                return ValueType.eByteValue;
289            if (c == Boolean.class || c == Boolean.TYPE)
290                return ValueType.eBooleanValue;
291            if (c == Float.class || c == Float.TYPE) {
292                usesFloats();
293                return ValueType.eFloatValue;
294            }
295            if (c == char.class || c == Character.TYPE) {
296                if (Platform.WCHAR_T_SIZE != 2)
297                    direct = false;
298                return ValueType.eWCharValue;
299            }
300            if (c == Double.class || c == Double.TYPE) {
301                usesFloats();
302                return ValueType.eDoubleValue;
303            }
304            if (c == CLong.class) {
305                            direct = false;
306                            return ValueType.eCLongObjectValue;
307            }
308            if (c == SizeT.class) {
309                            direct = false;
310                            return ValueType.eSizeTObjectValue;
311            }
312            if (c == TimeT.class) {
313                            direct = false;
314                            return ValueType.eTimeTObjectValue;
315            }
316            if (Pointer.class.isAssignableFrom(c)) {
317                direct = false;
318                CallIO cio = CallIO.Utils.createPointerCallIO(c, t);
319                if (BridJ.veryVerbose)
320                    BridJ.info("CallIO : " + cio);
321                addCallIO(cio);
322                            return ValueType.ePointerValue;
323            }
324            if (c.isArray() && iParam == nParams - 1) {
325                    direct = false;
326                    return ValueType.eEllipsis;
327            }
328            if (ValuedEnum.class.isAssignableFrom(c)) {
329                    direct = false;
330                CallIO cio = CallIO.Utils.createValuedEnumCallIO((Class)Utils.getClass(Utils.getUniqueParameterizedTypeParameter(t)));
331                if (BridJ.veryVerbose)
332                    BridJ.info("CallIO : " + cio);
333                addCallIO(cio);
334                    
335                    return ValueType.eIntFlagSet;
336            }
337            if (NativeObject.class.isAssignableFrom(c)) {
338                Pointer<DCstruct> pStruct = null;
339                if (StructObject.class.isAssignableFrom(c)) {
340                    StructIO io = StructIO.getInstance(c, t);
341                    try {
342                        pStruct = DyncallStructs.buildDCstruct(io);
343                    } catch (Throwable th) {
344                        BridJ.error("Unable to create low-level struct metadata for " + Utils.toString(t) + " : won't be able to use it as a by-value function argument.", th);
345                    }
346                }
347                    addCallIO(new CallIO.NativeObjectHandler((Class<? extends NativeObject>)c, t, pStruct));
348                    direct = false;
349                    return ValueType.eNativeObjectValue;
350            }
351    
352            throw new NoSuchElementException("No " + ValueType.class.getSimpleName() + " for class " + c.getName());
353        }
354        void usesFloats() {
355                    /*
356            if (direct && Platform.isMacOSX()) {
357                direct = false;
358                assert BridJ.warning("[unstable direct] FIXME Disable direct call due to float/double usage in " + method);
359            }
360            */
361        }
362    
363        public void appendToSignature(int iParam, ValueType type, Class<?> parameterType, Type genericParameterType, StringBuilder javaSig, StringBuilder dcSig, StringBuilder asmSig) {
364            char dcChar;
365            String javaChar, asmChar = null;
366            switch (type) {
367                case eVoidValue:
368                    dcChar = DC_SIGCHAR_VOID;
369                    javaChar = "V";
370                    break;
371                case eIntValue:
372                    dcChar = DC_SIGCHAR_INT;
373                    javaChar = "I";
374                    break;
375                case eLongValue:
376                    dcChar = DC_SIGCHAR_LONGLONG;
377                    javaChar = "J";
378                    break;
379                case eSizeTValue:
380                    javaChar = "J";
381                                    if (Platform.SIZE_T_SIZE == 8) {
382                        dcChar = DC_SIGCHAR_LONGLONG;
383                    } else {
384                        dcChar = DC_SIGCHAR_INT;
385                        direct = false;
386                    }
387                    break;
388                case eShortValue:
389                    dcChar = DC_SIGCHAR_SHORT;
390                    javaChar = "S";
391                    break;
392                case eDoubleValue:
393                    dcChar = DC_SIGCHAR_DOUBLE;
394                    javaChar = "D";
395                    break;
396                case eFloatValue:
397                    dcChar = DC_SIGCHAR_FLOAT;
398                    javaChar = "F";
399                    break;
400                case eByteValue:
401                    dcChar = DC_SIGCHAR_CHAR;
402                    javaChar = "B";
403                    break;
404                case eBooleanValue:
405                    dcChar = DC_SIGCHAR_BOOL;
406                    javaChar = "Z";
407                    break;
408                case eWCharValue:
409                    switch (Platform.WCHAR_T_SIZE) {
410                    case 1:
411                        dcChar = DC_SIGCHAR_CHAR;
412                        direct = false;
413                        break;
414                    case 2:
415                        dcChar = DC_SIGCHAR_SHORT;
416                        break;
417                    case 4:
418                        dcChar = DC_SIGCHAR_INT;
419                        direct = false;
420                        break;
421                    default:
422                        throw new RuntimeException("Unhandled sizeof(wchar_t) in GetJavaTypeSignature: " + Platform.WCHAR_T_SIZE);
423                    }
424                    javaChar = "C";
425                    break;
426                case eIntFlagSet:
427                    dcChar = DC_SIGCHAR_INT;
428                    javaChar = "L" + parameterType.getName().replace('.', '/') + ";";//"Lorg/bridj/ValuedEnum;";
429                    direct = false;
430                    break;
431                case eCLongObjectValue:
432                    dcChar = DC_SIGCHAR_POINTER;
433                    javaChar = "Lorg/bridj/CLong;";
434                    direct = false;
435                    break;
436                case eSizeTObjectValue:
437                    dcChar = DC_SIGCHAR_POINTER;
438                    javaChar = "Lorg/bridj/SizeT;";
439                    direct = false;
440                    break;
441                case eTimeTObjectValue:
442                    dcChar = DC_SIGCHAR_POINTER;
443                    javaChar = "Lorg/bridj/TimeT;";
444                    direct = false;
445                    break;
446                case ePointerValue:
447                    dcChar = DC_SIGCHAR_POINTER;
448                    javaChar = "L" + parameterType.getName().replace('.', '/') + ";";
449    //                javaChar = "Lorg/bridj/Pointer;";
450                    direct = false;
451                    break;
452                case eNativeObjectValue:
453                    dcChar = DC_SIGCHAR_STRUCT; // TODO : unroll struct signature ?
454                    javaChar = "L" + parameterType.getName().replace('.', '/') + ";";
455                    direct = false;
456    //              if (parameterType.equals(declaringClass)) {
457    //                    // special case of self-returning pointers
458    //                    dcChar = DC_SIGCHAR_POINTER;
459                    break;
460                            case eEllipsis:
461                                    javaChar = "[Ljava/lang/Object;";
462                                    dcChar = '?';
463                                    break;
464                default:
465                    direct = false;
466                    throw new RuntimeException("Unhandled " + ValueType.class.getSimpleName() + ": " + type);
467            }
468            if (genericParameterType instanceof ParameterizedType && iParam < 0)
469            {
470                ParameterizedType pt = (ParameterizedType)genericParameterType;
471                // TODO handle all cases !!!
472                Type[] ts = pt.getActualTypeArguments();
473                if (ts != null && ts.length == 1) {
474                    Type t = ts[0];
475                    if (t instanceof ParameterizedType)
476                        t = ((ParameterizedType)t).getRawType();
477                    if (t instanceof Class) {
478                        Class c = (Class)t;
479                        if (javaChar.endsWith(";")) {
480                            asmChar = javaChar.substring(0, javaChar.length() - 1) + "<*L" + c.getName().replace('.', '/') + ";>";
481                            //asmChar += ";";
482                        }
483                    }   
484                }
485            }
486            if (javaSig != null)
487                javaSig.append(javaChar);
488            if (asmChar == null)
489                asmChar = javaChar;
490            if (asmSig != null)
491                asmSig.append(asmChar);
492            if (dcSig != null)
493                dcSig.append(dcChar);
494        }
495    /*
496        public void setDefinition(Method definition) {
497            this.definition = definition;
498        }
499    
500        public Method getDefinition() {
501            return definition;
502        }
503    */
504    
505    
506            public void setMethod(Method method) {
507                    this.method = method;
508                    if (method != null)
509                            this.methodName = method.getName();
510            if (declaringClass == null)
511                            setDeclaringClass(method.getDeclaringClass());
512                            
513            }
514    
515        public void setJavaSignature(String javaSignature) {
516            this.javaSignature = javaSignature;
517        }
518        
519    
520    
521            public Method getMethod() {
522                    return method;
523            }
524    
525    
526            public void setDeclaringClass(Class<?> declaringClass) {
527                    this.declaringClass = declaringClass;
528            }
529    
530    
531            public Class<?> getDeclaringClass() {
532                    return declaringClass;
533            }
534    
535    
536            public void setForwardedPointer(long forwardedPointer) {
537                    this.forwardedPointer = forwardedPointer;
538            }
539    
540    
541            public long getForwardedPointer() {
542                    return forwardedPointer;
543            }
544    
545    
546            /**
547             * Used for C++ virtual indexes and for struct fields ids
548             * @param virtualIndex
549             */
550            public void setVirtualIndex(int virtualIndex) {
551                    //new RuntimeException("Setting virtualIndex of " + getMethod().getName() + " = " + virtualIndex).printStackTrace();
552                    this.virtualIndex = virtualIndex;
553            
554            if (BridJ.veryVerbose) {
555                BridJ.info("\t-> virtualIndex " + virtualIndex);
556            }
557            }
558    
559    
560            public int getVirtualIndex() {
561                    return virtualIndex;
562            }
563    
564            public String getSymbolName() {
565                    return symbolName;
566            }
567            public void setSymbolName(String symbolName) {
568                    this.symbolName = symbolName;
569            }
570            
571            static char getDcCallbackConvention(int dcCallingConvention) {
572                    switch (dcCallingConvention) {
573                    case DC_CALL_C_X86_WIN32_STD      :
574                            return DC_SIGCHAR_CC_STDCALL;
575                    case DC_CALL_C_X86_WIN32_FAST_MS  :
576                            return DC_SIGCHAR_CC_FASTCALL_MS;
577                    case DC_CALL_C_X86_WIN32_FAST_GNU :
578                            return DC_SIGCHAR_CC_FASTCALL_GNU;
579                    case DC_CALL_C_X86_WIN32_THIS_MS  :
580                            return DC_SIGCHAR_CC_THISCALL_MS;
581                    default:
582                            return 0;
583                }
584            }
585                
586            public void setDcCallingConvention(int dcCallingConvention) {
587                    hasCC = true;
588                    this.dcCallingConvention = dcCallingConvention;
589            }
590    
591    
592            public int getDcCallingConvention() {
593                    return dcCallingConvention;
594            }
595    
596        public Object getJavaCallback() {
597            return javaCallback;
598        }
599    
600        public void setJavaCallback(Object javaCallback) {
601            this.javaCallback = javaCallback;
602        }
603        
604        public void setGenericCallback(boolean genericCallback) {
605                    this.isGenericCallback = genericCallback;
606        }
607        
608        public boolean isGenericCallback() {
609                    return isGenericCallback;
610        }
611    
612        public void setNativeClass(long nativeClass) {
613            this.nativeClass = nativeClass;
614        }
615    }