blob: 7d125f5752860102c4956f396e7d396eb96a4467 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2019 Xored Software Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Xored Software Inc - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.rcptt.ecl.interop.internal.commands;
import static org.eclipse.rcptt.ecl.interop.internal.EclInteropPlugin.error;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.rcptt.ecl.core.Command;
import org.eclipse.rcptt.ecl.interop.Invoke;
import org.eclipse.rcptt.ecl.interop.InvokeUi;
import org.eclipse.rcptt.ecl.runtime.ICommandService;
import org.eclipse.rcptt.ecl.runtime.IPipe;
import org.eclipse.rcptt.ecl.runtime.IProcess;
import org.eclipse.rcptt.util.DisplayUtils;
import org.eclipse.rcptt.util.DisplayUtilsProvider;
public class InvokeService implements ICommandService {
private static final Object NOT_CONVERTIBLE = new Object();
@SuppressWarnings({ "unchecked", "rawtypes" })
public IStatus service(Command command, IProcess context) throws CoreException {
final Invoke cmd = (Invoke) command;
final Object object = cmd.getObject();
if (object == null)
return error("Null invocation target.");
final Class class_ = object.getClass();
final String name = cmd.getName();
if (name == null || name.length() == 0)
return error("Empty method name.");
final Object[] args = cmd.getArgs().toArray();
// --
if (class_.isArray())
return processArrayMethod(object, name, args, context.getOutput());
// --
Object result = null;
try {
final Method method = matchMethod(class_, name, args);
if (method == null) {
if (args.length > 0) {
return error("Method not found.");
}
result = getFieldValue(class_, object, name);
} else {
DisplayUtils utils = DisplayUtilsProvider.getDisplayUtils();
if (utils.isWidget(object) || cmd instanceof InvokeUi) {
// no reason to go into generics here, everything is just
// Object
RunnableFuture future = new FutureTask(new Callable() {
public Object call() throws Exception {
return method.invoke(object, args);
}
});
if (cmd.isNoResult() || method.getReturnType() == Void.TYPE) {
utils.asyncExec(object, future);
result = Status.OK_STATUS;
} else {
utils.syncExec(object, future);
result = future.get();
}
} else
result = method.invoke(object, args);
}
} catch (Exception e) {
return error(e, "%s: %s", e.getClass().getName(), e.getMessage());
}
if (result != null)
context.getOutput().write(result);
return Status.OK_STATUS;
}
private static Object getFieldValue(Class<?> class_, Object object, String name) throws CoreException {
if (class_ == null) {
throw new CoreException(error("method or field not found"));
}
try {
Field field = class_.getDeclaredField(name);
// Can't be null -- NoSuchFieldException raised otherwise
field.setAccessible(true);
return field.get(object);
} catch (IllegalArgumentException e) {
// should not happen
throw new CoreException(error(e, "Unexpected error getting field %s", name));
} catch (SecurityException e) {
throw new CoreException(error(e, "Unexpected error getting field %s", name));
} catch (IllegalAccessException e) {
throw new CoreException(error(e, "Unexpected error getting field %s", name));
} catch (NoSuchFieldException e) {
return getFieldValue(class_.getSuperclass(), object, name);
}
}
private static IStatus processArrayMethod(Object array, String name, Object[] args, IPipe out)
throws CoreException {
if (name.equals("get")) {
if (args.length != 1)
return error("Invalid number of arguments.");
Object index = convert(int.class, args[0], true, true);
if (index == NOT_CONVERTIBLE)
return error("Invalid index type.");
Object result = Array.get(array, (Integer) index);
if (result != null)
out.write(result);
return Status.OK_STATUS;
} else if (name.equals("set")) {
if (args.length != 2)
return error("Invalid number of arguments.");
Object index = convert(int.class, args[0], true, true);
if (index == NOT_CONVERTIBLE)
return error("Invalid index type.");
Object value = convert(array.getClass().getComponentType(), args[1], true, true);
if (value == NOT_CONVERTIBLE)
return error("Invalid value type.");
Array.set(array, (Integer) index, value);
return Status.OK_STATUS;
} else if (name.equals("length")) {
if (args.length != 0)
return error("Invalid number of arguments.");
out.write(Array.getLength(array));
return Status.OK_STATUS;
} else
return error("Unknown array pseudo-method name.");
}
@SuppressWarnings("rawtypes")
static Method matchMethod(Class class_, String name, Object[] args) {
Method result = matchMethod(class_, name, args, false, false);
if (result == null)
result = matchMethod(class_, name, args, true, false);
if (result == null)
result = matchMethod(class_, name, args, true, true);
return result;
}
/*
* TODO:
*
* 1. Null handling. 2. Proper overloading resolution.
*
* Standard says we must select the most "specific" method.
*
* 3. What will happen if arrays passed?
*
* Useful reading:
* http://geekexplains.blogspot.com/2009/06/choosing-most-specific
* -method-tricky.html
*/
@SuppressWarnings({ "rawtypes" })
static Method matchMethod(Class class_, String name, Object[] args, boolean doWiden, boolean doNarrow) {
final Method[] methods = class_.getMethods();
for (Method m : methods) {
if (!m.getName().equals(name))
continue;
final Class[] paramTypes = m.getParameterTypes();
if (paramTypes.length != args.length)
continue;
boolean done = true;
for (int i = 0; i < paramTypes.length; ++i) {
if (convert(paramTypes[i], args[i], doWiden, doNarrow) == NOT_CONVERTIBLE) {
done = false;
break;
}
}
if (done)
return m;
}
return null;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object convert(Class to, Object value, boolean doWiden, boolean doNarrow) {
Class from = value.getClass();
// if Java tells us it is convertible, let it go
if (to.isAssignableFrom(from))
return value;
to = toPrimitive(to);
from = toPrimitive(from);
// if one of the types or both are not convertible to primitive,
// we can not do anything about it
if (to == null || from == null)
return NOT_CONVERTIBLE;
// always self assignable
if (to == from)
return value;
if (doWiden) {
// try to widen
Object converted = widden(to, value);
if (converted != NOT_CONVERTIBLE)
return converted;
}
if (doNarrow) {
// try to narrow
Object converted = narrow(to, value);
if (converted != NOT_CONVERTIBLE)
return converted;
}
return NOT_CONVERTIBLE;
}
@SuppressWarnings("rawtypes")
private static Object widden(Class to, Object value) {
if (to == value.getClass())
return value;
if (to == toPrimitive(value.getClass()))
return value;
Number number = null;
Character character = null;
if (value instanceof Number)
number = (Number) value;
if (value instanceof Character)
character = (Character) value;
if (to == long.class) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer)
return new Long(number.longValue());
else if (value instanceof Character)
return new Long(character.charValue());
else
return NOT_CONVERTIBLE;
}
if (to == int.class) {
if (value instanceof Byte || value instanceof Short)
return new Integer(number.intValue());
else if (value instanceof Character)
return new Integer(character.charValue());
else
return NOT_CONVERTIBLE;
}
if (to == short.class) {
if (value instanceof Byte)
return new Short(number.shortValue());
else
return NOT_CONVERTIBLE;
}
if (to == byte.class) {
return NOT_CONVERTIBLE; // nothing can be widened to byte
}
if (to == double.class) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long
|| value instanceof Float)
return new Double(number.doubleValue());
else if (value instanceof Character)
return new Double(character.charValue());
else
return NOT_CONVERTIBLE;
}
if (to == float.class) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long)
return new Float(number.floatValue());
else if (value instanceof Character)
return new Float(character.charValue());
else
return NOT_CONVERTIBLE;
}
if (to == char.class) {
return NOT_CONVERTIBLE; // nothing can be widened to char
}
return NOT_CONVERTIBLE;
}
@SuppressWarnings("rawtypes")
private static Object narrow(Class to, Object value) {
if (to == value.getClass())
return value;
if (to == toPrimitive(value.getClass()))
return value;
Number number = null;
Character character = null;
if (value instanceof Number)
number = (Number) value;
if (value instanceof Character)
character = (Character) value;
if (to == long.class) {
if (value instanceof Number) {
long l = number.longValue();
double d = number.doubleValue();
if (l == d && d >= Long.MIN_VALUE && d <= Long.MAX_VALUE)
return new Long(l);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
if (to == int.class) {
if (value instanceof Number) {
long l = number.longValue();
double d = number.doubleValue();
if (l == d && l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE)
return new Integer((int) l);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
if (to == short.class) {
if (value instanceof Number) {
long l = number.longValue();
double d = number.doubleValue();
if (l == d && l >= Short.MIN_VALUE && l <= Short.MAX_VALUE)
return new Short((short) l);
else
return NOT_CONVERTIBLE;
} else if (value instanceof Character) {
char c = character.charValue();
if (c >= Short.MIN_VALUE && c <= Short.MAX_VALUE)
return new Short((short) c);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
if (to == byte.class) {
if (value instanceof Number) {
long l = number.longValue();
double d = number.doubleValue();
if (l == d && l >= Byte.MIN_VALUE && l <= Byte.MAX_VALUE)
return new Byte((byte) l);
else
return NOT_CONVERTIBLE;
} else if (value instanceof Character) {
char c = character.charValue();
if (c >= Byte.MIN_VALUE && c <= Byte.MAX_VALUE)
return new Byte((byte) c);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
if (to == double.class) {
return NOT_CONVERTIBLE; // double is the widest type in the world
}
if (to == float.class) {
if (value instanceof Double) {
double d = number.doubleValue();
if (d >= Float.MIN_VALUE && d <= Float.MAX_VALUE)
return new Float((float) d);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
if (to == char.class) {
if (value instanceof Number) {
long l = number.longValue();
double d = number.doubleValue();
if (l == d && l >= Character.MIN_VALUE && l <= Character.MAX_VALUE)
return new Character((char) l);
else
return NOT_CONVERTIBLE;
} else
return NOT_CONVERTIBLE;
}
return NOT_CONVERTIBLE;
}
@SuppressWarnings("rawtypes")
private static Class toPrimitive(Class boxed) {
if (boxed.isPrimitive())
return boxed;
if (boxed == Boolean.class)
return boolean.class;
if (boxed == Character.class)
return char.class;
if (boxed == Byte.class)
return byte.class;
if (boxed == Short.class)
return short.class;
if (boxed == Integer.class)
return int.class;
if (boxed == Long.class)
return long.class;
if (boxed == Float.class)
return float.class;
if (boxed == Double.class)
return double.class;
return null;
}
}