using [java] com.jacob.com::Variant as JVariant
**
** A multi-format data type used for all COM communications.
**
** A Variant's type may be queried using the 'isXXX()' methods and returned with the 'asXXX()'
** methods. Variants have the notion of being null, the equivalent of VB Nothing. Whereas Variant
** objects themselves are never null, their 'asXXX()' methods may return null.
**
** The [asType()]`#asType` method can be used to return any Fantom literal ('Bool', 'Int', 'Emum',
** etc...) and also Fancom Surrogates. Surrogates are user defined classes which wrap either a
** `Dispatch` or a `Variant` object. Surrogates make it easy to write boiler plate Fantom classes
** that mimic the behaviour of COM components.
**
**
** Variant Surrogates [#surrogate]
** ===============================
** A Variant Surrogate is any object that conforms to the following rules. (Think of it as an
** *implied* interface.)
**
** A Variant Surrogate **must** have either:
** - a ctor with the signature 'new makeFromVariant(Variant variant)' or
** - a static factory method with the signature 'static MyClass fromVariant(Variant variant)'
**
** A Variant Surrogate **must** also have either:
** - a method with the signature 'Variant toVariant()' or
** - a field with the signature 'Variant variant'
**
** This allows Variant Surrogates to be passed in as parameters to a Dispatch object, and to be
** instantiated from the [asType()]`#asType` method.
**
** Enums are good candidates for converting into surrogates. Also see `Flag` as another example.
**
** Note: Fancom Surrogate methods and fields may be of any visibility - which is useful if don't
** wish to expose them.
**
** Note: Fancom Surrogates don't require you to implement any Mixins, reflection is used to find
** your wrapper methods.
**
**
** Variant Types [#variantTypes]
** ===========================
** The conversion of types between the various underlying systems is given below:
**
** pre>
** Automation Type | JACOB Type | Fancom Type | Remarks
** ---------------------------------------------------------------------
** 0 VT_EMPTY null null Equivalent to VB Nothing
** 1 VT_NULL null null Equivalent to VB Null
** 2 VT_I2 short Int
** 3 VT_I4 int Int / Enum A Long in VC
** 4 VT_R4 float Float
** 5 VT_R8 double Float
** 6 VT_CY Currency ---
** 7 VT_DATE Date --- TODO -> DateTime
** 8 VT_BSTR String Str
** 9 VT_DISPATCH Dispatch Dispatch
** 10 VT_ERROR int --- TODO -> throw Err
** 11 VT_BOOL boolean Bool
** 12 VT_VARIANT Variant --- TODO -> Variant
** 14 VT_DECIMAL BigDecimal --- TODO -> Decimal
** 17 VT_UI1 byte Int
** 20 VT_I8 long Int
** <pre
**
** All other value types are unsupported by JACOB and hence unsupported by Fancom. For more information
** on Automation Types see [VARENUM enumeration (Automation)]`http://msdn.microsoft.com/en-us/library/windows/desktop/ms221170%28v=vs.85%29.aspx` on MSDN.
**
class Variant {
** The JACOB Variant this object wraps
JVariant variant { private set }
// names taken from the JACOB Variant class
private Str[] typeNames := "Nothing, Null, Short, Int, Float, Double, Currency, Date, String, Dispatch, Error, Boolean, Variant, Object, Decimal, ???, VT_I1, Byte, VT_UI2, VT_UI4, Long, VT_UI8, VT_INT, VT_UNIT, VT_VOID, VT_HRESULT, Pointer, VT_SAFEARRAY, VT_CARRARY, VT_USERDEFINED".split(',', true)
// ---- Constructors --------------------------------------------------------------------------
** Make a 'null' Variant
new make() {
this.variant = JVariant()
}
** Make a Variant representing a 'Bool' value
new makeFromBool(Bool value) {
this.variant = JVariant(value)
}
** Make a Variant representing a 'Float' value
new makeFromFloat(Float value) {
this.variant = JVariant(value)
}
** Make a Variant representing an 'Int' value
new makeFromInt(Int value) {
this.variant = fromLong(value)
}
** Make a Variant representing a 'Str' value
new makeFromStr(Str value) {
this.variant = JVariant(value)
}
** Make a Variant wrapping the given JACOB Variant
internal new makeFromVariant(JVariant variant) {
this.variant = variant
}
// ---- Methods -------------------------------------------------------------------------------
** Returns 'true' if this Variant represents a 'null' value
Bool isNull() {
variant.isNull
}
** Returns 'true' if this Variant represents a 'Bool' value.
** Will return 'false' if the Variant represents 'null' as the type cannot be determined.
Bool isBool() {
variantType == JVariant.VariantBoolean
}
** Returns 'true' if this Variant represents a `Dispatch` object.
** Will return 'false' if the Variant represents 'null' as the type cannot be determined.
Bool isDispatch() {
variantType == JVariant.VariantDispatch
}
** Returns 'true' if this Variant can be converted into the given 'Enum' type, checking that
** the Variant value is a valid ordinal for the type.
** Throws 'ArgErr' if the given type is not a subclass of 'Enum'.
Bool isEnum(Type enumType) {
if (isNull)
return false
if (!enumType.isEnum)
throw ArgErr(Messages.wrongType(enumType, Enum#))
vals := enumType.field("vals").get as Obj[]
if (asInt >= vals.size)
return false
return true
}
** Returns 'true' if this Variant represents a 'Float' value.
** Will return 'false' if the Variant represents 'null' as the type cannot be determined.
Bool isFloat() {
(variantType == JVariant.VariantFloat) ||
(variantType == JVariant.VariantDouble)
}
** Returns 'true' if this Variant represents a 'Int' value.
** Will return 'false' if the Variant represents 'null' as the type cannot be determined.
Bool isInt() {
(variantType == JVariant.VariantByte) ||
(variantType == JVariant.VariantShort) ||
(variantType == JVariant.VariantInt) ||
(variantType == JVariant.VariantLongInt)
}
** Returns 'true' if this Variant represents a 'Str' value.
** Will return 'false' if the Variant represents 'null' as the type cannot be determined.
Bool isStr() {
variantType == JVariant.VariantString
}
** Returns the Variant value as a 'Bool' or 'null' if the Variant represents 'null'.
** Throws `FancomErr` if the Variant is not a 'Bool' type.
Bool? asBool() {
if (isNull) return null
return isBool ? variant.getBoolean : throw FancomErr(Messages.wrongVariantType(this, Bool#))
}
** Returns the Variant value as a `Dispatch` object or 'null' if the Variant represents 'null'.
** Throws `FancomErr` if the Variant is not a `Dispatch` type.
Dispatch? asDispatch() {
if (isNull) return null
return isDispatch ? Dispatch(variant.getDispatch) : throw FancomErr(Messages.wrongVariantType(this, Dispatch#))
}
** Returns the Variant value as a 'Float' or 'null' if the Variant represents 'null'.
** Throws `FancomErr` if the Variant is not a 'Float' type.
Float? asFloat() {
if (isNull)
return null
if (variantType == JVariant.VariantFloat)
return variant.getFloat
if (variantType == JVariant.VariantDouble)
return variant.getDouble
throw FancomErr(Messages.wrongVariantType(this, Float#))
}
** Returns the Variant value as an 'Int' or 'null' if the Variant represents 'null'.
** Throws `FancomErr` if the Variant is not an 'Int' type.
Int? asInt() {
if (isNull)
return null
if (variantType == JVariant.VariantByte)
return variant.getByte
if (variantType == JVariant.VariantShort)
return variant.getShort
if (variantType == JVariant.VariantInt)
return variant.getInt
if (variantType == JVariant.VariantLongInt)
return variant.getLong
throw FancomErr(Messages.wrongVariantType(this, Int#))
}
** Returns the Variant value as a 'Str' or 'null' if the Variant represents 'null'.
** Throws `FancomErr` if the Variant is not a 'Str' type.
Str? asStr() {
if (isNull) return null
return isStr ? variant.getString : throw FancomErr(Messages.wrongVariantType(this, Str#))
}
** Returns the Variant value as an instance of the supplied 'Enum' type or 'null' if the
** Variant represents 'null'.
**
** Throws 'ArgErr' if the given type is not a subclass of 'Enum'.
** Throws `FancomErr` if the Variant is not an 'Enum' ('Int') type or if the 'Int' value is not a valid ordinal for the enum.
Enum? asEnum(Type enumType) {
if (isNull)
return null
if (!isEnum(enumType))
throw FancomErr(Messages.wrongVariantType(this, enumType))
vals := enumType.field("vals").get as Obj[]
if (asInt >= vals.size)
throw FancomErr(Messages.enumOrdinalOutOfRange(enumType, vals.size, asInt))
return vals[asInt]
}
** A Swiss Army knife this method is; attempts to convert the Variant value into the given Type, be
** it a 'Bool', 'Enum' or a Fancom wrapper. Throws `FancomErr` if unsuccessful.
Obj? asType(Type type) {
if (isNull) return null
return Helper.toFantomObj(this, type) ?: throw FancomErr(Messages.wrongVariantType(this, type))
}
** Returns 'true' if this Variant represents an array.
Bool isArray() {
variant.getvt.and(JVariant.VariantArray) == JVariant.VariantArray
}
** Returns 'true' if this Variant is a 'ByRef' value.
Bool isByRef() {
variant.getvt.and(JVariant.VariantByref) == JVariant.VariantByref
}
** Returns details of the underlying COM value.
override Str toStr() {
// toString() on a safe(!) byte array gave errors, so we just disp '...' instead
typeIndex := variantType
typeName := (typeNames.size > typeIndex) ? typeNames[typeIndex] : typeIndex.toStr
byRef := isByRef ? "*" : ""
return isArray ? "(${typeName}[])..." : "(${typeName}${byRef})$variant.toString"
}
private Int variantType() {
variant.getvt.and(JVariant.VariantTypeMask)
}
internal static native JVariant fromLong(Int value)
}