-
Notifications
You must be signed in to change notification settings - Fork 0
/
EmfParsleyDslValidator.xtend
343 lines (297 loc) · 11.2 KB
/
EmfParsleyDslValidator.xtend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*******************************************************************************
* Copyright (c) 2013 RCP Vision (http://www.rcp-vision.com) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Lorenzo Bettini - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.parsley.dsl.validation
import com.google.common.collect.ListMultimap
import com.google.inject.Inject
import java.util.List
import java.util.Set
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.parsley.dsl.model.EmfFeatureAccess
import org.eclipse.emf.parsley.dsl.model.FieldSpecification
import org.eclipse.emf.parsley.dsl.model.ModelPackage
import org.eclipse.emf.parsley.dsl.model.Module
import org.eclipse.emf.parsley.dsl.model.PartSpecification
import org.eclipse.emf.parsley.dsl.model.ProviderBinding
import org.eclipse.emf.parsley.dsl.model.TypeBinding
import org.eclipse.emf.parsley.dsl.model.ValueBinding
import org.eclipse.emf.parsley.dsl.model.ViewSpecification
import org.eclipse.emf.parsley.dsl.model.WithExtendsClause
import org.eclipse.emf.parsley.dsl.typing.EmfParsleyDslTypeSystem
import org.eclipse.emf.parsley.dsl.util.EmfParsleyDslGuiceModuleHelper
import org.eclipse.xtext.common.types.JvmGenericType
import org.eclipse.xtext.common.types.JvmOperation
import org.eclipse.xtext.common.types.JvmTypeReference
import org.eclipse.xtext.resource.IContainer
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider
import org.eclipse.xtext.validation.Check
import org.eclipse.xtext.validation.CheckType
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations
import org.eclipse.xtext.xbase.typesystem.util.Multimaps2
//import org.eclipse.xtext.validation.Check
/**
* Custom validation rules.
*
* see http://www.eclipse.org/Xtext/documentation.html#validation
*/
class EmfParsleyDslValidator extends AbstractEmfParsleyDslValidator {
public static val TYPE_MISMATCH = "org.eclipse.emf.parsley.dsl.TypeMismatch";
public static val CYCLIC_INHERITANCE = "org.eclipse.emf.parsley.dsl.CyclicInheritance";
public static val FINAL_FIELD_NOT_INITIALIZED = "org.eclipse.emf.parsley.dsl.FinalFieldNotInitialized";
public static val TOO_LITTLE_TYPE_INFORMATION = "org.eclipse.emf.parsley.dsl.TooLittleTypeInformation";
public static val DUPLICATE_BINDING = "org.eclipse.emf.parsley.dsl.DuplicateBinding";
public static val DUPLICATE_ELEMENT = "org.eclipse.emf.parsley.dsl.DuplicateElement";
public static val NON_COMPLIANT_BINDING = "org.eclipse.emf.parsley.dsl.NonCompliantBinding";
@Inject EmfParsleyDslTypeSystem typeSystem
@Inject extension EmfParsleyDslExpectedSuperTypes
@Inject extension EmfParsleyDslGuiceModuleHelper
@Inject extension IJvmModelAssociations
@Inject ResourceDescriptionsProvider rdp
@Inject IContainer.Manager cm
val modelPackage = ModelPackage.eINSTANCE
// perform this check only on file save
@Check(CheckType.NORMAL)
def void checkDuplicateViewSpecificationAcrossFiles(ViewSpecification viewSpecification) {
val descriptions = getVisibleEObjectDescriptions(viewSpecification,
ModelPackage.Literals.VIEW_SPECIFICATION)
for (desc : descriptions) {
if (desc.qualifiedName.toString == viewSpecification.id &&
desc.EObjectOrProxy != viewSpecification &&
desc.EObjectURI.trimFragment != viewSpecification.eResource.URI) {
error(
"The part id " + viewSpecification.id + " is already defined",
modelPackage.viewSpecification_Id,
DUPLICATE_ELEMENT
)
return
}
}
}
def private getVisibleEObjectDescriptions(EObject o, EClass type) {
o.getVisibleContainers.map[
container |
container.getExportedObjectsByType(type)
].flatten
}
def private getVisibleContainers(EObject o) {
val index = rdp.getResourceDescriptions(o.eResource)
val rd = index.getResourceDescription(o.eResource.URI)
cm.getVisibleContainers(rd, index)
}
@Check
def void checkViewSpecification(ViewSpecification viewSpecification) {
checkType(viewSpecification,
viewSpecification.type, viewSpecification.expectedSupertype,
ModelPackage.Literals.VIEW_SPECIFICATION__TYPE)
}
@Check
def void checkEmfFeatureAccess(EmfFeatureAccess emfFeatureAccess) {
checkType(emfFeatureAccess,
emfFeatureAccess.parameterType, emfFeatureAccess.expectedSupertype,
ModelPackage.Literals.EMF_FEATURE_ACCESS__PARAMETER_TYPE)
}
@Check
def void checkExtendsClause(WithExtendsClause withExtendsClause) {
if (withExtendsClause.getExtendsClause() != null && !withExtendsClause.hasCycleInHierarchy()) {
// it makes no sense to check for type conformance if there's a cycle in the
// hierarchy: there will always be a type mismatch in that case
checkType(withExtendsClause.extendsClause,
withExtendsClause.extendsClause.superType, withExtendsClause.expectedSupertype,
ModelPackage.Literals.EXTENDS_CLAUSE__SUPER_TYPE)
}
}
@Check
def void checkFieldInitialization(FieldSpecification f) {
if (!f.writeable && f.right == null) {
error("The blank final field " + f.name + " may not have been initialized",
ModelPackage.Literals.FIELD_SPECIFICATION__NAME,
FINAL_FIELD_NOT_INITIALIZED
)
}
if (f.type == null && f.right == null) {
error("The field "+f.name+" needs an explicit type since there is no initialization expression to infer the type from.",
f, ModelPackage.Literals.FIELD_SPECIFICATION__NAME,
TOO_LITTLE_TYPE_INFORMATION
);
}
}
@Check
def void checkModule(Module module) {
// the inferred Guice module for this DSL Module element
// we create a single class for the Module and it is a Guice module
// so we can take the first element of the filter
val guiceModuleClass = module.moduleInferredType
if (guiceModuleClass == null) {
return
}
val partsSpecifications = module.partsSpecifications
if (partsSpecifications != null) {
checkDuplicateViewSpecifications(partsSpecifications.parts)
}
val methods = guiceModuleClass.declaredOperations
if (methods.empty) {
return
}
checkDuplicateBindings(methods)
checkCorrectValueBindings(guiceModuleClass, methods, module)
for (t : module.allWithExtendsClauseInferredJavaTypes) {
checkDuplicateSpecifications(t)
}
}
private def checkDuplicateBindings(Iterable<JvmOperation> methods) {
val map = duplicatesMultimap
// create a multimap using method names
for (m : methods) {
map.put(m.simpleName, m)
}
checkDuplicates(map) [
d |
val source = d.sourceElements.head
error(
duplicateBindingMessage(source, d),
source,
source.duplicateBindingFeature,
DUPLICATE_BINDING
);
]
}
/**
* Since for fields we generate getter/setter, checking duplicate Java methods
* will automatically check for duplicate fields as well.
*/
private def checkDuplicateSpecifications(JvmGenericType inferredType) {
val inferredFeatures = inferredType.javaResolvedFeatures
val methods = inferredFeatures.declaredOperations
val map = duplicatesMultimap
// since they may be more than one Java method associated to the same
// source, we avoid reporting errors on the same source more than once
// e.g., for control factory specifications
val errorSourceSeen = newHashSet()
// create a multimap using method erased signature as key
for (m : methods) {
map.put(m.javaMethodResolvedErasedSignature, m.declaration)
}
checkDuplicates(map) [
d |
val source = d.sourceElements.head
if (errorSourceSeen.add(source)) {
error(
"Duplicate element",
source,
null,
DUPLICATE_ELEMENT
);
}
]
}
private def checkDuplicateViewSpecifications(List<PartSpecification> parts) {
val map = duplicatesMultimap
for (p : parts.filter(ViewSpecification)) {
map.put(p.id, p)
}
checkDuplicates(map) [
d |
error(
"Duplicate element",
d,
modelPackage.viewSpecification_Id,
DUPLICATE_ELEMENT
);
]
}
private def <T> checkDuplicates(ListMultimap<String, T> map, (T)=>void errorReporter) {
// check if there are duplicates
for (entry : map.asMap.entrySet) {
val duplicates = entry.value
if (duplicates.size > 1) {
for (d : duplicates) {
errorReporter.apply(d)
}
}
}
}
def checkCorrectValueBindings(JvmGenericType guiceModuleClass, Iterable<JvmOperation> methods, Module module) {
// These are all the value bindings in the superclass
val superClassValueBindings = guiceModuleClass.allGuiceValueBindingsMethodsInSuperclass
// check that the return type of the value bindings in this module
// are compliant (they can be subtypes)
for (superBinding : superClassValueBindings) {
val matching = methods.findFirst[simpleName == superBinding.simpleName]
if (matching != null && !(typeSystem.isConformant(module, superBinding.returnType, matching.returnType))) {
error("Incorrect value binding: " + matching.returnType.simpleName +
" is not compliant with inherited binding's type " + superBinding.returnType.simpleName,
matching.sourceElements.head,
modelPackage.valueBinding_TypeDecl,
NON_COMPLIANT_BINDING);
}
}
}
def protected checkType(EObject context, JvmTypeReference actualType, Class<?> expectedType,
EStructuralFeature feature) {
if (actualType != null) {
if (!typeSystem.isConformant(context, expectedType, actualType)) {
error("Type mismatch: cannot convert from " + actualType.simpleName +
" to " + expectedType.simpleName,
context,
feature,
TYPE_MISMATCH);
}
}
}
def protected boolean hasCycleInHierarchy(WithExtendsClause withExtendsClause) {
val superType = withExtendsClause.extendsClause.superType?.type
if (superType instanceof JvmGenericType) {
if (superType.hasCycleInHierarchy(newHashSet())) {
error("The inheritance hierarchy of " + superType.simpleName + " contains cycles",
withExtendsClause.extendsClause,
ModelPackage.Literals.EXTENDS_CLAUSE__SUPER_TYPE,
CYCLIC_INHERITANCE);
return true
}
}
return false
}
def protected boolean hasCycleInHierarchy(JvmGenericType type, Set<JvmGenericType> processedSuperTypes) {
if (processedSuperTypes.contains(type)) {
return true;
}
processedSuperTypes.add(type);
for (JvmTypeReference superTypeRef : type.getSuperTypes()) {
if (superTypeRef.getType() instanceof JvmGenericType) {
if (hasCycleInHierarchy(superTypeRef.getType() as JvmGenericType, processedSuperTypes))
return true;
}
}
processedSuperTypes.remove(type);
return false;
}
def private <K, T> duplicatesMultimap() {
return Multimaps2.<K, T> newLinkedHashListMultimap();
}
def private duplicateBindingMessage(EObject source, JvmOperation method) {
"Duplicate binding for: " +
switch (source) {
TypeBinding: method.returnType.simpleName
ProviderBinding: method.returnType.simpleName
ValueBinding: source.id
default: method.returnType.simpleName
}
}
def private duplicateBindingFeature(EObject e) {
switch (e) {
TypeBinding: modelPackage.typeBinding_TypeToBind
ProviderBinding: modelPackage.providerBinding_Type
ValueBinding: modelPackage.valueBinding_Id
default: null
}
}
}