001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031package org.bridj.objc;
032
033import java.lang.reflect.Method;
034import java.lang.reflect.Type;
035import org.bridj.*;
036import static org.bridj.Pointer.*;
037import static org.bridj.objc.ObjCJNI.*;
038import static org.bridj.Platform.*;
039import static org.bridj.objc.ObjectiveCRuntime.*;
040import java.util.*;
041import org.bridj.util.Pair;
042import org.bridj.util.Utils;
043
044public class ObjCProxy extends ObjCObject {
045
046    final Map<SEL, Pair<NSMethodSignature, Method>> signatures = new HashMap<SEL, Pair<NSMethodSignature, Method>>();
047    final Object invocationTarget;
048    final static String PROXY_OBJC_CLASS_NAME = "ObjCProxy";
049
050    protected ObjCProxy() {
051        super(null);
052        peer = createObjCProxyPeer(this);
053        assert getClass() != ObjCProxy.class;
054        this.invocationTarget = this;
055    }
056
057    public ObjCProxy(Object invocationTarget) {
058        super(null);
059        peer = createObjCProxyPeer(this);
060        assert invocationTarget != null;
061        this.invocationTarget = invocationTarget;
062    }
063
064    public void addProtocol(String name) throws ClassNotFoundException {
065        Pointer<? extends ObjCObject> protocol = objc_getProtocol(pointerToCString(name));
066        if (protocol == null) {
067            throw new ClassNotFoundException("Protocol " + name + " not found !");
068        }
069        Pointer<? extends ObjCObject> cls = getObjCClass(PROXY_OBJC_CLASS_NAME);
070        if (!class_addProtocol(cls, protocol)) {
071            throw new RuntimeException("Failed to add protocol " + name + " to class " + PROXY_OBJC_CLASS_NAME);
072        }
073    }
074
075    public Object getInvocationTarget() {
076        return invocationTarget;
077    }
078
079    public Pointer<NSMethodSignature> methodSignatureForSelector(SEL sel) {
080        Pair<NSMethodSignature, Method> sig = getMethodAndSignature(sel);
081        return sig == null ? null : getPointer(sig.getFirst());
082    }
083
084    public synchronized Pair<NSMethodSignature, Method> getMethodAndSignature(SEL sel) {
085        Pair<NSMethodSignature, Method> sig = signatures.get(sel);
086        if (sig == null) {
087            try {
088                sig = computeMethodAndSignature(sel);
089                if (sig != null) {
090                    signatures.put(sel, sig);
091                }
092            } catch (Throwable th) {
093                BridJ.error("Failed to compute Objective-C signature for selector " + sel + ": " + th, th);
094            }
095        }
096        return sig;
097    }
098
099    Pair<NSMethodSignature, Method> computeMethodAndSignature(SEL sel) {
100        String name = sel.getName();
101        ObjectiveCRuntime rt = ObjectiveCRuntime.getInstance();
102        for (Method method : invocationTarget.getClass().getMethods()) {
103            String msel = rt.getSelector(method);
104            //System.out.println("Selector for method " + method.getName() + " is '" + msel + "' (vs. sel = '" + sel + "')");
105            if (msel.equals(name)) {
106                String sig = rt.getMethodSignature(method);
107                if (BridJ.debug) {
108                    BridJ.info("Objective-C signature for method " + method + " = '" + sig + "'");
109                }
110                NSMethodSignature ms = NSMethodSignature.signatureWithObjCTypes(pointerToCString(sig)).get();
111                long nArgs = ms.numberOfArguments() - 2;
112                if (nArgs != method.getParameterTypes().length) {
113                    throw new RuntimeException("Bad method signature (mismatching arg types) : '" + sig + "' for " + method);
114                }
115                return new Pair<NSMethodSignature, Method>(ms, method);
116            }
117        }
118        //if (BridJ.debug)
119        BridJ.error("Missing method for " + sel + " in class " + classHierarchyToString(getInvocationTarget().getClass()));
120
121        return null;
122    }
123
124    static String classHierarchyToString(Class c) {
125        String s = Utils.toString(c);
126        Type p = c.getGenericSuperclass();
127        while (p != null && p != Object.class && p != ObjCProxy.class) {
128            s += " extends " + Utils.toString(p);
129            p = Utils.getClass(p).getGenericSuperclass();
130        }
131        return s;
132    }
133    /*
134     static Type promote(Type type) {
135     if (type == byte.class || type == short.class || type == char.class || type == boolean.class)
136     return int.class;
137     if (type == float.class)
138     return double.class;
139     return type;
140     }
141     */
142    /*
143     static final Map<Class, Class> wrapperClasses = new HashMap<Class, Class>();
144     static {
145     wrapperClasses.put(int.class, Integer.class);
146     wrapperClasses.put(short.class, Short.class);
147     wrapperClasses.put(long.class, Long.class);
148     wrapperClasses.put(char.class, Character.class);
149     wrapperClasses.put(byte.class, Byte.class);
150     wrapperClasses.put(boolean.class, Boolean.class);
151     wrapperClasses.put(double.class, Double.class);
152     wrapperClasses.put(float.class, Float.class);
153     }
154     */
155    /*
156     static Object constrain(Object value, Type type) {
157     Class c = Utils.getClass(type);
158     if (c.isPrimitive())
159     c = wrapperClasses.get(c);
160     if (c.isInstance(value))
161     return value;
162        
163     // Assume value is an Integer or a Double
164     if (type == byte.class)
165     return ((Integer)value).byteValue();
166     if (type == short.class)
167     return ((Integer)value).shortValue();
168     if (type == char.class)
169     return (char)((Integer)value).shortValue();
170     if (type == boolean.class)
171     return ((Integer)value).byteValue() != 0;
172     if (type == float.class)
173     return ((Double)value).floatValue();
174        
175     throw new UnsupportedOperationException("Don't know how to constrain value " + value + " to type " + Utils.toString(type));
176     }
177     */
178
179    public synchronized void forwardInvocation(Pointer<NSInvocation> pInvocation) {
180        NSInvocation invocation = pInvocation.get();
181        SEL sel = invocation.selector();
182        Pair<NSMethodSignature, Method> sigMet = getMethodAndSignature(sel);
183        NSMethodSignature sig = sigMet.getFirst();
184        Method method = sigMet.getSecond();
185
186        //System.out.println("forwardInvocation(" + invocation + ") : sel = " + sel);
187        Type[] paramTypes = method.getGenericParameterTypes();
188        int nArgs = paramTypes.length;//(int)sig.numberOfArguments();
189        Object[] args = new Object[nArgs];
190        for (int i = 0; i < nArgs; i++) {
191            Type paramType = paramTypes[i];
192            PointerIO<?> paramIO = PointerIO.getInstance(paramType);//promote(paramType));
193            Pointer<?> pArg = allocate(paramIO);
194            invocation.getArgument_atIndex(pArg, i + 2);
195            Object arg = pArg.get();
196            args[i] = arg;//constrain(arg, paramType);
197        }
198        try {
199            method.setAccessible(true);
200            Object ret = method.invoke(getInvocationTarget(), args);
201            //System.out.println("Invoked  " + method + " : " + ret);
202            Type returnType = method.getGenericReturnType();
203            if (returnType == void.class) {
204                assert ret == null;
205            } else {
206                PointerIO<?> returnIO = PointerIO.getInstance(returnType);
207                Pointer<Object> pRet = (Pointer) allocate(returnIO);
208                pRet.set(ret);
209                invocation.setReturnValue(pRet);
210            }
211        } catch (Throwable ex) {
212            throw new RuntimeException("Failed to forward invocation from Objective-C to Java invocation target " + getInvocationTarget() + " for method " + method + " : " + ex, ex);
213        }
214    }
215}