/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.gdt.eclipse.designer.ie.jsni;
import com.google.gdt.eclipse.designer.ie.util.Utils;
import com.google.gwt.dev.shell.CompilingClassLoader;
import com.google.gwt.dev.shell.JsValue;
import org.apache.commons.lang.NotImplementedException;
import org.eclipse.swt.internal.ole.win32.COM;
import org.eclipse.swt.internal.ole.win32.DISPPARAMS;
import org.eclipse.swt.internal.ole.win32.EXCEPINFO;
import org.eclipse.swt.internal.ole.win32.GUID;
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.internal.ole.win32.IUnknown;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.ole.win32.Variant;
/**
* Represents an IE JavaScript value.
*/
public class JsValueIE6 extends JsValue {
private static class JsCleanupIE6 implements JsCleanup {
private final Variant variant;
public JsCleanupIE6(Variant variant) {
this.variant = variant;
}
public void doCleanup() {
if (variant != null) {
variant.dispose();
}
}
}
private static Variant maybeCopyVariant(Variant variant) {
if (variant == null) {
return new Variant();
}
switch (variant.getType()) {
case COM.VT_DISPATCH: {
IDispatch dispatch = variant.getDispatch();
dispatch.AddRef();
return new Variant(dispatch);
}
case COM.VT_UNKNOWN: {
IUnknown unknown = variant.getUnknown();
unknown.AddRef();
return new Variant(unknown);
}
}
return variant;
}
/**
* Copied with modification from OleAutomation.getIDsOfNames(). The reason we
* don't use that method directly is that querying for typeInfo that occurs in
* the OleAutomation(IDispatch) constructor will cause a VM crash on some
* kinds of JavaScript objects, such as the window.alert function. So we do it
* by hand.
*/
private static int[] oleAutomationGetIdsOfNames(IDispatch dispatch) {
String[] names = new String[] {"valueOf"};
int[] ids = new int[names.length];
int result = dispatch.GetIDsOfNames(new GUID(), names, names.length,
COM.LOCALE_USER_DEFAULT, ids);
if (result != COM.S_OK) {
return null;
}
return ids;
}
/**
* Copied with modification from OleAutomation.invoke(). The reason we don't
* use that method directly is that querying for typeInfo that occurs in the
* OleAutomation(IDispatch) constructor will cause a VM crash on some kinds of
* JavaScript objects, such as the window.alert function. So we do it by hand.
*/
private static Variant oleAutomationInvoke(IDispatch dispatch, int dispId) {
int pVarResultAddress = 0;
try {
pVarResultAddress = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT,
Variant.sizeof);
int[] pArgErr = new int[1];
int hr = dispatch.Invoke(dispId, new GUID(), COM.LOCALE_USER_DEFAULT,
COM.DISPATCH_METHOD, new DISPPARAMS(), pVarResultAddress,
new EXCEPINFO(), pArgErr);
if (hr >= COM.S_OK) {
return Utils.win32_new(pVarResultAddress);
}
} finally {
if (pVarResultAddress != 0) {
COM.VariantClear(pVarResultAddress);
OS.GlobalFree(pVarResultAddress);
}
}
return null;
}
// a null variant means the JsValue is undefined (void)
private Variant variant;
/**
* Create a null JsValue.
*/
public JsValueIE6() {
this.variant = null;
}
/**
* Create a JsValue given a variant.
*
* @param variant JS value
*/
public JsValueIE6(Variant variant) {
this.variant = maybeCopyVariant(variant);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getBoolean()
*/
@Override
public boolean getBoolean() {
return variant.getBoolean();
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getInt()
*/
@Override
public int getInt() {
return variant.getInt();
}
@Override
public long getJavaScriptObjectPointer() {
if (isJavaScriptObject()) {
// The canonical pointer is obtained by QI'ing for IUnknown.
int[] ppvObject = new int[1];
variant.getDispatch().QueryInterface(COM.IIDIUnknown, ppvObject);
int pvObject = ppvObject[0];
// Release the returned pointer.
OS.VtblCall(2, pvObject);
return pvObject;
} else {
return 0;
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getNumber()
*/
@Override
public double getNumber() {
return variant.getDouble();
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getString()
*/
@Override
public String getString() {
return variant.getString();
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getTypeString()
*/
@Override
public String getTypeString() {
switch (variant.getType()) {
case COM.VT_BOOL:
return "boolean";
case 16 /*COM.VT_I1*/:
case COM.VT_I2:
case COM.VT_I4:
case COM.VT_I8:
case COM.VT_UI1:
case 18 /*COM.VT_UI2*/:
case COM.VT_UI4:
case COM.VT_R4:
case COM.VT_R8:
return "number";
case COM.VT_BSTR:
return "string";
case COM.VT_EMPTY:
return "undefined";
case COM.VT_NULL:
return "null";
case COM.VT_DISPATCH:
return isWrappedJavaObject() ? "Java Object" : "JavaScript object";
default:
return "unexpected Variant type";
}
}
/**
* Return the underlying Variant object. PLATFORM-SPECIFIC.
*/
public Variant getVariant() {
return maybeCopyVariant(variant);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getWrappedJavaObject()
*/
@Override
public Object getWrappedJavaObject() {
return tryToUnwrapWrappedJavaObject();
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isBoolean()
*/
@Override
public boolean isBoolean() {
return variant != null && variant.getType() == COM.VT_BOOL;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isInt()
*/
@Override
public boolean isInt() {
if (variant == null) {
return false;
}
switch (variant.getType()) {
case 16 /*COM.VT_I1*/:
case COM.VT_I2:
case COM.VT_I4:
case COM.VT_UI1:
case 18 /*COM.VT_UI2*/:
// note that VT_UI4 is excluded since it may not fit in an int
return true;
default:
return false;
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isJavaScriptObject()
*/
@Override
public boolean isJavaScriptObject() {
if (variant == null) {
return false;
}
if (variant.getType() != COM.VT_DISPATCH) {
return false;
}
return tryToUnwrapWrappedJavaObject() == null;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isNull()
*/
@Override
public boolean isNull() {
return variant != null && variant.getType() == COM.VT_NULL;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isNumber()
*/
@Override
public boolean isNumber() {
if (variant == null) {
return false;
}
switch (variant.getType()) {
case 16 /*COM.VT_I1*/:
case COM.VT_I2:
case COM.VT_I4:
case COM.VT_I8:
case COM.VT_UI1:
case 18 /*COM.VT_UI2*/:
case COM.VT_UI4:
case COM.VT_R4:
case COM.VT_R8:
return true;
default:
return false;
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isString()
*/
@Override
public boolean isString() {
if (variant == null) {
return false;
}
if (variant.getType() == COM.VT_BSTR) {
return true;
}
// see if the variant is a wrapper object
if (variant.getType() != COM.VT_DISPATCH) {
return false;
}
// see if it has a valueOf method
IDispatch dispatch = variant.getDispatch();
int[] ids = oleAutomationGetIdsOfNames(dispatch);
if (ids == null) {
return false;
}
Variant result = null;
try {
result = oleAutomationInvoke(dispatch, ids[0]);
/*
* If the return type of the valueOf method is string, we assume it is a
* String wrapper object.
*/
return result.getType() == COM.VT_BSTR;
} finally {
if (result != null) {
result.dispose();
}
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isUndefined()
*/
@Override
public boolean isUndefined() {
return variant == null || variant.getType() == COM.VT_EMPTY;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isWrappedJavaObject()
*/
@Override
public boolean isWrappedJavaObject() {
if (variant == null) {
return false;
}
if (variant.getType() != COM.VT_DISPATCH) {
return false;
}
return tryToUnwrapWrappedJavaObject() != null;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setBoolean(boolean)
*/
@Override
public void setBoolean(boolean val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setByte(byte)
*/
@Override
public void setByte(byte val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setChar(char)
*/
@Override
public void setChar(char val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setDouble(double)
*/
@Override
public void setDouble(double val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setInt(int)
*/
@Override
public void setInt(int val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setNull()
*/
@Override
public void setNull() {
setVariant(new Variant(0, COM.VT_NULL));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setShort(short)
*/
@Override
public void setShort(short val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setString(java.lang.String)
*/
@Override
public void setString(String val) {
setVariant(new Variant(val));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setUndefined()
*/
@Override
public void setUndefined() {
setVariant(null);
}
@Override
public void setValue(JsValue other) {
setVariant(maybeCopyVariant(((JsValueIE6) other).variant));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setWrappedJavaObject(com.google.gwt.dev.shell.CompilingClassLoader,
* java.lang.Object)
*/
@Override
public <T> void setWrappedJavaObject(CompilingClassLoader cl, T val) {
IDispatchImpl dispObj;
if (val == null) {
setNull();
return;
} else if (val instanceof IDispatchImpl) {
dispObj = (IDispatchImpl) val;
} else {
dispObj = (IDispatchImpl) cl.getWrapperForObject(val);
if (dispObj == null || dispObj.refCount < 1) {
dispObj = new IDispatchProxy(cl, val);
cl.putWrapperForObject(val, dispObj);
}
}
IDispatch disp = new IDispatch(dispObj.getAddress());
disp.AddRef();
setVariant(new Variant(disp));
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#createCleanupObject()
*/
@Override
protected JsCleanup createCleanupObject() {
return new JsCleanupIE6(variant);
}
/**
* Reset the underlying variant, freeing the old one if necessary.
*
* @param val the new Variant to store
*/
protected void setVariant(Variant val) {
if (variant != null) {
variant.dispose();
}
variant = val;
}
private Object tryToUnwrapWrappedJavaObject() {
int globalRef = 0;
Variant result = null;
try {
result = oleAutomationInvoke(variant.getDispatch(),
IDispatchProxy.DISPID_MAGIC_GETGLOBALREF);
if (result != null) {
globalRef = result.getInt();
if (globalRef != 0) {
// This is really a Java object being passed back via an
// IDispatchProxy.
IDispatchProxy proxy = (IDispatchProxy) Utils.objFromGlobalRefInt(globalRef);
return proxy.getTarget();
}
}
return null;
} finally {
if (result != null) {
result.dispose();
}
}
}
}