/* * Copyright 2013 Google Inc. * * 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 com.google.gwt.core.interop; import static jsinterop.annotations.JsPackage.GLOBAL; import com.google.gwt.core.client.ScriptInjector; import com.google.gwt.junit.client.GWTTestCase; import javaemul.internal.annotations.DoNotInline; import jsinterop.annotations.JsFunction; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsType; /** * Tests JsType functionality. */ @SuppressWarnings("cast") public class JsTypeVarargsTest extends GWTTestCase { @Override public String getModuleName() { return "com.google.gwt.core.Interop"; } @Override protected void gwtSetUp() throws Exception { ScriptInjector.fromString( "function JsTypeVarargsTest_MyNativeJsType() {}\n" + "function JsTypeVarargsTest_MyNativeJsTypeVarargsConstructor(i) {" + " this.a = arguments[i]; this.b = arguments.length; }\n") .setWindow(ScriptInjector.TOP_WINDOW) .inject(); setupGlobal(); } // $global always points to scope of exports private native void setupGlobal() /*-{ $global = window.goog && window.goog.global || $wnd; $wnd.$global = $global; }-*/; @JsMethod @DoNotInline public static native int varargsLengthThruArguments(Object... varargs) /*-{ return arguments.length; }-*/; @JsMethod @DoNotInline public static int varargsLength(Object... varargs) { return varargs.length; } @JsMethod @DoNotInline public static int stringVarargsLength(String... varargs) { return varargs.length; } @JsMethod @DoNotInline public static int stringVarargsLengthV2(int i, String... varargs) { return varargs.length; } @JsMethod(namespace = JsPackage.GLOBAL) @DoNotInline public static Object getVarargsSlot(int slot, Object... varargs) { return varargs[slot]; } @JsMethod(namespace = JsPackage.GLOBAL) @DoNotInline public static Object[] clrearVarargsSlot(int slot, Object... varargs) { varargs[slot] = null; return varargs; } @JsMethod(namespace = JsPackage.GLOBAL) @DoNotInline public static Class<?> getVarargsArrayClass(String... varargs) { return varargs.getClass(); } private static native Object callGetVarargsSlotUsingJsName() /*-{ return $global.getVarargsSlot(2, "1", "2", "3", "4"); }-*/; @JsType(isNative = true, namespace = GLOBAL, name = "Object") static class NativeJsType { } @JsType(isNative = true, namespace = GLOBAL, name = "JsTypeVarargsTest_MyNativeJsTypeVarargsConstructor") static class NativeJsTypeWithVarargsConstructor { public Object a; public int b; NativeJsTypeWithVarargsConstructor(int i, Object... args) { } } static class SubclassNativeWithVarargsConstructor extends NativeJsTypeWithVarargsConstructor { SubclassNativeWithVarargsConstructor(int i, Object... args) { super(i, args); } @JsMethod Object varargsMethod(int i, Object... args) { return args[i]; } } static class SubSubclassNativeWithVarargsConstructor extends SubclassNativeWithVarargsConstructor { SubSubclassNativeWithVarargsConstructor() { super(0, new Object[0]); } Object varargsMethod(int i, Object... args) { return super.varargsMethod(i, args); } Object nonJsVarargsMethod() { return super.varargsMethod(1, null ,this); } } public void testVarargsCall_regularMethods() { assertEquals(3, varargsLengthThruArguments("A", "B", "C")); assertEquals(4, varargsLength("A", "B", "C", "D")); assertEquals(2, varargsLengthThruArguments(new NativeJsType[]{null, null})); assertEquals(5, varargsLength(new NativeJsType[]{null, null, null, null, null})); assertEquals("C", getVarargsSlot(2, "A", "B", "C", "D")); assertEquals("3", callGetVarargsSlotUsingJsName()); assertNull(clrearVarargsSlot(1, "A", "B", "C")[1]); assertEquals("A", clrearVarargsSlot(1, "A", "B", "C")[0]); assertEquals(3, clrearVarargsSlot(1, "A", "B", "C").length); assertSame(String[].class, getVarargsArrayClass("A", "B", "C")); } public void testVarargsCall_edgeCases() { assertSame(String[].class, getVarargsArrayClass()); assertSame(String[].class, getVarargsArrayClass(new String[0])); assertSame(String[].class, getVarargsArrayClass((String) null)); try { assertSame(String[].class, getVarargsArrayClass(null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } try { assertSame(String[].class, getVarargsArrayClass((String[]) null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } assertEquals(0, stringVarargsLength()); assertEquals(0, stringVarargsLength(new String[0])); assertEquals(1, stringVarargsLength((String) null)); try { assertEquals(0, stringVarargsLength(null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } try { assertEquals(0, stringVarargsLength((String[]) null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } // Test with an additional parameter as it results in a slightly different call site. assertEquals(0, stringVarargsLengthV2(0)); assertEquals(0, stringVarargsLengthV2(0, new String[0])); assertEquals(1, stringVarargsLengthV2(0, (String) null)); try { assertEquals(0, stringVarargsLengthV2(0, null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } try { assertEquals(0, stringVarargsLengthV2(0, (String[]) null)); fail("Should have thrown exception"); } catch (NullPointerException expected) { } } public void testVarargsCall_constructors() { NativeJsType someNativeObject = new NativeJsType(); NativeJsTypeWithVarargsConstructor object = new NativeJsTypeWithVarargsConstructor(1, someNativeObject, null); assertSame(someNativeObject, object.a); assertEquals(3, object.b); Object[] params = new Object[] { someNativeObject, null }; object = new NativeJsTypeWithVarargsConstructor(1, params); assertSame(someNativeObject, object.a); assertEquals(3, object.b); object = new SubclassNativeWithVarargsConstructor(1, someNativeObject, null); assertSame(someNativeObject, object.a); assertEquals(3, object.b); } @JsMethod(namespace = JsPackage.GLOBAL) public static Double sumAndMultiply(Double multiplier, Double... numbers) { double result = 0.0d; for (double d : numbers) { result += d; } result *= multiplier; return result; } @JsMethod(namespace = JsPackage.GLOBAL) public static int sumAndMultiplyInt(int multiplier, int... numbers) { int result = 0; for (int d : numbers) { result += d; } result *= multiplier; return result; } @JsFunction interface Function { Object f(int i, Object... args); } static final class AFunction implements Function { @Override public Object f(int i, Object... args) { return args[i]; } static Function create() { return new AFunction(); } } public native void testVarargsCall_fromJavaScript() /*-{ @GWTTestCase::assertEquals(DDD)(60, $global.sumAndMultiply(2, 10, 20), 0); @GWTTestCase::assertEquals(II)(30, $global.sumAndMultiplyInt(3, 2, 8)); var f = @JsTypeVarargsTest.AFunction::create()() @GWTTestCase::assertSame(Ljava/lang/Object;Ljava/lang/Object;)( f, f(2, null, null, f, null)); }-*/; public void testVarargsCall_jsFunction() { Function function = new AFunction(); assertSame(function, function.f(2, null, null, function, null)); assertSame(null, function.f(1, null, null, function, null)); } public void testVarargsCall_superCalls() { SubSubclassNativeWithVarargsConstructor object = new SubSubclassNativeWithVarargsConstructor(); assertSame(object, object.nonJsVarargsMethod()); assertSame(object, object.varargsMethod(1, null, object, null)); } private static int sideEffectCount; private SubclassNativeWithVarargsConstructor doSideEffect( SubclassNativeWithVarargsConstructor obj) { sideEffectCount++; return obj; } public void testVarargsCall_sideEffectingInstance() { SubclassNativeWithVarargsConstructor object = new SubclassNativeWithVarargsConstructor(0, new Object[0]); sideEffectCount = 0; Object[] params = new Object[] { object, null }; assertSame(object, doSideEffect(object).varargsMethod(0, params)); assertSame(1, sideEffectCount); } static class UninstantiatedClass { } @JsMethod(namespace = JsPackage.GLOBAL) public static int varargJsMethodUninstantiatedVararg( UninstantiatedClass... varargs) { return varargs.length; } public native void testVarargsCall_uninstantiatedVararg() /*-{ @GWTTestCase::assertEquals(II)(0, $global.varargJsMethodUninstantiatedVararg()); }-*/; }