1/* 2 * Copyright (c) 2022-2023 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 * ObservedPropertyObjectPU 18 * implementation of @State and @Provide decorated variables of type class object 19 * 20 * all definitions in this file are framework internal 21 * 22 * class that holds an actual property value of type T 23 * uses its base class to manage subscribers to this 24 * property. 25*/ 26 27class ObservedPropertyPU<T> extends ObservedPropertyAbstractPU<T> 28 implements PeerChangeEventReceiverPU<T>, ObservedObjectEventsPUReceiver<T> { 29 30 private wrappedValue_: T; 31 32 constructor(localInitValue: T, owningView: IPropertySubscriber, propertyName: PropertyInfo) { 33 super(owningView, propertyName); 34 35 this.setValueInternal(localInitValue); 36 } 37 38 aboutToBeDeleted(unsubscribeMe?: IPropertySubscriber) { 39 this.unsubscribeWrappedObject(); 40 this.removeSubscriber(unsubscribeMe); 41 super.aboutToBeDeleted(); 42 } 43 44 45 public debugInfoDecorator() : string { 46 return `@State/@Provide (class ObservedPropertyPU)`; 47 } 48 49 50 /** 51 * Called by a SynchedPropertyObjectTwoWayPU (@Link, @Consume) that uses this as sync peer when it has changed 52 * @param eventSource 53 */ 54 syncPeerHasChanged(eventSource : ObservedPropertyAbstractPU<T>) { 55 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerHasChanged: from peer \ 56 '${eventSource && eventSource.debugInfo && eventSource.debugInfo()}'.`); 57 this.notifyPropertyHasChangedPU(); 58 } 59 60 /** 61 * Wraped ObservedObjectPU has changed 62 * @param souceObject 63 * @param changedPropertyName 64 */ 65 public objectPropertyHasChangedPU(souceObject: ObservedObject<T>, changedPropertyName : string) { 66 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasChangedPU: contained ObservedObject property \ 67 '${changedPropertyName}' has changed.`) 68 this.notifyPropertyHasChangedPU(); 69 } 70 71 public objectPropertyHasBeenReadPU(souceObject: ObservedObject<T>, changedPropertyName : string) { 72 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasBeenReadPU: contained ObservedObject property \ 73 '${changedPropertyName}' has been read.`); 74 75 this.notifyPropertyHasBeenReadPU(); 76 } 77 78 private unsubscribeWrappedObject() { 79 if (this.wrappedValue_) { 80 if (this.wrappedValue_ instanceof SubscribableAbstract) { 81 (this.wrappedValue_ as SubscribableAbstract).removeOwningProperty(this); 82 } else { 83 ObservedObject.removeOwningProperty(this.wrappedValue_, this); 84 } 85 } 86 } 87 88 /* 89 actually update this.wrappedValue_ 90 called needs to do value change check 91 and also notify with this.aboutToChange(); 92 */ 93 private setValueInternal(newValue: T): boolean { 94 if (newValue === this.wrappedValue_) { 95 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}'] newValue unchanged`); 96 return false; 97 } 98 99 if (!this.checkIsSupportedValue(newValue)) { 100 return false; 101 } 102 103 this.unsubscribeWrappedObject(); 104 if (!newValue || typeof newValue !== 'object') { 105 // undefined, null, simple type: 106 // nothing to subscribe to in case of new value undefined || null || simple type 107 this.wrappedValue_ = newValue; 108 } else if (newValue instanceof SubscribableAbstract) { 109 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an SubscribableAbstract, subscribing to it.`); 110 this.wrappedValue_ = newValue; 111 (this.wrappedValue_ as unknown as SubscribableAbstract).addOwningProperty(this); 112 } else if (ObservedObject.IsObservedObject(newValue)) { 113 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an ObservedObject already`); 114 ObservedObject.addOwningProperty(newValue, this); 115 this.wrappedValue_ = newValue; 116 } else { 117 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an Object, needs to be wrapped in an ObservedObject.`); 118 this.wrappedValue_ = ObservedObject.createNew(newValue, this); 119 } 120 return true; 121 } 122 123 public get(): T { 124 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`); 125 this.notifyPropertyHasBeenReadPU(); 126 return this.wrappedValue_; 127 } 128 129 public getUnmonitored(): T { 130 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 131 // unmonitored get access , no call to notifyPropertyRead ! 132 return this.wrappedValue_; 133 } 134 135 public set(newValue: T): void { 136 if (this.wrappedValue_ === newValue) { 137 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`); 138 return; 139 } 140 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to changed.`); 141 if (this.setValueInternal(newValue)) { 142 this.notifyPropertyHasChangedPU(); 143 } 144 } 145} 146 147// class definitions for backward compatibility 148class ObservedPropertyObjectPU<T> extends ObservedPropertyPU<T> { 149 150} 151 152class ObservedPropertySimplePU<T> extends ObservedPropertyPU<T> { 153 154} 155