1 import {Injectable} from '@angular/core';
3 // Added globally by /IDL2js
4 declare var _preload_fieldmapper_IDL: Object;
7 * Every IDL object class implements this interface.
9 export interface IdlObject {
12 _isfieldmapper: boolean;
13 // Dynamically appended functions from the IDL.
14 [fields: string]: any;
17 @Injectable({providedIn: 'root'})
18 export class IdlService {
20 classes: any = {}; // IDL class metadata
21 constructors = {}; // IDL instance generators
24 * Create a new IDL object instance.
26 create(cls: string, seed?: any[]): IdlObject {
27 if (this.constructors[cls]) {
28 return new this.constructors[cls](seed);
30 throw new Error(`No such IDL class ${cls}`);
36 this.classes = _preload_fieldmapper_IDL;
38 console.error('IDL (IDL2js) not found. Is the system running?');
43 * Creates the class constructor and getter/setter
44 * methods for each IDL class.
46 const mkclass = (cls, fields) => {
47 this.classes[cls].classname = cls;
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 => {
54 const x: any = function(seed) {
57 this._isfieldmapper = true;
60 fields.forEach(function(field, idx) {
61 x.prototype[field.name] = function(n) {
62 if (arguments.length === 1) {
69 field.label = field.name;
72 // Coerce 'aou' links to datatype org_unit for consistency.
73 if (field.datatype === 'link' && field.class === 'aou') {
74 field.datatype = 'org_unit';
81 this.constructors[cls] = generator();
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];
89 Object.keys(this.classes).forEach(class_ => {
90 mkclass(class_, this.classes[class_].fields);
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'
99 clone(source: any, depth?: number): any {
100 if (depth === undefined) {
105 if (typeof source === 'undefined' || source === null) {
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));
113 if (Array.isArray(source)) {
115 } else if (typeof source === 'object') { // source is not null
118 return source; // primitive
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) {
126 result[j] = this.clone(source[j], depth - 1);
129 result[j] = this.clone(source[j], depth);
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 let fieldDef = this.classes[fmClass].field_map[field];
145 `No such field "${field}" for IDL class "${fmClass}"`);
150 // For mapped fields, we want the selector field on the
151 // remotely linked object instead of the directly
153 const linkedClass = this.classes[fieldDef.class];
154 fieldDef = linkedClass.field_map[fieldDef.map];
157 if (fieldDef.class) {
158 return this.getClassSelector(fieldDef.class);
163 // Return the selector field for the class. If no selector is
164 // defined, use 'name' if it exists as a field on the class.
165 getClassSelector(idlClass: string): string {
168 const classDef = this.classes[idlClass];
171 const selector = classDef.field_map[classDef.pkey].selector;
172 if (selector) { return selector; }
174 // No selector defined in the IDL, try 'name'.
175 if ('name' in classDef.field_map) { return 'name'; }
182 toHash(obj: any, flatten?: boolean): any {
184 if (typeof obj !== 'object' || obj === null) {
188 if (Array.isArray(obj)) {
189 return obj.map(item => this.toHash(item));
192 const fieldNames = obj._isfieldmapper ?
193 Object.keys(this.classes[obj.classname].field_map) :
196 const hash: any = {};
197 fieldNames.forEach(field => {
199 const val = this.toHash(
200 typeof obj[field] === 'function' ? obj[field]() : obj[field],
204 if (val === undefined) { return; }
206 if (flatten && val !== null &&
207 typeof val === 'object' && !Array.isArray(val)) {
209 Object.keys(val).forEach(key => {
210 const fname = field + '.' + key;
211 hash[fname] = val[key];
222 // Returns true if both objects have the same IDL class and pkey value.
223 pkeyMatches(obj1: IdlObject, obj2: IdlObject) {
224 if (!obj1 || !obj2) { return false; }
225 const idlClass = obj1.classname;
226 if (idlClass !== obj2.classname) { return false; }
227 const pkeyField = this.classes[idlClass].pkey || 'id';
228 return obj1[pkeyField]() === obj2[pkeyField]();