1/* 2 * Copyright (c) 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 * SynchedPropertyObjectOneWayPU 18 * implementatio of @Prop decorated variables of type class object 19 * 20 * all definitions in this file are framework internal 21 * 22 */ 23 24 25type SyncSourceHasChangedCb<T> = (source : ObservedPropertyAbstract<T>) => void; 26 27class SynchedPropertyObjectOneWayPU<C extends Object> 28 extends ObservedPropertyObjectAbstractPU<C> 29 implements PeerChangeEventReceiverPU<C>, 30 ObservedObjectEventsPUReceiver<C> { 31 32 private wrappedValue_: C; 33 private source_: ObservedPropertyAbstract<C>; 34 35 // true for @Prop code path, 36 // false for @(Local)StorageProp 37 private sourceIsOwnObject : boolean; 38 39 constructor(source: ObservedPropertyAbstract<C> | C, 40 owningChildView: IPropertySubscriber, 41 thisPropertyName: PropertyInfo) { 42 super(owningChildView, thisPropertyName); 43 44 if (source && (typeof (source) === "object") && ("subscribeMe" in source)) { 45 // code path for @(Local)StorageProp, the souce is a ObservedPropertyObject in aLocalStorage) 46 this.source_ = source as ObservedPropertyAbstractPU<C>; 47 this.sourceIsOwnObject = false; 48 // subscribe to receive value change updates from LocalStorage source property 49 this.source_.subscribeMe(this); 50 } else { 51 // code path for @Prop 52 if (!ObservedObject.IsObservedObject(source)) { 53 stateMgmtConsole.warn(`@Prop ${this.info()} Provided source object's class 54 lacks @Observed class decorator. Object property changes will not be observed.`); 55 } 56 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: constructor @Prop wrapping source in a new ObservedPropertyObjectPU`); 57 this.source_ = new ObservedPropertyObjectPU<C>(source as C, this, thisPropertyName); 58 this.sourceIsOwnObject = true; 59 } 60 61 // deep copy source Object and wrap it 62 this.setWrappedValue(this.source_.get()); 63 64 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: constructor ready with wrappedValue '${JSON.stringify(this.wrappedValue_)}'.`); 65 } 66 67 /* 68 like a destructor, need to call this before deleting 69 the property. 70 */ 71 aboutToBeDeleted() { 72 if (this.source_) { 73 this.source_.unlinkSuscriber(this.id__()); 74 if (this.sourceIsOwnObject == true && this.source_.numberOfSubscrbers()==0){ 75 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: aboutToBeDeleted. owning source_ ObservedPropertySimplePU, calling its aboutToBeDeleted`); 76 this.source_.aboutToBeDeleted(); 77 } 78 79 this.source_ = undefined; 80 } 81 super.aboutToBeDeleted(); 82 } 83 84 public syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU<C>) { 85 if (eventSource && this.source_ == eventSource) { 86 // defensive programming: should always be the case! 87 stateMgmtConsole.debug(`SynchedPropertyNesedObjectPU[${this.id__()}]: syncPeerHasChanged(): Source '${eventSource.info()}' has changed'.`) 88 const newValue = this.source_.getUnmonitored(); 89 if (typeof newValue == "object") { 90 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: hasChanged: newValue '${JSON.stringify(newValue)}'.`); 91 this.setWrappedValue(newValue); 92 this.notifyPropertyHasChangedPU(); 93 } 94 } else { 95 stateMgmtConsole.warn(`SynchedPropertyNesedObjectPU[${this.id__()}]: syncPeerHasChanged Unexpected situation. Ignorning event.`) 96 } 97 } 98 99 /** 100 * event emited by wrapped ObservedObject, when one of its property values changes 101 * @param souceObject 102 * @param changedPropertyName 103 */ 104 public objectPropertyHasChangedPU(souceObject: ObservedObject<C>, changedPropertyName : string) { 105 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: \ 106 objectPropertyHasChangedPU: contained ObservedObject property '${changedPropertyName}' has changed.`) 107 this.notifyPropertyHasChangedPU(); 108 } 109 110 public objectPropertyHasBeenReadPU(souceObject: ObservedObject<C>, changedPropertyName : string) { 111 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: \ 112 objectPropertyHasBeenReadPU: contained ObservedObject property '${changedPropertyName}' has been read.`); 113 this.notifyPropertyHasBeenReadPU(); 114 } 115 116 public getUnmonitored(): C { 117 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: getUnmonitored returns '${JSON.stringify(this.wrappedValue_)}'.`); 118 // unmonitored get access , no call to notifyPropertyRead ! 119 return this.wrappedValue_; 120 } 121 122 // get 'read through` from the ObservedObject 123 public get(): C { 124 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: get returning ${JSON.stringify(this.wrappedValue_)}.`) 125 this.notifyPropertyHasBeenReadPU() 126 return this.wrappedValue_; 127 } 128 129 // assignment to local variable in the form of this.aProp = <object value> 130 // set 'writes through` to the ObservedObject 131 public set(newValue: C): void { 132 if (this.wrappedValue_ == newValue) { 133 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}IP, '${this.info() || "unknown"}']: set with unchanged value '${JSON.stringify(newValue)}'- ignoring.`); 134 return; 135 } 136 137 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: set to newValue: '${JSON.stringify(newValue)}'.`); 138 if (!ObservedObject.IsObservedObject(newValue)) { 139 stateMgmtConsole.warn(`@Prop ${this.info()} Set: Provided new object's class 140 lacks @Observed class decorator. Object property changes will not be observed.`); 141 } 142 143 this.setWrappedValue(newValue); 144 this.notifyPropertyHasChangedPU(); 145 } 146 147 public reset(sourceChangedValue: C): void { 148 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: reset from '${JSON.stringify(this.wrappedValue_)}' to '${JSON.stringify(sourceChangedValue)}'.`); 149 // if set causes an actual change, then, ObservedPropertyObject source_ will call syncPeerHasChanged 150 this.source_.set(sourceChangedValue); 151 } 152 153 private setWrappedValue(value: C): void { 154 let rawValue = ObservedObject.GetRawObject(value); 155 let copy: C; 156 157 // FIXME: Proper object deep copy missing here! 158 if (rawValue instanceof Array) { 159 copy = ObservedObject.createNew([ ...rawValue ], this) as unknown as C; 160 } else { 161 copy = ObservedObject.createNew({ ...rawValue }, this); 162 } 163 Object.setPrototypeOf(copy, Object.getPrototypeOf(rawValue)); 164 ObservedObject.addOwningProperty(this.wrappedValue_, this); 165 this.wrappedValue_ = copy; 166 } 167} 168