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 | ;
|
16 |
|
17 | const Decorated = require('./decorated');
|
18 | const ModelUtil = require('../modelutil');
|
19 |
|
20 | /**
|
21 | * Property representing an attribute of a class declaration,
|
22 | * either a Field or a Relationship.
|
23 | *
|
24 | * @class
|
25 | * @memberof module:concerto-core
|
26 | */
|
27 | class Property extends Decorated {
|
28 |
|
29 | /**
|
30 | * Create a Property.
|
31 | * @param {ClassDeclaration} parent - the owner of this property
|
32 | * @param {Object} ast - The AST created by the parser
|
33 | * @throws {IllegalModelException}
|
34 | */
|
35 | constructor(parent, ast) {
|
36 | super(parent.getModelFile(), ast);
|
37 | this.parent = parent;
|
38 | this.process();
|
39 | this._isProperty = true;
|
40 | }
|
41 |
|
42 | /**
|
43 | * Returns the owner of this property
|
44 | * @return {ClassDeclaration} the parent class declaration
|
45 | */
|
46 | getParent() {
|
47 | return this.parent;
|
48 | }
|
49 |
|
50 | /**
|
51 | * Process the AST and build the model
|
52 | * @throws {IllegalModelException}
|
53 | * @private
|
54 | */
|
55 | process() {
|
56 | super.process();
|
57 |
|
58 | this.name = this.ast.id.name;
|
59 | this.decorator = null;
|
60 |
|
61 | if(!this.name) {
|
62 | throw new Error('No name for type ' + this.ast );
|
63 | }
|
64 |
|
65 | if(this.ast.propertyType) {
|
66 | this.type = this.ast.propertyType.name;
|
67 | }
|
68 | else {
|
69 | this.type = null;
|
70 | }
|
71 | this.array = false;
|
72 |
|
73 | if(this.ast.array) {
|
74 | this.array = true;
|
75 | }
|
76 |
|
77 | if(this.ast.optional) {
|
78 | this.optional = true;
|
79 | }
|
80 | else {
|
81 | this.optional = false;
|
82 | }
|
83 | }
|
84 |
|
85 | /**
|
86 | * Validate the property
|
87 | * @param {ClassDeclaration} classDecl the class declaration of the property
|
88 | * @throws {IllegalModelException}
|
89 | * @private
|
90 | */
|
91 | validate(classDecl) {
|
92 | super.validate();
|
93 |
|
94 | if(this.type) {
|
95 | classDecl.getModelFile().resolveType( 'property ' + this.getFullyQualifiedName(), this.type);
|
96 | }
|
97 | }
|
98 |
|
99 | /**
|
100 | * Returns the name of a property
|
101 | * @return {string} the name of this field
|
102 | */
|
103 | getName() {
|
104 | return this.name;
|
105 | }
|
106 |
|
107 | /**
|
108 | * Returns the type of a property
|
109 | * @return {string} the type of this field
|
110 | */
|
111 | getType() {
|
112 | return this.type;
|
113 | }
|
114 |
|
115 | /**
|
116 | * Returns true if the field is optional
|
117 | * @return {boolean} true if the field is optional
|
118 | */
|
119 | isOptional() {
|
120 | return this.optional;
|
121 | }
|
122 |
|
123 | /**
|
124 | * Returns the fully qualified type name of a property
|
125 | * @return {string} the fully qualified type of this property
|
126 | */
|
127 | getFullyQualifiedTypeName() {
|
128 | if(this.isPrimitive()) {
|
129 | return this.type;
|
130 | }
|
131 |
|
132 | const parent = this.getParent();
|
133 | if(!parent) {
|
134 | throw new Error('Property ' + this.name + ' does not have a parent.');
|
135 | }
|
136 | const modelFile = parent.getModelFile();
|
137 | if(!modelFile) {
|
138 | throw new Error('Parent of property ' + this.name + ' does not have a ModelFile!');
|
139 | }
|
140 |
|
141 | const result = modelFile.getFullyQualifiedTypeName(this.type);
|
142 | if(!result) {
|
143 | throw new Error('Failed to find fully qualified type name for property ' + this.name + ' with type ' + this.type );
|
144 | }
|
145 |
|
146 | return result;
|
147 | }
|
148 |
|
149 | /**
|
150 | * Returns the fully name of a property (ns + class name + property name)
|
151 | * @return {string} the fully qualified name of this property
|
152 | */
|
153 | getFullyQualifiedName() {
|
154 | return this.getParent().getFullyQualifiedName() + '.' + this.getName();
|
155 | }
|
156 |
|
157 | /**
|
158 | * Returns the namespace of the parent of this property
|
159 | * @return {string} the namespace of the parent of this property
|
160 | */
|
161 | getNamespace() {
|
162 | return this.getParent().getNamespace();
|
163 | }
|
164 |
|
165 | /**
|
166 | * Returns true if the field is declared as an array type
|
167 | * @return {boolean} true if the property is an array type
|
168 | */
|
169 | isArray() {
|
170 | return this.array;
|
171 | }
|
172 |
|
173 |
|
174 | /**
|
175 | * Returns true if the field is declared as an enumerated value
|
176 | * @return {boolean} true if the property is an enumerated value
|
177 | */
|
178 | isTypeEnum() {
|
179 | if(this.isPrimitive()) {
|
180 | return false;
|
181 | }
|
182 | else {
|
183 | const type = this.getParent().getModelFile().getType(this.getType());
|
184 | return type.isEnum();
|
185 | }
|
186 | }
|
187 |
|
188 | /**
|
189 | * Returns true if this property is a primitive type.
|
190 | *@return {boolean} true if the property is a primitive type.
|
191 | */
|
192 | isPrimitive() {
|
193 | return ModelUtil.isPrimitiveType(this.getType());
|
194 | }
|
195 |
|
196 | /**
|
197 | * Alternative instanceof that is reliable across different module instances
|
198 | * @see https://github.com/hyperledger/composer-concerto/issues/47
|
199 | *
|
200 | * @param {object} object - The object to test against
|
201 | * @returns {boolean} - True, if the object is an instance of a Property
|
202 | */
|
203 | static [Symbol.hasInstance](object){
|
204 | return typeof object !== 'undefined' && object !== null && Boolean(object._isProperty);
|
205 | }
|
206 | }
|
207 |
|
208 | module.exports = Property;
|