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 * SynchedPropertyObjectTwoWayPU 18 * implementation of @Link and @Consume decorated variables of type class object 19 * 20 * all definitions in this file are framework internal 21*/ 22 23class SynchedPropertyTwoWayPU<C> extends ObservedPropertyAbstractPU<C> 24 implements PeerChangeEventReceiverPU<C>, ObservedObjectEventsPUReceiver<C> { 25 26 private source_: ObservedPropertyObjectAbstract<C>; 27 private changeNotificationIsOngoing_: boolean = false; 28 29 constructor(source: ObservedPropertyObjectAbstract<C>, 30 owningChildView: IPropertySubscriber, 31 thisPropertyName: PropertyInfo) { 32 super(owningChildView, thisPropertyName); 33 this.source_ = source; 34 if (this.source_) { 35 // register to the parent property 36 this.source_.addSubscriber(this); 37 } else { 38 throw new SyntaxError(`${this.debugInfo()}: constructor: source variable in parent/ancestor @Component must be defined. Application error!`); 39 } 40 } 41 42 /* 43 like a destructor, need to call this before deleting 44 the property. 45 */ 46 aboutToBeDeleted() { 47 // unregister from parent of this link 48 if (this.source_) { 49 this.source_.removeSubscriber(this); 50 51 // unregister from the ObservedObject 52 ObservedObject.removeOwningProperty(this.source_.getUnmonitored(), this); 53 } 54 super.aboutToBeDeleted(); 55 } 56 57 public debugInfoDecorator() : string { 58 return `@Link/@Consume (class SynchedPropertyTwoWayPU)`; 59 } 60 61 private isStorageLinkProp() : boolean { 62 return (this.source_ && this.source_ instanceof ObservedPropertyAbstract && (!(this.source_ instanceof ObservedPropertyAbstractPU))); 63 } 64 65 private setObject(newValue: C): void { 66 if (!this.source_) { 67 throw new SyntaxError(`${this.debugInfo()}: setObject (assign a new value), no source variable in parent/ancestor \ 68 @Component. Application error.`); 69 70 } 71 72 if (this.getUnmonitored() === newValue) { 73 stateMgmtConsole.debug(`SynchedPropertyObjectTwoWayPU[${this.id__()}IP, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`); 74 return; 75 } 76 77 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value has changed.`); 78 79 if (this.checkIsSupportedValue(newValue)) { 80 // the source_ ObservedProperty will call: this.syncPeerHasChanged(newValue); 81 this.source_.set(newValue) 82 } 83 } 84 85 86 /** 87 * Called when sync peer ObservedPropertyObject or SynchedPropertyObjectTwoWay has changed value 88 * that peer can be in either parent or child component if 'this' is used for a @Link 89 * that peer can be in either ancestor or descendant component if 'this' is used for a @Consume 90 * @param eventSource 91 */ 92 syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU<C>) { 93 if (!this.changeNotificationIsOngoing_) { 94 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerHasChanged: from peer '${eventSource && eventSource.debugInfo && eventSource.debugInfo()}' .`) 95 this.notifyPropertyHasChangedPU(); 96 } 97 } 98 99 /** 100 * called when wrapped ObservedObject has changed poperty 101 * @param souceObject 102 * @param changedPropertyName 103 */ 104 public objectPropertyHasChangedPU(sourceObject: ObservedObject<C>, changedPropertyName : string) { 105 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasChangedPU: property '${changedPropertyName}' of \ 106 object value has changed.`) 107 108 this.notifyPropertyHasChangedPU(); 109 } 110 111 public objectPropertyHasBeenReadPU(sourceObject: ObservedObject<C>, changedPropertyName : string) { 112 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasBeenReadPU: property '${changedPropertyName}' of object value has been read.`); 113 this.notifyPropertyHasBeenReadPU(); 114 } 115 116 public getUnmonitored(): C { 117 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 118 // unmonitored get access , no call to otifyPropertyRead ! 119 return (this.source_ ? this.source_.getUnmonitored() : undefined); 120 } 121 122 // get 'read through` from the ObservedProperty 123 public get(): C { 124 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`) 125 this.notifyPropertyHasBeenReadPU() 126 return this.getUnmonitored(); 127 } 128 129 // set 'writes through` to the ObservedProperty 130 public set(newValue: C): void { 131 if (this.getUnmonitored() === newValue) { 132 stateMgmtConsole.debug(`SynchedPropertyObjectTwoWayPU[${this.id__()}IP, '${this.info() || "unknown"}']: set with unchanged value - nothing to do.`); 133 return; 134 } 135 136 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to change.`); 137 138 // avoid circular notifications @Link -> source @State -> other but also back to same @Link 139 this.changeNotificationIsOngoing_ = true; 140 this.setObject(newValue); 141 this.notifyPropertyHasChangedPU(); 142 this.changeNotificationIsOngoing_ = false; 143 } 144} 145 146// class definitions for backward compatibility 147class SynchedPropertyObjectTwoWayPU<C> extends SynchedPropertyTwoWayPU<C> { 148 149} 150 151class SynchedPropertySimpleTwoWayPU<T> extends SynchedPropertyTwoWayPU<T> { 152 153} 154 155