diff options
Diffstat (limited to 'subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java')
-rw-r--r-- | subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java new file mode 100644 index 00000000..3f918c97 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java | |||
@@ -0,0 +1,254 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | public class TypeHierarchy { | ||
14 | private final Set<PartialRelation> allTypes; | ||
15 | private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap; | ||
16 | private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>(); | ||
17 | private final InferredType unknownType; | ||
18 | private final Map<PartialRelation, TypeAnalysisResult> preservedTypes; | ||
19 | |||
20 | TypeHierarchy(Map<PartialRelation, TypeInfo> typeInfoMap) { | ||
21 | int size = typeInfoMap.size(); | ||
22 | allTypes = Collections.unmodifiableSet(new LinkedHashSet<>(typeInfoMap.keySet())); | ||
23 | extendedTypeInfoMap = new LinkedHashMap<>(size); | ||
24 | var concreteTypes = new LinkedHashSet<PartialRelation>(); | ||
25 | int index = 0; | ||
26 | for (var entry : typeInfoMap.entrySet()) { | ||
27 | var type = entry.getKey(); | ||
28 | var typeInfo = entry.getValue(); | ||
29 | extendedTypeInfoMap.put(type, new ExtendedTypeInfo(index, type, typeInfo)); | ||
30 | if (!typeInfo.abstractType()) { | ||
31 | concreteTypes.add(type); | ||
32 | } | ||
33 | index++; | ||
34 | } | ||
35 | unknownType = new InferredType(Set.of(), concreteTypes, null); | ||
36 | computeAllSupertypes(); | ||
37 | computeAllAndConcreteSubtypes(); | ||
38 | computeDirectSubtypes(); | ||
39 | eliminateTrivialSupertypes(); | ||
40 | preservedTypes = computeAnalysisResults(); | ||
41 | } | ||
42 | |||
43 | public boolean isEmpty() { | ||
44 | return extendedTypeInfoMap.isEmpty(); | ||
45 | } | ||
46 | |||
47 | public InferredType getUnknownType() { | ||
48 | return unknownType; | ||
49 | } | ||
50 | |||
51 | public Set<PartialRelation> getAllTypes() { | ||
52 | return allTypes; | ||
53 | } | ||
54 | |||
55 | public Map<PartialRelation, TypeAnalysisResult> getPreservedTypes() { | ||
56 | return preservedTypes; | ||
57 | } | ||
58 | |||
59 | public Map<PartialRelation, PartialRelation> getEliminatedTypes() { | ||
60 | return Collections.unmodifiableMap(replacements); | ||
61 | } | ||
62 | |||
63 | public TypeAnalysisResult getAnalysisResult(PartialRelation type) { | ||
64 | var preservedResult = preservedTypes.get(type); | ||
65 | if (preservedResult != null) { | ||
66 | return preservedResult; | ||
67 | } | ||
68 | var eliminatedResult = replacements.get(type); | ||
69 | if (eliminatedResult != null) { | ||
70 | return preservedTypes.get(eliminatedResult); | ||
71 | } | ||
72 | throw new IllegalArgumentException("Unknown type: " + type); | ||
73 | } | ||
74 | |||
75 | private void computeAllSupertypes() { | ||
76 | boolean changed; | ||
77 | do { | ||
78 | changed = false; | ||
79 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
80 | var found = new HashSet<PartialRelation>(); | ||
81 | var allSupertypes = extendedTypeInfo.getAllSupertypes(); | ||
82 | for (var supertype : allSupertypes) { | ||
83 | var supertypeInfo = extendedTypeInfoMap.get(supertype); | ||
84 | if (supertypeInfo == null) { | ||
85 | throw new TranslationException(extendedTypeInfo.getType(), | ||
86 | "Supertype %s of %s is missing from the type hierarchy" | ||
87 | .formatted(supertype, extendedTypeInfo.getType())); | ||
88 | } | ||
89 | found.addAll(supertypeInfo.getAllSupertypes()); | ||
90 | } | ||
91 | if (allSupertypes.addAll(found)) { | ||
92 | changed = true; | ||
93 | } | ||
94 | } | ||
95 | } while (changed); | ||
96 | } | ||
97 | |||
98 | private void computeAllAndConcreteSubtypes() { | ||
99 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
100 | var type = extendedTypeInfo.getType(); | ||
101 | if (!extendedTypeInfo.isAbstractType()) { | ||
102 | extendedTypeInfo.getConcreteSubtypesAndSelf().add(type); | ||
103 | } | ||
104 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | ||
105 | if (type.equals(supertype)) { | ||
106 | throw new TranslationException(type, "%s cannot be a supertype of itself".formatted(type)); | ||
107 | } | ||
108 | var supertypeInfo = extendedTypeInfoMap.get(supertype); | ||
109 | supertypeInfo.getAllSubtypes().add(type); | ||
110 | if (!extendedTypeInfo.isAbstractType()) { | ||
111 | supertypeInfo.getConcreteSubtypesAndSelf().add(type); | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | private void computeDirectSubtypes() { | ||
118 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
119 | var allSubtypes = extendedTypeInfo.getAllSubtypes(); | ||
120 | var directSubtypes = new LinkedHashSet<>(allSubtypes); | ||
121 | var indirectSubtypes = new LinkedHashSet<PartialRelation>(allSubtypes.size()); | ||
122 | for (var subtype : allSubtypes) { | ||
123 | indirectSubtypes.addAll(extendedTypeInfoMap.get(subtype).getAllSubtypes()); | ||
124 | } | ||
125 | directSubtypes.removeAll(indirectSubtypes); | ||
126 | extendedTypeInfo.setDirectSubtypes(directSubtypes); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | private void eliminateTrivialSupertypes() { | ||
131 | Set<PartialRelation> toInspect = new HashSet<>(extendedTypeInfoMap.keySet()); | ||
132 | while (!toInspect.isEmpty()) { | ||
133 | var toRemove = new ArrayList<PartialRelation>(); | ||
134 | for (var partialRelation : toInspect) { | ||
135 | var extendedTypeInfo = extendedTypeInfoMap.get(partialRelation); | ||
136 | if (extendedTypeInfo != null && isTrivialSupertype(extendedTypeInfo)) { | ||
137 | toRemove.add(partialRelation); | ||
138 | } | ||
139 | } | ||
140 | toInspect.clear(); | ||
141 | for (var partialRelation : toRemove) { | ||
142 | removeTrivialType(partialRelation, toInspect); | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | |||
147 | private boolean isTrivialSupertype(ExtendedTypeInfo extendedTypeInfo) { | ||
148 | if (!extendedTypeInfo.isAbstractType()) { | ||
149 | return false; | ||
150 | } | ||
151 | var subtypeIterator = extendedTypeInfo.getDirectSubtypes().iterator(); | ||
152 | if (!subtypeIterator.hasNext()) { | ||
153 | // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e., | ||
154 | // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist. | ||
155 | return false; | ||
156 | } | ||
157 | var directSubtype = subtypeIterator.next(); | ||
158 | return !extendedTypeInfoMap.get(directSubtype).isAbstractType() && !subtypeIterator.hasNext(); | ||
159 | } | ||
160 | |||
161 | private void removeTrivialType(PartialRelation trivialType, Set<PartialRelation> toInspect) { | ||
162 | var extendedTypeInfo = extendedTypeInfoMap.get(trivialType); | ||
163 | var iterator = extendedTypeInfo.getDirectSubtypes().iterator(); | ||
164 | if (!iterator.hasNext()) { | ||
165 | throw new AssertionError("Expected trivial supertype %s to have a direct subtype" | ||
166 | .formatted(trivialType)); | ||
167 | } | ||
168 | PartialRelation replacement = setReplacement(trivialType, iterator.next()); | ||
169 | if (iterator.hasNext()) { | ||
170 | throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype" | ||
171 | .formatted(trivialType)); | ||
172 | } | ||
173 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | ||
174 | var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype); | ||
175 | if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) { | ||
176 | throw new AssertionError("Expected %s to be subtype of %s".formatted(trivialType, supertype)); | ||
177 | } | ||
178 | var directSubtypes = extendedSupertypeInfo.getDirectSubtypes(); | ||
179 | if (directSubtypes.remove(trivialType)) { | ||
180 | directSubtypes.add(replacement); | ||
181 | if (extendedSupertypeInfo.isAbstractType() && directSubtypes.size() == 1) { | ||
182 | toInspect.add(supertype); | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | for (var subtype : extendedTypeInfo.getAllSubtypes()) { | ||
187 | var extendedSubtypeInfo = extendedTypeInfoMap.get(subtype); | ||
188 | if (!extendedSubtypeInfo.getAllSupertypes().remove(trivialType)) { | ||
189 | throw new AssertionError("Expected %s to be supertype of %s".formatted(trivialType, subtype)); | ||
190 | } | ||
191 | } | ||
192 | extendedTypeInfoMap.remove(trivialType); | ||
193 | } | ||
194 | |||
195 | private PartialRelation setReplacement(PartialRelation trivialRelation, PartialRelation replacement) { | ||
196 | if (replacement == null) { | ||
197 | return trivialRelation; | ||
198 | } | ||
199 | var resolved = setReplacement(replacement, replacements.get(replacement)); | ||
200 | replacements.put(trivialRelation, resolved); | ||
201 | return resolved; | ||
202 | } | ||
203 | |||
204 | private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() { | ||
205 | var allExtendedTypeInfoList = sortTypes(); | ||
206 | var preservedResults = new LinkedHashMap<PartialRelation, TypeAnalysisResult>( | ||
207 | allExtendedTypeInfoList.size()); | ||
208 | for (var extendedTypeInfo : allExtendedTypeInfoList) { | ||
209 | var type = extendedTypeInfo.getType(); | ||
210 | preservedResults.put(type, new TypeAnalysisResult(extendedTypeInfo, allExtendedTypeInfoList)); | ||
211 | } | ||
212 | return Collections.unmodifiableMap(preservedResults); | ||
213 | } | ||
214 | |||
215 | private List<ExtendedTypeInfo> sortTypes() { | ||
216 | // Invert {@code directSubtypes} to keep track of the out-degree of types. | ||
217 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
218 | for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) { | ||
219 | var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype); | ||
220 | extendedDirectSubtypeInfo.getUnsortedDirectSupertypes().add(extendedTypeInfo.getType()); | ||
221 | } | ||
222 | } | ||
223 | // Build a <i>inverse</i> topological order ({@code extends} edges always points to earlier nodes in the order, | ||
224 | // breaking ties according to the original order ({@link ExtendedTypeInfo#index}) to form a 'stable' sort. | ||
225 | // See, e.g., https://stackoverflow.com/a/11236027. | ||
226 | var priorityQueue = new PriorityQueue<ExtendedTypeInfo>(); | ||
227 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
228 | if (extendedTypeInfo.getUnsortedDirectSupertypes().isEmpty()) { | ||
229 | priorityQueue.add(extendedTypeInfo); | ||
230 | } | ||
231 | } | ||
232 | var sorted = new ArrayList<ExtendedTypeInfo>(extendedTypeInfoMap.size()); | ||
233 | while (!priorityQueue.isEmpty()) { | ||
234 | var extendedTypeInfo = priorityQueue.remove(); | ||
235 | sorted.add(extendedTypeInfo); | ||
236 | for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) { | ||
237 | var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype); | ||
238 | var unsortedDirectSupertypes = extendedDirectSubtypeInfo.getUnsortedDirectSupertypes(); | ||
239 | if (!unsortedDirectSupertypes.remove(extendedTypeInfo.getType())) { | ||
240 | throw new AssertionError("Expected %s to be a direct supertype of %s" | ||
241 | .formatted(extendedTypeInfo.getType(), directSubtype)); | ||
242 | } | ||
243 | if (unsortedDirectSupertypes.isEmpty()) { | ||
244 | priorityQueue.add(extendedDirectSubtypeInfo); | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | return Collections.unmodifiableList(sorted); | ||
249 | } | ||
250 | |||
251 | public static TypeHierarchyBuilder builder() { | ||
252 | return new TypeHierarchyBuilder(); | ||
253 | } | ||
254 | } | ||