diff --git a/src/ts/@loafer/core/constants/decorator.ts b/src/ts/@loafer/core/constants/decorator.ts new file mode 100644 index 0000000..654a13c --- /dev/null +++ b/src/ts/@loafer/core/constants/decorator.ts @@ -0,0 +1,6 @@ +export enum DecoratorType { + CLASS, + PROPERTY, + PARAMETER, + METHOD, +} diff --git a/src/ts/@loafer/core/constants/index.ts b/src/ts/@loafer/core/constants/index.ts deleted file mode 100644 index 1cd2e39..0000000 --- a/src/ts/@loafer/core/constants/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './reflect'; diff --git a/src/ts/@loafer/core/constants/reflect.ts b/src/ts/@loafer/core/constants/reflect.ts index 7898d00..3bc624c 100644 --- a/src/ts/@loafer/core/constants/reflect.ts +++ b/src/ts/@loafer/core/constants/reflect.ts @@ -4,3 +4,6 @@ export const DESIGN_TYPE = 'design:type'; export const DESIGN_PARAMTYPES = 'design:paramtypes'; // used to access design time return type export const DESIGN_RETURNTYPE = 'design:returntype'; + +// used to access design time return type +export const REFLECT_META = 'loafer:reflectmeta'; diff --git a/src/ts/@loafer/core/constants/types.ts b/src/ts/@loafer/core/constants/types.ts index 6bc3e07..2f96eef 100644 --- a/src/ts/@loafer/core/constants/types.ts +++ b/src/ts/@loafer/core/constants/types.ts @@ -1,10 +1,3 @@ export type Construtorable = {new(...args: any[]): T}; export type ClassType = Object; export type PropertyType = string | symbol; - -export enum DecoratorType { - CLASS, - PROPERTY, - PARAMETER, - METHOD, -} diff --git a/src/ts/@loafer/core/decorator/Decorator.ts b/src/ts/@loafer/core/decorator/Decorator.ts new file mode 100644 index 0000000..20de349 --- /dev/null +++ b/src/ts/@loafer/core/decorator/Decorator.ts @@ -0,0 +1,90 @@ +import { + ClassType, + Construtorable, +} from '@loafer/core/constants/types'; +import * as ReflectConstants from '@loafer/core/constants/reflect'; +import Reflection from '@loafer/core/reflect/Reflection'; +import Class from '@loafer/core/reflect/Class'; +import DecoratorType from './DecoratorType'; + +export class Decorator { + public static create = (Annotation: Construtorable) => { + return (...handlerArgs: any[]) => { + let annotation = new Annotation(...handlerArgs); + + return (...decoratorArgs: any[]) => { + let decoratorType: DecoratorType = Decorator._detectDecoratorType(name, annotation, decoratorArgs); + let type = typeof decoratorArgs[0] === 'function' ? decoratorArgs[0].prototype : decoratorArgs[0]; + + let reflection: Reflection = Reflect.getMetadata(ReflectConstants.REFLECT_META, type); + if (undefined === reflection) { + reflection = new Reflection(type); + Reflect.defineMetadata(ReflectConstants.REFLECT_META, reflection, type); + } + + switch(decoratorType) { + case DecoratorType.CLASS: + reflection.addClassAnnotation(annotation); + return annotation.onClassDecorator.apply(annotation, decoratorArgs); + case DecoratorType.PROPERTY: + reflection.addPropertyAnnotation(annotation, decoratorArgs[1]); + return annotation.onPropertyDecorator.apply(annotation, decoratorArgs); + case DecoratorType.METHOD: + reflection.addMethodAnnotation(annotation, decoratorArgs[1]); + return annotation.onMethodDecorator.apply(annotation, decoratorArgs); + case DecoratorType.PARAMETER: + reflection.addParameterAnnotation(annotation, decoratorArgs[1], decoratorArgs[2]); + return annotation.onParameterDecorator.apply(annotation, decoratorArgs); + default: + } + }; + }; + } + + public static get = (targetType: ClassType) => { + let reflection: Reflection = Reflect.getMetadata(ReflectConstants.REFLECT_META, targetType); + if (undefined === reflection) { + return undefined; + } + return reflection.getClass(); + } + + private static _detectDecoratorType(name: string, annotation: any, args: any[]): DecoratorType { + let params = []; + for (let i = 0; i < args.length; i++) { + if (args[i]) { + params.push(args[i]); + } + } + const paramCount = params.length; + let decoratorType: DecoratorType; + + if (1 === paramCount) { + if ('onClassDecorator' in annotation) { + throw new Error(`Cannot apply @${name} decorator on Class.`); + } + decoratorType = DecoratorType.CLASS; + } else if (2 === paramCount) { + if ('onPropertyDecorator' in annotation) { + throw new Error(`Cannot apply @${name} decorator on Property.`); + } + decoratorType = DecoratorType.PROPERTY; + } else if (3 === paramCount) { + if(typeof args[2] === 'number') { + if ('onParameterDecorator' in annotation) { + throw new Error(`Cannot apply @${name} decorator on Parameter.`); + } + decoratorType = DecoratorType.PARAMETER; + } else { + if ('onMethodDecorator' in annotation) { + throw new Error(`Cannot apply @${name} decorator on Method.`); + } + decoratorType = DecoratorType.METHOD; + } + } else { + throw new Error(`@${name} decorator is not valid here!`); + } + + return decoratorType; + } +} diff --git a/src/ts/@loafer/core/decorator/DecoratorHandler.ts b/src/ts/@loafer/core/decorator/DecoratorHandler.ts new file mode 100644 index 0000000..c56f8f4 --- /dev/null +++ b/src/ts/@loafer/core/decorator/DecoratorHandler.ts @@ -0,0 +1,9 @@ +export interface DecoratorHandler { + onClassDecorator?: (target: TFunction) => TFunction | void; + onPropertyDecorator?: (target: Object, propertyKey: string | symbol) => void; + onMethodDecorator?: (target: Object, propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor) => TypedPropertyDescriptor | void; + onParameterDecorator?: (target: Object, propertyKey: string | symbol, parameterIndex: number) => void; +} + +export default DecoratorHandler; diff --git a/src/ts/@loafer/core/decorator/DecoratorType.ts b/src/ts/@loafer/core/decorator/DecoratorType.ts new file mode 100644 index 0000000..5b8e3a9 --- /dev/null +++ b/src/ts/@loafer/core/decorator/DecoratorType.ts @@ -0,0 +1,8 @@ +export enum DecoratorType { + CLASS = 'Class', + PROPERTY = 'Property', + METHOD = 'Method', + PARAMETER = 'Parameter', +} + +export default DecoratorType; diff --git a/src/ts/@loafer/core/decorator/index.ts b/src/ts/@loafer/core/decorator/index.ts new file mode 100644 index 0000000..86bd007 --- /dev/null +++ b/src/ts/@loafer/core/decorator/index.ts @@ -0,0 +1,3 @@ +export * from './Decorator'; +export * from './DecoratorHandler'; +export * from './DecoratorType'; diff --git a/src/ts/@loafer/core/reflect/AnnotatedElement.ts b/src/ts/@loafer/core/reflect/AnnotatedElement.ts new file mode 100644 index 0000000..43c4350 --- /dev/null +++ b/src/ts/@loafer/core/reflect/AnnotatedElement.ts @@ -0,0 +1,33 @@ +import { + ClassType, +} from '@loafer/core/constants/types'; + +export abstract class AnnotatedElement { + private _annotationMap: Map; + + protected constructor() { + this._annotationMap = new Map(); + } + + public getDeclaredAnnotation(type: ClassType): any { + return this._annotationMap.get(type); + } + public getDeclaredAnnotations(): IterableIterator { + return this._annotationMap.values(); + } + + public addAnnotation(annotation: any): void { + const type: ClassType = Object.getPrototypeOf(annotation); + this._annotationMap.set(type, annotation); + } + + public hasAnnotation(type: ClassType): boolean { + return this._annotationMap.has(type); + } + + // public abstract getAnnotation(type: ClassType): any; + // public abstract getAnnotations(): IterableIterator; + +} + +export default AnnotatedElement; diff --git a/src/ts/@loafer/core/reflect/Class.ts b/src/ts/@loafer/core/reflect/Class.ts new file mode 100644 index 0000000..416d562 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Class.ts @@ -0,0 +1,75 @@ +import { + ClassType, + PropertyType, +} from '@loafer/core/constants/types'; + +import AnnotatedElement from './AnnotatedElement'; +import Constructor from './Constructor'; +import Property from './Property'; +import Method from './Method'; + + +export class Class extends AnnotatedElement { + private _type: ClassType; + private _name: PropertyType; + private _constructor: Constructor; + private _properties: Map; + private _methodes: Map; + + public constructor(type: ClassType, parameterTypes: ClassType[]) { + super(); + this._type = type; + this._name = this._type.constructor.name; + this._constructor = new Constructor(parameterTypes); + this._properties = new Map(); + this._methodes = new Map(); + } + + public get Type(): ClassType { + return this._type; + } + + public get Name(): PropertyType { + return this._name; + } + + public get Constructor(): Constructor { + return this._constructor; + } + + public addProperty(propertyKey: PropertyType, type: ClassType): Property { + if (this._properties.has(propertyKey)) { + throw new Error(`Property[${propertyKey}:${type.constructor.name}] on Class[${this._name}] is exist already`); + } + let proerty = new Property(type, propertyKey); + this._properties.set(propertyKey, proerty); + return proerty; + } + + public getProperty(propertyKey: PropertyType): Property { + return this._properties.get(propertyKey); + } + + public hasProperty(propertyKey: PropertyType): boolean { + return this._properties.has(propertyKey); + } + + public addMethod(propertyKey: PropertyType, parameterTypes: ClassType[], returnType: ClassType): Method { + if (this._methodes.has(propertyKey)) { + throw new Error(`Method[${propertyKey}:${returnType.constructor.name}] on Class[${this._name}] is exist already`); + } + let method = new Method(propertyKey, parameterTypes, returnType); + this._methodes.set(propertyKey, method); + return method; + } + + public getMethod(propertyKey: PropertyType): Method { + return this._methodes.get(propertyKey); + } + + public hasMethod(propertyKey: PropertyType): boolean { + return this._methodes.has(propertyKey); + } +} + +export default Class; diff --git a/src/ts/@loafer/core/reflect/Constructor.ts b/src/ts/@loafer/core/reflect/Constructor.ts new file mode 100644 index 0000000..7dd6243 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Constructor.ts @@ -0,0 +1,15 @@ +import { + ClassType, +} from '@loafer/core/constants/types'; + +import Executable from '@loafer/core/reflect/Executable'; + + +export class Constructor extends Executable { + + public constructor(parameterTypes: ClassType[]) { + super(parameterTypes); + } +} + +export default Constructor; diff --git a/src/ts/@loafer/core/reflect/Executable.ts b/src/ts/@loafer/core/reflect/Executable.ts new file mode 100644 index 0000000..6ead932 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Executable.ts @@ -0,0 +1,30 @@ +import { + ClassType, +} from '@loafer/core/constants/types'; + +import AnnotatedElement from './AnnotatedElement'; +import Parameter from '@loafer/core/reflect/Parameter'; + +export abstract class Executable extends AnnotatedElement { + protected _parameters: Parameter[]; + + public constructor(parameterTypes: ClassType[]) { + super(); + + this._parameters = []; + parameterTypes.forEach((currentValue, index, array) => { + this._parameters.push(new Parameter(currentValue, index)); + }); + } + + public get Parameters(): Parameter[] { + return this._parameters; + } + + public getParameter(index: number): Parameter { + return this._parameters[index]; + } + +} + +export default Executable; diff --git a/src/ts/@loafer/core/reflect/Method.ts b/src/ts/@loafer/core/reflect/Method.ts new file mode 100644 index 0000000..ac0e404 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Method.ts @@ -0,0 +1,32 @@ +import { + ClassType, + PropertyType, +} from '@loafer/core/constants/types'; + +import Executable from '@loafer/core/reflect/Executable'; + + +export class Method extends Executable { + private _name: PropertyType; + private _returnType: ClassType; + + public constructor(name: PropertyType, parameterTypes: ClassType[], returnType: ClassType) { + super(parameterTypes); + this._name = name; + this._returnType = returnType; + } + + public get Name(): PropertyType { + return this._name; + } + public get ReturnType(): ClassType { + return this._returnType; + } + + public set ReturnType(returnType: ClassType) { + this._returnType = returnType; + } + +} + +export default Method; diff --git a/src/ts/@loafer/core/reflect/Parameter.ts b/src/ts/@loafer/core/reflect/Parameter.ts new file mode 100644 index 0000000..ec5ba4d --- /dev/null +++ b/src/ts/@loafer/core/reflect/Parameter.ts @@ -0,0 +1,29 @@ +import { + ClassType, +} from '@loafer/core/constants/types'; + +import AnnotatedElement from './AnnotatedElement'; +import Constructor from './Constructor'; + + +export class Parameter extends AnnotatedElement { + private _type: ClassType; + private _index: number; + + public constructor(type: ClassType, index: number) { + super(); + this._type = type; + this._index = index; + } + + public get Type(): ClassType { + return this._type; + } + + public get Index(): number { + return this._index; + } + +} + +export default Parameter; diff --git a/src/ts/@loafer/core/reflect/Property.ts b/src/ts/@loafer/core/reflect/Property.ts new file mode 100644 index 0000000..3e14e83 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Property.ts @@ -0,0 +1,29 @@ +import { + ClassType, + PropertyType, +} from '@loafer/core/constants/types'; + +import AnnotatedElement from './AnnotatedElement'; +import Constructor from './Constructor'; + + +export class Property extends AnnotatedElement { + private _type: ClassType; + private _name: PropertyType; + + public constructor(type: ClassType, name: PropertyType) { + super(); + this._type = type; + this._name = name; + } + + public get Name(): PropertyType { + return this._name; + } + + public get Type(): ClassType { + return this._type; + } +} + +export default Property; diff --git a/src/ts/@loafer/core/reflect/Reflection.ts b/src/ts/@loafer/core/reflect/Reflection.ts new file mode 100644 index 0000000..bf01679 --- /dev/null +++ b/src/ts/@loafer/core/reflect/Reflection.ts @@ -0,0 +1,106 @@ +import { + ClassType, + PropertyType, +} from '@loafer/core/constants/types'; + +import * as ReflectConstants from '@loafer/core/constants/reflect'; + +import AnnotatedElement from './AnnotatedElement'; +import Class from './Class'; + +export class Reflection { + private _clazz: Class; + + public constructor(type: ClassType) { + let parameterTypes: any[] = Reflect.getMetadata(ReflectConstants.DESIGN_PARAMTYPES, type.constructor); + + this._clazz = new Class(type, this.convertParamTypes(parameterTypes)); + } + + /** + * getClass + */ + public getClass(): Class { + return this._clazz; + } + + public addClassAnnotation(annotation: any): void { + let parameterTypes: any[] = Reflect.getMetadata(ReflectConstants.DESIGN_PARAMTYPES, this._clazz.Type); + + const {valid, name} = this.validateAnnotation(annotation, this._clazz); + if (!valid) { + throw new Error(`Cannot apply @${name} decorator multiple times on Class[${this._clazz.Name}].`); + } + + this._clazz.addAnnotation(annotation); + } + public addPropertyAnnotation(annotation: any, propertyKey: PropertyType): void { + let propertyType: any = Reflect.getMetadata(ReflectConstants.DESIGN_TYPE, this._clazz.Type, propertyKey); + + let property = this._clazz.getProperty(propertyKey); + if (undefined === property) { + property = this._clazz.addProperty(propertyKey, propertyType.prototype); + } + + const {valid, name} = this.validateAnnotation(annotation, property); + if (!valid) { + throw new Error(`Cannot apply @${name} decorator multiple times on Property[${this._clazz.Name}:${propertyKey}].`); + } + + property.addAnnotation(annotation); + } + public addMethodAnnotation(annotation: any, propertyKey: PropertyType): void { + let parameterTypes: any[] = Reflect.getMetadata(ReflectConstants.DESIGN_PARAMTYPES, this._clazz.Type, propertyKey); + let returnType: any = Reflect.getMetadata(ReflectConstants.DESIGN_RETURNTYPE, this._clazz.Type, propertyKey); + + let method = this._clazz.getMethod(propertyKey); + if (undefined === method) { + method = this._clazz.addMethod(propertyKey, this.convertParamTypes(parameterTypes), returnType.prototype); + } + + const {valid, name} = this.validateAnnotation(annotation, method); + if (!valid) { + throw new Error(`Cannot apply @${name} decorator multiple times on Method[${this._clazz.Name}:${propertyKey}].`); + } + + method.addAnnotation(annotation); + } + public addParameterAnnotation(annotation: any, propertyKey: PropertyType, parameterIndex: number): void { + let parameterTypes: any[] = Reflect.getMetadata(ReflectConstants.DESIGN_PARAMTYPES, this._clazz.Type, propertyKey); + let returnType: any = Reflect.getMetadata(ReflectConstants.DESIGN_RETURNTYPE, this._clazz.Type, propertyKey); + + let method = this._clazz.getMethod(propertyKey); + if (undefined === method) { + method = this._clazz.addMethod(propertyKey, this.convertParamTypes(parameterTypes), returnType.prototype); + } + + let parameter = method.getParameter(parameterIndex); + + const {valid, name} = this.validateAnnotation(annotation, parameter); + if (!valid) { +throw new Error(`Cannot apply @${name} decorator multiple times on Parameter[${this._clazz.Name}:${propertyKey}(${parameterIndex}})].`); + } + + parameter.addAnnotation(annotation); + } + + private validateAnnotation(annotation: any, element: AnnotatedElement): {valid: boolean, name: string} { + const annotationType: ClassType = Object.getPrototypeOf(annotation); + const name: string = annotationType.constructor.name; + + if (element.hasAnnotation(annotationType)) { + return {valid: false, name: name}; + } + return {valid: true, name: name}; + } + + private convertParamTypes(parameterTypes: any[]): ClassType[] { + let returnTypes: ClassType[] = []; + parameterTypes.forEach((currentValue, index, array) => { + returnTypes.push(currentValue.prototype); + }); + return returnTypes; + } +} + +export default Reflection; diff --git a/src/ts/@loafer/core/reflect/index.ts b/src/ts/@loafer/core/reflect/index.ts new file mode 100644 index 0000000..922687e --- /dev/null +++ b/src/ts/@loafer/core/reflect/index.ts @@ -0,0 +1,8 @@ +export * from './AnnotatedElement'; +export * from './Class'; +export * from './Constructor'; +export * from './Executable'; +export * from './Method'; +export * from './Parameter'; +export * from './Property'; +export * from './Reflection'; diff --git a/src/ts/@loafer/core/util/decorators/createDecorator.ts b/src/ts/@loafer/core/util/decorators/createDecorator.ts deleted file mode 100644 index 3d9c098..0000000 --- a/src/ts/@loafer/core/util/decorators/createDecorator.ts +++ /dev/null @@ -1,46 +0,0 @@ -export interface DecoratorFactory { - classDecorator?: ClassDecorator; - propertyDecorator?: PropertyDecorator; - parameterDecorator?: ParameterDecorator; - methodDecorator?: MethodDecorator; -} - -const createDecorator = (name: string, decoratorFactory: DecoratorFactory) => { - return (...args: any[]) => { - let params = []; - for (let i = 0; i < args.length; i++) { - if (args[i]) { - params.push(args[i]); - } - } - - switch(params.length) { - case 1: - if (undefined !== decoratorFactory.classDecorator) { - return decoratorFactory.classDecorator.apply(this, args); - } - throw new Error(`Cannot apply @${name} decorator on Class.`); - case 2: - case 1: - if (undefined !== decoratorFactory.propertyDecorator) { - return decoratorFactory.propertyDecorator.apply(this, args); - } - throw new Error(`Cannot apply @${name} decorator on Property.`); - case 3: - if(typeof args[2] === 'number') { - if (undefined !== decoratorFactory.parameterDecorator) { - return decoratorFactory.parameterDecorator.apply(this, args); - } - throw new Error(`Cannot apply @${name} decorator on Parameter.`); - } - if (undefined !== decoratorFactory.methodDecorator) { - return decoratorFactory.methodDecorator.apply(this, args); - } - throw new Error(`Cannot apply @${name} decorator on Method.`); - default: - throw new Error(`@${name} decorators are not valid here!`); - } - }; -}; - -export default createDecorator; diff --git a/src/ts/@loafer/core/util/metadata/getClassMetadata.ts b/src/ts/@loafer/core/util/metadata/getClassMetadata.ts deleted file mode 100644 index 765709d..0000000 --- a/src/ts/@loafer/core/util/metadata/getClassMetadata.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - ClassType, -} from '@loafer/core/constants'; - -export type MetadataDefinable = {new(clazz: ClassType): T}; - -const getClassMetadata = (clazz: ClassType, - metadataKey: any, - definitionType: MetadataDefinable, - isCreate: boolean = false): DefinitionType => { - - let definition: DefinitionType; - if (Reflect.hasOwnMetadata(metadataKey, clazz) !== true) { - if (!isCreate) { - return undefined; - } - definition = Object.create(definitionType.prototype); - definition.constructor.call(definition, clazz); - - Reflect.defineMetadata(metadataKey, definition, clazz); - } else { - definition = Reflect.getMetadata(metadataKey, clazz); - } - return definition; -}; - -export default getClassMetadata; diff --git a/src/ts/@loafer/pouches/decorator/Inject.ts b/src/ts/@loafer/pouches/decorator/Inject.ts index 00b1204..b73aeb4 100644 --- a/src/ts/@loafer/pouches/decorator/Inject.ts +++ b/src/ts/@loafer/pouches/decorator/Inject.ts @@ -2,7 +2,11 @@ import InjectConfig from '@loafer/pouches/decorator/InjectConfig'; import InjectDefinition from '@loafer/pouches/definition/InjectDefinition'; import createDecorator from '@loafer/core/util/decorators/createDecorator'; import { getInjectDefinition } from '@loafer/pouches/util/metadata'; - +import { + ClassType, + DESIGN_TYPE, + PropertyType, +} from '@loafer/core/constants'; const Inject = (injectConfig: InjectConfig = {}) => createDecorator('Inject', { propertyDecorator: (target: Object, propertyKey: string | symbol): void => { let injectDefinition: InjectDefinition = getInjectDefinition(target, InjectDefinition, true); @@ -16,3 +20,6 @@ const Inject = (injectConfig: InjectConfig = {}) => createDecorator('Inject', { }); export default Inject; + + +