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