diff options
Diffstat (limited to 'subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java')
-rw-r--r-- | subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java new file mode 100644 index 00000000..42950e61 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java | |||
@@ -0,0 +1,167 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2008, 2018 itemis AG (http://www.itemis.eu) and others. | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-2.0. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.language.serializer; | ||
10 | |||
11 | import com.google.common.collect.Lists; | ||
12 | import com.google.inject.Inject; | ||
13 | import org.eclipse.emf.common.util.URI; | ||
14 | import org.eclipse.emf.ecore.EObject; | ||
15 | import org.eclipse.emf.ecore.EReference; | ||
16 | import org.eclipse.xtext.CrossReference; | ||
17 | import org.eclipse.xtext.EcoreUtil2; | ||
18 | import org.eclipse.xtext.GrammarUtil; | ||
19 | import org.eclipse.xtext.IGrammarAccess; | ||
20 | import org.eclipse.xtext.conversion.IValueConverterService; | ||
21 | import org.eclipse.xtext.conversion.ValueConverterException; | ||
22 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
23 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
24 | import org.eclipse.xtext.naming.QualifiedName; | ||
25 | import org.eclipse.xtext.nodemodel.INode; | ||
26 | import org.eclipse.xtext.scoping.IScope; | ||
27 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
28 | import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic; | ||
29 | import org.eclipse.xtext.serializer.diagnostic.SerializationDiagnostic; | ||
30 | import org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer; | ||
31 | import org.eclipse.xtext.serializer.tokens.SerializerScopeProviderBinding; | ||
32 | import org.eclipse.xtext.util.EmfFormatter; | ||
33 | |||
34 | import java.util.List; | ||
35 | |||
36 | public class ProblemCrossReferenceSerializer extends CrossReferenceSerializer { | ||
37 | public static final String AMBIGUOUS_REFERENCE_DIAGNOSTIC = "tools.refinery.language.serializer" + | ||
38 | ".ProblemCrossReferenceSerializer.AMBIGUOUS_REFERENCE"; | ||
39 | |||
40 | @Inject | ||
41 | private LinkingHelper linkingHelper; | ||
42 | |||
43 | @Inject | ||
44 | private IQualifiedNameConverter qualifiedNameConverter; | ||
45 | |||
46 | @Inject | ||
47 | @SerializerScopeProviderBinding | ||
48 | private IScopeProvider scopeProvider; | ||
49 | |||
50 | @Inject | ||
51 | private IValueConverterService valueConverter; | ||
52 | |||
53 | @Inject | ||
54 | private IGrammarAccess grammarAccess; | ||
55 | |||
56 | @Override | ||
57 | public String serializeCrossRef(EObject semanticObject, CrossReference crossref, EObject target, INode node, | ||
58 | ISerializationDiagnostic.Acceptor errors) { | ||
59 | if ((target == null || target.eIsProxy()) && node != null) { | ||
60 | return tokenUtil.serializeNode(node); | ||
61 | } | ||
62 | |||
63 | final EReference ref = GrammarUtil.getReference(crossref, semanticObject.eClass()); | ||
64 | final IScope scope = scopeProvider.getScope(semanticObject, ref); | ||
65 | if (scope == null) { | ||
66 | if (errors != null) { | ||
67 | errors.accept(diagnostics.getNoScopeFoundDiagnostic(semanticObject, crossref, target)); | ||
68 | } | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | if (target != null && target.eIsProxy()) { | ||
73 | target = handleProxy(target, semanticObject, ref); | ||
74 | } | ||
75 | |||
76 | if (target != null && node != null) { | ||
77 | String text = linkingHelper.getCrossRefNodeAsString(node, true); | ||
78 | QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(text); | ||
79 | URI targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target); | ||
80 | if (isUniqueInScope(scope, qualifiedName, targetUri)) { | ||
81 | return tokenUtil.serializeNode(node); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | return getCrossReferenceNameFromScope(semanticObject, crossref, target, scope, errors); | ||
86 | } | ||
87 | |||
88 | private boolean isUniqueInScope(IScope scope, QualifiedName qualifiedName, URI targetUri) { | ||
89 | var iterator = scope.getElements(qualifiedName).iterator(); | ||
90 | if (!iterator.hasNext()) { | ||
91 | return false; | ||
92 | } | ||
93 | var description = iterator.next(); | ||
94 | return targetUri.equals(description.getEObjectURI()) && !iterator.hasNext(); | ||
95 | } | ||
96 | |||
97 | @Override | ||
98 | protected String getCrossReferenceNameFromScope(EObject semanticObject, CrossReference crossref, EObject target, | ||
99 | IScope scope, ISerializationDiagnostic.Acceptor errors) { | ||
100 | var ruleName = linkingHelper.getRuleNameFrom(crossref); | ||
101 | var targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target); | ||
102 | FoundName foundName = FoundName.NONE; | ||
103 | int shortestNameLength = Integer.MAX_VALUE; | ||
104 | String shortestName = null; | ||
105 | List<ISerializationDiagnostic> recordedErrors = null; | ||
106 | for (var description : scope.getElements(target)) { | ||
107 | var qualifiedName = description.getName(); | ||
108 | var segmentCount = qualifiedName.getSegmentCount(); | ||
109 | if (shortestName != null && segmentCount >= shortestNameLength) { | ||
110 | continue; | ||
111 | } | ||
112 | if (isUniqueInScope(scope, qualifiedName, targetUri)) { | ||
113 | var unconverted = qualifiedNameConverter.toString(qualifiedName); | ||
114 | try { | ||
115 | shortestName = valueConverter.toString(unconverted, ruleName); | ||
116 | shortestNameLength = segmentCount; | ||
117 | foundName = FoundName.VALID; | ||
118 | } catch (ValueConverterException e) { | ||
119 | if (recordedErrors == null) { | ||
120 | recordedErrors = Lists.newArrayList(); | ||
121 | } | ||
122 | recordedErrors.add(diagnostics.getValueConversionExceptionDiagnostic(semanticObject, crossref, | ||
123 | unconverted, e)); | ||
124 | } | ||
125 | } else if (foundName == FoundName.NONE) { | ||
126 | foundName = FoundName.AMBIGUOUS; | ||
127 | } | ||
128 | } | ||
129 | handleErrors(semanticObject, crossref, target, scope, errors, recordedErrors, foundName); | ||
130 | return shortestName; | ||
131 | } | ||
132 | |||
133 | private void handleErrors( | ||
134 | EObject semanticObject, CrossReference crossref, EObject target, IScope scope, | ||
135 | ISerializationDiagnostic.Acceptor errors, List<ISerializationDiagnostic> recordedErrors, | ||
136 | FoundName foundName) { | ||
137 | if (errors == null) { | ||
138 | return; | ||
139 | } | ||
140 | if (recordedErrors != null) { | ||
141 | recordedErrors.forEach(errors::accept); | ||
142 | } | ||
143 | if (foundName == FoundName.NONE) { | ||
144 | errors.accept(diagnostics.getNoEObjectDescriptionFoundDiagnostic(semanticObject, crossref, target, | ||
145 | scope)); | ||
146 | } else if (foundName == FoundName.AMBIGUOUS) { | ||
147 | // Computation of reference name copied from | ||
148 | // {@link org.eclipse.xtext.serializer.diagnostic.TokenDiagnosticProvider#getFullReferenceName}. | ||
149 | var ref = GrammarUtil.getReference(crossref); | ||
150 | var clazz = semanticObject.eClass().getName(); | ||
151 | if (ref.getEContainingClass() != semanticObject.eClass()) { | ||
152 | clazz = ref.getEContainingClass().getName() + "(" + clazz + ")"; | ||
153 | } | ||
154 | var message = "No unambiguous name could be found in scope %s.%s for %s" | ||
155 | .formatted(clazz, ref.getName(), EmfFormatter.objPath(target)); | ||
156 | var diagnostic = new SerializationDiagnostic(AMBIGUOUS_REFERENCE_DIAGNOSTIC, semanticObject, crossref, | ||
157 | grammarAccess.getGrammar(), message); | ||
158 | errors.accept(diagnostic); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | private enum FoundName { | ||
163 | NONE, | ||
164 | AMBIGUOUS, | ||
165 | VALID | ||
166 | } | ||
167 | } | ||