b6f81731596799bbaaa15e1da3fa94666b0c1f0a
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / core / idl.service.ts
1 import {Injectable} from '@angular/core';
2
3 // Added globally by /IDL2js
4 declare var _preload_fieldmapper_IDL: Object;
5
6 /**
7  * Every IDL object class implements this interface.
8  */
9 export interface IdlObject {
10     a: any[];
11     classname: string;
12     _isfieldmapper: boolean;
13     // Dynamically appended functions from the IDL.
14     [fields: string]: any;
15 }
16
17 @Injectable({providedIn: 'root'})
18 export class IdlService {
19
20     classes: any = {}; // IDL class metadata
21     constructors = {}; // IDL instance generators
22
23     /**
24      * Create a new IDL object instance.
25      */
26     create(cls: string, seed?: any[]): IdlObject {
27         if (this.constructors[cls]) {
28             return new this.constructors[cls](seed);
29         }
30         throw new Error(`No such IDL class ${cls}`);
31     }
32
33     parseIdl(): void {
34
35         try {
36             this.classes = _preload_fieldmapper_IDL;
37         } catch (E) {
38             console.error('IDL (IDL2js) not found.  Is the system running?');
39             return;
40         }
41
42         /**
43          * Creates the class constructor and getter/setter
44          * methods for each IDL class.
45          */
46         const mkclass = (cls, fields) => {
47             this.classes[cls].classname = cls;
48
49             // This dance lets us encode each IDL object with the
50             // IdlObject interface.  Useful for adding type restrictions
51             // where desired for functions, etc.
52             const generator: any = ((): IdlObject => {
53
54                 const x: any = function(seed) {
55                     this.a = seed || [];
56                     this.classname = cls;
57                     this._isfieldmapper = true;
58                 };
59
60                 fields.forEach(function(field, idx) {
61                     x.prototype[field.name] = function(n) {
62                         if (arguments.length === 1) {
63                             this.a[idx] = n;
64                         }
65                         return this.a[idx];
66                     };
67
68                     if (!field.label) {
69                         field.label = field.name;
70                     }
71
72                     // Coerce 'aou' links to datatype org_unit for consistency.
73                     if (field.datatype === 'link' && field.class === 'aou') {
74                         field.datatype = 'org_unit';
75                     }
76                 });
77
78                 return x;
79             });
80
81             this.constructors[cls] = generator();
82
83             // global class constructors required for JSON_v1.js
84             // TODO: polluting the window namespace w/ every IDL class
85             // is less than ideal.
86             window[cls] = this.constructors[cls];
87         };
88
89         Object.keys(this.classes).forEach(class_ => {
90             mkclass(class_, this.classes[class_].fields);
91         });
92     }
93
94     // Makes a deep copy of an IdlObject's / structures containing
95     // IdlObject's.  Note we don't use JSON cross-walk because our
96     // JSON lib does not handle circular references.
97     // @depth specifies the maximum number of steps through IdlObject'
98     // we will traverse.
99     clone(source: any, depth?: number): any {
100         if (depth === undefined) {
101             depth = 100;
102         }
103
104         let result;
105         if (typeof source === 'undefined' || source === null) {
106             return source;
107
108         } else if (source._isfieldmapper) {
109             // same depth because we're still cloning this same object
110             result = this.create(source.classname, this.clone(source.a, depth));
111
112         } else {
113             if (Array.isArray(source)) {
114                 result = [];
115             } else if (typeof source === 'object') { // source is not null
116                 result = {};
117             } else {
118                 return source; // primitive
119             }
120
121             for (const j in source) {
122                 if (source[j] === null || typeof source[j] === 'undefined') {
123                     result[j] = source[j];
124                 } else if (source[j]._isfieldmapper) {
125                     if (depth) {
126                         result[j] = this.clone(source[j], depth - 1);
127                     }
128                 } else {
129                     result[j] = this.clone(source[j], depth);
130                 }
131             }
132         }
133
134         return result;
135     }
136
137     // Given a field on an IDL class, returns the name of the field
138     // on the linked class that acts as the selector for the linked class.
139     // Returns null if no selector is found or the field is not a link.
140     getLinkSelector(fmClass: string, field: string): string {
141         const fieldDef = this.classes[fmClass].field_map[field];
142         if (fieldDef.class) {
143             const classDef = this.classes[fieldDef.class];
144             if (classDef.pkey) {
145                 return classDef.field_map[classDef.pkey].selector || null;
146             }
147         }
148         return null;
149     }
150 }
151