/* * Copyright (C) 2015 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 static org.junit.Assert.*; import java.lang.reflect.Method; import java.util.Set; import java.util.TreeSet; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.robovm.apple.foundation.NSObject; import org.robovm.apple.foundation.NSObjectProtocol; import org.robovm.apple.foundation.NSString; import org.robovm.objc.annotation.BindSelector; import org.robovm.objc.annotation.CustomClass; import org.robovm.objc.annotation.NotImplemented; import org.robovm.rt.bro.Bro; import org.robovm.rt.bro.NativeObject; import org.robovm.rt.bro.annotation.Callback; /** * Tests that the {@code ObjCMemberPlugin} generates the expected * {@link Callback} methods for classes subclassing native ObjC classes. */ public class CustomClassTest { public static class SubClass1 extends NSObject { @Override public String description() { return "(overridden) " + super.description(); } } @CustomClass("SubClass2") public static class SubClass2 extends NSObject {} public static class SubClass3 extends SubClass1 { @Override public String description() { return "(overridden again) " + super.description(); } } public interface Protocol extends NSObjectProtocol { @org.robovm.objc.annotation.Method(selector = "foo:") NSObject foo(NSObject obj); @org.robovm.objc.annotation.Method(selector = "bar:") NSObject bar(NSObject obj); } public static class ProtocolAdapter extends NSObject implements Protocol { @NotImplemented("foo:") public NSObject foo(NSObject obj) { return null; } @NotImplemented("bar:") public NSObject bar(NSObject obj) { return null; } } public static class ProtocolImpl extends ProtocolAdapter { @Override public NSObject foo(NSObject obj) { return new NSString("foo" + obj); } } public static abstract class AbstractBaseClass extends NSObject { @Override public String description() { return "(abstract base) " + super.description(); } } public static class ConcreteSubClass extends AbstractBaseClass { @Override public String description() { return "(concrete) " + super.description(); } } public interface NSObjectProxyProtocol extends NSObjectProtocol { @org.robovm.objc.annotation.Method(selector = "description") public String description(); } public static class NSObjectProxyProtocolReturner extends NSObject { @org.robovm.objc.annotation.Method(selector = "performSelector:") public native final NSObjectProxyProtocol performSelector2(Selector aSelector); } @Before public void setUp() { Assume.assumeTrue(Bro.IS_DARWIN); } @Test public void testCustomClassName() { SubClass1 o1 = new SubClass1(); assertEquals("j_" + SubClass1.class.getName().replace('.', '_'), o1.getObjCClass().getName()); SubClass2 o2 = new SubClass2(); assertEquals("SubClass2", o2.getObjCClass().getName()); } @Test public void testCallOverridenMethodFromJava() { SubClass1 o = new SubClass1(); assertTrue(o.description().startsWith("(overridden) " + "<j_org_robovm_objc_CustomClassTest$SubClass1: 0x")); } @Test public void testCallOverridenMethodFromObjC() { SubClass1 o = new SubClass1(); NSString description = (NSString) o.performSelector(Selector.register("description")); assertEquals(o.description(), description.toString()); } @Test public void testCallOverridenOverriddenMethodFromObjC() { SubClass3 o = new SubClass3(); NSString description = (NSString) o.performSelector(Selector.register("description")); assertEquals(o.description(), description.toString()); } @Test public void testOnlySingleCallbackInHierarchy() throws Exception { Method method1 = SubClass1.class.getDeclaredMethod("$cb$description", SubClass1.class, Selector.class); assertNotNull(method1.getAnnotation(Callback.class)); assertNotNull(method1.getAnnotation(BindSelector.class)); assertEquals("description", method1.getAnnotation(BindSelector.class).value()); try { SubClass3.class.getDeclaredMethod("$cb$description", SubClass2.class, Selector.class); fail("NoSuchMethodException expected"); } catch (NoSuchMethodException e) { } } @Test public void testNotImplemented() throws Exception { assertFalse(getMethodNames(ProtocolAdapter.class).contains("$cb$foo$")); } @Test public void testCallProtocolMethodFromObjC() throws Exception { assertTrue(getMethodNames(ProtocolImpl.class).contains("$cb$foo$")); assertFalse(getMethodNames(ProtocolImpl.class).contains("$cb$bar$")); ProtocolImpl p = new ProtocolImpl(); NSObject o = p.performSelector(Selector.register("foo:"), new NSString("bar")); assertEquals("foobar", o.toString()); } @Test public void testAbstractBaseClass() throws Exception { assertTrue(getMethodNames(AbstractBaseClass.class).contains("$cb$description")); assertFalse(getMethodNames(ConcreteSubClass.class).contains("$cb$description")); AbstractBaseClass o = new ConcreteSubClass(); NSString description = (NSString) o.performSelector(Selector.register("description")); assertEquals(o.description(), description.toString()); } @Test public void testObjCProxy() throws Exception { Class<?> objcProxyCls = Class.forName(NSObjectProxyProtocol.class.getName() + "$ObjCProxy"); assertTrue(getMethodNames(objcProxyCls).contains("$cb$description")); NSObjectProxyProtocolReturner pr = new NSObjectProxyProtocolReturner(); NSObjectProxyProtocol description = pr.performSelector2(Selector.register("description")); assertSame(objcProxyCls, description.getClass()); assertEquals(((NativeObject) description).as(NSObject.class).description(), description.toString()); // Make sure the proxy class isn't treated as a custom class @SuppressWarnings("unchecked") ObjCClass objcClass = ObjCClass.getByType((Class<? extends ObjCObject>) objcProxyCls); assertFalse(objcClass.isCustom()); } private Set<String> getMethodNames(Class<?> c) { TreeSet<String> result = new TreeSet<String>(); for (Method m : c.getDeclaredMethods()) { result.add(m.getName()); } return result; } }