1 /* 2 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.apple.jobjc; 26 27 import java.io.StringWriter; 28 import java.lang.reflect.Method; 29 import java.util.HashSet; 30 import java.util.Set; 31 32 import com.apple.jobjc.Coder.PrimitivePointerCoder; 33 import com.apple.jobjc.Coder.VoidCoder; 34 import com.apple.jobjc.Invoke.MsgSend; 35 36 37 final class Subclassing { 38 static native long allocateClassPair(long superClass, String name); 39 static native boolean addIVarForJObj(long clazz); 40 static native boolean patchAlloc(long classPtr); 41 static native boolean addMethod(long cls, String name, Method jMethod, CIF cif, long cifPtr, String objCEncodedType); 42 static native void registerClassPair(long clazz); 43 44 static native <T extends ID> T getJObjectFromIVar(long objPtr); 45 static native void initJObjectToIVar(long objPtr, ID object); 46 47 final Set<Long> registeredUserSubclasses = new HashSet<Long>(); 48 final JObjCRuntime runtime; 49 50 Subclassing(JObjCRuntime runtime){ 51 this.runtime = runtime; 52 } 53 54 boolean registerUserClass(final Class<? extends ID> clazz, final Class<? extends NSClass> clazzClazz) { 55 final String nativeClassName = clazz.getSimpleName(); 56 // Is it already registered? 57 if(0 != NSClass.getNativeClassByName(nativeClassName)) 58 return false; 59 60 if(clazz.isAnonymousClass()) 61 throw new RuntimeException("JObjC cannot register anonymous classes."); 62 63 // Verify superclass 64 long superClass = NSClass.getNativeClassByName(clazz.getSuperclass().getSimpleName()); 65 if(0 == superClass) 66 throw new RuntimeException(clazz.getSuperclass() + ", the superclass of " + clazz + ", must be a registered class."); 67 68 runtime.registerPackage(clazz.getPackage().getName()); 69 70 // Create class 71 long classPtr = Subclassing.allocateClassPair(superClass, nativeClassName); 72 if(classPtr == 0) throw new RuntimeException("objc_allocateClassPair returned 0."); 73 74 // Add ivar to hold jobject 75 boolean addedI = Subclassing.addIVarForJObj(classPtr); 76 if(!addedI) throw new RuntimeException("class_addIvar returned false."); 77 78 // Verify constructor 79 try { 80 clazz.getConstructor(ID.CTOR_ARGS); 81 } catch (Exception e) { 82 throw new RuntimeException("Could not access required constructor: " + ID.CTOR_ARGS, e); 83 } 84 85 // Patch alloc to create corresponding jobject on invoke 86 patchAlloc(classPtr); 87 88 // Add methods 89 Set<String> takenSelNames = new HashSet<String>(); 90 for(Method method : clazz.getDeclaredMethods()){ 91 // No overloading 92 String selName = SEL.selectorName(method.getName(), method.getParameterTypes().length > 0); 93 if(takenSelNames.contains(selName)) 94 throw new RuntimeException("Obj-C does not allow method overloading. The Objective-C selector '" 95 + selName + "' appears more than once in class " + clazz.getCanonicalName() + " / " + nativeClassName + "."); 96 97 method.setAccessible(true); 98 99 // Divine CIF 100 Coder returnCoder = Coder.getCoderAtRuntimeForType(method.getReturnType()); 101 Class[] paramTypes = method.getParameterTypes(); 102 Coder[] argCoders = new Coder[paramTypes.length]; 103 for(int i = 0; i < paramTypes.length; i++) 104 argCoders[i] = Coder.getCoderAtRuntimeForType(paramTypes[i]); 105 106 CIF cif = new MsgSend(runtime, selName, returnCoder, argCoders).funCall.cif; 107 108 // .. and objc encoding 109 StringWriter encType = new StringWriter(); 110 encType.append(returnCoder.getObjCEncoding()); 111 encType.append("@:"); 112 for(int i = 0; i < argCoders.length; i++) 113 encType.append(argCoders[i].getObjCEncoding()); 114 115 // Add it! 116 boolean addedM = Subclassing.addMethod(classPtr, selName, method, cif, cif.cif.bufferPtr, encType.toString()); 117 if(!addedM) throw new RuntimeException("Failed to add method."); 118 takenSelNames.add(selName); 119 } 120 121 // Seal it 122 Subclassing.registerClassPair(classPtr); 123 registeredUserSubclasses.add(classPtr); 124 125 return true; 126 } 127 128 boolean isUserClass(long clsPtr) { 129 return registeredUserSubclasses.contains(clsPtr); 130 } 131 132 // Called from JNI 133 134 private static void initJObject(final long objPtr){ 135 // System.err.println("initJObject " + objPtr + " / " + Long.toHexString(objPtr)); 136 ID newObj = ID.createNewObjCObjectFor(JObjCRuntime.inst(), objPtr, NSClass.getClass(objPtr)); 137 // System.err.println("... " + newObj); 138 initJObjectToIVar(objPtr, newObj); 139 } 140 141 private static void invokeFromJNI(ID obj, Method method, CIF cif, long result, long args){ 142 assert obj != null; 143 assert obj.getClass().equals(method.getDeclaringClass()) : 144 obj.getClass().toString() + " != " + method.getDeclaringClass().toString(); 145 146 final int argCount = method.getParameterTypes().length; 147 148 // The first two args & coders are for objc id and sel. Skip them. 149 final Object[] argObjects = new Object[argCount]; 150 for(int i = 0; i < argCount; i++){ 151 final long argAddrAddr = args + ((i+2) * JObjCRuntime.PTR_LEN); 152 final long argAddr = PrimitivePointerCoder.INST.popPtr(obj.runtime, argAddrAddr); 153 argObjects[i] = cif.argCoders[i + 2].pop(obj.runtime, argAddr); 154 } 155 156 Object retVal; 157 try { 158 retVal = method.invoke(obj, argObjects); 159 } catch (Exception e) { 160 e.printStackTrace(); 161 throw new RuntimeException(e); 162 } 163 164 if(!(cif.returnCoder instanceof VoidCoder)) 165 cif.returnCoder.push(obj.runtime, result, retVal); 166 } 167 }