/* * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.apple.jobjc; import java.io.StringWriter; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import com.apple.jobjc.Coder.PrimitivePointerCoder; import com.apple.jobjc.Coder.VoidCoder; import com.apple.jobjc.Invoke.MsgSend; final class Subclassing { static native long allocateClassPair(long superClass, String name); static native boolean addIVarForJObj(long clazz); static native boolean patchAlloc(long classPtr); static native boolean addMethod(long cls, String name, Method jMethod, CIF cif, long cifPtr, String objCEncodedType); static native void registerClassPair(long clazz); static native T getJObjectFromIVar(long objPtr); static native void initJObjectToIVar(long objPtr, ID object); final Set registeredUserSubclasses = new HashSet(); final JObjCRuntime runtime; Subclassing(JObjCRuntime runtime){ this.runtime = runtime; } boolean registerUserClass(final Class clazz, final Class clazzClazz) { final String nativeClassName = clazz.getSimpleName(); // Is it already registered? if(0 != NSClass.getNativeClassByName(nativeClassName)) return false; if(clazz.isAnonymousClass()) throw new RuntimeException("JObjC cannot register anonymous classes."); // Verify superclass long superClass = NSClass.getNativeClassByName(clazz.getSuperclass().getSimpleName()); if(0 == superClass) throw new RuntimeException(clazz.getSuperclass() + ", the superclass of " + clazz + ", must be a registered class."); runtime.registerPackage(clazz.getPackage().getName()); // Create class long classPtr = Subclassing.allocateClassPair(superClass, nativeClassName); if(classPtr == 0) throw new RuntimeException("objc_allocateClassPair returned 0."); // Add ivar to hold jobject boolean addedI = Subclassing.addIVarForJObj(classPtr); if(!addedI) throw new RuntimeException("class_addIvar returned false."); // Verify constructor try { clazz.getConstructor(ID.CTOR_ARGS); } catch (Exception e) { throw new RuntimeException("Could not access required constructor: " + ID.CTOR_ARGS, e); } // Patch alloc to create corresponding jobject on invoke patchAlloc(classPtr); // Add methods Set takenSelNames = new HashSet(); for(Method method : clazz.getDeclaredMethods()){ // No overloading String selName = SEL.selectorName(method.getName(), method.getParameterTypes().length > 0); if(takenSelNames.contains(selName)) throw new RuntimeException("Obj-C does not allow method overloading. The Objective-C selector '" + selName + "' appears more than once in class " + clazz.getCanonicalName() + " / " + nativeClassName + "."); method.setAccessible(true); // Divine CIF Coder returnCoder = Coder.getCoderAtRuntimeForType(method.getReturnType()); Class[] paramTypes = method.getParameterTypes(); Coder[] argCoders = new Coder[paramTypes.length]; for(int i = 0; i < paramTypes.length; i++) argCoders[i] = Coder.getCoderAtRuntimeForType(paramTypes[i]); CIF cif = new MsgSend(runtime, selName, returnCoder, argCoders).funCall.cif; // .. and objc encoding StringWriter encType = new StringWriter(); encType.append(returnCoder.getObjCEncoding()); encType.append("@:"); for(int i = 0; i < argCoders.length; i++) encType.append(argCoders[i].getObjCEncoding()); // Add it! boolean addedM = Subclassing.addMethod(classPtr, selName, method, cif, cif.cif.bufferPtr, encType.toString()); if(!addedM) throw new RuntimeException("Failed to add method."); takenSelNames.add(selName); } // Seal it Subclassing.registerClassPair(classPtr); registeredUserSubclasses.add(classPtr); return true; } boolean isUserClass(long clsPtr) { return registeredUserSubclasses.contains(clsPtr); } // Called from JNI private static void initJObject(final long objPtr){ // System.err.println("initJObject " + objPtr + " / " + Long.toHexString(objPtr)); ID newObj = ID.createNewObjCObjectFor(JObjCRuntime.inst(), objPtr, NSClass.getClass(objPtr)); // System.err.println("... " + newObj); initJObjectToIVar(objPtr, newObj); } private static void invokeFromJNI(ID obj, Method method, CIF cif, long result, long args){ assert obj != null; assert obj.getClass().equals(method.getDeclaringClass()) : obj.getClass().toString() + " != " + method.getDeclaringClass().toString(); final int argCount = method.getParameterTypes().length; // The first two args & coders are for objc id and sel. Skip them. final Object[] argObjects = new Object[argCount]; for(int i = 0; i < argCount; i++){ final long argAddrAddr = args + ((i+2) * JObjCRuntime.PTR_LEN); final long argAddr = PrimitivePointerCoder.INST.popPtr(obj.runtime, argAddrAddr); argObjects[i] = cif.argCoders[i + 2].pop(obj.runtime, argAddr); } Object retVal; try { retVal = method.invoke(obj, argObjects); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } if(!(cif.returnCoder instanceof VoidCoder)) cif.returnCoder.push(obj.runtime, result, retVal); } }