summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/analysis/ClassCache.java
blob: 6c8af1c12180c2ce2d860d133a3902c526a03848 (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
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;
	}
}