package org.jruby.ext.win32ole;
import java.lang.reflect.Array;
import org.racob.com.Dispatch;
import org.racob.com.EnumVariant;
import org.racob.com.Variant;
import java.util.Calendar;
import java.util.Date;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyInteger;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.racob.com.SafeArray;
import win32ole.Win32oleService;
/**
*/
public class RubyWIN32OLE extends RubyObject {
private static final Object[] EMPTY_OBJECT_ARGS = new Object[0];
private static final int[] EMPTY_ERROR_ARGS = new int[0];
public static ObjectAllocator WIN32OLE_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyWIN32OLE(runtime, klass);
}
};
public Dispatch dispatch = null;
public RubyWIN32OLE(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
private RubyWIN32OLE(Ruby runtime, RubyClass metaClass, Dispatch dispatch) {
this(runtime, metaClass);
this.dispatch = dispatch;
}
public Dispatch getDispatch() {
return dispatch;
}
// Accessor for Ruby side of win32ole to get ahold of this object
@JRubyMethod()
public IRubyObject dispatch(ThreadContext context) {
return JavaUtil.convertJavaToUsableRubyObject(context.getRuntime(), dispatch);
}
@JRubyMethod()
public IRubyObject each(ThreadContext context, Block block) {
Ruby runtime = context.getRuntime();
EnumVariant enumVariant = dispatch.toEnumVariant();
// FIXME: when no block is passed handling
while (enumVariant.hasMoreElements()) {
Variant value = enumVariant.nextElement();
block.yield(context, fromVariant(runtime, value));
}
enumVariant.safeRelease();
return runtime.getNil();
}
@JRubyMethod(required = 3)
public IRubyObject _getproperty(ThreadContext context, IRubyObject dispid,
IRubyObject args, IRubyObject argTypes) {
Object[] objectArgs = makeObjectArgs(args.convertToArray());
int id = (int) RubyInteger.num2long(dispid);
Ruby runtime = context.getRuntime();
if (objectArgs.length == 0) return fromObject(runtime, dispatch.callO(id));
return fromVariant(runtime, dispatch.call(id, objectArgs));
}
@JRubyMethod(required = 1, rest = true)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
String id = args[0].convertToString().asJavaString();
String progId = toProgID(id);
dispatch = new Dispatch(progId);
return this;
}
@JRubyMethod(required = 1, rest = true)
public IRubyObject invoke(ThreadContext context, IRubyObject[] args) {
return method_missing(context, args);
}
@JRubyMethod()
public IRubyObject _invoke(ThreadContext context, IRubyObject dispid,
IRubyObject args, IRubyObject typesArray) {
return invokeInternal(context, dispid, args, args, Dispatch.Method);
}
@JRubyMethod(required = 1, rest = true)
public IRubyObject method_missing(ThreadContext context, IRubyObject[] args) {
String methodName = args[0].asJavaString();
if (methodName.endsWith("=")) return invokeSet(context,
methodName.substring(0, methodName.length() - 1), args);
return invokeMethodOrGet(context, methodName, args);
}
@JRubyMethod()
public IRubyObject ole_free(ThreadContext context) {
dispatch.safeRelease();
return context.getRuntime().getNil();
}
@JRubyMethod(name = "[]", required = 1)
public IRubyObject op_aref(ThreadContext context, IRubyObject property) {
String propertyName = property.asJavaString();
return fromVariant(context.getRuntime(), dispatch.get(propertyName));
}
@JRubyMethod(name = "[]=", required = 2)
public IRubyObject op_aset(ThreadContext context, IRubyObject property, IRubyObject value) {
String propertyName = property.asJavaString();
dispatch.put(propertyName, toObject(value));
return context.getRuntime().getNil();
}
@JRubyMethod()
public IRubyObject _setproperty(ThreadContext context, IRubyObject dispid,
IRubyObject args, IRubyObject argTypes) {
return invokeInternal(context, dispid, args, argTypes, Dispatch.Put);
}
@JRubyMethod(required = 1, rest = true)
public IRubyObject setproperty(ThreadContext context, IRubyObject[] args) {
String methodName = args[0].asJavaString();
return invokeSet(context, methodName, args);
}
private IRubyObject invokeSet(ThreadContext context, String methodName, IRubyObject[] args) {
Object[] objectArgs = makeObjectArgs(args, 1);
int[] errorArgs = new int[objectArgs.length];
dispatch.invoke(methodName, Dispatch.Put, objectArgs, errorArgs);
return context.getRuntime().getNil();
}
private IRubyObject invokeInternal(ThreadContext context, IRubyObject dispid,
IRubyObject args, IRubyObject argTypes, int dispatchType) {
RubyArray argsArray = args.convertToArray();
int dispatchId = (int) RubyInteger.num2long(dispid);
Object[] objectArgs = makeObjectArgs(argsArray);
int[] errorArgs = makeErrorArgs(objectArgs.length);
Variant returnValue = dispatch.invoke(dispatchId, dispatchType,
objectArgs, errorArgs);
return fromVariant(context.getRuntime(), returnValue);
}
private int[] makeErrorArgs(int size) {
return size <= 0 ? EMPTY_ERROR_ARGS : new int[size];
}
private Object[] makeObjectArgs(IRubyObject[] rubyArgs, int startIndex) {
int length = rubyArgs.length;
if (length - startIndex <= 0) return EMPTY_OBJECT_ARGS;
Object[] objectArgs = new Object[length - startIndex];
for (int i = startIndex; i < length; i++) {
objectArgs[i - startIndex] = RubyWIN32OLE.toObject(rubyArgs[i]);
}
return objectArgs;
}
private Object[] makeObjectArgs(RubyArray argsArray) {
int length = argsArray.size();
if (length <= 0) return EMPTY_OBJECT_ARGS;
Object[] objectArgs = new Object[length];
for (int i = 0; i < length; i++) {
Object object = toObject(argsArray.eltInternal(i));
objectArgs[i] = object;
}
return objectArgs;
}
private IRubyObject invokeMethodOrGet(ThreadContext context, String methodName, IRubyObject[] args) {
if (args.length == 1) { // No-arg call
return fromObject(context.getRuntime(), dispatch.callO(methodName));
}
Variant variant = dispatch.callN(methodName, makeObjectArgs(args, 1));
return fromVariant(context.getRuntime(), variant);
}
@Override
public Object toJava(Class klass) {
return dispatch;
}
private static final Class OBJECT_ARRAY_CLASS = Array.newInstance(Object.class, 1).getClass();
public static Object toObject(IRubyObject rubyObject) {
if (rubyObject instanceof RubyArray) {
return ((RubyArray) rubyObject).toJava(OBJECT_ARRAY_CLASS);
}
return rubyObject.toJava(Object.class);
}
public static IRubyObject fromObject(Ruby runtime, Object object) {
if (object == null) return runtime.getNil();
if (object instanceof Boolean) {
return runtime.newBoolean(((Boolean) object).booleanValue());
} else if (object instanceof Dispatch) {
return new RubyWIN32OLE(runtime, Win32oleService.getMetaClass(), (Dispatch) object);
} else if (object instanceof Date) {
return date2ruby(runtime, (Date) object);
} else if (object instanceof Number) {
if (object instanceof Double) {
return runtime.newFloat(((Double) object).doubleValue());
} else if (object instanceof Float) {
return runtime.newFloat(((Float) object).doubleValue());
}
return runtime.newFixnum(((Number) object).intValue());
} else if (object instanceof String) {
return runtime.newString((String) object);
} else if (object instanceof SafeArray) {
return listFromSafeArray(runtime, (SafeArray) object);
}
return JavaUtil.convertJavaToUsableRubyObject(runtime, object);
}
private static IRubyObject listFromSafeArray(Ruby runtime, SafeArray array) {
Variant[] values = array.getValues();
RubyArray newArray = runtime.newArray();
if (values != null) {
for (int i = 0; i < values.length; i++) {
newArray.append(fromVariant(runtime, values[i]));
}
}
return newArray;
}
public static IRubyObject fromVariant(Ruby runtime, Variant variant) {
if (variant == null) return runtime.getNil();
if (variant.isArray()) return listFromSafeArray(runtime, variant.getSafeArray());
switch (variant.getType()) {
case Variant.VariantBoolean:
return runtime.newBoolean(variant.getBoolean());
case Variant.VariantDispatch:
return new RubyWIN32OLE(runtime, Win32oleService.getMetaClass(), variant.getDispatch());
case Variant.VariantDate:
return date2ruby(runtime, variant.getDate());
case Variant.VariantInt:
case Variant.VariantShort:
return runtime.newFixnum(variant.getInt());
case Variant.VariantDouble:
return runtime.newFloat(variant.getDouble());
case Variant.VariantFloat:
return runtime.newFloat(variant.getFloat());
case Variant.VariantString:
return runtime.newString(variant.getString());
}
return JavaUtil.convertJavaToUsableRubyObject(runtime, variant.toJavaObject());
}
public static IRubyObject date2ruby(Ruby runtime, Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return runtime.newTime(cal.getTimeInMillis());
}
public static String toProgID(String id) {
if (id != null && id.startsWith("{") && id.endsWith("}")) {
return "clsid:" + id.substring(1, id.length() - 1);
}
return id;
}
}