File

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

Index

Properties

Properties

GetInnerFields
GetInnerFields: function
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;
}

result-matching ""

    No results matching ""