| /* |
| * 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)); |
| } |
| } |