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 16class AppStorage { 17 private static Instance_: AppStorage = undefined; 18 19 // FIXME: Perhaps "GetInstance" would be better name for this 20 // static Get(): AppStorage { return AppStorage.Instance_; } 21 static GetOrCreate(): AppStorage { 22 if (!AppStorage.Instance_) { 23 AppStorage.Instance_ = new AppStorage(); 24 } 25 return AppStorage.Instance_; 26 } 27 28 private storage_: Map<string, ObservedPropertyAbstract<any>>; 29 30 static Link<T>(key: string): ObservedPropertyAbstract<T> { 31 return AppStorage.GetOrCreate().link(key); 32 } 33 34 static SetAndLink<T>(key: string, defaultValue: T): ObservedPropertyAbstract<T> { 35 return AppStorage.GetOrCreate().setAndLink(key, defaultValue); 36 } 37 38 static Prop<T>(key: string): ObservedPropertyAbstract<T> { 39 return AppStorage.GetOrCreate().prop(key); 40 } 41 42 static SetAndProp<S>(key: string, defaultValue: S): ObservedPropertyAbstract<S> { 43 return AppStorage.GetOrCreate().setAndProp(key, defaultValue); 44 } 45 46 static Has(key: string): boolean { 47 return AppStorage.GetOrCreate().has(key); 48 } 49 50 static Get<T>(key: string): T | undefined { 51 return AppStorage.GetOrCreate().get(key); 52 } 53 54 static Set<T>(key: string, newValue: T): boolean { 55 return AppStorage.GetOrCreate().set(key, newValue); 56 } 57 58 // FIXME(cvetan): No mechanism to create "immutable" properties 59 static SetOrCreate<T>(key: string, newValue: T): void { 60 AppStorage.GetOrCreate().setOrCreate(key, newValue); 61 } 62 63 static Delete(key: string): boolean { 64 return AppStorage.GetOrCreate().delete(key); 65 } 66 67 static Keys(): IterableIterator<string> { 68 return AppStorage.GetOrCreate().keys(); 69 } 70 71 static Size(): number { 72 return AppStorage.GetOrCreate().size(); 73 } 74 75 static Clear(): boolean { 76 return AppStorage.GetOrCreate().clear(); 77 } 78 79 static AboutToBeDeleted(): void { 80 AppStorage.GetOrCreate().aboutToBeDeleted(); 81 } 82 83 static NumberOfSubscribersTo(propName: string): number | undefined { 84 return AppStorage.GetOrCreate().numberOfSubscrbersTo(propName); 85 } 86 87 static SubscribeToChangesOf<T>(propName: string, subscriber: ISinglePropertyChangeSubscriber<T>): boolean { 88 return AppStorage.GetOrCreate().subscribeToChangesOf(propName, subscriber); 89 } 90 91 static UnsubscribeFromChangesOf(propName: string, subscriberId: number): boolean { 92 return AppStorage.GetOrCreate().unsubscribeFromChangesOf(propName, subscriberId); 93 } 94 95 static IsMutable(key: string): boolean { 96 // FIXME(cvetan): No mechanism for immutable/mutable properties 97 return true; 98 } 99 100 constructor() { 101 this.storage_ = new Map<string, ObservedPropertyAbstract<any>>(); 102 } 103 104 /** 105 * App should call this method to order close down app storage before 106 * terminating itself. 107 * Before deleting a prop from app storage all its subscribers need to 108 * unsubscribe from the property. 109 * 110 * @returns true if all properties could be removed from app storage 111 */ 112 public aboutToBeDeleted(): boolean { 113 return this.clear(); 114 } 115 116 public get<T>(propName: string): T | undefined { 117 var p: ObservedPropertyAbstract<T> | undefined = this.storage_.get(propName); 118 return (p) ? p.get() : undefined; 119 } 120 121 public set<T>(propName: string, newValue: T): boolean { 122 var p: ObservedPropertyAbstract<T> | undefined = this.storage_.get(propName); 123 if (p) { 124 p.set(newValue); 125 return true; 126 } else { 127 return false; 128 } 129 } 130 131 public setOrCreate<T>(propName: string, newValue: T): void { 132 var p: ObservedPropertyAbstract<T> = this.storage_.get(propName); 133 if (p) { 134 console.log(`AppStorage.setOrCreate(${propName}, ${newValue}) update existing property`); 135 p.set(newValue); 136 } else { 137 console.log(`AppStorage.setOrCreate(${propName}, ${newValue}) create new entry and set value`); 138 const newProp = (typeof newValue === "object") ? 139 new ObservedPropertyObject<T>(newValue, undefined, propName) 140 : new ObservedPropertySimple<T>(newValue, undefined, propName); 141 this.storage_.set(propName, newProp); 142 } 143 } 144 145 public has(propName: string): boolean { 146 console.log(`AppStorage.has(${propName})`); 147 return this.storage_.has(propName); 148 } 149 150 151 /** 152 * Delete poperty from AppStorage 153 * must only use with caution: 154 * Before deleting a prop from app storage all its subscribers need to 155 * unsubscribe from the property. 156 * This method fails and returns false if given property still has subscribers 157 * Another reason for failing is unkmown property. 158 * 159 * @param propName 160 * @returns false if method failed 161 */ 162 public delete(propName: string): boolean { 163 var p: ObservedPropertyAbstract<any> | undefined = this.storage_.get(propName); 164 if (p) { 165 if (p.numberOfSubscrbers()) { 166 console.error(`Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 167 return false; 168 } 169 p.aboutToBeDeleted(); 170 this.storage_.delete(propName); 171 return true; 172 } else { 173 console.warn(`Attempt to delete unknown property ${propName}.`); 174 return false; 175 } 176 } 177 178 /** 179 * delete all properties from the AppStorage 180 * precondition is that there are no subscribers anymore 181 * method returns false and deletes no poperties if there is any property 182 * that still has subscribers 183 */ 184 protected clear(): boolean { 185 for (let propName of this.keys()) { 186 var p: ObservedPropertyAbstract<any> = this.storage_.get(propName); 187 if (p.numberOfSubscrbers()) { 188 console.error(`AppStorage.deleteAll: Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 189 return false; 190 } 191 } 192 for (let propName of this.keys()) { 193 var p: ObservedPropertyAbstract<any> = this.storage_.get(propName); 194 p.aboutToBeDeleted(); 195 } 196 console.error(`AppStorage.deleteAll: success`); 197 } 198 199 public keys(): IterableIterator<string> { 200 return this.storage_.keys(); 201 } 202 203 public size(): number { 204 return this.storage_.size; 205 } 206 207 public link<T>(propName: string, linkUser?: IPropertySubscriber, contentObserver?: ObservedPropertyAbstract<T>): ObservedPropertyAbstract<T> | undefined { 208 var p: ObservedPropertyAbstract<T> | undefined = this.storage_.get(propName); 209 return (p) ? p.createLink(linkUser, propName, contentObserver) : undefined 210 } 211 212 public setAndLink<T>(propName: string, defaultValue: T, linkUser?: IPropertySubscriber): ObservedPropertyAbstract<T> { 213 var p: ObservedPropertyAbstract<T> | undefined = this.storage_.get(propName); 214 if (!p) { 215 this.setOrCreate(propName, defaultValue); 216 } 217 if (linkUser && (linkUser as View).getContentStorage()) { 218 var contentObserver = (linkUser as View).getContentStorage().setAndLink(propName, defaultValue, linkUser); 219 return this.link(propName, linkUser, contentObserver) 220 } 221 return this.link(propName, linkUser); 222 } 223 224 public prop<S>(propName: string, propUser?: IPropertySubscriber, contentObserver?: ObservedPropertyAbstract<S>): ObservedPropertyAbstract<S> | undefined { 225 var p: ObservedPropertyAbstract<S> | undefined = this.storage_.get(propName); 226 return (p) ? p.createProp(propUser, propName, contentObserver) : undefined 227 } 228 229 public setAndProp<S>(propName: string, defaultValue: S, propUser?: IPropertySubscriber): ObservedPropertyAbstract<S> { 230 var p: ObservedPropertyAbstract<S> | undefined = this.storage_.get(propName); 231 232 if (!p) { 233 if (typeof defaultValue === "boolean" || 234 typeof defaultValue === "number" || typeof defaultValue === "string") { 235 this.setOrCreate(propName, defaultValue); 236 } else { 237 return undefined; 238 } 239 } 240 241 if (propUser && (propUser as View).getContentStorage()) { 242 var contentObserver = (propUser as View).getContentStorage().setAndProp(propName, defaultValue, propUser); 243 return this.prop(propName, propUser, contentObserver) 244 } 245 246 return this.prop(propName, propUser); 247 } 248 249 250 public subscribeToChangesOf<T>(propName: string, subscriber: ISinglePropertyChangeSubscriber<T>): boolean { 251 var p: ObservedPropertyAbstract<T> | undefined = this.storage_.get(propName); 252 if (p) { 253 p.subscribeMe(subscriber); 254 return true; 255 } 256 return false; 257 } 258 259 public unsubscribeFromChangesOf(propName: string, subscriberId: number): boolean { 260 var p: ObservedPropertyAbstract<any> | undefined = this.storage_.get(propName); 261 if (p) { 262 p.unlinkSuscriber(subscriberId); 263 return true; 264 } 265 return false; 266 } 267 268 /* 269 return number of subscribers to this property 270 mostly useful for unit testin 271 */ 272 public numberOfSubscrbersTo(propName: string): number | undefined { 273 var p: ObservedPropertyAbstract<any> | undefined = this.storage_.get(propName); 274 if (p) { 275 return p.numberOfSubscrbers(); 276 } 277 return undefined; 278 } 279} 280