This commit is contained in:
crusader 2017-08-02 22:36:26 +09:00
parent d08a77a279
commit 4992e39e3f
20 changed files with 484 additions and 83 deletions

View File

@ -0,0 +1,6 @@
export enum DecoratorType {
CLASS,
PROPERTY,
PARAMETER,
METHOD,
}

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './reflect';

View File

@ -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';

View File

@ -1,10 +1,3 @@
export type Construtorable<T> = {new(...args: any[]): T};
export type ClassType = Object;
export type PropertyType = string | symbol;
export enum DecoratorType {
CLASS,
PROPERTY,
PARAMETER,
METHOD,
}

View File

@ -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<any>) => {
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;
}
}

View File

@ -0,0 +1,9 @@
export interface DecoratorHandler {
onClassDecorator?: <TFunction extends Function>(target: TFunction) => TFunction | void;
onPropertyDecorator?: (target: Object, propertyKey: string | symbol) => void;
onMethodDecorator?: <T>(target: Object, propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
onParameterDecorator?: (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
}
export default DecoratorHandler;

View File

@ -0,0 +1,8 @@
export enum DecoratorType {
CLASS = 'Class',
PROPERTY = 'Property',
METHOD = 'Method',
PARAMETER = 'Parameter',
}
export default DecoratorType;

View File

@ -0,0 +1,3 @@
export * from './Decorator';
export * from './DecoratorHandler';
export * from './DecoratorType';

View File

@ -0,0 +1,33 @@
import {
ClassType,
} from '@loafer/core/constants/types';
export abstract class AnnotatedElement {
private _annotationMap: Map<ClassType, any>;
protected constructor() {
this._annotationMap = new Map();
}
public getDeclaredAnnotation(type: ClassType): any {
return this._annotationMap.get(type);
}
public getDeclaredAnnotations(): IterableIterator<any> {
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<any>;
}
export default AnnotatedElement;

View File

@ -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<PropertyType, Property>;
private _methodes: Map<PropertyType, Method>;
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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';

View File

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

View File

@ -1,27 +0,0 @@
import {
ClassType,
} from '@loafer/core/constants';
export type MetadataDefinable<T> = {new(clazz: ClassType): T};
const getClassMetadata = <DefinitionType>(clazz: ClassType,
metadataKey: any,
definitionType: MetadataDefinable<DefinitionType>,
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;

View File

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