/* * SPDX-FileCopyrightText: 2024 The Refinery Authors * * SPDX-License-Identifier: EPL-2.0 */ package tools.refinery.language.ide.syntaxcoloring; 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.resource.IEObjectDescription; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.scoping.IScopeProvider; import org.eclipse.xtext.scoping.impl.GlobalResourceDescriptionProvider; import org.eclipse.xtext.util.IResourceScopeCache; import tools.refinery.language.documentation.DocumentationCommentParser; import tools.refinery.language.model.problem.*; import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; import tools.refinery.language.scoping.imports.ImportCollector; 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; @Inject private ImportCollector importCollector; @Inject private GlobalResourceDescriptionProvider globalResourceDescriptionProvider; 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 computeHashes(Problem problem) { var resourceDescriptions = getResourceDescriptions(problem); var map = new HashMap(); var qualifiedNameStrings = new TreeSet(); for (var resourceDescription : resourceDescriptions) { for (var description : resourceDescription.getExportedObjectsByType(ProblemPackage.Literals.RELATION)) { if (ProblemResourceDescriptionStrategy.COLOR_RELATION_TRUE.equals( description.getUserData(ProblemResourceDescriptionStrategy.COLOR_RELATION))) { var qualifiedNameString = qualifiedNameConverter.toString(description.getQualifiedName()); var presetColor = getPresetColor(description); if (presetColor != null) { map.put(qualifiedNameString, presetColor); } qualifiedNameStrings.add(qualifiedNameString); } } } var stringList = new ArrayList<>(qualifiedNameStrings); int size = stringList.size(); if (size != 0) { shuffleColors(size, stringList, map); } return Collections.unmodifiableMap(map); } private static void shuffleColors(int size, ArrayList stringList, Map map) { // 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); for (int i = 0; i < size; i++) { var key = stringList.get(i); if (key != null) { int colorId = i % COLOR_COUNT; map.putIfAbsent(key, Integer.toString(colorId)); } } } private List getResourceDescriptions(Problem problem) { var resource = problem.eResource(); if (resource == null) { return List.of(); } var resourceDescriptions = new ArrayList(); var resourceDescription = globalResourceDescriptionProvider.getResourceDescription(resource); if (resourceDescription != null) { resourceDescriptions.add(resourceDescription); } var resourceSet = resource.getResourceSet(); if (resourceSet != null) { for (var importedUri : importCollector.getAllImports(resource).toUriSet()) { var importedResource = resourceSet.getResource(importedUri, false); if (importedResource != null) { var importedResourceDescription = globalResourceDescriptionProvider.getResourceDescription( importedResource); if (importedResourceDescription != null) { resourceDescriptions.add(importedResourceDescription); } } } } return resourceDescriptions; } private String getPresetColor(IEObjectDescription description) { return description.getUserData(DocumentationCommentParser.COLOR_TAG); } }