Skip to content

Commit 5bd1751

Browse files
committed
Make minifier more modular, convert Flags class into interface, add license
1 parent bf26dd7 commit 5bd1751

12 files changed

+312
-210
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Vineflower team
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

src/main/java/org/vineflower/tools/minifier/ExtensionsFinder.java

-53
This file was deleted.

src/main/java/org/vineflower/tools/minifier/FileVisitor.java

-154
This file was deleted.

src/main/java/org/vineflower/tools/minifier/Minifier.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
5+
import org.vineflower.tools.minifier.patch.ClassPatch;
6+
import org.vineflower.tools.minifier.patch.ClassPatches;
7+
import org.vineflower.tools.minifier.patch.Visitors;
58

69
import java.nio.file.*;
710
import java.io.IOException;
@@ -24,9 +27,9 @@ public static void main(String[] args) throws IOException {
2427
try (FileSystem inputFs = FileSystems.newFileSystem(srcJar)) {
2528
Path root = inputFs.getPath("/");
2629
try (FileSystem outputFs = FileSystems.newFileSystem(destJar, Map.of("create", "true"))) {
27-
Set<String> protobufExtensions = new HashSet<>();
28-
Files.walkFileTree(root, new ExtensionsFinder(protobufExtensions));
29-
Files.walkFileTree(root, new FileVisitor(outputFs.getPath("/"), root, protobufExtensions));
30+
ClassPatch[] patches = ClassPatches.getPatches();
31+
Files.walkFileTree(root, new Visitors.FirstPass(patches));
32+
Files.walkFileTree(root, new Visitors.TransformPass(patches, outputFs.getPath("/"), root));
3033
}
3134
}
3235
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
@NullMarked
3+
package org.vineflower.tools.minifier;
4+
5+
import org.jspecify.annotations.NullMarked;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.vineflower.tools.minifier.patch;
2+
3+
import org.jspecify.annotations.Nullable;
4+
5+
import java.lang.classfile.*;
6+
import java.lang.classfile.attribute.InnerClassesAttribute;
7+
import java.util.HashSet;
8+
import java.util.Set;
9+
10+
public class BuilderRemoval implements ClassPatch {
11+
private final Set<String> protobufExtensions = new HashSet<>();
12+
13+
private boolean isExtension(String check) {
14+
return protobufExtensions.stream().anyMatch(check::startsWith);
15+
}
16+
17+
private void parseTypeArg(Signature.TypeArg arg) {
18+
if (arg instanceof Signature.TypeArg.Bounded boundedArg) {
19+
Signature.ClassTypeSig classTypeSig = (Signature.ClassTypeSig) boundedArg.boundType();
20+
protobufExtensions.add(classTypeSig.className());
21+
if (!classTypeSig.typeArgs().isEmpty()) {
22+
for (Signature.TypeArg arg2 : classTypeSig.typeArgs()) {
23+
parseTypeArg(arg2);
24+
}
25+
}
26+
}
27+
}
28+
29+
private boolean shouldExcludeMethod(MethodModel method) {
30+
String desc = method.methodTypeSymbol().returnType().descriptorString();
31+
if (desc.length() == 1) return false;
32+
String returnType = desc.substring(desc.indexOf('L') + 1, desc.length() - 1);
33+
34+
if (isExtension(returnType)) return false;
35+
if (isBuilder(returnType)) return true;
36+
37+
return false;
38+
}
39+
40+
private static boolean isBuilder(String className) {
41+
return className.endsWith("$Builder") || className.endsWith("OrBuilder");
42+
}
43+
44+
@Override
45+
public void firstPass(ClassModel clazz) {
46+
for (FieldModel field : clazz.fields()) {
47+
if (!field.fieldType().equalsString("Lkotlin/metadata/internal/protobuf/GeneratedMessageLite$GeneratedExtension;")) continue;
48+
49+
field.findAttribute(Attributes.signature()).ifPresent(sig -> {
50+
Signature.ClassTypeSig classTypeSig = (Signature.ClassTypeSig) sig.asTypeSignature();
51+
for (Signature.TypeArg arg : classTypeSig.typeArgs()) {
52+
parseTypeArg(arg);
53+
}
54+
});
55+
}
56+
}
57+
58+
@Override
59+
@Nullable
60+
public ClassTransform patch(ClassModel clazz) {
61+
String className = clazz.thisClass().name().stringValue();
62+
if (isExtension(className)) return ClassTransform.ACCEPT_ALL;
63+
if (!className.startsWith("kotlin/metadata/internal/metadata")) return ClassTransform.ACCEPT_ALL;
64+
65+
if (isBuilder(className)) return null;
66+
67+
return ClassTransform.dropping(el -> {
68+
if (el instanceof MethodModel method) {
69+
return shouldExcludeMethod(method);
70+
}
71+
return false;
72+
}).andThen((builder, el) -> {
73+
switch (el) {
74+
case InnerClassesAttribute attr -> builder.with(InnerClassesAttribute.of(
75+
attr.classes().stream().filter(c -> {
76+
String name = c.innerClass().asInternalName();
77+
if (isExtension(name)) return true;
78+
if (isBuilder(name)) return false;
79+
return true;
80+
}).toList()
81+
));
82+
case Interfaces itfs -> builder.with(Interfaces.of(
83+
itfs.interfaces().stream().filter(
84+
entry -> isExtension(entry.asInternalName()) || !isBuilder(entry.asInternalName())
85+
).toList()
86+
));
87+
default -> builder.with(el);
88+
}
89+
});
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.vineflower.tools.minifier.patch;
2+
3+
import org.jspecify.annotations.Nullable;
4+
5+
import java.lang.classfile.ClassModel;
6+
import java.lang.classfile.ClassTransform;
7+
8+
public interface ClassPatch {
9+
/**
10+
* Always called with every class to allow for state initialization.
11+
*/
12+
default void firstPass(ClassModel clazz) {}
13+
14+
/**
15+
* @return a {@link ClassTransform} instance to transform, {@link ClassTransform#ACCEPT_ALL ACCEPT_ALL}
16+
* to skip transforming by this patch, and {@code null} to remove the class.
17+
*/
18+
@Nullable ClassTransform patch(ClassModel clazz);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.vineflower.tools.minifier.patch;
2+
3+
import java.io.IOException;
4+
import java.lang.classfile.ClassFile;
5+
import java.lang.classfile.ClassModel;
6+
import java.lang.classfile.ClassTransform;
7+
import java.nio.file.FileVisitResult;
8+
import java.nio.file.Path;
9+
import java.nio.file.SimpleFileVisitor;
10+
import java.nio.file.attribute.BasicFileAttributes;
11+
12+
public class ClassPatches {
13+
public static ClassPatch[] getPatches() {
14+
return new ClassPatch[] {
15+
new KotlinRemoval(),
16+
new BuilderRemoval(),
17+
new FlagsToInterface(),
18+
};
19+
}
20+
}

0 commit comments

Comments
 (0)