1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// class of Class in TS 17// ad defined in @angular/core 18interface Type<T> extends Function { 19 new(...args: any[]): T; 20} 21 22/** 23* @Observed Decorator function, use 24* @Observed class ClassA { ... } 25* when defining ClassA 26* 27* Can also be used to create a new Object and wrap it in 28* ObservedObject by calling 29* obsObj = Observed(ClassA)(params to ClassA constructor) 30* 31* Note this works only for classes, not for ClassA[] 32* Also does not work for classes with genetics it seems 33* In that case use factory function 34* obsObj = ObservedObject.createNew<ClassA[]>([]) 35*/ 36 37function Observed<C extends Object>(target: Type<C>): any { 38 var original = target; 39 // the new constructor behaviour 40 var f: any = function (...args: any[]) { 41 console.log(`New ${original.name}, gets wrapped inside ObservableObject proxy.`); 42 return new ObservedObject<C>(new original(...args), undefined); 43 }; 44 45 Object.setPrototypeOf(f, Object.getPrototypeOf(original)); 46 // return new constructor (will override original) 47 return f; 48} 49 50 51class SubscribableHandler { 52 static IS_OBSERVED_OBJECT = Symbol("_____is_observed_object__"); 53 static RAW_OBJECT = Symbol("_____raw_object__"); 54 static SUBSCRIBE = Symbol("_____subscribe__"); 55 static UNSUBSCRIBE = Symbol("_____unsubscribe__") 56 57 private owningProperties_: Set<number> 58 59 constructor(owningProperty: IPropertySubscriber) { 60 this.owningProperties_ = new Set<number>(); 61 if (owningProperty) { 62 this.addOwningProperty(owningProperty); 63 } 64 console.debug(`SubscribableHandler: construcstor done`); 65 } 66 67 addOwningProperty(subscriber: IPropertySubscriber): void { 68 console.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id__()}'.`) 69 this.owningProperties_.add(subscriber.id__()); 70 } 71 72 /* 73 the inverse function of createOneWaySync or createTwoWaySync 74 */ 75 public removeOwningProperty(property: IPropertySubscriber): void { 76 return this.removeOwningPropertyById(property.id__()); 77 } 78 79 public removeOwningPropertyById(subscriberId: number): void { 80 console.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`) 81 this.owningProperties_.delete(subscriberId); 82 } 83 84 85 protected notifyPropertyHasChanged(propName: string, newValue: any) { 86 console.debug(`SubscribableHandler: notifyPropertyHasChanged '${propName}'.`) 87 var registry: IPropertySubscriberLookup = SubscriberManager.Get(); 88 this.owningProperties_.forEach((subscribedId) => { 89 var owningProperty: IPropertySubscriber = registry!.get(subscribedId) 90 if (owningProperty) { 91 if ('hasChanged' in owningProperty) { 92 (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue); 93 } 94 if ('propertyHasChanged' in owningProperty) { 95 (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName); 96 } 97 } else { 98 console.error(`SubscribableHandler: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 99 } 100 }); 101 } 102 103 public get(target: Object, property: PropertyKey): any { 104 console.error(`SubscribableHandler: get '${property.toString()}'.`); 105 return (property === SubscribableHandler.IS_OBSERVED_OBJECT) ? true : 106 (property === SubscribableHandler.RAW_OBJECT) ? target : target[property]; 107 } 108 109 public set(target: Object, property: PropertyKey, newValue: any): boolean { 110 switch (property) { 111 case SubscribableHandler.SUBSCRIBE: 112 // assignment obsObj[SubscribableHandler.SUBSCRCRIBE] = subscriber 113 this.addOwningProperty(newValue as IPropertySubscriber); 114 return true; 115 break; 116 case SubscribableHandler.UNSUBSCRIBE: 117 // assignment obsObj[SubscribableHandler.UN_SUBSCRCRIBE] = subscriber 118 this.removeOwningProperty(newValue as IPropertySubscriber); 119 return true; 120 break; 121 default: 122 if (target[property] == newValue) { 123 return true; 124 } 125 console.log(`SubscribableHandler: set property '${property.toString()}' to new value'`); 126 target[property] = newValue; 127 this.notifyPropertyHasChanged(property.toString(), newValue); // FIXME PropertyKey.toString 128 return true; 129 break; 130 } 131 132 // unreachable 133 return false; 134 } 135} 136 137 138class ExtendableProxy { 139 constructor(obj: Object, handler: SubscribableHandler) { 140 return new Proxy(obj, handler); 141 } 142} 143 144class ObservedObject<T extends Object> extends ExtendableProxy { 145 146 /** 147 * Factory function for ObservedObjects / 148 * wrapping of objects for proxying 149 * 150 * @param rawObject unproxied Object or ObservedObject 151 * @param objOwner owner of this Object to sign uop for propertyChange 152 * notifications 153 * @returns the rawObject if object is already an ObservedObject, 154 * otherwise the newly created ObservedObject 155 */ 156 public static createNew<T extends Object>(rawObject: T, 157 owningProperty: IPropertySubscriber): any { 158 159 if (ObservedObject.IsObservedObject(rawObject)) { 160 ObservedObject.addOwningProperty(rawObject, owningProperty); 161 return rawObject; 162 } else { 163 return new ObservedObject<T>(rawObject, owningProperty); 164 } 165 } 166 167 /* 168 Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy 169 no set observation, no notification of changes! 170 Use with caution, do not store any references 171 */ 172 static GetRawObject(obj: any): any { 173 return !ObservedObject.IsObservedObject(obj) ? obj : obj[SubscribableHandler.RAW_OBJECT]; 174 } 175 176 /** 177 * 178 * @param obj anything 179 * @returns true if the parameter is an Object wrpped with a ObservedObject 180 * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use 181 * this static function instead. 182 */ 183 static IsObservedObject(obj: any): boolean { 184 return obj ? (obj[SubscribableHandler.IS_OBSERVED_OBJECT] == true) : false; 185 } 186 187 static addOwningProperty(obj: Object, subscriber: IPropertySubscriber): boolean { 188 if (!ObservedObject.IsObservedObject(obj)) { 189 return false; 190 } 191 192 obj[SubscribableHandler.SUBSCRIBE] = subscriber; 193 return true; 194 } 195 196 static removeOwningProperty(obj: Object, 197 subscriber: IPropertySubscriber): boolean { 198 if (!ObservedObject.IsObservedObject(obj)) { 199 return false; 200 } 201 202 obj[SubscribableHandler.UNSUBSCRIBE] = subscriber; 203 return true; 204 } 205 206 /** 207 * Create a new ObservableObject and subscribe its owner to propertyHasChanged 208 * ntifications 209 * @param obj raw Object, if obj is a ObservableOject throws an error 210 * @param objectOwner 211 */ 212 constructor(obj: T, objectOwningProperty: IPropertySubscriber) { 213 if (ObservedObject.IsObservedObject(obj)) { 214 throw new Error("Invalid constructor argument error: ObservableObject contructor called with an ObservedObject as parameer"); 215 } 216 let handler = new SubscribableHandler(objectOwningProperty); 217 super(obj, handler); 218 219 if (ObservedObject.IsObservedObject(obj)) { 220 console.error("ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already"); 221 } 222 } // end of constructor 223 224} 225