/*******************************************************************************
* 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 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;
import com.google.gdt.eclipse.designer.ie.util.Utils;
import com.google.gwt.dev.shell.designtime.DispatchIdOracle;
import com.google.gwt.dev.shell.designtime.JsValue;
import com.google.gwt.dev.shell.designtime.WrappersCache;
/**
* 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(ClassLoader cl, DispatchIdOracle dispIdOracle, T val) {
IDispatchImpl dispObj;
if (val == null) {
setNull();
return;
} else if (val instanceof IDispatchImpl) {
dispObj = (IDispatchImpl) val;
} else {
dispObj = (IDispatchImpl) WrappersCache.getWrapperForObject(cl, val);
if (dispObj == null || dispObj.refCount < 1) {
dispObj = new IDispatchProxy(cl, dispIdOracle, val);
WrappersCache.putWrapperForObject(cl, 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();
}
}
}
}