projects/wms-framework/src/lib/baseframework/ReflectionSupport.ts
Properties |
|
GetInnerFields |
GetInnerFields:
|
Type : function
|
Optional |
Optional method to get inner field (mainly used for enums) |
name |
name:
|
Type name |
import 'reflect-metadata';
import { explicitInterfaceCompatibilityMetadataKey } from '../decorators/AsExplicitImplementation';
import {
fullClassNameCompatibilityMetadataKey,
supportedInterfacesCompatibilityMetadataKey,
} from '../decorators/ClassInfo';
import { Debugger } from '../diagnostics/Debugger';
import { TypeResolver } from '../helpers/TypeResolver';
import { iuAll, iuSelectMany, iuToArray } from './collections';
import { HashUtils } from './Hashable';
import { MetadataAttribute } from './MetadataAttribute';
export interface ReifiedType {
/**
* Type name
*
* @memberof ReifiedType
*/
name;
/**
* Optional method to get inner field (mainly used for enums)
*
* @memberof ReifiedType
*/
GetInnerFields?: () => FieldInfo[];
}
export type ClassType = (new (...p: any[]) => any) | ReifiedType;
/**
* Type used to represent an interface at runtime
*
* @export
* @class ReifiedInterfaceInfo
* @extends {Function}
*/
export class ReifiedInterfaceInfo implements ReifiedType {
/**
* Gets the interface simple name
*
* @readonly
* @memberof ReifiedInterfaceInfo
*/
public get simpleName() {
return this.interfaceName.replace(/^.*\.([^.]*)$/, '$1');
}
/**
* Creates an instance of ReifiedInterfaceInfo.
* @param {string} interfaceName full name of the interface
* @memberof ReifiedInterfaceInfo
*/
constructor(public interfaceName: string) {
this.name = interfaceName;
}
name: string;
}
/**
* Type used to represent an enum at runtime
*
* @export
* @class ReifiedEnumInfo
* @extends {Function}
*/
export class ReifiedEnumInfo implements ReifiedType {
private cachedFieldInfos: FieldInfo[];
name: string;
fullName: string;
/**
* Creates an instance of ReifiedEnumInfo.
* @param {string} enumName
* @param {*} innerEnumDefinition
* @memberof ReifiedEnumInfo
*/
constructor(public enumName: string, public innerEnumDefinition: any) {
this.name = enumName;
const lastIndexOfDot = enumName.lastIndexOf('.');
if (lastIndexOfDot !== -1) {
this.name = enumName.substr(lastIndexOfDot + 1);
}
this.fullName = enumName;
}
/**
* Gets the inner collection of field infos
*
* @return {*} {FieldInfo[]}
* @memberof ReifiedEnumInfo
*/
GetInnerFields(): FieldInfo[] {
if (!this.cachedFieldInfos) {
const result: Array<FieldInfo> = [];
for (const propertyName of Object.keys(this.innerEnumDefinition)) {
if (isNaN(parseInt(propertyName, 10))) {
const fieldInfo = new FieldInfo(propertyName);
fieldInfo.IsLiteral = true;
result.push(fieldInfo);
}
}
this.cachedFieldInfos = result;
}
return this.cachedFieldInfos;
}
}
/**
* Type used to represent an built-in supported type at runtime
*
* @export
* @class ReifiedBuiltinTypeInfo
* @extends {Function}
*/
export class ReifiedBuiltinTypeInfo implements ReifiedType {
/**
* Gets the built-int simple name
*
* @readonly
* @memberof ReifiedBuiltinTypeInfo
*/
public get simpleName() {
return this.builtinName.replace(/^.*\.([^.]*)$/, '$1');
}
/**
* Creates an instance of ReifiedBuiltinTypeInfo.
* @param {string} builtinName full name of the built-in type
* @memberof ReifiedBuiltinTypeInfo
*/
constructor(public builtinName: string) {
this.name = builtinName;
}
/**
* The built-in type name
*
* @type {string}
* @memberof ReifiedBuiltinTypeInfo
*/
name: string;
}
/**
* Helper funtions to replace reflection functionallity
*
* @export
* @class ReflectionHelper
*/
export class ReflectionHelper {
/**
* A map to make sure runtime types are unique
*
* @private
* @static
* @type {Map<Function, RuntimeTypeInfo>}
* @memberof ReflectionHelper
*/
private static runtimeTypes: Map<
string,
Array<[ClassType, RuntimeTypeInfo]>
> = new Map();
/**
* Collection used to keep unique instances of compatibility assembly objects
*
* @private
* @static
* @type {Map<string, CompatibilityAssemblyInformation>}
* @memberof ReflectionHelper
*/
private static assemblyInfoObjets: Map<
string,
CompatibilityAssemblyInformation
> = new Map();
/**
* Collection used to keep unique instances of enum classes
*
* @private
* @static
* @type {Map<string, ReifiedEnumInfo>}
* @memberof ReflectionHelper
*/
private static enumMapInfo: Map<string, ReifiedEnumInfo> = new Map();
/**
* Creates an object to represent interfaces at runtime for the given interface identifier
*
* @static
* @param {string} intefaceName
* @return {*} {ReifiedInterfaceInfo}
* @memberof ReflectionHelper
*/
static getInterfaceRuntimeTypeInfo(
intefaceName: string
): ReifiedInterfaceInfo {
return new ReifiedInterfaceInfo(intefaceName);
}
/**
* Creates an object to represent built-in supported at runtime for the given name
*
* @static
* @param {string} builtinName
* @return {*} {ReifiedBuiltinTypeInfo}
* @memberof ReflectionHelper
*/
static getBuiltinRuntimeTypeInfo(
builtinName: string
): ReifiedBuiltinTypeInfo {
return new ReifiedBuiltinTypeInfo(builtinName);
}
/**
* Retrieves a unique object representing the compatibility info
*
* @static
* @param {string} name
* @return {*} {CompatibilityAssemblyInformation}
* @memberof ReflectionHelper
*/
static getCompatibilityAssemblyInfo(
name: string
): CompatibilityAssemblyInformation {
let existing = this.assemblyInfoObjets.get(name);
if (!existing) {
existing = new CompatibilityAssemblyInformation(name);
this.assemblyInfoObjets.set(name, existing);
}
return existing;
}
/**
* Verifiers if the given inteface is support
*
* NOT IMPLEMENTED
*
* @static
* @param {unknown} obj
* @param {string} interfaceName
* @return {*} {boolean}
* @memberof ReflectionHelper
*/
static isInterfaceIsSupported(obj: unknown, interfaceName: string): boolean {
let found = false;
if (obj) {
let type = obj['constructor'];
while (type) {
const interfaceMetadata = Reflect.getOwnMetadata(
supportedInterfacesCompatibilityMetadataKey,
type
);
if (interfaceMetadata && interfaceMetadata.length) {
for (let i = 0; i < interfaceMetadata.length; i++) {
if (interfaceName === interfaceMetadata[i]) {
found = true;
break;
}
}
}
const prototypeOfType = Object.getPrototypeOf(type);
if (prototypeOfType && prototypeOfType !== type) {
type = prototypeOfType;
} else {
type = null;
}
}
}
return found;
}
static isTypeSupported(obj: unknown, classType: ClassType): boolean {
if (classType instanceof ReifiedInterfaceInfo) {
return ReflectionHelper.isInterfaceIsSupported(obj, classType.name);
} else if (classType instanceof ReifiedEnumInfo) {
return typeof obj === 'number';
} else if (classType instanceof Function) {
return obj instanceof classType;
}
return false;
}
/**
* Verifies if the given type is an instance of a type with type arguments
*
* NOT IMPLEMNETED
*
* @static
* @param {unknown} obj
* @param {Function} type
* @param {any[]} typeArguments
* @return {*} {boolean}
* @memberof ReflectionHelper
*/
static isInstanceOfTypeWithTypeArgs(
obj: unknown,
type: any,
typeArguments: any[]
): boolean {
if (obj instanceof type) {
return true;
}
return false;
}
/**
* Determines if the given string is a member of the given enum
*
* @static
* @param {unknown} object
* @param {string} enumName
* @return {*} {boolean}
* @memberof ReflectionHelper
*/
static isEnumElement(object: unknown, enumName: any): boolean {
let isEnum = false;
if (
typeof object === 'number' &&
typeof enumName === 'object' &&
typeof enumName[object] === 'string'
) {
isEnum = true;
}
return isEnum;
}
/**
* Gets the RuntimeTypeInfo object for the given class
*
* @static
* @param {*} type
* @return {*} {RuntimeTypeInfo}
* @memberof ReflectionHelper
*/
static getTypeInfo(type: any): RuntimeTypeInfo {
if (type instanceof Function && !type.prototype) {
return ReflectionHelper.createOrAddRuntimeType(Function);
} else if (
type instanceof Function ||
type instanceof ReifiedInterfaceInfo ||
type instanceof ReifiedEnumInfo ||
type instanceof ReifiedBuiltinTypeInfo
) {
return ReflectionHelper.createOrAddRuntimeType(type);
} else if (type?.constructor) {
return ReflectionHelper.createOrAddRuntimeType(type.constructor);
} else if (typeof type === 'string') {
return ReflectionHelper.createOrAddRuntimeType(String);
} else {
throw new Error(
'Cannot create runtime type information for the given type'
);
}
}
/**
* Gets the runtime enum information for the given enum definition
*
* @static
* @param {string} enumName
* @return {*} {Function}
* @memberof ReflectionHelper
*/
static getEnumInfo(enumName: string, enumObject: unknown): ClassType {
if (this.enumMapInfo.has(enumName)) {
return this.enumMapInfo.get(enumName);
} else {
let enumInfoObject = new ReifiedEnumInfo(enumName, enumObject);
this.enumMapInfo.set(enumName, enumInfoObject);
return enumInfoObject;
}
}
/**
* Creates or returns the RuntimeTypeInfo object for the given type
*
* @private
* @static
* @param {*} type
* @return {*} {RuntimeTypeInfo}
* @memberof ReflectionHelper
*/
private static createOrAddRuntimeType(type: any): RuntimeTypeInfo {
const typeName = type.simpleName ?? type.name;
let existing = this.runtimeTypes.get(typeName);
let result: RuntimeTypeInfo = null;
if (!existing) {
existing = [];
this.runtimeTypes.set(typeName, existing);
} else {
for (let i = 0; i < existing.length; i++) {
if (existing[i][0] === type) {
result = existing[i][1];
break;
}
}
}
if (!result) {
result = new RuntimeTypeInfoImpl(type);
existing.push([type, result]);
}
return result;
}
}
/**
* Class for object that represent runtime type information
*
* @export
* @abstract
* @class RuntimeTypeInfo
* @wType System.Type
* @wNetSupport
*/
export abstract class RuntimeTypeInfo {
/**
* Creates an instance of RuntimeTypeInfo.
*
* @param {Function} type JavaScript class
* @memberof RuntimeTypeInfo
*/
constructor(private type: ClassType) {}
/**
* Gets the type information for the type specified in the `className` parameter.
*
* @static
* @param {string} className
* @param {boolean} [throwOnError]
* @param {boolean} [caseInsensitive]
* @return {*} {RuntimeTypeInfo}
* @memberof RuntimeTypeInfo
*/
public static GetType(
classNameId: string,
throwOnError?: boolean,
caseInsensitive?: boolean
): RuntimeTypeInfo {
let assemblyName = '';
let className = '';
const parts = classNameId.split(',');
if (parts.length > 1) {
assemblyName = parts[1];
}
className = parts[0];
const classType = TypeResolver.getClassType(className);
if (classType) {
return ReflectionHelper.getTypeInfo(classType);
} else if (throwOnError) {
throw new Error('Type not found: ' + classNameId);
}
}
/**
* Gets the base type of a type
*
* @readonly
* @type {RuntimeTypeInfo}
* @memberof RuntimeTypeInfo
*/
public get BaseType(): RuntimeTypeInfo {
const base = Object.getPrototypeOf(this.type);
if (base != null && base.name !== '') {
return ReflectionHelper.getTypeInfo(base);
} else {
return null;
}
}
/**
* Gets the name of the class with the compatibility assembly information
*
* @readonly
* @type {string}
* @memberof RuntimeTypeInfo
*/
public get AssemblyQualifiedName(): string {
return `${this.FullName},${this.CompatibilityAssemblyInfo?.FullName}`;
}
/**
* Gets the simple name of the class
*
* @readonly
* @memberof RuntimeTypeInfo
*/
public get Name() {
return this.type.name;
}
/**
* Gets the qualified (obtained with decoratore) name of the given class
*
* @readonly
* @memberof RuntimeTypeInfo
*/
public get FullName() {
return this.calculateFullName();
}
/**
* Gets the JavaScript class for the given type
*
* @readonly
* @type {*}
* @memberof RuntimeTypeInfo
* @wIgnore
*/
public get JSType(): any {
return this.type;
}
/**
* Gets the compatibilty assembly information for the current class
*
* @readonly
* @type {CompatibilityAssemblyInformation}
* @memberof RuntimeTypeInfo
* @wProperty Assembly
*/
public get CompatibilityAssemblyInfo(): CompatibilityAssemblyInformation {
const fullClassName = this.calculateFullName();
const classInfo = TypeResolver.getClassInfo(fullClassName);
if (classInfo) {
return ReflectionHelper.getCompatibilityAssemblyInfo(classInfo.assembly);
} else {
throw new Error(
`Compatibility assembly information was not found for: ${
fullClassName ?? '<name not available>'
}`
);
}
}
/**
* Gets the property information for the current class
*
* @param {boolean} [inherited]
* @return {*} {PropertyInfo[]}
* @memberof RuntimeTypeInfo
* @wMethod GetProperties
*/
public getProperties(inherited?: boolean): PropertyInfo[] {
if (this.type instanceof Function) {
let properties: PropertyInfo[] = this.processTypeProperties(
this.type.prototype
);
const prototypeOf = Object.getPrototypeOf(this.type.prototype);
if (prototypeOf) {
properties = this.getBaseProperties(properties, prototypeOf);
}
return properties;
}
return [];
}
/**
* Gets the runtime information for the given property
*
* @param {string} propName
* @param {*} [idx]
* @return {*} {PropertyInfo}
* @memberof RuntimeTypeInfo
* @wMethod GetProperty
*/
public getProperty(propName: string, idx?: any): PropertyInfo {
if (this.type instanceof Function) {
const descriptor = Object.getOwnPropertyDescriptor(
this.type.prototype,
propName
);
if (descriptor) {
return new PropertyInfo(
propName,
this,
Object.getOwnPropertyDescriptor(this.type.prototype, propName)
);
} else {
return this.getPropertyInfoFromBase(this.type.prototype, propName);
}
}
}
/**
* Verifies if an instanceof of type `t` can be assigned to a variable
* of the type of the current instance.
*
* NOTE: this won't work for interface inheritance.
*
* @param {RuntimeTypeInfo} t
* @return {*} {boolean}
* @memberof RuntimeTypeInfo
*/
public IsAssignableFrom(t: RuntimeTypeInfo): boolean {
const thisType = this.type as any;
const tType = t.type as any;
// `t` and `this` are the same type, incidentally also checks if
// `t` is value type, and `this` is `Nullable<t>`
return (
thisType === tType ||
// `t` inherits from `this`
(thisType instanceof Function && tType.prototype instanceof thisType) ||
// `this` is an interface and `t` implements `this`
this.isImplementedBy(t)
);
// Currently we don't have the infrastructure to check this:
// `t` is generic parameter, and `this` is a constrain of `t`
}
/**
* Gets the runtime information for the given property in the parent herarchy
*
* @param {any} prototype
* @param {string} propName
* @return {*} {PropertyInfo[]}
* @memberof RuntimeTypeInfo
*/
private getPropertyInfoFromBase(
typeProto: any,
propName: string
): PropertyInfo {
if (typeProto) {
const protoType = Object.getPrototypeOf(typeProto);
if (protoType) {
const descriptor = Object.getOwnPropertyDescriptor(protoType, propName);
if (descriptor) {
return new PropertyInfo(propName, this, descriptor);
} else {
return this.getPropertyInfoFromBase(protoType, propName);
}
}
}
return null;
}
/**
* Gets information about generic arguments
*
* NOT IMPLEMENTED
*
* @return {*} {any[]}
* @memberof RuntimeTypeInfo
* @wNoMap
*/
public GetGenericArguments(): any[] {
Debugger.Throw('Not implemented');
return null;
}
/**
* Gets information for the given field
*
* NOT IMPLEMENTED
*
* @param {string} fieldName
* @return {*} {FieldInfo}
* @memberof RuntimeTypeInfo
* @wNoMap
*/
public GetField(fieldName: string): FieldInfo {
Debugger.Throw('Not implemented');
return null;
}
/**
* Gets all fields for the current type
*
* NOT IMPLEMENTED
*
* @param {*} [flags]
* @return {*} {FieldInfo[]}
* @memberof RuntimeTypeInfo
*/
public GetFields(flags?: any): FieldInfo[] {
if (this.innerType instanceof Function) {
Debugger.Throw('Not implemented');
} else if (this.innerType.GetInnerFields) {
const fields = this.innerType.GetInnerFields();
for (const field of fields) {
field.DeclaringType = this;
}
return fields;
}
return null;
}
/**
* Verifies if the current type is an enum
*
* @readonly
* @type {boolean}
* @memberof RuntimeTypeInfo
*/
public get IsEnum(): boolean {
return this.type instanceof ReifiedEnumInfo;
}
/**
* Invokes a method of the current type
*
* @param {...any[]} args
* @return {*} {*}
* @memberof RuntimeTypeInfo
* @wNoMap
*/
public InvokeMember(...args: any[]): any {
Debugger.Throw('Not implemented');
}
/**
* Gets the instance of the JavaScript class
*
* @readonly
* @memberof RuntimeTypeInfo
*/
public get innerType() {
return this.type;
}
/**
* Verifies is `obj` is an instance of the type represented by this instance
*
* @param {unknown} obj
* @return {*} {boolean}
* @memberof RuntimeTypeInfo
*/
public IsInstanceOfType(obj: unknown): boolean {
return this.type instanceof Function && obj instanceof this.type;
}
/**
* Custom comparison criteria for runtime type information
*
* @param {*} obj
* @return {*} {boolean}
* @memberof RuntimeTypeInfo
*/
public Equals(obj): boolean {
return this === obj;
}
/**
* Get the hash code for runtime type information.
*
* @return {*} {number}
* @memberof RuntimeTypeInfo
*/
public GetHashCode(): number {
return HashUtils.stringToHash(this.FullName);
}
/**
* Gets metadata attributes for the current type
*
* @param {boolean} [inherited]
* @return {*} {MetadataAttribute[]}
* @memberof RuntimeTypeInfo
* @wMethod GetMetadataAttributes
*/
public getMetadataAttributes(inherited?: boolean): MetadataAttribute[];
public getMetadataAttributes(
attType: RuntimeTypeInfo,
inherited?: boolean
): MetadataAttribute[];
public getMetadataAttributes(
arg1: unknown,
arg2?: unknown
): MetadataAttribute[] {
if (this.JSType instanceof Function) {
const keys = Reflect.getMetadataKeys(this.JSType);
const metadataValues = keys.map((key) =>
Reflect.getMetadata(key, this.JSType)
);
const flattendValues = iuToArray(
iuSelectMany(
(md) =>
isArrayOfMetadataElements(md)
? (md as Array<MetadataAttribute>)
: [],
metadataValues
)
);
return metadataValues
.filter((obj) => obj instanceof MetadataAttribute)
.concat(flattendValues);
} else {
return [];
}
}
/**
* Gets the property information for the current class base class
*
* @param {boolean} [inherited]
* @return {*} {PropertyInfo[]}
* @memberof RuntimeTypeInfo
*/
private getBaseProperties(
properties: PropertyInfo[],
basePrototype: any,
inherited?: boolean
): PropertyInfo[] {
if (this.type instanceof Function) {
// Getting base class properties
properties = properties.concat(this.processTypeProperties(basePrototype));
const prototypeOf = Object.getPrototypeOf(basePrototype);
if (prototypeOf) {
properties = this.getBaseProperties(properties, prototypeOf);
}
return properties;
}
}
private processTypeProperties(prototype: any): PropertyInfo[] {
const properties: PropertyInfo[] = [];
for (const propName of Object.getOwnPropertyNames(prototype)) {
if (propName === 'constructor' || propName === '__proto__') {
continue;
}
const descriptor = Object.getOwnPropertyDescriptor(prototype, propName);
if (descriptor.value !== undefined) {
continue;
}
properties.push(new PropertyInfo(propName, this, descriptor));
}
return properties;
}
private calculateFullName() {
return (
Reflect.getOwnMetadata(
fullClassNameCompatibilityMetadataKey,
this.type
) ?? this.type.name
);
}
/**
* Check if the current type is an interface and `type` implements it.
* NOTE: this won't work for interface inheritance.
*
* @private
* @param {RuntimeTypeInfo} type
* @return {*} {boolean}
* @memberof RuntimeTypeInfo
*/
private isImplementedBy(type: RuntimeTypeInfo): boolean {
if (this.type instanceof ReifiedInterfaceInfo) {
const interfaces = Reflect.getMetadata(
supportedInterfacesCompatibilityMetadataKey,
type.type
) as string[];
if (interfaces) {
return interfaces.indexOf(this.type.name) !== -1;
}
const info = TypeResolver.getClassInfo(type.FullName);
if (info?.implements) {
return info.implements.indexOf(this.type.name) !== -1;
}
}
return false;
}
}
/**
* This type is not exported (at the library level) to avoid allowing creating instances of RuntimeTypeInfo
*
* @class RuntimeTypeInfoImpl
* @extends {RuntimeTypeInfo}
*/
export class RuntimeTypeInfoImpl extends RuntimeTypeInfo {}
/**
* Base class for runtime member information
*
* @export
* @class MemberInfo
* @wType System.Reflection.MemberInfo
* @wNetSupport
*/
export class MemberInfo {
public Name: string;
/**
* Stub property
*
* @type {*}
* @memberof MemberInfo
* @wNoMap
*/
public DeclaringType: any = null;
/**
* Get metadata attributes
*
* @param {boolean} [inherited]
* @returns {MetadataAttribute[]}
* @memberof MemberInfo
* @wMethod GetCustomAttributes
*/
public getMetadataAttributes(inherited?: boolean): MetadataAttribute[];
public getMetadataAttributes(
attType: RuntimeTypeInfo,
inherited?: boolean
): MetadataAttribute[];
public getMetadataAttributes(
arg1: unknown,
arg2?: unknown
): MetadataAttribute[] {
return [];
}
}
/**
* Runtime information for fields
*
* @export
* @class FieldInfo
* @extends {MemberInfo}
* @wType System.Reflection.FieldInfo
* @wNetSupport
*/
export class FieldInfo extends MemberInfo {
/**
* Field to determine if the current property is literal
*
* @type {boolean}
* @memberof FieldInfo
*/
public IsLiteral: boolean;
/**
* Creates an instance of FieldInfo.
* @param {string} name
* @memberof FieldInfo
*/
public constructor(name: string) {
super();
this.Name = name;
}
/**
* Gets the value of the current field
*
* @param {*} obj
* @return {*}
* @memberof FieldInfo
*/
public GetValue(obj: any): any {
if (obj instanceof RuntimeTypeInfo) {
if (obj.innerType instanceof ReifiedEnumInfo) {
obj = obj.innerType.innerEnumDefinition;
} else {
obj = obj.innerType;
}
} else if (
obj === null &&
this.DeclaringType?.innerType instanceof ReifiedEnumInfo
) {
obj = this.DeclaringType.innerType.innerEnumDefinition;
}
return obj[this.Name];
}
}
/**
* Runtime information for properties
*
* @export
* @class PropertyInfo
* @extends {MemberInfo}
* @wType System.Reflection.PropertyInfo
* @wNetSupport
*/
export class PropertyInfo extends MemberInfo {
constructor(
name: string,
private parentType: RuntimeTypeInfo,
private descriptor: PropertyDescriptor
) {
super();
this.Name = name;
}
/**
* Gets metadata attributes for the current property
*
* @param {boolean} [inherited]
* @return {*} {MetadataAttribute[]}
* @memberof PropertyInfo
* @wMethod GetMetadataAttributes
*/
public getMetadataAttributes(inherited?: boolean): MetadataAttribute[];
public getMetadataAttributes(
attType: RuntimeTypeInfo,
inherited?: boolean
): MetadataAttribute[];
public getMetadataAttributes(
arg1: unknown,
arg2?: unknown
): MetadataAttribute[] {
if (this.parentType.innerType instanceof Function) {
const proto = this.parentType.innerType.prototype;
const keys = Reflect.getMetadataKeys(proto, this.Name);
return keys
.map((key) => Reflect.getMetadata(key, proto, this.Name))
.filter((obj) => obj instanceof MetadataAttribute);
} else {
return [];
}
}
/**
* Verifies if this property can be assigned
*
* @readonly
* @memberof PropertyInfo
* @wProperty CanWrite
*/
get canWrite() {
return typeof this.descriptor.set !== 'undefined';
}
/**
* Verifies if the current property can be accessed
*
* @readonly
* @memberof PropertyInfo
* @wProperty CanRead
*/
get canRead() {
return typeof this.descriptor.get !== 'undefined';
}
/**
* Get the runtime type information for the current property (if available)
*
* @readonly
* @memberof PropertyInfo
* @wProperty PropertyType
*/
get propertyType() {
if (this.parentType.innerType instanceof Function) {
// First check for interface info if available
let interfaceInfo = this.getInterfaceInfoIfAvailable(
this.parentType.innerType.prototype
);
if (typeof interfaceInfo === 'string') {
return ReflectionHelper.getTypeInfo(
ReflectionHelper.getInterfaceRuntimeTypeInfo(interfaceInfo)
);
} else {
const extendedTypeInfo = Reflect.getMetadata(
'original:extendedPropertyTypeInfo',
this.parentType.innerType.prototype,
this.Name
) as ExtendedPropertyTypeInfo;
/* istanbul ignore else */
if (extendedTypeInfo && extendedTypeInfo.forcedRuntimeTypeInfo) {
return extendedTypeInfo.forcedRuntimeTypeInfo;
}
const builtinTypeInfo = Reflect.getMetadata(
'original:builtinType',
this.parentType.innerType.prototype,
this.Name
) as string;
if (typeof builtinTypeInfo === 'string') {
return ReflectionHelper.getTypeInfo(
ReflectionHelper.getBuiltinRuntimeTypeInfo(builtinTypeInfo)
);
}
const designType = Reflect.getMetadata(
'design:type',
this.parentType.innerType.prototype,
this.Name
);
/* istanbul ignore else */
if (designType) {
return ReflectionHelper.getTypeInfo(designType);
}
}
}
return null;
}
/**
* Gets the collection generic type of current property.
*
* @readonly
* @memberof PropertyInfo
*/
get collectionGenericType() {
/* istanbul ignore else */
if (this.parentType.innerType instanceof Function) {
return Reflect.getMetadata(
'collectionGenericType',
this.parentType.innerType.prototype,
this.Name
);
}
return null;
}
/**
* Gets the value of the property for the given instance
*
* @param {*} instance
* @param {*} [indexers]
* @return {*}
* @memberof PropertyInfo
* @wMethod GetValue
*/
public getValue(instance: any, indexers?: any) {
return instance[this.Name];
}
/**
* Sets the value of the property for the given instance
*
* @param {*} instance
* @param {*} value
* @param {*} [indexers]
* @memberof PropertyInfo
* @wMethod SetValue
*/
public setValue(instance: any, value: any, indexers?: any) {
instance[this.Name] = value;
}
private getInterfaceInfoIfAvailable(prototypeFromJsType: any) {
let interfaceInfo = Reflect.getMetadata(
'original:interfaceType',
prototypeFromJsType,
this.Name
);
if (typeof interfaceInfo === 'undefined') {
const extendedTypeInfo = Reflect.getMetadata(
'original:extendedPropertyTypeInfo',
prototypeFromJsType,
this.Name
) as ExtendedPropertyTypeInfo;
if (extendedTypeInfo && extendedTypeInfo.interfaceTypeOriginalFullName) {
interfaceInfo = extendedTypeInfo.interfaceTypeOriginalFullName;
}
}
return interfaceInfo;
}
}
/**
* Compatibility assembly information for a runtime type
*
* @export
* @class CompatibilityAssemblyInformation
* @wType System.Reflection.Assembly
* @wNetSupport
*/
export class CompatibilityAssemblyInformation {
constructor(private name: string) {}
/**
* Full name of this compatibilty object
*
* @readonly
* @memberof CompatibilityAssemblyInformation
*/
public get FullName() {
return this.name;
}
/**
* Get an array of objects that represent runtime types
* tagged as members of an assembly.
*
* @return {*} {Array<RuntimeTypeInfo>}
* @memberof CompatibilityAssemblyInformation
*/
public GetTypes(): Array<RuntimeTypeInfo> {
let result: Array<RuntimeTypeInfo> = [];
for (const type of TypeResolver.getTypesRegisteredForAssembly(this.name)) {
result.push(ReflectionHelper.getTypeInfo(type));
}
return result;
}
/**
* Gets type information for the given type name
*
* @param {string} typeName
* @param {boolean} [exceptionIfNotFound]
* @return {*} {RuntimeTypeInfo}
* @memberof CompatibilityAssemblyInformation
*/
public GetType(
typeName: string,
exceptionIfNotFound?: boolean
): RuntimeTypeInfo {
let result: RuntimeTypeInfo = null;
for (const type of TypeResolver.getTypesRegisteredForAssembly(this.name)) {
const typeInfo = ReflectionHelper.getTypeInfo(type);
/* istanbul ignore else */
if (typeInfo.FullName === typeName) {
result = typeInfo;
}
}
/* istanbul ignore else */
if (exceptionIfNotFound) {
throw new Error('Type not found: ' + typeName);
}
return result;
}
/**
* Gets compatibility assembly attributes
*
* @param {RuntimeTypeInfo} [attributeType]
* @param {boolean} [inherit]
* @memberof CompatibilityAssemblyInformation
* @wNoMap
*/
public GetCustomAttributes(
attributeType?: RuntimeTypeInfo,
inherit?: boolean
): any[] {
Debugger.Throw('Compatibility assembly info attributes not supported');
return null;
}
}
/**
*
*/
export type ExtendedPropertyTypeInfo = {
interfaceTypeOriginalFullName?: string;
genericParameterTypeIds?: Array<
string | StringConstructor | NumberConstructor
>;
forcedRuntimeTypeInfo?: RuntimeTypeInfo;
};
/**
* Property type information aditional options
*/
export type PropertyTypeInfoOptions = {
/**
* Indicates if the type information is for a built-in type
*
* @type {boolean}
*/
isBuiltin?: boolean;
/**
* Generic Type associated to the given property info
*
* @type {*}
*/
genericType?: any;
};
function isArrayOfMetadataElements(md: any): boolean {
return (
md instanceof Array &&
iuAll(md, (nestedEntry) => nestedEntry instanceof MetadataAttribute)
);
}
/**
* Decorator for properties to force generating `design:` metadata
*
* @export
* @param {string} [interfaceOrTypeOriginalFullName]
* @param {any} [options] indicates the options associated to the property info
* @return {*}
*/
export function propertyInfo(
interfaceOrTypeOriginalFullName?: string | ExtendedPropertyTypeInfo,
options?: PropertyTypeInfoOptions
) {
if (
typeof interfaceOrTypeOriginalFullName !== 'undefined' &&
interfaceOrTypeOriginalFullName !== null
) {
if (
typeof interfaceOrTypeOriginalFullName === 'string' &&
options?.isBuiltin
) {
return Reflect.metadata(
'original:builtinType',
interfaceOrTypeOriginalFullName
);
} else if (typeof interfaceOrTypeOriginalFullName === 'string') {
return Reflect.metadata(
'original:interfaceType',
interfaceOrTypeOriginalFullName
);
} else {
return Reflect.metadata(
'original:extendedPropertyTypeInfo',
interfaceOrTypeOriginalFullName
);
}
} else if (typeof options?.genericType !== 'undefined') {
return Reflect.metadata('collectionGenericType', options.genericType);
} else {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {};
}
}
/**
* Decorator function to define metadata attributes
*
* @export
* @param {MetadataAttribute} att
* @return {*}
*/
export function defineCustomAttributeMetadata(att: MetadataAttribute) {
return Reflect.metadata(att.constructor.name, att);
}
/**
* Decorator function to define metadata attributes for classes
*
* @export
* @param {MetadataAttribute} att
* @return {*}
*/
export function defineClassCustomAttributeMetadata(att: MetadataAttribute) {
return function (target: any): any {
const metadataKey = `${att.constructor.name}_Metadata`;
let metadataValue = Reflect.getMetadata(metadataKey, target);
if (!metadataValue) {
metadataValue = att;
} else if (metadataValue instanceof Array) {
metadataValue.push(att);
} else {
metadataValue = [att, metadataValue];
}
Reflect.metadata(metadataKey, metadataValue)(target);
att.OnAppliedTo(target);
return target;
};
}
/**
* HashCode helper
* NOT IMPLEMENTED
*
* @export
* @param {*} obj
* @return {*} {number}
*/
export function objectHashCodeHelper(obj: any): number {
Debugger.Throw('not implemented');
return 0;
}
/**
* Checks if two objects are equal using their `equals()` or `Equals()`
* implementation, or using `===` if an equality function is not available.
*
* @export
* @param {*} obj
* @param {*} [obj2]
* @return {*} {boolean}
*/
export function areEqual(obj: any, obj2?: any): boolean {
if (obj != null && typeof obj.equals === 'function') {
return obj.equals(obj2);
}
if (obj != null && typeof obj.Equals === 'function') {
return obj.Equals(obj2);
}
if (obj == null && obj2 == null) {
return true;
}
return obj === obj2;
}
/**
* Utility function to convert an object to a boolean value
*
* @export
* @param {unknown} obj
* @return {*} {boolean}
*/
export function convertToBoolean(obj: unknown): boolean {
if (typeof obj === 'boolean') {
return obj;
} else if (typeof obj === 'number') {
return !Number.isNaN(obj) && obj !== 0;
} else if (typeof obj === 'string' && obj.toLowerCase() === 'true') {
return true;
} else if (typeof obj === 'string' && obj.toLowerCase() === 'false') {
return false;
} else if (typeof obj === 'object' && obj === null) {
return false;
} else {
throw Error('Cannot convert value to boolean');
}
}
/**
* Function used to convert between types
*
* @export
* @template T
* @param {unknown} obj
* @param {*} reifiedType
* @return {*} {T}
*/
export function convertTypeTo<T>(obj: unknown, reifiedType: any): T {
if (obj == null) {
return null;
} else {
const result = tryToConvertType<T>(obj, reifiedType);
if (result === null) {
throw Error('Cannot convert type');
}
return result;
}
}
/**
* Conversion case for reified interface type (not to be exported)
* @param obj object to convert
* @param reifiedType reified interface type
* @returns conversion result
*/
const convertReifiedType = <T>(
obj: unknown,
reifiedType: ReifiedInterfaceInfo
) => {
const explicitContractForType = getAsExplicitContractForType(
reifiedType.interfaceName,
obj
);
if (explicitContractForType !== null && obj[explicitContractForType]) {
return obj[explicitContractForType]();
} else {
if (
ReflectionHelper.isInterfaceIsSupported(obj, reifiedType.interfaceName)
) {
return obj as T;
} else {
if (
obj instanceof Object &&
typeof obj[Symbol.iterator] === 'function' &&
(reifiedType.interfaceName === 'System.Collections.IEnumerable' ||
reifiedType.interfaceName === 'System.Collections.IEnumerable`1')
) {
return obj as T;
}
return null;
}
}
};
/**
* Function used to convert type
*
* @export
* @template T
* @param {unknown} obj
* @param {*} reifiedType
* @return {*} {T}
*/
export function tryToConvertType<T>(obj: unknown, reifiedType: any): T {
if (obj && obj['isExplicitInterfaceImplementationWrapper'] === true) {
obj = obj['instance'];
}
if (obj == null) {
return null;
} else if (reifiedType === Object) {
// always succeed when converting to `Object`
return obj as T;
} else if (reifiedType instanceof ReifiedInterfaceInfo) {
return convertReifiedType(obj, reifiedType);
} else if (
reifiedType instanceof ReifiedEnumInfo &&
(typeof obj === 'number' || typeof obj === 'boolean')
) {
return obj as any as T;
} else if (
obj instanceof reifiedType ||
(reifiedType === Array && Array.isArray(obj))
) {
return obj as T;
} else if (
obj !== null &&
typeof obj !== 'undefined' &&
obj.constructor &&
obj.constructor === reifiedType
) {
return obj as T;
} else {
return null;
}
}
export function getAsExplicitContractForType(
interfaceFullName: string,
type: any
): string {
const typeInfo = ReflectionHelper.getTypeInfo(type);
const explicitUses =
Reflect.getMetadata(
explicitInterfaceCompatibilityMetadataKey,
typeInfo.JSType
) || [];
const explicitUseTuple = explicitUses.filter(
(t) => t[0] === interfaceFullName
)[0];
if (explicitUseTuple !== undefined) {
return explicitUseTuple[1];
}
return null;
}