UNPKG

8.59 kBJavaScriptView Raw
1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15'use strict';
16
17const ClassDeclaration = require('../introspect/classdeclaration');
18const EnumDeclaration = require('../introspect/enumdeclaration');
19const Field = require('../introspect/field');
20const ModelUtil = require('../modelutil');
21const RelationshipDeclaration = require('../introspect/relationshipdeclaration');
22const Util = require('../util');
23const Globalize = require('../globalize');
24
25/**
26 * Generate sample instance data for the specified class declaration
27 * and resource instance. The specified resource instance will be
28 * updated with either default values or generated sample data.
29 * @private
30 * @class
31 * @memberof module:concerto-core
32 */
33class InstanceGenerator {
34
35 /**
36 * Visitor design pattern
37 * @param {Object} thing - the object being visited
38 * @param {Object} parameters - the parameter
39 * @return {Object} the result of visiting or null
40 * @private
41 */
42 visit(thing, parameters) {
43 if (thing instanceof ClassDeclaration) {
44 return this.visitClassDeclaration(thing, parameters);
45 } else if (thing instanceof RelationshipDeclaration) {
46 return this.visitRelationshipDeclaration(thing, parameters);
47 } else if (thing instanceof Field) {
48 return this.visitField(thing, parameters);
49 } else {
50 throw new Error('Unrecognised ' + JSON.stringify(thing) );
51 }
52 }
53
54 /**
55 * Visitor design pattern
56 * @param {ClassDeclaration} classDeclaration - the object being visited
57 * @param {Object} parameters - the parameter
58 * @return {Object} the result of visiting or null
59 * @private
60 */
61 visitClassDeclaration(classDeclaration, parameters) {
62 const obj = parameters.stack.pop();
63 const properties = classDeclaration.getProperties();
64 for (const property of properties) {
65 if (!parameters.includeOptionalFields && property.isOptional()) {
66 continue;
67 }
68 const value = obj[property.getName()];
69 if(Util.isNull(value)) {
70 obj[property.getName()] = property.accept(this,parameters);
71 }
72 }
73 return obj;
74 }
75
76 /**
77 * Visitor design pattern
78 * @param {Field} field - the object being visited
79 * @param {Object} parameters - the parameter
80 * @return {Object} the result of visiting or null
81 * @private
82 */
83 visitField(field, parameters) {
84 if(!field.isPrimitive()){
85 let type = field.getFullyQualifiedTypeName();
86 let classDeclaration = parameters.modelManager.getType(type);
87 classDeclaration = this.findConcreteSubclass(classDeclaration);
88 let fqn = classDeclaration.getFullyQualifiedName();
89
90 if (parameters.seen.includes(fqn)){
91 if (field.isArray()) {
92 return [];
93 }
94 if (field.isOptional()) {
95 return null;
96 }
97 throw new Error('Model is recursive.');
98 }
99 parameters.seen.push(fqn);
100 } else { parameters.seen.push('Primitve');
101 }
102 let result;
103 if (field.isArray()) {
104 const valueSupplier = () => this.getFieldValue(field, parameters);
105 result = parameters.valueGenerator.getArray(valueSupplier);
106 } else {
107 result = this.getFieldValue(field, parameters);
108 }
109 parameters.seen.pop();
110 return result;
111 }
112
113
114 /**
115 * Get a value for the specified field.
116 * @param {Field} field - the object being visited
117 * @param {Object} parameters - the parameter
118 * @return {*} A value for the specified field.
119 */
120 getFieldValue(field, parameters) {
121 let type = field.getFullyQualifiedTypeName();
122
123 if (ModelUtil.isPrimitiveType(type)) {
124 switch(type) {
125 case 'DateTime':
126 return parameters.valueGenerator.getDateTime();
127 case 'Integer':
128 return parameters.valueGenerator.getInteger();
129 case 'Long':
130 return parameters.valueGenerator.getLong();
131 case 'Double':
132 return parameters.valueGenerator.getDouble();
133 case 'Boolean':
134 return parameters.valueGenerator.getBoolean();
135 default:
136 return parameters.valueGenerator.getString();
137 }
138 }
139
140 let classDeclaration = parameters.modelManager.getType(type);
141
142 if (classDeclaration instanceof EnumDeclaration) {
143 let enumValues = classDeclaration.getOwnProperties();
144 return parameters.valueGenerator.getEnum(enumValues).getName();
145 }
146
147 classDeclaration = this.findConcreteSubclass(classDeclaration);
148
149 if (classDeclaration.isConcept()) {
150 let concept = parameters.factory.newConcept(classDeclaration.getNamespace(), classDeclaration.getName());
151 parameters.stack.push(concept);
152 return classDeclaration.accept(this, parameters);
153 } else {
154 const id = this.generateRandomId(classDeclaration);
155 let resource = parameters.factory.newResource(classDeclaration.getNamespace(), classDeclaration.getName(), id);
156 parameters.stack.push(resource);
157 return classDeclaration.accept(this, parameters);
158 }
159 }
160
161 /**
162 * Find a concrete type that extends the provided type. If the supplied type argument is
163 * not abstract then it will be returned.
164 * TODO: work out whether this has to be a leaf node or whether the closest type can be used
165 * It depends really since the closest type will satisfy the model but whether it satisfies
166 * any transaction code which attempts to use the generated resource is another matter.
167 * @param {ClassDeclaration} declaration the class declaration.
168 * @return {ClassDeclaration} the closest extending concrete class definition.
169 * @throws {Error} if no concrete subclasses exist.
170 */
171 findConcreteSubclass(declaration) {
172 if (!declaration.isAbstract()) {
173 return declaration;
174 }
175
176 const concreteSubclasses = declaration.getAssignableClassDeclarations()
177 .filter(subclass => !subclass.isAbstract())
178 .filter(subclass => !subclass.isSystemType());
179
180 if (concreteSubclasses.length === 0) {
181 const formatter = Globalize.messageFormatter('instancegenerator-newinstance-noconcreteclass');
182 throw new Error(formatter({ type: declaration.getFullyQualifiedName() }));
183 }
184
185 return concreteSubclasses[0];
186 }
187
188
189
190 /**
191 * Visitor design pattern
192 * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited
193 * @param {Object} parameters - the parameter
194 * @return {Relationship} the result of visiting
195 * @private
196 */
197 visitRelationshipDeclaration(relationshipDeclaration, parameters) {
198 let classDeclaration = parameters.modelManager.getType(relationshipDeclaration.getFullyQualifiedTypeName());
199 classDeclaration = this.findConcreteSubclass(classDeclaration);
200 const factory = parameters.factory;
201 const valueSupplier = () => {
202 const id = this.generateRandomId(classDeclaration);
203 return factory.newRelationship(classDeclaration.getNamespace(), classDeclaration.getName(), id);
204 };
205 if (relationshipDeclaration.isArray()) {
206 return parameters.valueGenerator.getArray(valueSupplier);
207 } else {
208 return valueSupplier();
209 }
210 }
211
212 /**
213 * Generate a random ID for a given type.
214 * @private
215 * @param {ClassDeclaration} classDeclaration - class declaration for a type.
216 * @return {String} an ID.
217 */
218 generateRandomId(classDeclaration) {
219 let id = Math.round(Math.random() * 9999).toString();
220 id = id.padStart(4, '0');
221 return id;
222 }
223
224}
225
226module.exports = InstanceGenerator;