package org.jruby.ext.win32ole; 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 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) { RubyArray argsArray = args.convertToArray(); Object[] objectArgs = makeObjectArgs(argsArray); int dispatchId = (int) RubyInteger.num2long(dispid); if (objectArgs.length == 0) { return fromObject(context.getRuntime(), Dispatch.callO(dispatch, dispatchId)); } return fromVariant(context.getRuntime(), Dispatch.call(dispatch, dispatchId, objectArgs)); } @JRubyMethod(required = 1, rest = true) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { String id = args[0].convertToString().asJavaString(); dispatch = new Dispatch(toProgID(id)); 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(dispatch, propertyName)); } @JRubyMethod(name = "[]=", required = 2) public IRubyObject op_aset(ThreadContext context, IRubyObject property, IRubyObject value) { String propertyName = property.asJavaString(); Dispatch.put(dispatch, 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(dispatch, 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(dispatch, 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(dispatch, methodName)); } return fromVariant(context.getRuntime(), Dispatch.callN(dispatch, methodName, makeObjectArgs(args, 1))); } @Override public Object toJava(Class klass) { return dispatch; } public static Object toObject(IRubyObject rubyObject) { 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); } return JavaUtil.convertJavaToUsableRubyObject(runtime, object); } public static IRubyObject fromVariant(Ruby runtime, Variant variant) { if (variant == null) return runtime.getNil(); 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 id.substring(2, id.length() - 2); } return id; } }