blob: c0e72b13e33c34ab4b304f3327758af284ebf4cf [file] [log] [blame]
/*
* 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 extends ID> T getJObjectFromIVar(long objPtr);
static native void initJObjectToIVar(long objPtr, ID object);
final Set<Long> registeredUserSubclasses = new HashSet<Long>();
final JObjCRuntime runtime;
Subclassing(JObjCRuntime runtime){
this.runtime = runtime;
}
boolean registerUserClass(final Class<? extends ID> clazz, final Class<? extends NSClass> 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<String> takenSelNames = new HashSet<String>();
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);
}
}