/* * Copyright (C) 2013 RoboVM AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.robovm.objc; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.IdentityHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.robovm.objc.block.VoidBooleanBlock; import org.robovm.rt.VM; import org.robovm.rt.bro.Struct; import org.robovm.rt.bro.annotation.Callback; import org.robovm.rt.bro.annotation.MachineSizedUInt; import org.robovm.rt.bro.annotation.Pointer; import org.robovm.rt.bro.annotation.StructMember; /** * {@link Struct} mapping the {@code Block_literal} struct used internally by * the Objective-C runtime. This class is only for internal use. See the * source code of the block interfaces in the {@link org.robovm.objc.block} * package (e.g. {@link VoidBooleanBlock} for examples on how to marshal * Objective-C block types. * The <a href="http://clang.llvm.org/docs/Block-ABI-Apple.html">Block ABI * documentation</a> has more information on how blocks are implemented in * Objective-C. */ public final class ObjCBlock extends Struct<ObjCBlock> { @StructMember(0) public native @Pointer long isa(); @StructMember(0) public native ObjCBlock isa(@Pointer long isa); @StructMember(1) public native int flags(); @StructMember(1) public native ObjCBlock flags(int flags); @StructMember(2) public native int reserved(); @StructMember(2) public native ObjCBlock reserved(int reserved); @StructMember(3) public native @Pointer long invoke(); @StructMember(3) public native ObjCBlock invoke(@Pointer long invoke); @StructMember(4) public native Descriptor descriptor(); @StructMember(4) public native ObjCBlock descriptor(Descriptor descriptor); @StructMember(5) public native @Pointer long object_addr(); @StructMember(5) public native ObjCBlock object_addr(@Pointer long object_addr); @StructMember(6) public native @Pointer long wrapper_addr(); @StructMember(6) public native ObjCBlock wrapper_addr(@Pointer long wrapper_addr); public Object object() { return VM.castAddressToObject(object_addr()); } public ObjCBlock object(Object o) { object_addr(VM.getObjectAddress(o)); return this; } public Wrapper wrapper() { return (Wrapper) VM.castAddressToObject(wrapper_addr()); } public ObjCBlock wrapper(Wrapper o) { wrapper_addr(VM.getObjectAddress(o)); return this; } public boolean hasObject() { return descriptor().getHandle() == Wrapper.DESCRIPTOR.getHandle(); } public static final class Descriptor extends Struct<Descriptor> { @StructMember(0) public native @MachineSizedUInt long reserved(); @StructMember(0) public native Descriptor reserved(@MachineSizedUInt long reserved); @StructMember(1) public native @MachineSizedUInt long literal_size(); @StructMember(1) public native Descriptor literal_size(@MachineSizedUInt long literal_size); @StructMember(2) public native @Pointer long copy_helper(); @StructMember(2) public native Descriptor copy_helper(@Pointer long copy_helper); @StructMember(3) public native @Pointer long dispose_helper(); @StructMember(3) public native Descriptor dispose_helper(@Pointer long dispose_helper); } public static final class Wrapper { private static final Descriptor DESCRIPTOR; private static final long NSStackBlock; private static final int BLOCK_HAS_COPY_DISPOSE = (1 << 25); private static final int BLOCK_HAS_STRET = (1 << 29); private static final int BLOCK_HAS_SIGNATURE = (1 << 30); static { try { long copyImpl = VM.getCallbackMethodImpl( Wrapper.class.getDeclaredMethod("copy", ObjCBlock.class, ObjCBlock.class)); long disposeImpl = VM.getCallbackMethodImpl( Wrapper.class.getDeclaredMethod("dispose", ObjCBlock.class)); DESCRIPTOR = new Descriptor() .literal_size(ObjCBlock.sizeOf()) .copy_helper(copyImpl) .dispose_helper(disposeImpl); } catch (Exception e) { throw new Error(e); } NSStackBlock = ObjCRuntime.objc_getClass(VM.getStringUTFChars("__NSStackBlock__")); if (NSStackBlock == 0L) { throw new Error("Objective-C class __NSStackBlock__ not found"); } } private final long callbackImpl; private final int flags; private final IdentityHashMap<Object, AtomicInteger> refCounts = new IdentityHashMap<Object, AtomicInteger>(); public Wrapper(Class<?> callbacks) { this(findCallback(callbacks), false); } public Wrapper(Method method) { this(method, true); } public Wrapper(Class<?> cls, String methodName) { this(findCallback(cls, methodName), true); } private Wrapper(Method method, boolean validate) { if (validate && method.getAnnotation(Callback.class) == null) { throw new IllegalArgumentException("Method " + method + " is not a @Callback method"); } callbackImpl = VM.getCallbackMethodImpl(method); int flags = BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_SIGNATURE; if (ObjCRuntime.isStret(method)) { flags |= BLOCK_HAS_STRET; } this.flags = flags; } private static Method findCallback(Class<?> cls, String methodName) { for (Method m : cls.getDeclaredMethods()) { if (m.getName().equals(methodName)) { return m; } } throw new NoSuchMethodError(methodName); } private static Method findCallback(Class<?> cls) { Method method = null; for (Method m : cls.getDeclaredMethods()) { if (m.getAnnotation(Callback.class) != null) { if (method != null) { throw new IllegalArgumentException("Several @Callback " + "methods found in class " + cls.getName()); } method = m; } } if (method == null) { throw new IllegalArgumentException("No @Callback method found " + "in class " + cls.getName()); } return method; } public static void initWrappers(Class<?> cls) { // The ObjCBlockPlugin generates @Callback methods named $cb$block$<id> // which each have a matching Wrapper static field named // $cb$block$<id>$wrapper. try { for (Method m : cls.getDeclaredMethods()) { if (m.getName().startsWith("$cb$block$")) { Field f = cls.getDeclaredField(m.getName() + "$wrapper"); f.setAccessible(true); f.set(null, new Wrapper(m, true)); } } } catch (Throwable t) { throw new Error(t); } } public Object toObject(long handle) { ObjCBlock block = Struct.toStruct(ObjCBlock.class, handle); if (block == null) { return null; } return block.object(); } public ObjCBlock toObjCBlock(Object o) { if (o == null) { return null; } // Create an Objective-C block struct which looks like it was // allocated on the stack. The struct will be GC reachable during // the duration of the call to the Objective-C side. The Objective-C // side will copy the block if it needs to call it later which will // trigger a call to the copy() method below. The GC will reclaim // the struct memory area some time after the call has finished but // the Java block instance (e.g. VoidBlock) will be retained on // every copy and only GCed after all copies have been deallocated. ObjCBlock block = new ObjCBlock() .isa(NSStackBlock) .flags(flags) .invoke(callbackImpl) .descriptor(DESCRIPTOR) .object(o) .wrapper(this); return block; } @Callback private static void copy(ObjCBlock dst, ObjCBlock src) { // Called from the Objective-C side every time the block is copied. // Increase the ref count of the block object on the Java side to // prevent it from being GCed. Object blockObj = src.object(); Wrapper wrapper = src.wrapper(); IdentityHashMap<Object, AtomicInteger> refCounts = wrapper.refCounts; synchronized (refCounts) { AtomicInteger count = refCounts.get(blockObj); if (count == null) { count = new AtomicInteger(0); refCounts.put(blockObj, count); } count.incrementAndGet(); } } @Callback private static void dispose(ObjCBlock block) { // Called from the Objective-C side when a previously copied block // is released and about to be deallocated. Decrease the ref count // of the object on the Java side. Object blockObj = block.object(); Wrapper wrapper = block.wrapper(); IdentityHashMap<Object, AtomicInteger> refCounts = wrapper.refCounts; synchronized (refCounts) { if (refCounts.get(blockObj).decrementAndGet() == 0) { refCounts.remove(blockObj); } } } } }