aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java
blob: f05abc4587d177e5d30ca61795ff0ea6a5228510 (plain) (blame)
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
/*
 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package tools.refinery.language.web.semantics.metadata;

import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import tools.refinery.language.model.problem.*;
import tools.refinery.language.semantics.ProblemTrace;
import tools.refinery.language.semantics.TracedException;
import tools.refinery.language.utils.ProblemUtil;
import tools.refinery.store.model.Model;
import tools.refinery.store.reasoning.ReasoningAdapter;
import tools.refinery.store.reasoning.literal.Concreteness;
import tools.refinery.store.reasoning.representation.PartialRelation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MetadataCreator {
	@Inject
	private IScopeProvider scopeProvider;

	@Inject
	private IQualifiedNameProvider qualifiedNameProvider;

	@Inject
	private IQualifiedNameConverter qualifiedNameConverter;

	@Inject
	private Provider<NodeMetadataFactory> nodeMetadataFactoryProvider;

	private ProblemTrace problemTrace;
	private IScope nodeScope;
	private IScope relationScope;

	public void setProblemTrace(ProblemTrace problemTrace) {
		if (this.problemTrace != null) {
			throw new IllegalArgumentException("Problem trace was already set");
		}
		this.problemTrace = problemTrace;
		var problem = problemTrace.getProblem();
		nodeScope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
		relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
	}

	public List<NodeMetadata> getNodesMetadata(Model model, Concreteness concreteness) {
		int nodeCount = model.getAdapter(ReasoningAdapter.class).getNodeCount();
		var nodeTrace = problemTrace.getNodeTrace();
		var nodes = new NodeMetadata[Math.max(nodeTrace.size(), nodeCount)];
		var nodeMetadataFactory = nodeMetadataFactoryProvider.get();
		nodeMetadataFactory.initialize(problemTrace, concreteness, model);
		boolean preserveNewNodes = concreteness == Concreteness.PARTIAL;
		for (var entry : nodeTrace.keyValuesView()) {
			var node = entry.getOne();
			var id = entry.getTwo();
			nodes[id] = getNodeMetadata(id, node, preserveNewNodes, nodeMetadataFactory);
		}
		for (int i = 0; i < nodes.length; i++) {
			if (nodes[i] == null) {
				nodes[i] = nodeMetadataFactory.createFreshlyNamedMetadata(i);
			}
		}
		return List.of(nodes);
	}

	private NodeMetadata getNodeMetadata(int nodeId, Node node, boolean preserveNewNodes,
										 NodeMetadataFactory nodeMetadataFactory) {
		var kind = getNodeKind(node);
		if (!preserveNewNodes && kind == NodeKind.NEW) {
			return nodeMetadataFactory.createFreshlyNamedMetadata(nodeId);
		}
		var qualifiedName = getQualifiedName(node);
		var simpleName = getSimpleName(node, qualifiedName, nodeScope);
		return nodeMetadataFactory.doCreateMetadata(nodeId, qualifiedNameConverter.toString(qualifiedName),
				qualifiedNameConverter.toString(simpleName), getNodeKind(node));
	}

	private NodeKind getNodeKind(Node node) {
		if (ProblemUtil.isImplicitNode(node)) {
			return NodeKind.IMPLICIT;
		} else if (ProblemUtil.isIndividualNode(node)) {
			return NodeKind.INDIVIDUAL;
		} else if (ProblemUtil.isNewNode(node)) {
			return NodeKind.NEW;
		} else {
			throw new TracedException(node, "Unknown node type");
		}
	}

	public List<RelationMetadata> getRelationsMetadata() {
		var relationTrace = problemTrace.getRelationTrace();
		var relations = new ArrayList<RelationMetadata>(relationTrace.size());
		for (var entry : relationTrace.entrySet()) {
			var relation = entry.getKey();
			var partialRelation = entry.getValue();
			var metadata = getRelationMetadata(relation, partialRelation);
			relations.add(metadata);
		}
		return Collections.unmodifiableList(relations);
	}

	private RelationMetadata getRelationMetadata(Relation relation, PartialRelation partialRelation) {
		var qualifiedName = getQualifiedName(relation);
		var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
		var simpleName = getSimpleName(relation, qualifiedName, relationScope);
		var simpleNameString = qualifiedNameConverter.toString(simpleName);
		var arity = partialRelation.arity();
		var detail = getRelationDetail(relation, partialRelation);
		return new RelationMetadata(qualifiedNameString, simpleNameString, arity, detail);
	}

	private RelationDetail getRelationDetail(Relation relation, PartialRelation partialRelation) {
		if (ProblemUtil.isBuiltIn(relation) && !ProblemUtil.isError(relation)) {
			return getBuiltInDetail();
		}
		if (relation instanceof ClassDeclaration classDeclaration) {
			return getClassDetail(classDeclaration);
		} else if (relation instanceof ReferenceDeclaration) {
			return getReferenceDetail(partialRelation);
		} else if (relation instanceof EnumDeclaration) {
			return getEnumDetail();
		} else if (relation instanceof PredicateDefinition predicateDefinition) {
			return getPredicateDetail(predicateDefinition);
		} else {
			throw new TracedException(relation, "Unknown relation");
		}
	}

	private RelationDetail getBuiltInDetail() {
		return BuiltInDetail.INSTANCE;
	}

	private RelationDetail getClassDetail(ClassDeclaration classDeclaration) {
		return ClassDetail.ofAbstractClass(classDeclaration.isAbstract());
	}

	private RelationDetail getReferenceDetail(PartialRelation partialRelation) {
		var metamodel = problemTrace.getMetamodel();
		var opposite = metamodel.oppositeReferences().get(partialRelation);
		if (opposite == null) {
			boolean isContainment = metamodel.containmentHierarchy().containsKey(partialRelation);
			return ReferenceDetail.ofContainment(isContainment);
		} else {
			boolean isContainer = metamodel.containmentHierarchy().containsKey(opposite);
			return new OppositeReferenceDetail(isContainer, opposite.name());
		}
	}

	private RelationDetail getEnumDetail() {
		return ClassDetail.CONCRETE_CLASS;
	}

	private RelationDetail getPredicateDetail(PredicateDefinition predicate) {
		return PredicateDetail.ofError(predicate.isError());
	}

	private QualifiedName getQualifiedName(EObject eObject) {
		var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(eObject);
		if (qualifiedName == null) {
			throw new TracedException(eObject, "Unknown qualified name");
		}
		return qualifiedName;
	}

	private QualifiedName getSimpleName(EObject eObject, QualifiedName qualifiedName, IScope scope) {
		var descriptions = scope.getElements(eObject);
		var names = new ArrayList<QualifiedName>();
		for (var description : descriptions) {
			// {@code getQualifiedName()} will refer to the full name for objects that are loaded from the global
			// scope, but {@code getName()} returns the qualified name that we set in
			// {@code ProblemResourceDescriptionStrategy}.
			names.add(description.getName());
		}
		names.sort(Comparator.comparingInt(QualifiedName::getSegmentCount));
		for (var simpleName : names) {
			if (names.contains(simpleName) && isUnique(scope, simpleName)) {
				return simpleName;
			}
		}
		throw new TracedException(eObject, "Ambiguous qualified name: " +
				qualifiedNameConverter.toString(qualifiedName));
	}

	private boolean isUnique(IScope scope, QualifiedName name) {
		var iterator = scope.getElements(name).iterator();
		if (!iterator.hasNext()) {
			return false;
		}
		iterator.next();
		return !iterator.hasNext();
	}
}