/*******************************************************************************
* 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 java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.swt.internal.ole.win32.COM;
import org.eclipse.swt.internal.ole.win32.COMObject;
import org.eclipse.swt.internal.ole.win32.DISPPARAMS;
import org.eclipse.swt.internal.ole.win32.GUID;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.ole.win32.OleAutomation;
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.MethodAdaptor;
import com.google.gwt.dev.shell.designtime.ModuleSpace;
/**
* Basic IDispatch implementation for use by {@link com.google.gwt.shell.ie.IDispatchProxy} and
* {@link com.google.gwt.shell.ie.IDispatchStatic}.
*/
public abstract class IDispatchImpl extends COMObject {
/**
* An exception for wrapping bad HR's.
*/
public static class HResultException extends Exception {
private final int hr;
private final String source;
/**
* Constructs a standard bad HR exception.
*/
public HResultException(int hr) {
super(Integer.toString(hr));
this.hr = hr;
source = "Java";
}
/**
* Constructs a DISP_E_EXCEPTION bad HR.
*/
public HResultException(String message) {
super(message);
hr = COM.DISP_E_EXCEPTION;
source = "Java";
}
/**
* Constructs a DISP_E_EXCEPTION bad HR.
*/
public HResultException(Throwable e) {
super(getStackTraceAsString(e), e);
hr = COM.DISP_E_EXCEPTION;
source = "Java";
}
/**
* If the HR is DISP_E_EXCEPTION, this method will fill in the EXCEPINFO structure. Otherwise,
* it does nothing.
*/
public void fillExcepInfo(int pExcepInfo) {
if (hr == COM.DISP_E_EXCEPTION) {
String desc = getMessage();
// 0: wCode (size = 2)
// 4: bstrSource (size = 4)
// 8: bstrDescription (size = 4)
// 28: scode (size = 4)
//
OS.MoveMemory(pExcepInfo + 0, new short[]{(short) hr}, 2);
if (source != null && source.length() != 0) {
int bstrSource = SwtOleGlue.sysAllocString(source);
OS.MoveMemory(pExcepInfo + 4, new int[]{bstrSource}, 4);
}
if (desc != null && desc.length() != 0) {
int bstrDesc = SwtOleGlue.sysAllocString(desc);
OS.MoveMemory(pExcepInfo + 8, new int[]{bstrDesc}, 4);
}
OS.MoveMemory(pExcepInfo + 28, new int[]{0}, 4);
}
}
/**
* Gets the HR.
*/
public int getHResult() {
return hr;
}
}
// This one isn't defined in SWT for some reason.
protected static final int DISP_E_UNKNOWNNAME = 0x80020006;
protected static Variant callMethod(ClassLoader cl,
DispatchIdOracle ora,
Object jthis,
Variant[] params,
MethodAdaptor method) throws InstantiationException, InvocationTargetException,
HResultException {
// TODO: make sure we have enough args! It's okay if there are too many.
Object[] javaParams =
SwtOleGlue.convertVariantsToObjects(
cl,
method.getParameterTypes(),
params,
"Calling method '" + method.getName() + "'");
Object result = null;
try {
try {
result = method.invoke(jthis, javaParams);
} catch (IllegalAccessException e) {
// should never, ever happen
e.printStackTrace();
throw new RuntimeException(e);
}
} catch (NullPointerException e) {
/*
* The JavaScript expected the method to be static, having forgotten an
* instance reference (most often "this.").
*/
StringBuffer sb = new StringBuffer();
sb.append("Instance method '");
sb.append(method.getName());
sb.append("' needed a qualifying instance ");
sb.append("(did you forget to prefix the call with 'this.'?)");
throw new HResultException(sb.toString());
} finally {
for (int i = 0; i < javaParams.length; i++) {
if (javaParams[i] instanceof OleAutomation) {
OleAutomation tmp = (OleAutomation) javaParams[i];
tmp.dispose();
}
}
}
return SwtOleGlue.convertObjectToVariant(cl, ora, method.getReturnType(), result);
}
protected int refCount;
public IDispatchImpl() {
super(new int[]{2, 0, 0, 1, 3, 5, 8});
}
// CHECKSTYLE_OFF
public int AddRef() {
return ++refCount;
}
// CHECKSTYLE_ON
@Override
public int method0(int[] args) {
return QueryInterface(args[0], args[1]);
}
@Override
public int method1(int[] args) {
return AddRef();
}
// method3 GetTypeInfoCount - not implemented
// method4 GetTypeInfo - not implemented
@Override
public int method2(int[] args) {
return Release();
}
@Override
public int method5(int[] args) {
return GetIDsOfNames(args[0], args[1], args[2], args[3], args[4]);
}
@Override
public int method6(int[] args) {
return Invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
}
// CHECKSTYLE_OFF
public int QueryInterface(int riid, int ppvObject) {
if (riid == 0 || ppvObject == 0) {
return COM.E_NOINTERFACE;
}
GUID guid = new GUID();
COM.MoveMemory(guid, riid, GUID.sizeof);
if (COM.IsEqualGUID(guid, COM.IIDIUnknown)) {
OS.MoveMemory(ppvObject, new int[]{getAddress()}, 4);
AddRef();
return COM.S_OK;
}
if (COM.IsEqualGUID(guid, COM.IIDIDispatch)) {
OS.MoveMemory(ppvObject, new int[]{getAddress()}, 4);
AddRef();
return COM.S_OK;
}
OS.MoveMemory(ppvObject, new int[]{0}, 4);
return COM.E_NOINTERFACE;
}
public int Release() {
if (--refCount == 0) {
dispose();
}
return refCount;
}
// CHECKSTYLE_ON
/**
* Override this method to implement GetIDsOfNames().
*/
protected abstract void getIDsOfNames(String[] names, int[] ids) throws HResultException;
/**
* Override this method to implement Invoke().
*/
protected abstract Variant invoke(int dispId, int flags, Variant[] params)
throws HResultException, InstantiationException, InvocationTargetException;
private Variant[] extractVariantArrayFromDispParamsPtr(int pDispParams) {
DISPPARAMS dispParams = new DISPPARAMS();
COM.MoveMemory(dispParams, pDispParams, DISPPARAMS.sizeof);
Variant[] variants = new Variant[dispParams.cArgs];
// Reverse the order as we pull the variants in.
for (int i = 0, n = dispParams.cArgs; i < n; ++i) {
int varArgAddr = dispParams.rgvarg + Variant.sizeof * i;
variants[n - i - 1] = Utils.win32_new(varArgAddr);
}
return variants;
}
// CHECKSTYLE_OFF
@SuppressWarnings("unused")
private final int GetIDsOfNames(int riid, int rgszNames, int cNames, int lcid, int rgDispId) {
try {
if (cNames < 1) {
return COM.E_INVALIDARG;
}
// Extract the requested names and build an answer array init'ed with -1.
//
String[] names = SwtOleGlue.extractStringArrayFromOleCharPtrPtr(rgszNames, cNames);
int[] ids = new int[names.length];
Arrays.fill(ids, -1);
getIDsOfNames(names, ids);
OS.MoveMemory(rgDispId, ids, ids.length * 4);
} catch (HResultException e) {
return e.getHResult();
} catch (Throwable e) {
e.printStackTrace();
return COM.E_FAIL;
}
return COM.S_OK;
}
@SuppressWarnings("unused")
private int Invoke(int dispIdMember,
int riid,
int lcid,
int dwFlags,
int pDispParams,
int pVarResult,
int pExcepInfo,
int pArgErr) {
HResultException ex = null;
Variant[] vArgs = null;
Variant result = null;
try {
vArgs = extractVariantArrayFromDispParamsPtr(pDispParams);
result = invoke(dispIdMember, dwFlags, vArgs);
if (pVarResult != 0) {
Utils.win32_copy(pVarResult, result);
}
} catch (HResultException e) {
// Log to the console for detailed examination.
//
e.printStackTrace();
ex = e;
} catch (InvocationTargetException e) {
// If we get here, it means an exception is being thrown from
// Java back into JavaScript
Throwable t = e.getTargetException();
ex = new HResultException(t);
ModuleSpace.setThrownJavaException(t);
} catch (Exception e) {
// Log to the console for detailed examination.
//
e.printStackTrace();
ex = new HResultException(e);
} finally {
// We allocated variants for all arguments, so we must dispose them all.
//
for (int i = 0; i < vArgs.length; ++i) {
if (vArgs[i] != null) {
vArgs[i].dispose();
}
}
if (result != null) {
result.dispose();
}
}
if (ex != null) {
// Set up an exception for IE to throw.
//
ex.fillExcepInfo(pExcepInfo);
return ex.getHResult();
}
return COM.S_OK;
}
// CHECKSTYLE_ON
private static String getStackTraceAsString(Throwable e) {
// Show the exception info for anything other than "UnableToComplete".
if (e == null || e.getClass().getName().equals("UnableToCompleteException")) {
return null;
}
// For each cause, print the requested number of entries of its stack
// trace, being careful to avoid getting stuck in an infinite loop.
//
StringBuffer message = new StringBuffer();
Throwable currentCause = e;
String causedBy = "";
HashSet<Throwable> seenCauses = new HashSet<Throwable>();
while (currentCause != null && !seenCauses.contains(currentCause)) {
seenCauses.add(currentCause);
message.append(causedBy);
causedBy = "\nCaused by: "; // after 1st, all say "caused by"
message.append(currentCause.getClass().getName());
message.append(": " + currentCause.getMessage());
StackTraceElement[] stackElems = currentCause.getStackTrace();
if (stackElems != null) {
for (int i = 0; i < stackElems.length; ++i) {
message.append("\n\tat ");
message.append(stackElems[i].toString());
}
}
currentCause = currentCause.getCause();
}
return message.toString();
}
}