• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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