aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java
blob: f75ecdb2291bb252e46a7028bd897c9feafdfa02 (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
/*
 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package tools.refinery.language.ide.syntaxcoloring;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.util.IResourceScopeCache;
import tools.refinery.language.model.problem.*;
import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
import tools.refinery.language.utils.ProblemUtil;

import java.util.*;

@Singleton
public class TypeHashProvider {
	private static final String CACHE_KEY = "tools.refinery.language.ide.syntaxcoloring.TypeHashProvider";
	private static final int COLOR_COUNT = 10;

	@Inject
	private IResourceScopeCache resourceScopeCache;

	@Inject
	private IScopeProvider scopeProvider;

	@Inject
	private IQualifiedNameProvider qualifiedNameProvider;

	@Inject
	private IQualifiedNameConverter qualifiedNameConverter;

	public String getTypeHash(Relation relation) {
		if (!(relation instanceof ClassDeclaration || relation instanceof EnumDeclaration) ||
				ProblemUtil.isBuiltIn(relation)) {
			return null;
		}
		var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(relation);
		if (qualifiedName == null) {
			return null;
		}
		var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
		var problem = EcoreUtil2.getContainerOfType(relation, Problem.class);
		if (problem == null) {
			return null;
		}
		var cache = resourceScopeCache.get(CACHE_KEY, problem.eResource(), () -> computeHashes(problem));
		return cache.get(qualifiedNameString);
	}

	private Map<String, String> computeHashes(Problem problem) {
		var qualifiedNameStrings = new TreeSet<String>();
		var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
		for (var description : scope.getAllElements()) {
			if (ProblemResourceDescriptionStrategy.COLOR_RELATION_TRUE.equals(
					description.getUserData(ProblemResourceDescriptionStrategy.COLOR_RELATION))) {
				var qualifiedNameString = qualifiedNameConverter.toString(description.getQualifiedName());
				qualifiedNameStrings.add(qualifiedNameString);
			}
		}
		var stringList = new ArrayList<>(qualifiedNameStrings);
		int size = stringList.size();
		if (size == 0) {
			return Map.of();
		}
		// The use of a non-cryptographic random generator is safe here, because we only use it to shuffle the color
		// IDs in a pseudo-random way. The shuffle depends on the size of the list of identifiers before padding to
		// make sure that adding a new class randomizes all color IDs.
		@SuppressWarnings("squid:S2245")
		var random = new Random(size);
		int padding = COLOR_COUNT - (size % COLOR_COUNT);
		for (int i = 0; i < padding; i++) {
			stringList.add(null);
		}
		size += padding;
		Collections.shuffle(stringList, random);
		var mapBuilder = ImmutableMap.<String, String>builder();
		for (int i = 0; i < size; i++) {
			var key = stringList.get(i);
			if (key != null) {
				int colorId = i % COLOR_COUNT;
				mapBuilder.put(key, Integer.toString(colorId));
			}
		}
		return mapBuilder.build();
	}
}