blob: 54527b33b97aeb7b1524f7327ccb20b1a7349a46 [file] [log] [blame]
/*
* 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.JavaScriptObject;
import com.google.gwt.junit.client.GWTTestCase;
import javaemul.internal.annotations.DoNotInline;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* Tests native JsType functionality.
*/
@SuppressWarnings("cast")
public class NativeJsTypeTest extends GWTTestCase {
@Override
public String getModuleName() {
return "com.google.gwt.core.Interop";
}
@JsType(isNative = true)
static class MyNativeJsType {
// TODO(rluble): these methods should be synthesized by the compiler.
@Override
public native String toString();
@Override
public native boolean equals(Object o);
@Override
public native int hashCode();
}
@JsType(isNative = true)
interface MyNativeJsTypeInterface {
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class NativeObject implements MyNativeJsTypeInterface {
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
final static class FinalNativeObject implements MyNativeJsTypeInterface {
}
@JsType(isNative = true)
interface MyNativeJsTypeInterfaceOnlyOneConcreteImplementor {
}
public void testClassLiterals() {
assertEquals(JavaScriptObject.class, MyNativeJsType.class);
assertEquals(JavaScriptObject.class, MyNativeJsTypeInterface.class);
assertEquals(JavaScriptObject[].class, MyNativeJsType[].class);
assertEquals(JavaScriptObject[].class, MyNativeJsTypeInterface[].class);
assertEquals(JavaScriptObject[].class, MyNativeJsType[][].class);
assertEquals(JavaScriptObject[].class, MyNativeJsTypeInterface[][].class);
assertEquals(JavaScriptObject[].class, JavaScriptObject.createArray().getClass());
}
public void testGetClass() {
Object object = createNativeObjectWithoutToString();
assertEquals(JavaScriptObject.class, object.getClass());
MyNativeJsTypeInterface nativeInterface =
(MyNativeJsTypeInterface) createNativeObjectWithoutToString();
assertEquals(JavaScriptObject.class, nativeInterface.getClass());
// Test that the dispatch to getClass in not messed up by incorrectly marking nativeObject1 as
// exact and inlining Object.getClass() implementation.
NativeObject nativeObject1 = new NativeObject();
assertEquals(JavaScriptObject.class, nativeObject1.getClass());
// Test that the dispatch to getClass in not messed up by incorrectly marking nativeObject2 as
// exact and inlining Object.getClass() implementation.
FinalNativeObject nativeObject2 = createNativeObject();
assertEquals(JavaScriptObject.class, nativeObject2.getClass());
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
final static class AnotherFinalNativeObject implements MyNativeJsTypeInterface {
}
private static boolean same(Object thisObject, Object thatObject) {
return thisObject == thatObject;
}
public void testEqualityOptimization() {
// Makes sure that == does not get optimized away due to static class incompatibility.
FinalNativeObject finalNativeObject = new FinalNativeObject();
AnotherFinalNativeObject anotherFinalNativeObject =
(AnotherFinalNativeObject) (Object) finalNativeObject;
// DeadCodeElimination could optimize statically to false due to type incompatibility, which
// could happen if both variables were marked as exact.
assertTrue(same(anotherFinalNativeObject, finalNativeObject));
}
public void testToString() {
Object nativeObjectWithToString = createNativeObjectWithToString();
assertEquals("Native type", nativeObjectWithToString.toString());
Object nativeObjectWithoutToString = createNativeObjectWithoutToString();
assertEquals("[object Object]", nativeObjectWithoutToString.toString());
Object nativeArray = createNativeArray();
assertEquals("", nativeArray.toString());
}
private static native FinalNativeObject createNativeObject() /*-{
return {};
}-*/;
private static native MyNativeJsType createNativeObjectWithToString() /*-{
return {toString: function() { return "Native type"; } };
}-*/;
private static native MyNativeJsType createNativeObjectWithoutToString() /*-{
return {};
}-*/;
private static native Object createNativeArray() /*-{
return [];
}-*/;
@JsType(isNative = true, namespace = GLOBAL, name = "Object")
static class NativeJsTypeWithOverlay {
@JsOverlay
public static final int x = 2;
public static native String[] keys(Object o);
@JsOverlay @DoNotInline
public static final boolean hasM(Object obj) {
return keys(obj)[0].equals("m");
}
public native boolean hasOwnProperty(String name);
@JsOverlay @DoNotInline
public final boolean hasM() {
return hasOwnProperty("m");
}
public int k;
@JsOverlay
public final NativeJsTypeWithOverlay setK(int k) {
this.k = k;
return this;
}
}
private native NativeJsTypeWithOverlay createNativeJsTypeWithOverlay() /*-{
return { m: function() { return 6; } };
}-*/;
public void testNativeJsTypeWithOverlay() {
NativeJsTypeWithOverlay object = createNativeJsTypeWithOverlay();
assertTrue(object.hasM());
assertTrue(NativeJsTypeWithOverlay.hasM(object));
assertEquals(2, NativeJsTypeWithOverlay.x);
assertEquals(42, object.setK(3).setK(42).k);
}
@JsType(isNative = true)
static class NativeJsTypeWithStaticInitializationAndFieldAccess {
@JsOverlay
public static Object object = new Integer(3);
}
@JsType(isNative = true)
static class NativeJsTypeWithStaticInitializationAndStaticOverlayMethod {
@JsOverlay
public static Object object = new Integer(4);
@JsOverlay
public static Object getObject() {
return object;
}
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class NativeJsTypeWithStaticInitializationAndInstanceOverlayMethod {
@JsOverlay
public static Object object = new Integer(5);
@JsOverlay
public final Object getObject() {
return object;
}
static {
clinitCalled++;
}
}
private static int clinitCalled = 0;
public void testNativeJsTypeWithStaticIntializer() {
assertEquals(new Integer(3), NativeJsTypeWithStaticInitializationAndFieldAccess.object);
assertEquals(0, clinitCalled);
assertEquals(
new Integer(4), NativeJsTypeWithStaticInitializationAndStaticOverlayMethod.getObject());
assertEquals(new Integer(5),
new NativeJsTypeWithStaticInitializationAndInstanceOverlayMethod().getObject());
assertEquals(1, clinitCalled);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Function")
static class NativeFunction {
}
private static native Object createFunction() /*-{
return function() {};
}-*/;
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array")
static class NativeArray {
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Number")
static class NativeNumber {
}
private static native Object createNumber() /*-{
return 1;
}-*/;
private static native Object createBoxedNumber() /*-{
return new Number(1);
}-*/;
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "String")
static class NativeString {
}
private static native Object createBoxedString() /*-{
return new String("hello");
}-*/;
@JsFunction
interface SomeFunctionInterface {
void m();
}
static final class SomeFunction implements SomeFunctionInterface {
public void m() {
}
}
public void testSpecialNativeInstanceOf() {
Object aJsFunction = new SomeFunction();
// True cases.
assertTrue(aJsFunction instanceof NativeFunction);
assertTrue(aJsFunction instanceof SomeFunctionInterface);
assertTrue(aJsFunction instanceof NativeObject);
// False cases.
assertFalse(aJsFunction instanceof NativeArray);
assertFalse(aJsFunction instanceof NativeNumber);
assertFalse(aJsFunction instanceof NativeString);
Object anotherFunction = createFunction();
// True cases.
assertTrue(anotherFunction instanceof NativeFunction);
assertTrue(anotherFunction instanceof SomeFunctionInterface);
assertTrue(anotherFunction instanceof NativeObject);
// False cases.
assertFalse(anotherFunction instanceof NativeArray);
assertFalse(anotherFunction instanceof NativeNumber);
assertFalse(anotherFunction instanceof NativeString);
Object aString = "Hello";
// True cases.
assertTrue(aString instanceof NativeString);
// False cases.
assertFalse(aString instanceof NativeFunction);
assertFalse(aString instanceof NativeObject);
assertFalse(aString instanceof NativeArray);
assertFalse(aString instanceof NativeNumber);
Object aBoxedString = createBoxedString();
// True cases.
// Note that boxed strings are (surprisingly) not strings but objects.
assertTrue(aBoxedString instanceof NativeObject);
// False cases.
assertFalse(aBoxedString instanceof NativeFunction);
assertFalse(aBoxedString instanceof NativeArray);
assertFalse(aBoxedString instanceof NativeNumber);
assertFalse(aBoxedString instanceof NativeString);
Object anArray = new String[0];
// True cases.
assertTrue(anArray instanceof NativeArray);
assertTrue(anArray instanceof NativeObject);
// False cases.
assertFalse(anArray instanceof NativeFunction);
assertFalse(anArray instanceof NativeNumber);
assertFalse(anArray instanceof NativeString);
Object aNativeArray = JavaScriptObject.createArray();
// True cases.
assertTrue(aNativeArray instanceof NativeArray);
assertTrue(anArray instanceof NativeObject);
// False cases.
assertFalse(aNativeArray instanceof NativeFunction);
assertFalse(aNativeArray instanceof NativeNumber);
assertFalse(aNativeArray instanceof NativeString);
Object aNumber = new Double(3);
// True cases.
assertTrue(aNumber instanceof NativeNumber);
// False cases.
assertFalse(aNumber instanceof NativeArray);
assertFalse(aNumber instanceof NativeObject);
assertFalse(aNumber instanceof NativeFunction);
assertFalse(aNumber instanceof NativeString);
Object anotherNumber = createNumber();
// True cases.
assertTrue(anotherNumber instanceof NativeNumber);
// False cases.
assertFalse(anotherNumber instanceof NativeArray);
assertFalse(anotherNumber instanceof NativeObject);
assertFalse(anotherNumber instanceof NativeFunction);
assertFalse(anotherNumber instanceof NativeString);
Object aBoxedNumber = createBoxedNumber();
// True cases.
assertTrue(aBoxedNumber instanceof NativeObject);
// False cases.
assertFalse(aBoxedNumber instanceof NativeNumber);
assertFalse(aBoxedNumber instanceof NativeArray);
assertFalse(aBoxedNumber instanceof NativeFunction);
assertFalse(aBoxedNumber instanceof NativeString);
Object anObject = new Object();
// True cases.
assertTrue(anObject instanceof NativeObject);
// False cases.
assertFalse(anObject instanceof NativeNumber);
assertFalse(anObject instanceof NativeArray);
assertFalse(anObject instanceof NativeFunction);
assertFalse(anObject instanceof NativeString);
Object nullObject = null;
assertFalse(nullObject instanceof NativeObject);
assertFalse(nullObject instanceof NativeArray);
assertFalse(nullObject instanceof NativeFunction);
assertFalse(nullObject instanceof NativeString);
assertFalse(nullObject instanceof NativeNumber);
Object undefined = getUndefined();
assertFalse(undefined instanceof NativeObject);
assertFalse(undefined instanceof NativeArray);
assertFalse(undefined instanceof NativeFunction);
assertFalse(undefined instanceof NativeString);
assertFalse(undefined instanceof NativeNumber);
}
private static native Object getUndefined() /*-{
}-*/;
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
interface NativeInterface {
void add(String element);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class NativeSuperClass {
public native void add(String element);
public native boolean remove(String element);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class NativeSubClassAccidentalOverride
extends NativeSuperClass implements NativeInterface {
}
public native NativeSubClassAccidentalOverride createNativeSubclass() /*-{
return {
add:
function(e) {
this[0] = e;
},
remove:
function(e) {
var ret = this[0] == e;
this[0] = undefined;
return ret;
}
};
}-*/;
public void testForwaringMethodsOnNativeClasses() {
NativeSubClassAccidentalOverride subClass = createNativeSubclass();
subClass.add("Hi");
assertTrue(subClass.remove("Hi"));
assertFalse(subClass.remove("Hi"));
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class NativeClassWithStaticOverlayFields {
@JsOverlay
static String uninitializedString;
@JsOverlay
static int uninitializedInt;
@JsOverlay
static int initializedInt = 5;
}
public void testUninitializedStaticOverlayField() {
assertEquals(0, NativeClassWithStaticOverlayFields.uninitializedInt);
assertEquals(5, NativeClassWithStaticOverlayFields.initializedInt);
assertNull(NativeClassWithStaticOverlayFields.uninitializedString);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "window")
private static class MainWindow {
public static Object window;
}
// <window> is a special qualifier that allows referencing the iframe window instead of the main
// window.
@JsType(isNative = true, namespace = "<window>", name = "window")
private static class IFrameWindow {
public static Object window;
}
@JsType(isNative = true)
private static class AlsoMainWindow {
@JsProperty(namespace = JsPackage.GLOBAL)
public static Object window;
}
@JsType(isNative = true)
private static class AlsoIFrameWindow {
@JsProperty(namespace = "<window>")
public static Object window;
}
public void testMainWindowIsNotIFrameWindow() {
assertSame(IFrameWindow.window, AlsoIFrameWindow.window);
assertNotSame(AlsoIFrameWindow.window, AlsoMainWindow.window);
assertNotSame(IFrameWindow.window, MainWindow.window);
assertSame(MainWindow.window, AlsoMainWindow.window);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "goog.global")
private static class WindowThroughGoogGlobal {
public static Object window;
}
public void testGoogGlobalAlias() {
assertSame(MainWindow.window, WindowThroughGoogGlobal.window);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Error")
private static class NativeError {
public String message;
}
private static final String ERROR_FROM_NATIVE_ERROR_SUBCLASS = "error from NativeErrorSubclass";
private static class NativeErrorSubclass extends NativeError {
public NativeErrorSubclass() {
message = ERROR_FROM_NATIVE_ERROR_SUBCLASS;
}
}
public void testObjectPropertiesAreCopied() {
Object error = new NativeErrorSubclass();
assertTrue(error instanceof NativeError);
// Make sure the subclass is a proper Java object (the typeMarker should be one of the
// properties copied from java.lang.Object).
assertFalse(error instanceof JavaScriptObject);
assertTrue(error.toString().contains(ERROR_FROM_NATIVE_ERROR_SUBCLASS));
}
}