1 /* 2 * Copyright (c) 2011, 2012, 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 import javax.tools.annotation.GenerateNativeHeader; 37 38 /* No native methods here, but the constants are needed in the supporting JNI code */ 39 @GenerateNativeHeader 40 final class Subclassing { 41 static native long allocateClassPair(long superClass, String name); 42 static native boolean addIVarForJObj(long clazz); 43 static native boolean patchAlloc(long classPtr); 44 static native boolean addMethod(long cls, String name, Method jMethod, CIF cif, long cifPtr, String objCEncodedType); 45 static native void registerClassPair(long clazz); 46 47 static native <T extends ID> T getJObjectFromIVar(long objPtr); 48 static native void initJObjectToIVar(long objPtr, ID object); 49 50 final Set<Long> registeredUserSubclasses = new HashSet<Long>(); 51 final JObjCRuntime runtime; 52 53 Subclassing(JObjCRuntime runtime){ 54 this.runtime = runtime; 55 } 56 57 boolean registerUserClass(final Class<? extends ID> clazz, final Class<? extends NSClass> clazzClazz) { 58 final String nativeClassName = clazz.getSimpleName(); 59 // Is it already registered? 60 if(0 != NSClass.getNativeClassByName(nativeClassName)) 61 return false; 62 63 if(clazz.isAnonymousClass()) 64 throw new RuntimeException("JObjC cannot register anonymous classes."); 65 66 // Verify superclass 67 long superClass = NSClass.getNativeClassByName(clazz.getSuperclass().getSimpleName()); 68 if(0 == superClass) 69 throw new RuntimeException(clazz.getSuperclass() + ", the superclass of " + clazz + ", must be a registered class."); 70 71 runtime.registerPackage(clazz.getPackage().getName()); 72 73 // Create class 74 long classPtr = Subclassing.allocateClassPair(superClass, nativeClassName); 75 if(classPtr == 0) throw new RuntimeException("objc_allocateClassPair returned 0."); 76 77 // Add ivar to hold jobject 78 boolean addedI = Subclassing.addIVarForJObj(classPtr); 79 if(!addedI) throw new RuntimeException("class_addIvar returned false."); 80 81 // Verify constructor 82 try { 83 clazz.getConstructor(ID.CTOR_ARGS); 84 } catch (Exception e) { 85 throw new RuntimeException("Could not access required constructor: " + ID.CTOR_ARGS, e); 86 } 87 88 // Patch alloc to create corresponding jobject on invoke 89 patchAlloc(classPtr); 90 91 // Add methods 92 Set<String> takenSelNames = new HashSet<String>(); 93 for(Method method : clazz.getDeclaredMethods()){ 94 // No overloading 95 String selName = SEL.selectorName(method.getName(), method.getParameterTypes().length > 0); 96 if(takenSelNames.contains(selName)) 97 throw new RuntimeException("Obj-C does not allow method overloading. The Objective-C selector '" 98 + selName + "' appears more than once in class " + clazz.getCanonicalName() + " / " + nativeClassName + "."); 99 100 method.setAccessible(true); 101 102 // Divine CIF 103 Coder returnCoder = Coder.getCoderAtRuntimeForType(method.getReturnType()); 104 Class[] paramTypes = method.getParameterTypes(); 105 Coder[] argCoders = new Coder[paramTypes.length]; 106 for(int i = 0; i < paramTypes.length; i++) 107 argCoders[i] = Coder.getCoderAtRuntimeForType(paramTypes[i]); 108 109 CIF cif = new MsgSend(runtime, selName, returnCoder, argCoders).funCall.cif; 110 111 // .. and objc encoding 112 StringWriter encType = new StringWriter(); 113 encType.append(returnCoder.getObjCEncoding()); 114 encType.append("@:"); 115 for(int i = 0; i < argCoders.length; i++) 116 encType.append(argCoders[i].getObjCEncoding()); 117 118 // Add it! 119 boolean addedM = Subclassing.addMethod(classPtr, selName, method, cif, cif.cif.bufferPtr, encType.toString()); 120 if(!addedM) throw new RuntimeException("Failed to add method."); 121 takenSelNames.add(selName); 122 } 123 124 // Seal it 125 Subclassing.registerClassPair(classPtr); 126 registeredUserSubclasses.add(classPtr); 127 128 return true; 129 } 130 131 boolean isUserClass(long clsPtr) { 132 return registeredUserSubclasses.contains(clsPtr); 133 } 134 135 // Called from JNI 136 137 private static void initJObject(final long objPtr){ 138 // System.err.println("initJObject " + objPtr + " / " + Long.toHexString(objPtr)); 139 ID newObj = ID.createNewObjCObjectFor(JObjCRuntime.inst(), objPtr, NSClass.getClass(objPtr)); 140 // System.err.println("... " + newObj); 141 initJObjectToIVar(objPtr, newObj); 142 } 143 144 private static void invokeFromJNI(ID obj, Method method, CIF cif, long result, long args){ 145 assert obj != null; 146 assert obj.getClass().equals(method.getDeclaringClass()) : 147 obj.getClass().toString() + " != " + method.getDeclaringClass().toString(); 148 149 final int argCount = method.getParameterTypes().length; 150 151 // The first two args & coders are for objc id and sel. Skip them. 152 final Object[] argObjects = new Object[argCount]; 153 for(int i = 0; i < argCount; i++){ 154 final long argAddrAddr = args + ((i+2) * JObjCRuntime.PTR_LEN); 155 final long argAddr = PrimitivePointerCoder.INST.popPtr(obj.runtime, argAddrAddr); 156 argObjects[i] = cif.argCoders[i + 2].pop(obj.runtime, argAddr); 157 } 158 159 Object retVal; 160 try { 161 retVal = method.invoke(obj, argObjects); 162 } catch (Exception e) { 163 e.printStackTrace(); 164 throw new RuntimeException(e); 165 } 166 167 if(!(cif.returnCoder instanceof VoidCoder)) 168 cif.returnCoder.push(obj.runtime, result, retVal); 169 } 170 }