Skip to content

Commit 2875ad8

Browse files
committed
Merge branch 'pr/1008'
2 parents ed9e2a3 + bba7037 commit 2875ad8

5 files changed

Lines changed: 216 additions & 56 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package software.coley.recaf.services.inheritance;
2+
3+
import jakarta.annotation.Nonnull;
4+
import jakarta.annotation.Nullable;
5+
import software.coley.recaf.path.ClassPathNode;
6+
import software.coley.recaf.workspace.model.Workspace;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.stream.Stream;
11+
12+
/**
13+
* Provider of class path nodes.
14+
*
15+
* @author xDark
16+
*/
17+
sealed interface ClassPathNodeProvider {
18+
/**
19+
* @param name
20+
* Class name to look up.
21+
*
22+
* @return Path node for the class with the given name, or {@code null} if no such class exists in the provider.
23+
*/
24+
@Nullable
25+
ClassPathNode getNode(@Nonnull String name);
26+
27+
/**
28+
* Create a cached provider that contains all nodes from the workspace at the time of creation.
29+
*
30+
* @param workspace
31+
* Workspace to cache nodes from.
32+
*
33+
* @return Provider that caches all nodes from the workspace at the time of creation.
34+
*/
35+
static ClassPathNodeProvider.Cached cache(@Nonnull Workspace workspace) {
36+
Stream<ClassPathNode> stream = workspace.classesStream();
37+
Map<String, ClassPathNode> nodes = new HashMap<>(4096);
38+
stream.forEach(classPathNode -> {
39+
nodes.putIfAbsent(classPathNode.getValue().getName(), classPathNode);
40+
});
41+
return new Cached(Map.copyOf(nodes));
42+
}
43+
44+
/**
45+
* Provider that looks up nodes directly from the workspace.
46+
* This is not recommended for repeated lookups, but it is useful for one-off lookups or when the workspace is expected to be changing frequently.
47+
*
48+
* @param workspace
49+
* Workspace to look up nodes from.
50+
*/
51+
record Live(@Nonnull Workspace workspace) implements ClassPathNodeProvider {
52+
@Nullable
53+
@Override
54+
public ClassPathNode getNode(@Nonnull String name) {
55+
return workspace.findClass(name);
56+
}
57+
}
58+
59+
/**
60+
* Provider that caches all nodes from the workspace at the time of creation.
61+
* This is recommended for repeated lookups, but it is not suitable for workspaces that are expected to be changing frequently.
62+
*
63+
* @param nodes
64+
* Map of class names to their corresponding path nodes. This map is expected to be immutable.
65+
*
66+
* @see #cache(Workspace)
67+
*/
68+
record Cached(@Nonnull Map<String, ClassPathNode> nodes) implements ClassPathNodeProvider {
69+
int size() {
70+
return nodes.size();
71+
}
72+
73+
@Nullable
74+
@Override
75+
public ClassPathNode getNode(@Nonnull String name) {
76+
return nodes.get(name);
77+
}
78+
}
79+
}

