aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java
diff options
context:
space:
mode:
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.java167
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 *******************************************************************************/
9package tools.refinery.language.serializer;
10
11import com.google.common.collect.Lists;
12import com.google.inject.Inject;
13import org.eclipse.emf.common.util.URI;
14import org.eclipse.emf.ecore.EObject;
15import org.eclipse.emf.ecore.EReference;
16import org.eclipse.xtext.CrossReference;
17import org.eclipse.xtext.EcoreUtil2;
18import org.eclipse.xtext.GrammarUtil;
19import org.eclipse.xtext.IGrammarAccess;
20import org.eclipse.xtext.conversion.IValueConverterService;
21import org.eclipse.xtext.conversion.ValueConverterException;
22import org.eclipse.xtext.linking.impl.LinkingHelper;
23import org.eclipse.xtext.naming.IQualifiedNameConverter;
24import org.eclipse.xtext.naming.QualifiedName;
25import org.eclipse.xtext.nodemodel.INode;
26import org.eclipse.xtext.scoping.IScope;
27import org.eclipse.xtext.scoping.IScopeProvider;
28import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic;
29import org.eclipse.xtext.serializer.diagnostic.SerializationDiagnostic;
30import org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer;
31import org.eclipse.xtext.serializer.tokens.SerializerScopeProviderBinding;
32import org.eclipse.xtext.util.EmfFormatter;
33
34import java.util.List;
35
36public 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}