| /** | 
 |  * @license | 
 |  * Copyright (C) 2021 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  * http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | // A finalizable object has a single method `finalize` that is called when | 
 | // the object is no longer needed and should clean itself up. | 
 | export interface Finalizable { | 
 |   finalize(): void; | 
 | } | 
 |  | 
 | // A factory can take a partially created TContext and generate a property | 
 | // for a given key on that TContext. | 
 | export type Factory<TContext, K extends keyof TContext> = ( | 
 |   ctx: Partial<TContext> | 
 | ) => TContext[K] & Finalizable; | 
 |  | 
 | // A registry contains a factory for each key in TContext. | 
 | export type Registry<TContext> = {[P in keyof TContext]: Factory<TContext, P>} & | 
 |   Record<string, (_: TContext) => Finalizable>; | 
 |  | 
 | // Creates a context given a registry. | 
 | export function create<TContext>( | 
 |   registry: Registry<TContext> | 
 | ): TContext & Finalizable { | 
 |   const context: Partial<TContext> & Finalizable = { | 
 |     finalize() { | 
 |       for (const key of Object.getOwnPropertyNames(registry)) { | 
 |         const name = key as keyof TContext; | 
 |         try { | 
 |           if (this[name]) { | 
 |             (this[name] as unknown as Finalizable).finalize(); | 
 |           } | 
 |         } catch (e) { | 
 |           console.info(`Failed to finalize ${name}`); | 
 |           throw e; | 
 |         } | 
 |       } | 
 |     }, | 
 |   } as Partial<TContext> & Finalizable; | 
 |  | 
 |   const initialized: Map<keyof TContext, Finalizable> = new Map< | 
 |     keyof TContext, | 
 |     Finalizable | 
 |   >(); | 
 |   for (const key of Object.keys(registry)) { | 
 |     const name = key as keyof TContext; | 
 |     const factory = registry[name]; | 
 |     let initializing = false; | 
 |     Object.defineProperty(context, name, { | 
 |       configurable: true, // Tests can mock properties | 
 |       get() { | 
 |         if (!initialized.has(name)) { | 
 |           // Notice that this is the getter for the property in question. | 
 |           // It is possible that during the initialization of one property, | 
 |           // another property is required. This extra check ensures that | 
 |           // the construction of propertiers on Context are not circularly | 
 |           // dependent. | 
 |           if (initializing) throw new Error(`Circular dependency for ${key}`); | 
 |           try { | 
 |             initializing = true; | 
 |             initialized.set(name, factory(context)); | 
 |           } catch (e) { | 
 |             console.error(`Failed to initialize ${name}`, e); | 
 |           } finally { | 
 |             initializing = false; | 
 |           } | 
 |         } | 
 |         return initialized.get(name); | 
 |       }, | 
 |     }); | 
 |   } | 
 |   return context as TContext & Finalizable; | 
 | } |