recaf-core/src/main/java/software/coley/recaf/services/inheritance/InheritanceGraph.java

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class InheritanceGraph {
4949
private final Set<String> stubs = ConcurrentHashMap.newKeySet();
5050
private final ListenerHost listener = new ListenerHost();
5151
private final Workspace workspace;
52+
private final ClassPathNodeProvider workspaceNodeProvider;
5253

5354
/**
5455
* Create an inheritance graph.
@@ -58,6 +59,7 @@ public class InheritanceGraph {
5859
*/
5960
public InheritanceGraph(@Nonnull Workspace workspace) {
6061
this.workspace = workspace;
62+
this.workspaceNodeProvider = new ClassPathNodeProvider.Live(workspace);
6163

6264
// Populate map lookups with the initial capacity of the number of classes in the workspace plus a buffer.
6365
int classesInWorkspace = workspace.allResourcesStream(false /* dont count internal resource classes */)
@@ -111,10 +113,9 @@ private void refreshChildLookup() {
111113
parentToChild.clear();
112114

113115
// Repopulate
114-
workspace.findClasses(false, cls -> {
115-
populateParentToChildLookup(cls);
116-
return false;
117-
});
116+
ClassPathNodeProvider.Cached nodeProvider = ClassPathNodeProvider.cache(workspace);
117+
Set<ClassInfo> visited = Collections.newSetFromMap(new IdentityHashMap<>(nodeProvider.size() + 1024 /* leeway */));
118+
workspace.forEachClass(false, cls -> populateParentToChildLookup(cls, visited, nodeProvider));
118119
}
119120

120121
/**
@@ -124,25 +125,39 @@ private void refreshChildLookup() {
124125
* Child class name.
125126
* @param parentName
126127
* Parent class name.
128+
* @param provider
129+
* Node provider.
127130
*/
128-
private void populateParentToChildLookup(@Nonnull String name, @Nonnull String parentName) {
131+
private void populateParentToChildLookup(@Nonnull String name, @Nonnull String parentName, @Nonnull ClassPathNodeProvider provider) {
129132
parentToChild.computeIfAbsent(parentName, k -> ConcurrentHashMap.newKeySet()).add(name);
130133

131134
// Clear any cached relationships in the vertex and the parent vertex.
132-
InheritanceVertex parentVertex = getVertex(parentName);
133-
InheritanceVertex childVertex = getVertex(name);
135+
InheritanceVertex parentVertex = getVertex(parentName, provider);
136+
InheritanceVertex childVertex = getVertex(name, provider);
134137
if (parentVertex != null) parentVertex.clearCachedVertices();
135138
if (childVertex != null) childVertex.clearCachedVertices();
136139
}
137140

141+
/**
142+
* Populate a references from the given child class to the parent class.
143+
*
144+
* @param name
145+
* Child class name.
146+
* @param parentName
147+
* Parent class name.
148+
*/
149+
private void populateParentToChildLookup(@Nonnull String name, @Nonnull String parentName) {
150+
populateParentToChildLookup(name, parentName, workspaceNodeProvider);
151+
}
152+
138153
/**
139154
* Populate all references from the given child class to its parents.
140155
*
141156
* @param info
142157
* Child class.
143158
*/
144159
private void populateParentToChildLookup(@Nonnull ClassInfo info) {
145-
populateParentToChildLookup(info, Collections.newSetFromMap(new IdentityHashMap<>()));
160+
populateParentToChildLookup(info, Collections.newSetFromMap(new IdentityHashMap<>()), workspaceNodeProvider);
146161
}
147162

148163
/**
@@ -152,8 +167,10 @@ private void populateParentToChildLookup(@Nonnull ClassInfo info) {
152167
* Child class.
153168
* @param visited
154169
* Classes already visited in population.
170+
* @param provider
171+
* Node provider.
155172
*/
156-
private void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<ClassInfo> visited) {
173+
private void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<ClassInfo> visited, @Nonnull ClassPathNodeProvider provider) {
157174
// Since we have observed this class to exist, we will remove the "stub" placeholder for this name.
158175
stubs.remove(info.getName());
159176

@@ -167,31 +184,43 @@ private void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<C
167184

168185
// Add direct parent
169186
String name = info.getName();
170-
InheritanceVertex vertex = getVertex(name);
187+
InheritanceVertex vertex = getVertex(name, provider);
171188
if (vertex != null)
172189
vertex.clearCachedVertices();
173190

174191
String superName = info.getSuperName();
175192
if (superName != null) {
176-
populateParentToChildLookup(name, superName);
193+
populateParentToChildLookup(name, superName, provider);
177194

178195
// Visit parent
179-
InheritanceVertex superVertex = getVertex(superName);
196+
InheritanceVertex superVertex = getVertex(superName, provider);
180197
if (superVertex != null && !superVertex.isJavaLangObject() && !superVertex.isLoop())
181-
populateParentToChildLookup(superVertex.getValue(), visited);
198+
populateParentToChildLookup(superVertex.getValue(), visited, provider);
182199
}
183200

184201
// Add direct interfaces
185202
for (String itf : info.getInterfaces()) {
186-
populateParentToChildLookup(name, itf);
203+
populateParentToChildLookup(name, itf, provider);
187204

188205
// Visit interfaces
189-
InheritanceVertex interfaceVertex = getVertex(itf);
206+
InheritanceVertex interfaceVertex = getVertex(itf, provider);
190207
if (interfaceVertex != null)
191-
populateParentToChildLookup(interfaceVertex.getValue(), visited);
208+
populateParentToChildLookup(interfaceVertex.getValue(), visited, provider);
192209
}
193210
}
194211

212+
/**
213+
* Populate all references from the given child class to its parents.
214+
*
215+
* @param info
216+
* Child class.
217+
* @param visited
218+
* Classes already visited in population.
219+
*/
220+
private void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<ClassInfo> visited) {
221+
populateParentToChildLookup(info, visited, workspaceNodeProvider);
222+
}
223+
195224
/**
196225
* Remove all references from the given child class to its parents.
197226
*
@@ -253,16 +282,18 @@ private Set<String> getDirectChildren(@Nonnull String parent) {
253282
/**
254283
* @param name
255284
* Class name.
285+
* @param provider
286+
* Node provider.
256287
*
257288
* @return Vertex in graph of class. {@code null} if no such class was found in the inputs.
258289
*/
259290
@Nullable
260-
public InheritanceVertex getVertex(@Nonnull String name) {
291+
private InheritanceVertex getVertex(@Nonnull String name, @Nonnull ClassPathNodeProvider provider) {
261292
InheritanceVertex vertex = vertices.get(name);
262293
if (vertex == null && !stubs.contains(name)) {
263294
// Vertex does not exist and was not marked as a stub.
264295
// We want to look up the vertex for the given class and figure out if its valid or needs to be stubbed.
265-
InheritanceVertex provided = createVertex(name);
296+
InheritanceVertex provided = createVertex(name, provider);
266297
if (provided == STUB || provided == null) {
267298
// Provider yielded either a stub OR no result. Discard it.
268299
stubs.add(name);
@@ -275,6 +306,17 @@ public InheritanceVertex getVertex(@Nonnull String name) {
275306
return vertex;
276307
}
277308

309+
/**
310+
* @param name
311+
* Class name.
312+
*
313+
* @return Vertex in graph of class. {@code null} if no such class was found in the inputs.
314+
*/
315+
@Nullable
316+
public InheritanceVertex getVertex(@Nonnull String name) {
317+
return getVertex(name, workspaceNodeProvider);
318+
}
319+
278320
/**
279321
* @param name
280322
* Class name.
@@ -441,11 +483,13 @@ public boolean isLibraryMethod(@Nonnull String name, @Nonnull String methodName,
441483
*
442484
* @param name
443485
* Internal class name.
486+
* @param provider
487+
* Node provider.
444488
*
445489
* @return Vertex of class.
446490
*/
447491
@Nullable
448-
private InheritanceVertex createVertex(@Nullable String name) {
492+
private InheritanceVertex createVertex(@Nullable String name, @Nonnull ClassPathNodeProvider provider) {
449493
// Edge case handling for 'java/lang/Object' doing a parent lookup.
450494
// There is no parent, do not use STUB.
451495
if (name == null)
@@ -456,7 +500,7 @@ private InheritanceVertex createVertex(@Nullable String name) {
456500
return null;
457501

458502
// Find class in workspace, if not found yield stub.
459-
ClassPathNode result = workspace.findClass(name);
503+
ClassPathNode result = provider.getNode(name);
460504
if (result == null)
461505
return STUB;
462506

@@ -577,7 +621,7 @@ public void onPostApply(@Nonnull Workspace workspace, @Nonnull MappingResults ma
577621
mappingResults.getPreMappingPaths().forEach((name, path) -> {
578622
// If we see a 'stub' from the vertex creator, we know it is no longer
579623
// in the workspace and should be removed from our cache.
580-
InheritanceVertex vertex = createVertex(name);
624+
InheritanceVertex vertex = createVertex(name, workspaceNodeProvider);
581625
if (vertex == STUB) {
582626
vertices.remove(name);
583627
parentToChild.remove(name);

0 commit comments

Comments
 (0)