• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-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/**
18 * ObservedObject, i.e. wrapper for class Object type state variable
19 *
20 * This class and all other definitoons in this file are framework
21 * internal / non-SDK
22 *
23 */
24
25
26// class of Class in TS
27// ad defined in @angular/core
28interface Type<T> extends Function {
29  new(...args: any[]): T;
30}
31
32/**
33* @Observed Decorator function, use
34*    @Observed class ClassA { ... }
35* when defining ClassA
36*
37* Can also be used to create a new Object and wrap it in
38* ObservedObject by calling
39*   obsObj = Observed(ClassA)(params to ClassA constructor)
40*
41* Note this works only for classes, not for ClassA[]
42* Also does not work for classes with genetics it seems
43* In that case use factory function
44*   obsObj = ObservedObject.createNew<ClassA[]>([])
45*/
46
47function Observed<C extends Object>(target: Type<C>): any {
48  var original = target;
49  // the new constructor behaviour
50  var f: any = function (...args: any[]) {
51    stateMgmtConsole.debug(`New ${original.name}, gets wrapped inside ObservableObject proxy.`);
52    return ObservedObject.createNew(new original(...args), undefined);
53//    return new ObservedObject<C>(new original(...args), undefined);
54  };
55
56  Object.setPrototypeOf(f, Object.getPrototypeOf(original));
57  // return new constructor (will override original)
58  return f;
59}
60
61
62class SubscribableHandler {
63  static IS_OBSERVED_OBJECT = Symbol("_____is_observed_object__");
64  static RAW_OBJECT = Symbol("_____raw_object__");
65  static SUBSCRIBE = Symbol("_____subscribe__");
66  static UNSUBSCRIBE = Symbol("_____unsubscribe__")
67
68  private owningProperties_: Set<number>
69
70  constructor(owningProperty: IPropertySubscriber) {
71    this.owningProperties_ = new Set<number>();
72    if (owningProperty) {
73      this.addOwningProperty(owningProperty);
74    }
75    stateMgmtConsole.debug(`SubscribableHandler: construcstor done`);
76  }
77
78  addOwningProperty(subscriber: IPropertySubscriber): void {
79    if (subscriber) {
80        stateMgmtConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id__()}'.`)
81        this.owningProperties_.add(subscriber.id__());
82    } else {
83        stateMgmtConsole.warn(`SubscribableHandler: addOwningProperty: undefined subscriber. - Internal error?`);
84    }
85  }
86
87  /*
88      the inverse function of createOneWaySync or createTwoWaySync
89    */
90  public removeOwningProperty(property: IPropertySubscriber): void {
91    return this.removeOwningPropertyById(property.id__());
92  }
93
94  public removeOwningPropertyById(subscriberId: number): void {
95    stateMgmtConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`)
96    this.owningProperties_.delete(subscriberId);
97  }
98
99
100  protected notifyPropertyHasChanged(propName: string, newValue: any) {
101    stateMgmtConsole.debug(`SubscribableHandler: notifyPropertyHasChanged '${propName}'.`)
102    this.owningProperties_.forEach((subscribedId) => {
103      var owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId)
104      if (owningProperty) {
105        if ('hasChanged' in owningProperty) {
106          (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue);
107        }
108        if ('propertyHasChanged' in owningProperty) {
109          (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName);
110        }
111      } else {
112        stateMgmtConsole.warn(`SubscribableHandler: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`);
113      }
114    });
115  }
116
117  public get(target: Object, property: PropertyKey): any {
118    return (property === SubscribableHandler.IS_OBSERVED_OBJECT) ? true :
119      (property === SubscribableHandler.RAW_OBJECT) ? target : target[property];
120  }
121
122  public set(target: Object, property: PropertyKey, newValue: any): boolean {
123    switch (property) {
124      case SubscribableHandler.SUBSCRIBE:
125        // assignment obsObj[SubscribableHandler.SUBSCRCRIBE] = subscriber
126        this.addOwningProperty(newValue as IPropertySubscriber);
127        return true;
128        break;
129      case SubscribableHandler.UNSUBSCRIBE:
130        // assignment obsObj[SubscribableHandler.UN_SUBSCRCRIBE] = subscriber
131        this.removeOwningProperty(newValue as IPropertySubscriber);
132        return true;
133        break;
134      default:
135        if (target[property] == newValue) {
136          return true;
137        }
138        target[property] = newValue;
139        this.notifyPropertyHasChanged(property.toString(), newValue);
140        return true;
141        break;
142    }
143
144    // unreachable
145    return false;
146  }
147}
148
149
150class ExtendableProxy {
151  constructor(obj: Object, handler: SubscribableHandler) {
152    return new Proxy(obj, handler);
153  }
154}
155
156class ObservedObject<T extends Object> extends ExtendableProxy {
157
158  /**
159   * Factory function for ObservedObjects /
160   *  wrapping of objects for proxying
161   *
162   * @param rawObject unproxied Object or ObservedObject
163   * @param objOwner owner of this Object to sign uop for propertyChange
164   *          notifications
165   * @returns the rawObject if object is already an ObservedObject,
166   *          otherwise the newly created ObservedObject
167   */
168  public static createNew<T extends Object>(rawObject: T,
169    owningProperty: IPropertySubscriber): any {
170
171    if (rawObject === null || rawObject === undefined) {
172      stateMgmtConsole.error(`ObservedObject.CreateNew, input object must not be null or undefined.`);
173      return null;
174    }
175
176    if (ObservedObject.IsObservedObject(rawObject)) {
177      ObservedObject.addOwningProperty(rawObject, owningProperty);
178      return rawObject;
179    } else {
180      return new ObservedObject<T>(rawObject, owningProperty);
181    }
182  }
183
184  /*
185    Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy
186    no set observation, no notification of changes!
187    Use with caution, do not store any references
188  */
189  static GetRawObject(obj: any): any {
190    return !ObservedObject.IsObservedObject(obj) ? obj : obj[SubscribableHandler.RAW_OBJECT];
191  }
192
193  /**
194   *
195   * @param obj anything
196   * @returns true if the parameter is an Object wrpped with a ObservedObject
197   * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use
198   * this static function instead.
199   */
200  static IsObservedObject(obj: any): boolean {
201    return obj ? (obj[SubscribableHandler.IS_OBSERVED_OBJECT] === true) : false;
202  }
203
204  static addOwningProperty(obj: Object, subscriber: IPropertySubscriber): boolean {
205    if (!ObservedObject.IsObservedObject(obj)) {
206      return false;
207    }
208
209    obj[SubscribableHandler.SUBSCRIBE] = subscriber;
210    return true;
211  }
212
213  static removeOwningProperty(obj: Object,
214    subscriber: IPropertySubscriber): boolean {
215    if (!ObservedObject.IsObservedObject(obj)) {
216      return false;
217    }
218
219    obj[SubscribableHandler.UNSUBSCRIBE] = subscriber;
220    return true;
221  }
222
223  /**
224   * Create a new ObservableObject and subscribe its owner to propertyHasChanged
225   * ntifications
226   * @param obj  raw Object, if obj is a ObservableOject throws an error
227   * @param objectOwner
228   */
229  constructor(obj: T, objectOwningProperty: IPropertySubscriber) {
230    if (ObservedObject.IsObservedObject(obj)) {
231      throw new Error("Invalid constructor argument error: ObservableObject contructor called with an ObservedObject as parameer");
232    }
233    let handler = new SubscribableHandler(objectOwningProperty);
234    super(obj, handler);
235
236    if (ObservedObject.IsObservedObject(obj)) {
237      stateMgmtConsole.error("ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already");
238    }
239  } // end of constructor
240
241}
242