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
|
package cuchaz.enigma.analysis;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import cuchaz.enigma.CompiledSource;
import cuchaz.enigma.ProgressListener;
import cuchaz.enigma.analysis.index.JarIndex;
import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public final class ClassCache implements AutoCloseable, CompiledSource {
private final FileSystem fileSystem;
private final ImmutableSet<String> classNames;
private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder()
.maximumSize(128)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) {
this.fileSystem = fileSystem;
this.classNames = classNames;
}
public static ClassCache of(Path jarPath) throws IOException {
FileSystem fileSystem = FileSystems.newFileSystem(jarPath, null);
ImmutableSet<String> classNames = collectClassNames(fileSystem);
return new ClassCache(fileSystem, classNames);
}
private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException {
ImmutableSet.Builder<String> classNames = ImmutableSet.builder();
for (Path root : fileSystem.getRootDirectories()) {
Files.walk(root).map(Path::toString)
.forEach(path -> {
if (path.endsWith(".class")) {
String name = path.substring(1, path.length() - ".class".length());
classNames.add(name);
}
});
}
return classNames.build();
}
@Nullable
@Override
public ClassNode getClassNode(String name) {
if (!classNames.contains(name)) {
return null;
}
try {
return nodeCache.get(name, () -> parseNode(name));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
private ClassNode parseNode(String name) throws IOException {
ClassReader reader = getReader(name);
ClassNode node = new ClassNode();
LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Opcodes.ASM5, node);
reader.accept(visitor, 0);
return node;
}
private ClassReader getReader(String name) throws IOException {
Path path = fileSystem.getPath(name + ".class");
byte[] bytes = Files.readAllBytes(path);
return new ClassReader(bytes);
}
public int getClassCount() {
return classNames.size();
}
public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) {
for (String className : classNames) {
ClassVisitor visitor = visitorSupplier.get();
ClassNode cached = nodeCache.getIfPresent(className);
if (cached != null) {
cached.accept(visitor);
continue;
}
try {
ClassReader reader = getReader(className);
reader.accept(visitor, readFlags);
} catch (IOException e) {
System.out.println("Failed to visit class " + className);
e.printStackTrace();
}
}
}
@Override
public void close() throws IOException {
this.fileSystem.close();
}
public JarIndex index(ProgressListener progress) {
JarIndex index = JarIndex.empty();
index.indexJar(this, progress);
return index;
}
}
|