1/* 2 * Copyright (c) 2021-2022 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 17/** 18 * ObservedObject, i.e. wrapper for class Object type state variable 19 * 20 * This class and all other definitoons in this file are framework 21 * internal / non-SDK 22 * 23 */ 24 25 26// class of Class in TS 27// ad defined in @angular/core 28interface Type<T> extends Function { 29 new(...args: any[]): T; 30} 31 32/** 33* @Observed Decorator function, use 34* @Observed class ClassA { ... } 35* when defining ClassA 36* 37* Can also be used to create a new Object and wrap it in 38* ObservedObject by calling 39* obsObj = Observed(ClassA)(params to ClassA constructor) 40* 41* Note this works only for classes, not for ClassA[] 42* Also does not work for classes with genetics it seems 43* In that case use factory function 44* obsObj = ObservedObject.createNew<ClassA[]>([]) 45*/ 46 47function Observed<C extends Object>(target: Type<C>): any { 48 var original = target; 49 // the new constructor behaviour 50 var f: any = function (...args: any[]) { 51 stateMgmtConsole.debug(`New ${original.name}, gets wrapped inside ObservableObject proxy.`); 52 return ObservedObject.createNew(new original(...args), undefined); 53// return new ObservedObject<C>(new original(...args), undefined); 54 }; 55 56 Object.setPrototypeOf(f, Object.getPrototypeOf(original)); 57 // return new constructor (will override original) 58 return f; 59} 60 61 62class SubscribableHandler { 63 static IS_OBSERVED_OBJECT = Symbol("_____is_observed_object__"); 64 static RAW_OBJECT = Symbol("_____raw_object__"); 65 static SUBSCRIBE = Symbol("_____subscribe__"); 66 static UNSUBSCRIBE = Symbol("_____unsubscribe__") 67 68 private owningProperties_: Set<number> 69 70 constructor(owningProperty: IPropertySubscriber) { 71 this.owningProperties_ = new Set<number>(); 72 if (owningProperty) { 73 this.addOwningProperty(owningProperty); 74 } 75 stateMgmtConsole.debug(`SubscribableHandler: construcstor done`); 76 } 77 78 addOwningProperty(subscriber: IPropertySubscriber): void { 79 if (subscriber) { 80 stateMgmtConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id__()}'.`) 81 this.owningProperties_.add(subscriber.id__()); 82 } else { 83 stateMgmtConsole.warn(`SubscribableHandler: addOwningProperty: undefined subscriber. - Internal error?`); 84 } 85 } 86 87 /* 88 the inverse function of createOneWaySync or createTwoWaySync 89 */ 90 public removeOwningProperty(property: IPropertySubscriber): void { 91 return this.removeOwningPropertyById(property.id__()); 92 } 93 94 public removeOwningPropertyById(subscriberId: number): void { 95 stateMgmtConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`) 96 this.owningProperties_.delete(subscriberId); 97 } 98 99 100 protected notifyPropertyHasChanged(propName: string, newValue: any) { 101 stateMgmtConsole.debug(`SubscribableHandler: notifyPropertyHasChanged '${propName}'.`) 102 this.owningProperties_.forEach((subscribedId) => { 103 var owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId) 104 if (owningProperty) { 105 if ('hasChanged' in owningProperty) { 106 (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue); 107 } 108 if ('propertyHasChanged' in owningProperty) { 109 (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName); 110 } 111 } else { 112 stateMgmtConsole.warn(`SubscribableHandler: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 113 } 114 }); 115 } 116 117 public get(target: Object, property: PropertyKey): any { 118 return (property === SubscribableHandler.IS_OBSERVED_OBJECT) ? true : 119 (property === SubscribableHandler.RAW_OBJECT) ? target : target[property]; 120 } 121 122 public set(target: Object, property: PropertyKey, newValue: any): boolean { 123 switch (property) { 124 case SubscribableHandler.SUBSCRIBE: 125 // assignment obsObj[SubscribableHandler.SUBSCRCRIBE] = subscriber 126 this.addOwningProperty(newValue as IPropertySubscriber); 127 return true; 128 break; 129 case SubscribableHandler.UNSUBSCRIBE: 130 // assignment obsObj[SubscribableHandler.UN_SUBSCRCRIBE] = subscriber 131 this.removeOwningProperty(newValue as IPropertySubscriber); 132 return true; 133 break; 134 default: 135 if (target[property] == newValue) { 136 return true; 137 } 138 target[property] = newValue; 139 this.notifyPropertyHasChanged(property.toString(), newValue); 140 return true; 141 break; 142 } 143 144 // unreachable 145 return false; 146 } 147} 148 149 150class ExtendableProxy { 151 constructor(obj: Object, handler: SubscribableHandler) { 152 return new Proxy(obj, handler); 153 } 154} 155 156class ObservedObject<T extends Object> extends ExtendableProxy { 157 158 /** 159 * Factory function for ObservedObjects / 160 * wrapping of objects for proxying 161 * 162 * @param rawObject unproxied Object or ObservedObject 163 * @param objOwner owner of this Object to sign uop for propertyChange 164 * notifications 165 * @returns the rawObject if object is already an ObservedObject, 166 * otherwise the newly created ObservedObject 167 */ 168 public static createNew<T extends Object>(rawObject: T, 169 owningProperty: IPropertySubscriber): any { 170 171 if (rawObject === null || rawObject === undefined) { 172 stateMgmtConsole.error(`ObservedObject.CreateNew, input object must not be null or undefined.`); 173 return null; 174 } 175 176 if (ObservedObject.IsObservedObject(rawObject)) { 177 ObservedObject.addOwningProperty(rawObject, owningProperty); 178 return rawObject; 179 } else { 180 return new ObservedObject<T>(rawObject, owningProperty); 181 } 182 } 183 184 /* 185 Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy 186 no set observation, no notification of changes! 187 Use with caution, do not store any references 188 */ 189 static GetRawObject(obj: any): any { 190 return !ObservedObject.IsObservedObject(obj) ? obj : obj[SubscribableHandler.RAW_OBJECT]; 191 } 192 193 /** 194 * 195 * @param obj anything 196 * @returns true if the parameter is an Object wrpped with a ObservedObject 197 * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use 198 * this static function instead. 199 */ 200 static IsObservedObject(obj: any): boolean { 201 return obj ? (obj[SubscribableHandler.IS_OBSERVED_OBJECT] === true) : false; 202 } 203 204 static addOwningProperty(obj: Object, subscriber: IPropertySubscriber): boolean { 205 if (!ObservedObject.IsObservedObject(obj)) { 206 return false; 207 } 208 209 obj[SubscribableHandler.SUBSCRIBE] = subscriber; 210 return true; 211 } 212 213 static removeOwningProperty(obj: Object, 214 subscriber: IPropertySubscriber): boolean { 215 if (!ObservedObject.IsObservedObject(obj)) { 216 return false; 217 } 218 219 obj[SubscribableHandler.UNSUBSCRIBE] = subscriber; 220 return true; 221 } 222 223 /** 224 * Create a new ObservableObject and subscribe its owner to propertyHasChanged 225 * ntifications 226 * @param obj raw Object, if obj is a ObservableOject throws an error 227 * @param objectOwner 228 */ 229 constructor(obj: T, objectOwningProperty: IPropertySubscriber) { 230 if (ObservedObject.IsObservedObject(obj)) { 231 throw new Error("Invalid constructor argument error: ObservableObject contructor called with an ObservedObject as parameer"); 232 } 233 let handler = new SubscribableHandler(objectOwningProperty); 234 super(obj, handler); 235 236 if (ObservedObject.IsObservedObject(obj)) { 237 stateMgmtConsole.error("ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already"); 238 } 239 } // end of constructor 240 241} 242