File

projects/wms-framework/src/lib/baseframework/ReflectionSupport.ts

Description

Class for object that represent runtime type information

Index

Methods
Accessors

Constructor

constructor(type: ClassType)

Creates an instance of RuntimeTypeInfo.

Parameters :
Name Type Optional Description
type ClassType No

JavaScript class

Methods

Private calculateFullName
calculateFullName()
Returns : any
Public Equals
Equals(obj)

Custom comparison criteria for runtime type information

Parameters :
Name Optional
obj No
Returns : boolean

{boolean}

Private getBaseProperties
getBaseProperties(properties: PropertyInfo[], basePrototype: any, inherited?: boolean)

Gets the property information for the current class base class

Parameters :
Name Type Optional
properties PropertyInfo[] No
basePrototype any No
inherited boolean Yes
Returns : PropertyInfo[]

{PropertyInfo[]}

Public GetField
GetField(fieldName: string)

Gets information for the given field

NOT IMPLEMENTED

Parameters :
Name Type Optional
fieldName string No
Returns : FieldInfo

{FieldInfo}

Public GetFields
GetFields(flags?: any)

Gets all fields for the current type

NOT IMPLEMENTED

Parameters :
Name Type Optional
flags any Yes
Returns : FieldInfo[]

{FieldInfo[]}

Public GetGenericArguments
GetGenericArguments()

Gets information about generic arguments

NOT IMPLEMENTED

Returns : any[]

{any[]}

Public GetHashCode
GetHashCode()

Get the hash code for runtime type information.

Returns : number

{number}

Public getMetadataAttributes
getMetadataAttributes(inherited?: boolean)

Gets metadata attributes for the current type

Parameters :
Name Type Optional
inherited boolean Yes

{MetadataAttribute[]}

Public getMetadataAttributes
getMetadataAttributes(attType: RuntimeTypeInfo, inherited?: boolean)
Parameters :
Name Type Optional
attType RuntimeTypeInfo No
inherited boolean Yes
Public getMetadataAttributes
getMetadataAttributes(arg1: unknown, arg2?: unknown)
Parameters :
Name Type Optional
arg1 unknown No
arg2 unknown Yes
Public getProperties
getProperties(inherited?: boolean)

Gets the property information for the current class

Parameters :
Name Type Optional
inherited boolean Yes
Returns : PropertyInfo[]

{PropertyInfo[]}

Public getProperty
getProperty(propName: string, idx?: any)

Gets the runtime information for the given property

Parameters :
Name Type Optional
propName string No
idx any Yes
Returns : PropertyInfo

{PropertyInfo}

Private getPropertyInfoFromBase
getPropertyInfoFromBase(typeProto: any, propName: string)

Gets the runtime information for the given property in the parent herarchy

@return {*} {PropertyInfo[]}

Parameters :
Name Type Optional
typeProto any No
propName string No
Returns : PropertyInfo

{PropertyInfo[]}

Static GetType
GetType(classNameId: string, throwOnError?: boolean, caseInsensitive?: boolean)

Gets the type information for the type specified in the className parameter.

Parameters :
Name Type Optional
classNameId string No
throwOnError boolean Yes
caseInsensitive boolean Yes
Returns : RuntimeTypeInfo

{RuntimeTypeInfo}

Public InvokeMember
InvokeMember(...args: any[])

Invokes a method of the current type

Parameters :
Name Type Optional
args any[] No
Returns : any

{*}

Public IsAssignableFrom
IsAssignableFrom(t: RuntimeTypeInfo)

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.

Parameters :
Name Type Optional
t RuntimeTypeInfo No
Returns : boolean

{boolean}

Private isImplementedBy
isImplementedBy(type: RuntimeTypeInfo)

Check if the current type is an interface and type implements it. NOTE: this won't work for interface inheritance.

Parameters :
Name Type Optional
type RuntimeTypeInfo No
Returns : boolean

{boolean}

Public IsInstanceOfType
IsInstanceOfType(obj: unknown)

Verifies is obj is an instance of the type represented by this instance

Parameters :
Name Type Optional
obj unknown No
Returns : boolean

{boolean}

Private processTypeProperties
processTypeProperties(prototype: any)
Parameters :
Name Type Optional
prototype any No
Returns : PropertyInfo[]

Accessors

BaseType
getBaseType()

Gets the base type of a type

Returns : RuntimeTypeInfo
AssemblyQualifiedName
getAssemblyQualifiedName()

Gets the name of the class with the compatibility assembly information

Returns : string
Name
getName()

Gets the simple name of the class

FullName
getFullName()

Gets the qualified (obtained with decoratore) name of the given class

JSType
getJSType()

Gets the JavaScript class for the given type

Returns : any
CompatibilityAssemblyInfo
getCompatibilityAssemblyInfo()

Gets the compatibilty assembly information for the current class

IsEnum
getIsEnum()

Verifies if the current type is an enum

Returns : boolean
innerType
getinnerType()

Gets the instance of the JavaScript class

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;
}

result-matching ""

    No results matching ""