001    package org.bridj.objc;
002    
003    import java.lang.reflect.Method;
004    import java.lang.reflect.Type;
005    import org.bridj.*;
006    import static org.bridj.Pointer.*;
007    import static org.bridj.objc.ObjCJNI.*;
008    import static org.bridj.Platform.*;
009    import static org.bridj.objc.ObjectiveCRuntime.*;
010    import java.util.*;
011    import org.bridj.util.Pair;
012    import org.bridj.util.Utils;
013    
014    public class ObjCProxy extends ObjCObject {
015            final Map<SEL, Pair<NSMethodSignature, Method>> signatures = new HashMap<SEL, Pair<NSMethodSignature, Method>>();
016            final Object invocationTarget;
017        final static String PROXY_OBJC_CLASS_NAME = "ObjCProxy";
018            
019            protected ObjCProxy() {
020                    super(null);
021                    peer = createObjCProxyPeer(this);
022                    assert getClass() != ObjCProxy.class;
023                    this.invocationTarget = this;
024            }
025            public ObjCProxy(Object invocationTarget) {
026                    super(null);
027                    peer = createObjCProxyPeer(this);
028                    assert invocationTarget != null;
029                    this.invocationTarget = invocationTarget;
030            }
031            
032        public void addProtocol(String name) throws ClassNotFoundException {
033            Pointer<? extends ObjCObject> protocol = objc_getProtocol(pointerToCString(name));
034            if (protocol == null)
035                throw new ClassNotFoundException("Protocol " + name + " not found !");
036            Pointer<? extends ObjCObject> cls = getObjCClass(PROXY_OBJC_CLASS_NAME);
037            if (!class_addProtocol(cls, protocol))
038                throw new RuntimeException("Failed to add protocol " + name + " to class " + PROXY_OBJC_CLASS_NAME);
039        }
040            public Object getInvocationTarget() {
041                    return invocationTarget;
042            }
043            public Pointer<NSMethodSignature> methodSignatureForSelector(SEL sel) {
044                    Pair<NSMethodSignature, Method> sig = getMethodAndSignature(sel);
045            return sig == null ? null : pointerTo(sig.getFirst());
046            }
047        public synchronized Pair<NSMethodSignature, Method> getMethodAndSignature(SEL sel) {
048                    Pair<NSMethodSignature, Method> sig = signatures.get(sel);
049                    if (sig == null) {
050                            try {
051                                    sig = computeMethodAndSignature(sel);
052                                    if (sig != null)
053                                            signatures.put(sel, sig);
054                            } catch (Throwable th) {
055                                    BridJ.error("Failed to compute Objective-C signature for selector " + sel + ": " + th, th);
056                            }
057                    }
058                    return sig;
059            }
060            Pair<NSMethodSignature, Method> computeMethodAndSignature(SEL sel) {
061                    String name = sel.getName();
062                    ObjectiveCRuntime rt = ObjectiveCRuntime.getInstance();
063                    for (Method method : invocationTarget.getClass().getMethods()) {
064                            String msel = rt.getSelector(method);
065                            //System.out.println("Selector for method " + method.getName() + " is '" + msel + "' (vs. sel = '" + sel + "')");
066                            if (msel.equals(name)) {
067                                    String sig = rt.getMethodSignature(method);
068                                    if (BridJ.debug)
069                                            BridJ.info("Objective-C signature for method " + method + " = '" + sig + "'");
070                                    NSMethodSignature ms = NSMethodSignature.signatureWithObjCTypes(pointerToCString(sig)).get();
071                    long nArgs = ms.numberOfArguments() - 2;
072                    if (nArgs != method.getParameterTypes().length)
073                        throw new RuntimeException("Bad method signature (mismatching arg types) : '" + sig + "' for " + method);
074                                    return new Pair<NSMethodSignature, Method>(ms, method);
075                            }
076                    }
077                    //if (BridJ.debug)
078            BridJ.error("Missing method for " + sel + " in class " + classHierarchyToString(getInvocationTarget().getClass()));
079                                    
080                    return null;
081            }
082        static String classHierarchyToString(Class c) {
083            String s = Utils.toString(c);
084            Type p = c.getGenericSuperclass();
085            while (p != null && p != Object.class && p != ObjCProxy.class) {
086                s += " extends " + Utils.toString(p);
087                p = Utils.getClass(p).getGenericSuperclass();
088            }
089            return s;
090        }
091        /*
092        static Type promote(Type type) {
093            if (type == byte.class || type == short.class || type == char.class || type == boolean.class)
094                return int.class;
095            if (type == float.class)
096                return double.class;
097            return type;
098        }
099        */
100        /*
101        static final Map<Class, Class> wrapperClasses = new HashMap<Class, Class>();
102        static {
103            wrapperClasses.put(int.class, Integer.class);
104            wrapperClasses.put(short.class, Short.class);
105            wrapperClasses.put(long.class, Long.class);
106            wrapperClasses.put(char.class, Character.class);
107            wrapperClasses.put(byte.class, Byte.class);
108            wrapperClasses.put(boolean.class, Boolean.class);
109            wrapperClasses.put(double.class, Double.class);
110            wrapperClasses.put(float.class, Float.class);
111        }
112        */
113        /*
114        static Object constrain(Object value, Type type) {
115            Class c = Utils.getClass(type);
116            if (c.isPrimitive())
117                c = wrapperClasses.get(c);
118            if (c.isInstance(value))
119                return value;
120            
121            // Assume value is an Integer or a Double
122            if (type == byte.class)
123                return ((Integer)value).byteValue();
124            if (type == short.class)
125                return ((Integer)value).shortValue();
126            if (type == char.class)
127                return (char)((Integer)value).shortValue();
128            if (type == boolean.class)
129                return ((Integer)value).byteValue() != 0;
130            if (type == float.class)
131                return ((Double)value).floatValue();
132            
133            throw new UnsupportedOperationException("Don't know how to constrain value " + value + " to type " + Utils.toString(type));
134        }
135        */
136            public synchronized void forwardInvocation(Pointer<NSInvocation> pInvocation) {
137            NSInvocation invocation = pInvocation.get();
138            SEL sel = invocation.selector();
139                    Pair<NSMethodSignature, Method> sigMet = getMethodAndSignature(sel);
140            NSMethodSignature sig = sigMet.getFirst();
141            Method method = sigMet.getSecond();
142            
143            //System.out.println("forwardInvocation(" + invocation + ") : sel = " + sel);
144            Type[] paramTypes = method.getGenericParameterTypes();
145            int nArgs = paramTypes.length;//(int)sig.numberOfArguments();
146            Object[] args = new Object[nArgs];
147            for (int i = 0; i < nArgs; i++) {
148                Type paramType = paramTypes[i];
149                PointerIO<?> paramIO = PointerIO.getInstance(paramType);//promote(paramType));
150                Pointer<?> pArg = allocate(paramIO);
151                invocation.getArgument_atIndex(pArg, i + 2);
152                Object arg = pArg.get();
153                args[i] = arg;//constrain(arg, paramType);
154            }
155                    try {
156                method.setAccessible(true);
157                Object ret = method.invoke(getInvocationTarget(), args);
158                //System.out.println("Invoked  " + method + " : " + ret);
159                Type returnType = method.getGenericReturnType();
160                if (returnType == void.class) {
161                    assert ret == null;
162                } else {
163                    PointerIO<?> returnIO = PointerIO.getInstance(returnType);
164                    Pointer<Object> pRet = (Pointer)allocate(returnIO);
165                    pRet.set(ret);
166                    invocation.setReturnValue(pRet);
167                }
168            } catch (Throwable ex) {
169                throw new RuntimeException("Failed to forward invocation from Objective-C to Java invocation target " + getInvocationTarget() + " for method " + method + " : " + ex, ex);
170            }
171            }
172            
173            
174    }
175