blob: ae0775159a0d7571e62f97300568fdff8389f147 [file] [log] [blame]
/*
* Copyright 2009 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.dev.jjs.test;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dev.jjs.test.SingleJsoImplTest.JsoHasInnerJsoType.InnerType;
import com.google.gwt.dev.jjs.test.jsointfs.JsoInterfaceWithUnreferencedImpl;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.io.IOException;
/**
* Ensures that JavaScriptObjects may implement interfaces with methods.
*/
public class SingleJsoImplTest extends GWTTestCase {
interface Adder {
int ADDER_CONST = 6;
/**
* @see SameDescriptors#SAME_NAME
*/
String SAME_NAME = "Same Name";
Object DIFFERENT_OBJECT = new Object();
Object SAME_OBJECT = new Object();
Object SAME_OBJECT2 = SAME_OBJECT;
/*
* NB: Picking a mix of double and int because doubles have size 2 on the
* stack, so this ensures that we're handling that case correctly.
*/
double add(double a, int b);
/*
* Ensure that we can return types whose sizes are larger than the size of
* the arguments.
*/
long returnLong();
}
interface CallsMethodInInnerType {
interface InnerInterface {
void call(int a);
int get();
}
InnerInterface call(InnerInterface o, int a);
}
interface CallsStaticMethodInSubclass {
String call(int a, int b);
}
interface CreatedWithCast {
String foo();
}
interface CreatedWithCastToTag {
}
interface CreatedWithCastToTagSub extends CreatedWithCastToTag {
String foo();
}
interface Divider extends Multiplier {
int divide(int a, int b);
}
static class JavaAdder implements Adder {
public double add(double a, int b) {
return a + b;
}
public long returnLong() {
return 5L;
}
}
/**
* Even though this type is never instantiated, it's necessary to ensure that
* the CreatedWithCast test isn't short-circuited due to type tightening.
*/
static class JavaCreatedWithCast implements CreatedWithCast {
public String foo() {
return "foo";
}
}
static class JavaCreatedWithCastToTag implements CreatedWithCastToTagSub {
public String foo() {
return "foo";
}
}
/**
* The extra declaration of implementing Multiplier should still be legal.
*/
static class JavaDivider extends JavaMultiplier implements Divider,
Multiplier, Tag {
public int divide(int a, int b) {
return a / b;
}
}
/**
* Ensure that SingleJsoImpl interfaces can be extended and implemented by
* regular Java types.
*/
static class JavaLog2 extends JavaDivider implements Log2 {
public double log2(int a) {
return Math.log(a) / Math.log(2);
}
}
static class JavaMultiplier implements Multiplier {
public int multiply(int a, int b) {
return a * b;
}
}
static class JavaUsesArrays implements UsesArrays {
public void acceptInt3Array(int[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptIntArray(int[] arr) {
assertTrue(arr.length == 1);
}
public void acceptObject3Array(Object[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptObjectArray(Object[] arr) {
assertTrue(arr.length == 1);
}
public void acceptString3Array(String[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptStringArray(String[] arr) {
assertTrue(arr.length == 1);
}
public int[][][] returnInt3Array() {
return new int[3][2][1];
}
public int[] returnIntArray() {
return new int[1];
}
public Object[][][] returnObject3Array() {
return new Object[3][2][1];
}
public Object[] returnObjectArray() {
return new Object[1];
}
public String[][][] returnString3Array() {
return new String[3][2][1];
}
public String[] returnStringArray() {
return new String[1];
}
}
static class JsoAdder extends JavaScriptObject implements Adder {
protected JsoAdder() {
}
public final native double add(double a, int b) /*-{
return this.offset * (a + b);
}-*/;
public final long returnLong() {
return 5L;
}
}
static class JsoCallsStaticMethodInSubclass extends JavaScriptObject
implements CallsStaticMethodInSubclass {
protected JsoCallsStaticMethodInSubclass() {
}
public final native String call(int a, int b) /*-{
return "foo" + @com.google.gwt.dev.jjs.test.SingleJsoImplTest.JsoCallsStaticMethodInSubclassSubclass::actual(II)(a, b);
}-*/;
}
static class JsoCallsStaticMethodInSubclassSubclass extends
JsoCallsStaticMethodInSubclass {
public static String actual(int a, int b) {
return String.valueOf(a + b);
}
protected JsoCallsStaticMethodInSubclassSubclass() {
}
}
static class JsoCreatedWithCast extends JavaScriptObject implements
CreatedWithCast {
protected JsoCreatedWithCast() {
}
public final String foo() {
return "foo";
}
}
static class JsoCreatedWithCastToTag extends JavaScriptObject implements
CreatedWithCastToTagSub {
protected JsoCreatedWithCastToTag() {
}
public final String foo() {
return "foo";
}
}
static class JsoDivider extends JsoMultiplier implements Divider, Tag {
protected JsoDivider() {
}
public final native int divide(int a, int b) /*-{
return this.offset * a / b;
}-*/;
}
static class JsoHasInnerJsoType extends JavaScriptObject implements
CallsMethodInInnerType {
static class InnerType extends JavaScriptObject implements InnerInterface {
protected InnerType() {
}
public final native void call(int a) /*-{
this.foo = a;
}-*/;
public final native int get() /*-{
return this.foo;
}-*/;
}
protected JsoHasInnerJsoType() {
}
public final InnerInterface call(InnerInterface o, int a) {
o.call(a);
return o;
}
}
static class JsoMultiplier extends JavaScriptObject implements Multiplier {
protected JsoMultiplier() {
}
public final native int multiply(int a, int b) /*-{
return this.offset * a * b;
}-*/;
}
/**
* Just a random JSO type for testing cross-casting.
*/
final static class JsoRandom extends JavaScriptObject implements
SameDescriptors, Tag {
protected JsoRandom() {
}
public int add(int a, int b) {
return -1;
}
public int divide(int a, int b) {
return -1;
}
public int multiply(int a, int b) {
return -1;
}
String b() {
return "b";
}
}
final static class JsoSimple extends JavaScriptObject implements Simple {
protected JsoSimple() {
}
public String a() {
return "a";
}
public String a(boolean overload) {
return overload ? "Kaboom!" : "OK";
}
public String ex() throws IOException {
throw new IOException();
}
public String rte() {
throw new IllegalArgumentException();
}
}
static final class JsoUsesArrays extends JavaScriptObject implements
UsesArrays {
protected JsoUsesArrays() {
}
public void acceptInt3Array(int[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptIntArray(int[] arr) {
assertTrue(arr.length == 1);
}
public void acceptObject3Array(Object[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptObjectArray(Object[] arr) {
assertTrue(arr.length == 1);
}
public void acceptString3Array(String[][][] arr) {
assertTrue(arr.length == 3);
}
public void acceptStringArray(String[] arr) {
assertTrue(arr.length == 1);
}
public int[][][] returnInt3Array() {
return new int[3][2][1];
}
public int[] returnIntArray() {
return new int[1];
}
public Object[][][] returnObject3Array() {
return new Object[3][2][1];
}
public Object[] returnObjectArray() {
return new Object[1];
}
public String[][][] returnString3Array() {
return new String[3][2][1];
}
public String[] returnStringArray() {
return new String[1];
}
}
static class JsoUsesGeneric extends JavaScriptObject implements UsesGenerics {
public static native <T extends CharSequence> JsoUsesGeneric create() /*-{
return {suffix : "42"};
}-*/;
protected JsoUsesGeneric() {
}
public final native <T> String acceptsGeneric(T chars) /*-{
return chars + this.suffix;
}-*/;
public final native <T> void callback(AsyncCallback<T> callback, T chars) /*-{
callback.@com.google.gwt.user.client.rpc.AsyncCallback::onSuccess(Ljava/lang/Object;)(chars + this.suffix);
}-*/;
/**
* What you're seeing here is would be achieved by an unsafe (T) cast and
* would break with a ClassCastException if accessed via JsoIsGenericFinal
* in normal Java.
*/
public final native <T> T returnsGeneric(String chars) /*-{
return chars + this.suffix;
}-*/;
}
/**
* Ensure that SingleJsoImpl interfaces can be extended and implemented by
* regular Java types.
*/
interface Log2 extends Divider {
double log2(int a);
}
interface Multiplier {
int multiply(int a, int b);
}
/**
* This interface makes sure that types with identical method signatures will
* still dispatch correctly.
*/
interface SameDescriptors {
int SAME_NAME = 6;
int add(int a, int b);
int divide(int a, int b);
int multiply(int a, int b);
}
interface Simple {
String a();
String a(boolean overload);
String ex() throws IOException;
String rte();
}
/**
* Ensure that a Java-only implementation of a SingleJsoImpl interface works.
*/
static class SimpleOnlyJava implements SimpleOnlyJavaInterface {
public String simpleOnlyJava() {
return "simpleOnlyJava";
}
}
interface SimpleOnlyJavaInterface {
String simpleOnlyJava();
}
interface Tag {
}
/**
* Ensure that arrays are valid return and parameter types.
*/
interface UsesArrays {
void acceptInt3Array(int[][][] arr);
void acceptIntArray(int[] arr);
void acceptObject3Array(Object[][][] arr);
void acceptObjectArray(Object[] arr);
void acceptString3Array(String[][][] arr);
void acceptStringArray(String[] arr);
int[][][] returnInt3Array();
int[] returnIntArray();
Object[][][] returnObject3Array();
Object[] returnObjectArray();
String[][][] returnString3Array();
String[] returnStringArray();
}
interface UsesGenerics {
<T extends Object> String acceptsGeneric(T chars);
<T> void callback(AsyncCallback<T> callback, T chars);
<T> T returnsGeneric(String chars);
}
private static native JsoAdder makeAdder(int offset) /*-{
return {offset:offset};
}-*/;
private static native JsoDivider makeDivider(int offset) /*-{
return {offset:offset};
}-*/;
private static native JsoMultiplier makeMultiplier(int offset) /*-{
return {offset:offset};
}-*/;
private static native JsoSimple makeSimple() /*-{
return {};
}-*/;
@Override
public String getModuleName() {
return "com.google.gwt.dev.jjs.CompilerSuite";
}
public void testCallsToInnerTypes() {
CallsMethodInInnerType a = (CallsMethodInInnerType) JavaScriptObject.createObject();
InnerType i = (InnerType) JavaScriptObject.createObject();
assertEquals(5, a.call(i, 5).get());
assertEquals(5, i.get());
}
public void testCallsWithArrays() {
UsesArrays a = JavaScriptObject.createObject().<JsoUsesArrays> cast();
a.acceptIntArray(a.returnIntArray());
a.acceptInt3Array(a.returnInt3Array());
a.acceptStringArray(a.returnStringArray());
a.acceptString3Array(a.returnString3Array());
a.acceptObjectArray(a.returnStringArray());
a.acceptObject3Array(a.returnString3Array());
a = new JavaUsesArrays();
a.acceptIntArray(a.returnIntArray());
a.acceptInt3Array(a.returnInt3Array());
a.acceptStringArray(a.returnStringArray());
a.acceptString3Array(a.returnString3Array());
a.acceptObjectArray(a.returnStringArray());
a.acceptObject3Array(a.returnString3Array());
}
/**
* Ensure that SingleJSO types that are referred to only via a cast to the
* interface type are retained. If the JsoCreatedWithCast type isn't rescued
* correctly, the cast in this test will throw a ClassCastException since the
* compiler would assume there are types that implement the interface.
*/
public void testCreatedWithCast() {
try {
Object a = (CreatedWithCast) JavaScriptObject.createObject();
} catch (ClassCastException e) {
fail("a");
}
try {
Object b = (CreatedWithCastToTag) JavaScriptObject.createObject();
} catch (ClassCastException e) {
fail("b");
}
}
public void testDualCase() {
// Direct dispatch
{
JavaAdder java = new JavaAdder();
assertEquals(2.0, java.add(1, 1));
JsoAdder jso = makeAdder(2);
assertEquals(4.0, jso.add(1, 1));
assertEquals(5L, jso.returnLong());
}
// Just check dispatch via the interface
{
Adder a = new JavaAdder();
assertEquals(2.0, a.add(1, 1));
a = makeAdder(2);
assertEquals(4.0, a.add(1, 1));
assertEquals(5L, a.returnLong());
}
// Check casting
{
Object a = new JavaAdder();
assertEquals(2.0, ((Adder) a).add(1, 1));
assertEquals(2.0, ((JavaAdder) a).add(1, 1));
assertEquals(5L, ((Adder) a).returnLong());
assertEquals(5L, ((JavaAdder) a).returnLong());
assertTrue(a instanceof JavaAdder);
assertFalse(a instanceof JsoAdder);
assertFalse(a instanceof Tag);
try {
((JsoAdder) a).add(1, 1);
fail("Should have thrown CCE");
} catch (ClassCastException e) {
// OK
}
a = makeAdder(2);
assertEquals(4.0, ((Adder) a).add(1, 1));
assertEquals(4.0, ((JsoAdder) a).add(1, 1));
assertEquals(5L, ((Adder) a).returnLong());
assertEquals(5L, ((JsoAdder) a).returnLong());
assertTrue(a instanceof JsoAdder);
assertFalse(a instanceof JavaAdder);
// NB: This is unexpected until you consider JSO$ as a roll-up type
assertTrue(a instanceof Tag);
try {
((JavaAdder) a).add(1, 1);
fail("Should have thrown CCE");
} catch (ClassCastException e) {
// OK
}
}
}
public void testFields() {
assertEquals(6, Adder.ADDER_CONST);
assertEquals("Same Name", Adder.SAME_NAME);
assertEquals(6, SameDescriptors.SAME_NAME);
assertSame(Adder.SAME_OBJECT, Adder.SAME_OBJECT2);
assertNotSame(Adder.DIFFERENT_OBJECT, Adder.SAME_OBJECT);
}
public void testGenerics() {
UsesGenerics j = JsoUsesGeneric.create();
assertEquals("Hello42", j.acceptsGeneric("Hello"));
assertEquals("Hello42", j.returnsGeneric("Hello"));
j.callback(new AsyncCallback<CharSequence>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(CharSequence result) {
assertEquals("Hello42", result);
}
}, "Hello");
}
@SuppressWarnings("cast")
public void testSimpleCase() {
{
JsoSimple asJso = makeSimple();
assertTrue(asJso instanceof Object);
assertTrue(asJso instanceof Simple);
assertEquals("a", asJso.a());
assertEquals("OK", asJso.a(false));
try {
asJso.ex();
fail("Should have thrown IOException");
} catch (IOException e) {
// OK
}
try {
asJso.rte();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK
}
}
{
Simple asSimple = makeSimple();
assertTrue(asSimple instanceof Object);
assertTrue(asSimple instanceof JavaScriptObject);
assertTrue(asSimple instanceof JsoSimple);
assertEquals("a", asSimple.a());
assertEquals("OK", asSimple.a(false));
assertEquals("a", ((JsoSimple) asSimple).a());
assertEquals("OK", ((JsoSimple) asSimple).a(false));
try {
asSimple.ex();
fail("Should have thrown IOException");
} catch (IOException e) {
// OK
}
try {
asSimple.rte();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK
}
}
{
Object asObject = "Defeat type-tightening";
assertTrue(asObject instanceof String);
asObject = makeSimple();
assertTrue(asObject instanceof Object);
assertTrue(asObject instanceof JavaScriptObject);
assertTrue(asObject instanceof JsoSimple);
assertTrue(asObject instanceof Simple);
assertEquals("a", ((Simple) asObject).a());
assertEquals("a", ((JsoSimple) asObject).a());
assertEquals("OK", ((Simple) asObject).a(false));
assertEquals("OK", ((JsoSimple) asObject).a(false));
// Test a cross-cast that's normally not allowed by the type system
assertTrue(asObject instanceof JsoRandom);
assertEquals("b", ((JsoRandom) asObject).b());
assertEquals(-1, ((JsoRandom) asObject).add(1, 1));
}
{
Object o = "Defeat type-tightening";
assertTrue(o instanceof String);
o = new SimpleOnlyJava();
assertTrue(o instanceof SimpleOnlyJavaInterface);
assertEquals("simpleOnlyJava", ((SimpleOnlyJava) o).simpleOnlyJava());
}
}
public void testStaticCallsToSubclasses() {
Object o = "String";
assertEquals(String.class, o.getClass());
o = JavaScriptObject.createObject();
assertTrue(o instanceof CallsStaticMethodInSubclass);
assertEquals("foo5", ((CallsStaticMethodInSubclass) o).call(2, 3));
}
@SuppressWarnings("cast")
public void testSubclassing() {
{
JsoDivider d = makeDivider(1);
assertTrue(d instanceof Divider);
assertTrue(d instanceof Multiplier);
assertTrue(d instanceof Tag);
assertEquals(5, d.divide(10, 2));
assertEquals(10, d.multiply(5, 2));
assertEquals(10, ((JsoMultiplier) d).multiply(5, 2));
}
{
Object d = makeDivider(1);
assertTrue(d instanceof Divider);
assertTrue(d instanceof Multiplier);
assertTrue(d instanceof JsoDivider);
assertTrue(d instanceof JsoMultiplier);
assertFalse(d instanceof JavaDivider);
assertFalse(d instanceof JavaMultiplier);
assertTrue(d instanceof Tag);
assertEquals(5, ((Divider) d).divide(10, 2));
assertEquals(10, ((Divider) d).multiply(5, 2));
assertEquals(10, ((Multiplier) d).multiply(5, 2));
assertEquals(5, ((JsoDivider) d).divide(10, 2));
assertEquals(10, ((JsoMultiplier) d).multiply(5, 2));
d = new JavaDivider();
assertTrue(d instanceof Divider);
assertTrue(d instanceof Multiplier);
assertTrue(d instanceof JavaDivider);
assertTrue(d instanceof JavaMultiplier);
assertFalse(d instanceof JsoDivider);
assertFalse(d instanceof JsoMultiplier);
assertTrue(d instanceof Tag);
assertEquals(5, ((Divider) d).divide(10, 2));
assertEquals(10, ((Divider) d).multiply(5, 2));
assertEquals(10, ((Multiplier) d).multiply(5, 2));
assertEquals(5, ((JavaDivider) d).divide(10, 2));
assertEquals(10, ((JavaMultiplier) d).multiply(5, 2));
}
{
Object m = makeMultiplier(1);
// This only works because JSO$ implements every SingleJsoImpl interface
assertTrue(m instanceof Divider);
assertEquals(2, ((Divider) m).divide(10, 5));
m = new JavaMultiplier();
// but this should fail, since there's proper type information
assertFalse(m instanceof Divider);
try {
assertEquals(2, ((Divider) m).divide(10, 5));
fail("Should have thrown CCE");
} catch (ClassCastException e) {
// OK
}
}
{
Object l2 = "Prevent type-tightening";
assertTrue(l2 instanceof String);
l2 = new JavaLog2();
assertTrue(l2 instanceof Log2);
assertTrue(l2 instanceof Multiplier);
assertTrue(l2 instanceof Divider);
assertEquals(4.0, ((Log2) l2).log2(16));
}
}
public void testUnreferencedType() {
JsoInterfaceWithUnreferencedImpl o = (JsoInterfaceWithUnreferencedImpl) JavaScriptObject.createObject();
assertNotNull(o);
assertTrue(o.isOk());
}
}