• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 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
16const enum PersistError {
17  Quota = 'quota',
18  Serialization = 'serialization',
19  Unknown = 'unknown'
20};
21
22type PersistErrorType = PersistError.Quota | PersistError.Serialization | PersistError.Unknown;
23type PersistErrorCallback = ((key: string, reason: PersistErrorType, message: string) => void) | undefined;
24type StorageDefaultCreator<T> = () => T;
25
26interface TypeConstructorWithArgs<T> {
27  new(...args: any): T;
28}
29
30class ConnectOptions<T extends object> {
31  type: TypeConstructorWithArgs<T>;
32  key?: string;
33  defaultCreator?: StorageDefaultCreator<T>;
34  areaMode?: number;
35}
36
37const enum AreaMode {
38  EL1 = 0,
39  EL2 = 1,
40  EL3 = 2,
41  EL4 = 3,
42  EL5 = 4
43}
44
45const enum MapType {
46  NOT_IN_MAP = -1,
47  MODULE_MAP = 0,
48  GLOBAL_MAP = 1
49}
50
51class StorageHelper {
52  protected static readonly INVALID_DEFAULT_VALUE: string = 'The default creator should be function when first connect';
53  protected static readonly DELETE_NOT_EXIST_KEY: string = 'The key to be deleted does not exist';
54  protected static readonly INVALID_TYPE: string = 'The type should have function constructor signature when use storage';
55  protected static readonly EMPTY_STRING_KEY: string = 'Cannot use empty string as the key';
56  protected static readonly INVALID_LENGTH_KEY: string = 'Cannot use the key! The length of key should be 2 to 255';
57  protected static readonly INVALID_CHARACTER_KEY: string = 'Cannot use the key! The value of key can only consist of letters, digits and underscores';
58  protected static readonly NULL_OR_UNDEFINED_KEY: string = `The parameter cannot be null or undefined`;
59
60  // sotre old type name to check the type matches or not
61  protected oldTypeValues_: Map<string, string>;
62
63  constructor() {
64    this.oldTypeValues_ = new Map<string, string>();
65  }
66
67  protected getConnectedKey<T>(type: TypeConstructorWithArgs<T>,
68    keyOrDefaultCreator?: string | StorageDefaultCreator<T>): string | undefined {
69    if (keyOrDefaultCreator === null || keyOrDefaultCreator === undefined) {
70      stateMgmtConsole.applicationWarn(StorageHelper.NULL_OR_UNDEFINED_KEY + ', try to use the type name as key');
71    }
72
73    if (typeof keyOrDefaultCreator === 'string') {
74      return keyOrDefaultCreator;
75    }
76
77    return this.getTypeName(type);
78  }
79
80  protected getKeyOrTypeName<T>(keyOrType: string | TypeConstructorWithArgs<T>): string | undefined {
81    if (typeof keyOrType === 'function') {
82      keyOrType = this.getTypeName(keyOrType);
83    }
84    return keyOrType;
85  }
86
87  protected checkTypeByName<T>(key: string, type: TypeConstructorWithArgs<T>, oldType: string): void {
88    if (this.getTypeName(type) !== oldType) {
89      throw new Error(`The type mismatches when use the key '${key}' in storage`);
90    }
91  }
92
93  protected checkTypeByInstanceOf<T>(key: string, type: TypeConstructorWithArgs<T>, ins: T): void {
94    this.checkTypeIsFunc(type);
95
96    if (!(ins instanceof type)) {
97      throw new Error(`The type mismatches when use the key '${key}' in storage`);
98    }
99  }
100
101  protected getTypeName<T>(type: TypeConstructorWithArgs<T>): string | undefined {
102    this.checkTypeIsFunc(type);
103
104    let name: string | undefined = type.name;
105    while (name === undefined) {
106      type = Object.getPrototypeOf(type);
107      if (!type) {
108        break;
109      }
110      name = type.name;
111    }
112    return name;
113  }
114
115  protected isKeyValid(key: string | null | undefined): boolean {
116    if (typeof key !== 'string') {
117      throw new Error(StorageHelper.INVALID_TYPE);
118    }
119
120    // The key string is empty
121    if (key === '') {
122      stateMgmtConsole.applicationError(StorageHelper.EMPTY_STRING_KEY);
123      return false;
124    }
125
126    const len: number = key.length;
127    // The key string length should shorter than 1024
128    if (len >= 1024) {
129      stateMgmtConsole.applicationError(StorageHelper.INVALID_LENGTH_KEY);
130      return false;
131    }
132
133    if (len < 2 || len > 255) {
134      stateMgmtConsole.applicationWarn(StorageHelper.INVALID_LENGTH_KEY);
135    }
136
137    if (!/^[0-9a-zA-Z_]+$/.test(key)) {
138      stateMgmtConsole.applicationWarn(StorageHelper.INVALID_CHARACTER_KEY);
139    }
140
141    return true;
142  }
143
144  private checkTypeIsFunc<T>(type: TypeConstructorWithArgs<T>): void {
145    if (typeof type !== 'function') {
146      throw new Error(StorageHelper.INVALID_TYPE);
147    }
148  }
149}
150
151class AppStorageV2Impl extends StorageHelper {
152  private static instance_: AppStorageV2Impl = undefined;
153
154  private memorizedValues_: Map<string, any>;
155
156  constructor() {
157    super();
158    this.memorizedValues_ = new Map<string, any>();
159  }
160
161  public static instance(): AppStorageV2Impl {
162    if (AppStorageV2Impl.instance_) {
163      return AppStorageV2Impl.instance_;
164    }
165    AppStorageV2Impl.instance_ = new AppStorageV2Impl();
166    return AppStorageV2Impl.instance_;
167  }
168
169  public connect<T extends object>(type: TypeConstructorWithArgs<T>, keyOrDefaultCreator?: string | StorageDefaultCreator<T>,
170    defaultCreator?: StorageDefaultCreator<T>): T | undefined {
171    const key: string = this.getConnectedKey(type, keyOrDefaultCreator);
172
173    if (!this.isKeyValid(key)) {
174      return undefined;
175    }
176
177    if (typeof keyOrDefaultCreator === 'function') {
178      defaultCreator = keyOrDefaultCreator;
179    }
180
181    if (!this.memorizedValues_.has(key)) {
182      if (typeof defaultCreator !== 'function') {
183        throw new Error(AppStorageV2Impl.INVALID_DEFAULT_VALUE);
184      }
185
186      const defaultValue: T = defaultCreator();
187
188      this.checkTypeByInstanceOf(key, type, defaultValue);
189
190      this.memorizedValues_.set(key, defaultValue);
191      this.oldTypeValues_.set(key, this.getTypeName(type));
192      return defaultValue;
193    }
194
195    this.checkTypeByName(key, type, this.oldTypeValues_.get(key));
196
197    const existedValue: T = this.memorizedValues_.get(key);
198    return existedValue;
199  }
200
201  public remove<T>(keyOrType: string | TypeConstructorWithArgs<T>): void {
202    if (keyOrType === null || keyOrType === undefined) {
203      stateMgmtConsole.applicationWarn(AppStorageV2Impl.NULL_OR_UNDEFINED_KEY);
204      return;
205    }
206
207    const key: string = this.getKeyOrTypeName(keyOrType);
208
209    if (!this.isKeyValid(key)) {
210      return;
211    }
212
213    this.removeFromMemory(key);
214  }
215
216  public getMemoryKeys(): Array<string> {
217    return Array.from(this.memorizedValues_.keys());
218  }
219
220  private removeFromMemory(key: string): void {
221    const isDeleted: boolean = this.memorizedValues_.delete(key);
222
223    if (!isDeleted) {
224      stateMgmtConsole.applicationWarn(AppStorageV2Impl.DELETE_NOT_EXIST_KEY);
225    } else {
226      this.oldTypeValues_.delete(key);
227    }
228  }
229}
230
231class PersistenceV2Impl extends StorageHelper {
232  public static readonly MIN_PERSISTENCE_ID = 0x1010000000000;
233  public static nextPersistId_ = PersistenceV2Impl.MIN_PERSISTENCE_ID;
234
235  private static readonly NOT_SUPPORT_TYPE_MESSAGE_: string = 'Not support! Can only use the class object in Persistence';
236  private static readonly NOT_SUPPORT_AREAMODE_MESSAGE_: string = 'AreaMode Value Error! value range can only in EL1-EL5';
237  private static readonly KEYS_DUPLICATE_: string = 'ERROR, Duplicate key used when connect';
238  private static readonly KEYS_ARR_: string = '___keys_arr';
239  private static readonly MAX_DATA_LENGTH_: number = 8000;
240  private static readonly NOT_SUPPORT_TYPES_: Array<any> =
241    [Array, Set, Map, WeakSet, WeakMap, Date, Boolean, Number, String, Symbol, BigInt, RegExp, Function, Promise, ArrayBuffer];
242  private static storage_: IStorage;
243  private static instance_: PersistenceV2Impl = undefined;
244
245  // the map is used to store the persisted value in memory, can reuse when re-connect if the key exists
246  private map_: Map<string, any>;
247  private globalMap_: Map<string, any>;
248  private globalMapAreaMode_: Map<string, number>;
249  private keysArr_: Set<string>;
250  private globalKeysArr_: Array<Set<string>>;
251  private cb_: PersistErrorCallback = undefined;
252  private idToKey_: Map<number, string>;
253
254  constructor() {
255    super();
256    this.map_ = new Proxy(new Map<string, any>(), new SetMapProxyHandler());
257    this.globalMap_ = new Proxy(new Map<string, any>(), new SetMapProxyHandler());
258    this.globalMapAreaMode_ = new Map<string, number>();
259    this.keysArr_ = new Set<string>();
260    this.globalKeysArr_ = [new Set(), new Set(), new Set(), new Set(), new Set()];
261    this.idToKey_ = new Map<number, string>();
262  }
263
264  public static instance(): PersistenceV2Impl {
265    if (PersistenceV2Impl.instance_) {
266      return PersistenceV2Impl.instance_;
267    }
268    PersistenceV2Impl.instance_ = new PersistenceV2Impl();
269    return PersistenceV2Impl.instance_;
270  }
271
272  public static configureBackend(storage: IStorage): void {
273    PersistenceV2Impl.storage_ = storage;
274  }
275
276  public connect<T extends object>(type: TypeConstructorWithArgs<T>, keyOrDefaultCreator?: string | StorageDefaultCreator<T>,
277    defaultCreator?: StorageDefaultCreator<T>): T | undefined {
278    this.checkTypeIsClassObject(type);
279
280    const key: string | undefined = this.getRightKey(type, keyOrDefaultCreator);
281    if (!this.isKeyValid(key)) {
282      return undefined;
283    }
284
285    if (typeof keyOrDefaultCreator === 'function') {
286      defaultCreator = keyOrDefaultCreator;
287    }
288
289    // In memory
290    if (this.globalMap_.has(key)) {
291      throw new Error(PersistenceV2Impl.KEYS_DUPLICATE_);
292    }
293    if (this.map_.has(key)) {
294      const existedValue: T = this.map_.get(key);
295      this.checkTypeByName(key, type, this.oldTypeValues_.get(key));
296      return existedValue;
297    }
298
299    const observedValue: T | undefined = this.getConnectDefaultValue(key, type, defaultCreator);
300    if (!observedValue) {
301      return undefined;
302    }
303
304    const id: number = ++PersistenceV2Impl.nextPersistId_;
305    this.idToKey_.set(id, key);
306
307    // Not in memory, but in disk
308    if (PersistenceV2Impl.storage_.has(key)) {
309      return this.getValueFromDisk(key, id, observedValue, type);
310    }
311
312    // Neither in memory or in disk
313    return this.setValueToDisk(key, id, observedValue, type);
314  }
315
316  public globalConnect<T extends object>(connectOptions: ConnectOptions<T>): T | undefined {
317    this.checkTypeIsClassObject(connectOptions.type);
318
319    const key: string | undefined = this.getRightGlobalKey(connectOptions.type, connectOptions.key);
320    if (!this.isKeyValid(key)) {
321      return undefined;
322    }
323
324    // In memory, do duplicate key check
325    if (this.map_.has(key)) {
326      throw new Error(PersistenceV2Impl.KEYS_DUPLICATE_);
327    }
328    // In memory, return if globalMap_ exist
329    if (this.globalMap_.has(key)) {
330      const existedValue: T = this.globalMap_.get(key);
331      this.checkTypeByName(key, connectOptions.type, this.oldTypeValues_.get(key));
332      return existedValue;
333    }
334
335    const observedValue: T | undefined = this.getConnectDefaultValue(key,
336      connectOptions.type, connectOptions.defaultCreator);
337    if (!observedValue) {
338      return undefined;
339    }
340
341    const id: number = ++PersistenceV2Impl.nextPersistId_;
342    this.idToKey_.set(id, key);
343
344    // Not in memory, but in disk
345    const areaMode: number = this.getAreaMode(connectOptions.areaMode);
346    this.globalMapAreaMode_.set(key, areaMode);
347    if (PersistenceV2Impl.storage_.has(key, areaMode)) {
348      return this.getValueFromDisk(key, id, observedValue, connectOptions.type, areaMode);
349    }
350
351    // Neither in memory or in disk
352    return this.setValueToDisk(key, id, observedValue, connectOptions.type, areaMode);
353  }
354
355  public keys(): Array<string> {
356    const allKeys: Set<string> = new Set<string>();
357    try {
358      // add module path key
359      if (!this.keysArr_.size) {
360        this.keysArr_ = this.getKeysArrFromStorage();
361      }
362      for (const key of this.keysArr_) {
363        allKeys.add(key);
364      }
365      // add global path key
366      for (let i = 0; i < this.globalKeysArr_.length; i++) {
367        if (!this.globalKeysArr_[i].size) {
368          this.globalKeysArr_[i] = this.getKeysArrFromStorage(i);
369        }
370        for (const key of this.globalKeysArr_[i]) {
371          allKeys.add(key);
372        }
373      }
374    } catch (err) {
375      if (this.cb_ && typeof this.cb_ === 'function') {
376        this.cb_('', PersistError.Unknown, `fail to get all persisted keys`);
377        return [];
378      }
379      throw new Error(err);
380    }
381
382    return Array.from(allKeys);
383  }
384
385  public remove<T>(keyOrType: string | TypeConstructorWithArgs<T>): void {
386    if (keyOrType === null || keyOrType === undefined) {
387      stateMgmtConsole.applicationWarn(PersistenceV2Impl.NULL_OR_UNDEFINED_KEY);
388      return;
389    }
390
391    this.checkTypeIsClassObject(keyOrType);
392
393    const key: string = this.getKeyOrTypeName(keyOrType);
394
395    if (!this.isKeyValid(key)) {
396      return;
397    }
398
399    this.disconnectValue(key);
400  }
401
402  public save<T>(keyOrType: string | TypeConstructorWithArgs<T>): void {
403    if (keyOrType === null || keyOrType === undefined) {
404      stateMgmtConsole.applicationWarn(PersistenceV2Impl.NULL_OR_UNDEFINED_KEY);
405      return;
406    }
407
408    this.checkTypeIsClassObject(keyOrType);
409
410    const key: string = this.getKeyOrTypeName(keyOrType);
411
412    if (!this.isKeyValid(key)) {
413      return;
414    }
415
416    if (this.globalMap_.has(key)) {
417      // find in global path
418      const areaMode = this.globalMapAreaMode_.get(key);
419      try {
420        PersistenceV2Impl.storage_.set(key, JSONCoder.stringify(this.globalMap_.get(key)), areaMode);
421      } catch (err) {
422        this.errorHelper(key, PersistError.Serialization, err);
423      }
424    } else if (this.map_.has(key)) {
425      // find in module path
426      try {
427        PersistenceV2Impl.storage_.set(key, JSONCoder.stringify(this.map_.get(key)));
428      } catch (err) {
429        this.errorHelper(key, PersistError.Serialization, err);
430      }
431    } else {
432      stateMgmtConsole.applicationWarn(`Cannot save the key '${key}'! The key is disconnected`);
433    }
434  }
435
436  public notifyOnError(cb: PersistErrorCallback): void {
437    this.cb_ = cb;
438  }
439
440  public onChangeObserved(persistKeys: Array<number>): void {
441    this.writeAllChangedToFile(persistKeys);
442  }
443
444  private connectNewValue(key: string, newValue: any, typeName: string, areaMode?: number | undefined): void {
445    if (typeof areaMode === 'number') {
446      this.globalMap_.set(key, newValue);
447    } else {
448      this.map_.set(key, newValue);
449    }
450    this.oldTypeValues_.set(key, typeName);
451
452    this.storeKeyToPersistenceV2(key, areaMode);
453  }
454
455  private disconnectValue(key: string): void {
456    const keyType: MapType = this.getKeyMapType(key);
457    let areaMode: number | undefined = undefined;
458    if (keyType === MapType.GLOBAL_MAP) {
459      this.globalMap_.delete(key);
460      areaMode = this.globalMapAreaMode_.get(key);
461      this.globalMapAreaMode_.delete(key);
462    } else if (keyType === MapType.MODULE_MAP) {
463      this.map_.delete(key);
464    }
465
466    this.oldTypeValues_.delete(key);
467    this.removeFromPersistenceV2(key, areaMode);
468  }
469
470  private checkTypeIsClassObject<T>(keyOrType: string | TypeConstructorWithArgs<T>) {
471    if ((typeof keyOrType !== 'string' && typeof keyOrType !== 'function') ||
472      PersistenceV2Impl.NOT_SUPPORT_TYPES_.includes(keyOrType as any)) {
473      throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_);
474    }
475  }
476
477  private getRightKey<T extends object>(type: TypeConstructorWithArgs<T>,
478    keyOrDefaultCreator?: string | StorageDefaultCreator<T>) {
479    const key: string = this.getConnectedKey(type, keyOrDefaultCreator);
480
481    if (key === undefined) {
482      throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_);
483    }
484
485    if (key === PersistenceV2Impl.KEYS_ARR_) {
486      this.errorHelper(key, PersistError.Quota, `The key '${key}' cannot be used`);
487      return undefined;
488    }
489
490    return key;
491  }
492
493  private getRightGlobalKey<T extends object>(type: TypeConstructorWithArgs<T>,
494    key: string | undefined): string {
495    if (key === undefined || key === null) {
496      stateMgmtConsole.applicationWarn(StorageHelper.NULL_OR_UNDEFINED_KEY + ', try to use the type name as key');
497      key = this.getTypeName(type);
498      if (key === undefined) {
499        throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_);
500      }
501    }
502
503    if (key === PersistenceV2Impl.KEYS_ARR_) {
504      this.errorHelper(key, PersistError.Quota, `The key '${key}' cannot be used`);
505      return undefined;
506    }
507    return key;
508  }
509
510  private getAreaMode(areaMode: number | undefined): number {
511    if (typeof areaMode === 'undefined') {
512      return AreaMode.EL2;
513    }
514    else if (areaMode >= AreaMode.EL1 && areaMode <= AreaMode.EL5) {
515      return areaMode;
516    } else {
517      throw new Error(PersistenceV2Impl.NOT_SUPPORT_AREAMODE_MESSAGE_);
518    }
519  }
520
521  private getKeyMapType(key: string): MapType {
522    if (this.globalMap_.has(key)) {
523      return MapType.GLOBAL_MAP;
524    } else if (this.map_.has(key)) {
525      return MapType.MODULE_MAP;
526    } else {
527      return MapType.NOT_IN_MAP;
528    }
529  }
530
531  private getConnectDefaultValue<T extends object>(key: string, type: TypeConstructorWithArgs<T>,
532    defaultCreator?: StorageDefaultCreator<T>): T | undefined {
533    if (!PersistenceV2Impl.storage_) {
534      this.errorHelper(key, PersistError.Unknown, `The storage is null`);
535      return undefined;
536    }
537
538    if (typeof defaultCreator !== 'function') {
539      throw new Error(PersistenceV2Impl.INVALID_DEFAULT_VALUE);
540    }
541
542    const observedValue: T = defaultCreator();
543
544    this.checkTypeByInstanceOf(key, type, observedValue);
545
546    if (this.isNotClassObject(observedValue)) {
547      throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_);
548    }
549
550    return observedValue;
551  }
552
553  private getValueFromDisk<T extends object>(key: string, id: number, observedValue: T,
554    type: TypeConstructorWithArgs<T>, areaMode?: number | undefined): T | undefined {
555    let newObservedValue: T;
556
557    try {
558      const json: string = PersistenceV2Impl.storage_.get(key, areaMode);
559
560      // Adding ref for persistence
561      ObserveV2.getObserve().startRecordDependencies(this, id);
562      newObservedValue = JSONCoder.parseTo(observedValue, json) as T;
563      ObserveV2.getObserve().stopRecordDependencies();
564    } catch (err) {
565      this.errorHelper(key, PersistError.Serialization, err);
566    }
567
568    this.checkTypeByInstanceOf(key, type, newObservedValue);
569
570    this.connectNewValue(key, newObservedValue, this.getTypeName(type), areaMode);
571    return newObservedValue;
572  }
573
574  private setValueToDisk<T extends object>(key: string, id: number, observedValue: T,
575    type: TypeConstructorWithArgs<T>, areaMode?: number | undefined): T | undefined {
576    ObserveV2.getObserve().startRecordDependencies(this, id);
577    // Map is a proxy object. When it is connected for the first time, map.has is used to add dependencies,
578    // and map.set is used to trigger writing to disk.
579    const keyType: MapType = this.getKeyMapType(key);
580    ObserveV2.getObserve().stopRecordDependencies();
581
582    // Writing to persistence by ref
583    if (keyType === MapType.NOT_IN_MAP) {
584      if (typeof areaMode === 'number') {
585        this.connectNewValue(key, observedValue, this.getTypeName(type), areaMode);
586      } else {
587        this.connectNewValue(key, observedValue, this.getTypeName(type));
588      }
589    }
590    return observedValue;
591  }
592
593  private writeAllChangedToFile(persistKeys: Array<number>): void {
594    for (let i = 0; i < persistKeys.length; ++i) {
595      const id: number = persistKeys[i];
596      const key: string = this.idToKey_.get(id);
597
598      try {
599        const keyType: MapType = this.getKeyMapType(key);
600
601        if (keyType !== MapType.NOT_IN_MAP) {
602          const value: object = keyType === MapType.GLOBAL_MAP ? this.globalMap_.get(key) : this.map_.get(key);
603
604          ObserveV2.getObserve().startRecordDependencies(this, id);
605          const json: string = JSONCoder.stringify(value);
606          ObserveV2.getObserve().stopRecordDependencies();
607
608          if (this.isOverSizeLimit(json)) {
609            stateMgmtConsole.applicationError(
610              `Cannot store the key '${key}'! The length of data must be less than ${PersistenceV2Impl.MAX_DATA_LENGTH_}`);
611          } else {
612            if (keyType === MapType.GLOBAL_MAP) {
613              const areaMode = this.globalMapAreaMode_.get(key);
614              PersistenceV2Impl.storage_.set(key, json, areaMode);
615            } else {
616              PersistenceV2Impl.storage_.set(key, json);
617            }
618          }
619        }
620      } catch (err) {
621        if (this.cb_ && typeof this.cb_ === 'function') {
622          this.cb_(key, PersistError.Serialization, err);
623          continue;
624        }
625
626        stateMgmtConsole.applicationError(`For '${key}' key: ` + err);
627      }
628    }
629  }
630
631  private isOverSizeLimit(json: string): boolean {
632    if (typeof json !== 'string') {
633      return false;
634    }
635
636    return json.length >= PersistenceV2Impl.MAX_DATA_LENGTH_;
637  }
638
639  private isNotClassObject(value: object): boolean {
640    return Array.isArray(value) || this.isNotSupportType(value);
641  }
642
643  private isNotSupportType(value: object): boolean {
644    for (let i = 0; i < PersistenceV2Impl.NOT_SUPPORT_TYPES_.length; ++i) {
645      if (value instanceof PersistenceV2Impl.NOT_SUPPORT_TYPES_[i]) {
646        return true;
647      }
648    }
649    return false;
650  }
651
652  private storeKeyToPersistenceV2(key: string, areaMode?: number | undefined): void {
653    try {
654      if (typeof areaMode === 'number') {
655        if (this.globalKeysArr_[areaMode].has(key)) {
656          return;
657        }
658
659        // Initializing the keys arr in memory
660        if (!this.globalKeysArr_[areaMode].size) {
661          this.globalKeysArr_[areaMode] = this.getKeysArrFromStorage(areaMode);
662        }
663
664        this.globalKeysArr_[areaMode].add(key);
665
666        // Updating the keys arr in disk
667        this.storeKeysArrToStorage(this.globalKeysArr_[areaMode], areaMode);
668      } else {
669        if (this.keysArr_.has(key)) {
670          return;
671        }
672
673        // Initializing the keys arr in memory
674        if (!this.keysArr_.size) {
675          this.keysArr_ = this.getKeysArrFromStorage();
676        }
677
678        this.keysArr_.add(key);
679
680        // Updating the keys arr in disk
681        this.storeKeysArrToStorage(this.keysArr_);
682      }
683    } catch (err) {
684      this.errorHelper(key, PersistError.Unknown, `fail to store the key '${key}'`);
685    }
686  }
687
688  private removeForModulePath(key: string): void {
689    PersistenceV2Impl.storage_.delete(key);
690    // The first call for module path
691    if (!this.keysArr_.has(key)) {
692      this.keysArr_ = this.getKeysArrFromStorage();
693    }
694
695    this.keysArr_.delete(key);
696    this.storeKeysArrToStorage(this.keysArr_);
697  }
698
699  private getRemoveFlagForGlobalPath(key: string): boolean {
700    let removeFlag = false;
701    // first call for global path
702    for (let i = 0; i < this.globalKeysArr_.length; i++) {
703      if (PersistenceV2Impl.storage_.has(key, i)) {
704        removeFlag = true;
705        PersistenceV2Impl.storage_.delete(key, i);
706        this.globalKeysArr_[i] = this.getKeysArrFromStorage(i);
707        this.globalKeysArr_[i].delete(key);
708        this.storeKeysArrToStorage(this.globalKeysArr_[i], i);
709      }
710    }
711    return removeFlag;
712  }
713
714  private removeFromPersistenceV2(key: string, areaMode: number | undefined): void {
715    try {
716      // check for global path
717      if (typeof areaMode === 'number') {
718        PersistenceV2Impl.storage_.delete(key, areaMode);
719        this.globalKeysArr_[areaMode].delete(key);
720        this.storeKeysArrToStorage(this.globalKeysArr_[areaMode], areaMode);
721      } else {
722        let removeFlag: boolean = false;
723        // check for module path
724        if (PersistenceV2Impl.storage_.has(key)) {
725          removeFlag = true;
726          this.removeForModulePath(key);
727        } else {
728          removeFlag = this.getRemoveFlagForGlobalPath(key);
729        }
730        if (!removeFlag) {
731          stateMgmtConsole.applicationWarn(PersistenceV2Impl.DELETE_NOT_EXIST_KEY + `keys:${key}`);
732        }
733      }
734    } catch (err) {
735      this.errorHelper(key, PersistError.Unknown, `fail to remove the key '${key}'`);
736    }
737  }
738
739  private getKeysArrFromStorage(areaMode?: number | undefined): Set<string> {
740    if (typeof areaMode === 'number' && !PersistenceV2Impl.storage_.has(PersistenceV2Impl.KEYS_ARR_, areaMode)) {
741      return this.globalKeysArr_[areaMode];
742    }
743    else if (typeof areaMode === 'undefined' && !PersistenceV2Impl.storage_.has(PersistenceV2Impl.KEYS_ARR_)) {
744      return this.keysArr_;
745    }
746    const jsonKeysArr: string = PersistenceV2Impl.storage_.get(PersistenceV2Impl.KEYS_ARR_, areaMode);
747    return new Set(JSON.parse(jsonKeysArr));
748  }
749
750  private storeKeysArrToStorage(keysArr: Set<string>, areaMode?: number | undefined): void {
751    PersistenceV2Impl.storage_.set(PersistenceV2Impl.KEYS_ARR_, JSON.stringify(Array.from(keysArr)), areaMode);
752  }
753
754  private errorHelper(key: string, reason: PersistError, message: string) {
755    if (this.cb_ && typeof this.cb_ === 'function') {
756      this.cb_(key, reason, message);
757      return;
758    }
759
760    if (!key) {
761      key = 'unknown';
762    }
763    throw new Error(`For '${key}' key: ` + message);
764  }
765}
766