Skip to content

Commit 1bcb28a

Browse files
committed
Allow feature level requirement to be set at package level
1 parent d51b0f6 commit 1bcb28a

3 files changed

Lines changed: 77 additions & 17 deletions

File tree

annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresFeatureLevel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.lang.annotation.Target;
77

88
@Retention(RetentionPolicy.CLASS)
9-
@Target(ElementType.TYPE)
9+
@Target({ElementType.TYPE, ElementType.PACKAGE})
1010
public @interface RequiresFeatureLevel {
1111
FeatureLevel value() default FeatureLevel.GA;
1212
}

annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresMod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.lang.annotation.Target;
77

88
@Retention(RetentionPolicy.CLASS)
9-
@Target(ElementType.TYPE)
9+
@Target({ElementType.TYPE, ElementType.PACKAGE})
1010
public @interface RequiresMod {
1111
String value() default "";
1212
}

src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,58 @@ public static String sanitize(String mixinClassName) {
8989
private final Set<String> mixinOptions = new ObjectOpenHashSet<>();
9090
private final Map<String, String> mixinsMissingMods = new Object2ObjectOpenHashMap<>();
9191

92+
private static class PackageMetadata {
93+
String requiredModId;
94+
FeatureLevel requiredLevel;
95+
}
96+
97+
private final Map<String, PackageMetadata> packageMetadataCache = new HashMap<>();
98+
9299
public static boolean isFabric = ModernFixEarlyConfig.class.getClassLoader().getResourceAsStream("modernfix-fabric.mixins.json") != null;
93100

94101
public Map<String, String> getPermanentlyDisabledMixins() {
95102
return mixinsMissingMods;
96103
}
97104

105+
@SuppressWarnings("unchecked")
106+
private static <T> T getAnnotationValue(AnnotationNode ann, String key) {
107+
if (ann.values == null) return null;
108+
for (int i = 0; i < ann.values.size(); i += 2) {
109+
if (ann.values.get(i).equals(key)) return (T) ann.values.get(i + 1);
110+
}
111+
return null;
112+
}
113+
114+
private PackageMetadata loadPackageMetadata(String packageResourcePath) {
115+
String classPath = packageResourcePath + "/package-info.class";
116+
try (InputStream stream = ModernFixEarlyConfig.class.getClassLoader().getResourceAsStream(classPath)) {
117+
if (stream == null) return new PackageMetadata();
118+
ClassReader reader = new ClassReader(stream);
119+
ClassNode node = new ClassNode();
120+
reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
121+
PackageMetadata meta = new PackageMetadata();
122+
List<AnnotationNode> annotations = new ArrayList<>();
123+
if (node.invisibleAnnotations != null) annotations.addAll(node.invisibleAnnotations);
124+
if (node.visibleAnnotations != null) annotations.addAll(node.visibleAnnotations);
125+
for (AnnotationNode annotation : annotations) {
126+
if (Objects.equals(annotation.desc, MIXIN_REQUIRES_MOD_DESC)) {
127+
meta.requiredModId = getAnnotationValue(annotation, "value");
128+
} else if (Objects.equals(annotation.desc, FEATURE_LEVEL_ANNOTATION_DESC)) {
129+
String[] enumVal = getAnnotationValue(annotation, "value");
130+
meta.requiredLevel = FeatureLevel.valueOf(enumVal[1]);
131+
}
132+
}
133+
return meta;
134+
} catch (IOException e) {
135+
LOGGER.error("Error scanning package-info " + classPath, e);
136+
return new PackageMetadata();
137+
}
138+
}
139+
140+
private PackageMetadata getOrLoadPackageMetadata(String packageResourcePath) {
141+
return packageMetadataCache.computeIfAbsent(packageResourcePath, this::loadPackageMetadata);
142+
}
143+
98144
private void scanForAndBuildMixinOptions() {
99145
List<String> configFiles = ImmutableList.of("modernfix-modernfix.mixins.json");
100146
List<String> mixinPaths = new ArrayList<>();
@@ -133,27 +179,41 @@ private void scanForAndBuildMixinOptions() {
133179
} else if(Objects.equals(annotation.desc, MIXIN_CLIENT_ONLY_DESC)) {
134180
isClientOnly = true;
135181
} else if(Objects.equals(annotation.desc, MIXIN_REQUIRES_MOD_DESC)) {
136-
for(int i = 0; i < annotation.values.size(); i += 2) {
137-
if(annotation.values.get(i).equals("value")) {
138-
String modId = (String)annotation.values.get(i + 1);
139-
if(modId != null) {
140-
requiredModPresent = modId.startsWith("!") ? !modPresent(modId.substring(1)) : modPresent(modId);
141-
requiredModId = modId;
142-
}
143-
break;
144-
}
182+
String modId = getAnnotationValue(annotation, "value");
183+
if(modId != null) {
184+
requiredModPresent = modId.startsWith("!") ? !modPresent(modId.substring(1)) : modPresent(modId);
185+
requiredModId = modId;
145186
}
146187
} else if(Objects.equals(annotation.desc, MIXIN_DEV_ONLY_DESC)) {
147188
isDevOnly = true;
148189
} else if(Objects.equals(annotation.desc, FEATURE_LEVEL_ANNOTATION_DESC)) {
149-
for(int i = 0; i < annotation.values.size(); i += 2) {
150-
if(annotation.values.get(i).equals("value")) {
151-
// ASM stores enum annotation values as String[]{typeDescriptor, constantName}
152-
String[] enumVal = (String[]) annotation.values.get(i + 1);
153-
requiredLevel = FeatureLevel.valueOf(enumVal[1]);
154-
break;
190+
// ASM stores enum annotation values as String[]{typeDescriptor, constantName}
191+
String[] enumVal = getAnnotationValue(annotation, "value");
192+
requiredLevel = FeatureLevel.valueOf(enumVal[1]);
193+
}
194+
}
195+
// Merge constraints from ancestor package-info files (up to the mixin root)
196+
String classPackagePath = mixinPath.substring(0, mixinPath.lastIndexOf('/'));
197+
int mixinRootEnd = classPackagePath.indexOf("/mixin");
198+
if (mixinRootEnd >= 0) {
199+
String mixinRoot = classPackagePath.substring(0, mixinRootEnd + "/mixin".length());
200+
String walkPkg = mixinRoot;
201+
while (walkPkg.length() < classPackagePath.length()) {
202+
int nextSlash = classPackagePath.indexOf('/', walkPkg.length() + 1);
203+
walkPkg = (nextSlash == -1) ? classPackagePath : classPackagePath.substring(0, nextSlash);
204+
PackageMetadata pkgMeta = getOrLoadPackageMetadata(walkPkg);
205+
if (requiredModPresent && pkgMeta.requiredModId != null) {
206+
boolean present = pkgMeta.requiredModId.startsWith("!")
207+
? !modPresent(pkgMeta.requiredModId.substring(1))
208+
: modPresent(pkgMeta.requiredModId);
209+
if (!present) {
210+
requiredModPresent = false;
211+
requiredModId = pkgMeta.requiredModId;
155212
}
156213
}
214+
if (pkgMeta.requiredLevel != null && pkgMeta.requiredLevel.ordinal() > requiredLevel.ordinal()) {
215+
requiredLevel = pkgMeta.requiredLevel;
216+
}
157217
}
158218
}
159219
if(isMixin && (!isDevOnly || ModernFixPlatformHooks.INSTANCE.isDevEnv())) {

0 commit comments

Comments
 (0)