package com.oracle.mxtool.junit;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.module.Modules;
class ModuleSupport {
private final PrintStream out;
ModuleSupport(PrintStream out) {
this.out = out;
}
void processAddExportsAnnotations(Set<Class<?>> classes, Set<String> opened, Set<String> exported) {
Set<Class<?>> types = new HashSet<>();
for (Class<?> cls : classes) {
gatherSupertypes(cls, types);
}
for (Class<?> cls : types) {
Annotation[] annos = cls.getAnnotations();
for (Annotation a : annos) {
Class<? extends Annotation> annotationType = a.annotationType();
if (annotationType.getSimpleName().equals("AddExports")) {
Optional<String[]> value = getElement("value", String[].class, a);
if (value.isPresent()) {
for (String spec : value.get()) {
openPackages(spec, cls, opened, exported);
}
} else {
out.printf("%s: Ignoring \"AddExports\" annotation without `String value` element: %s%n", cls.getName(), a);
}
}
}
}
}
public static List<Module> findModules(String spec) {
ModuleLayer bootLayer = ModuleLayer.boot();
Set<Module> modules = bootLayer.modules();
List<Module> result = new ArrayList<>();
StringSpec matcher = new StringSpec(spec);
for (Module module : modules) {
if (matcher.matches(module.getName())) {
result.add(module);
}
}
return result;
}
static class StringSpec {
final String key;
final boolean isPrefix;
StringSpec(String spec) {
this.isPrefix = spec.endsWith("*");
this.key = isPrefix ? spec.substring(0, spec.length() - 1) : spec;
}
boolean matches(String s) {
if (isPrefix) {
return s.startsWith(key);
} else {
return s.equals(key);
}
}
}
private static final Pattern OPEN_PACKAGE_SPEC = Pattern.compile("([^/]+)/([^=]+)(?:=(.+))?");
void openPackages(String spec, Object context, Set<String> opened, Set<String> exported) {
Matcher m = OPEN_PACKAGE_SPEC.matcher(spec);
if (m.matches()) {
String moduleSpec = m.group(1);
String packageSpec = m.group(2);
String targetSpecs = m.group(3);
List<Module> modules = findModules(moduleSpec);
if (modules.isEmpty()) {
out.printf("%s: Cannot find module(s) matching %s: %s%n", context, moduleSpec, spec);
} else {
List<Module> targets = new ArrayList<>();
if (context instanceof Class) {
targets.add(((Class<?>) context).getModule());
}
boolean allUnnamedTarget = false;
if (targetSpecs != null) {
for (String targetSpec : targetSpecs.split(",")) {
if (targetSpec.equals("ALL-UNNAMED")) {
allUnnamedTarget = true;
} else {
List<Module> list = findModules(targetSpec);
if (list.isEmpty()) {
out.printf("%s: Cannot find target module(s) matching %s: %s%n", context, targetSpec, spec);
} else {
targets.addAll(list);
}
}
}
} else {
allUnnamedTarget = true;
}
StringSpec packageMatcher = new StringSpec(packageSpec);
for (Module module : modules) {
for (String pn : module.getPackages()) {
if (packageMatcher.matches(pn)) {
List<String> openTargets = new ArrayList<>();
List<String> exportTargets = new ArrayList<>();
if (allUnnamedTarget) {
if (!module.isExported(pn)) {
exportTargets.add("ALL-UNNAMED");
Modules.addExportsToAllUnnamed(module, pn);
}
if (!module.isOpen(pn)) {
openTargets.add("ALL-UNNAMED");
Modules.addOpensToAllUnnamed(module, pn);
}
}
for (Module target : targets) {
if (!module.isExported(pn, target)) {
if (target.isNamed()) {
exportTargets.add(target.getName());
}
Modules.addExports(module, pn, target);
}
if (!module.isOpen(pn, target)) {
if (target.isNamed()) {
openTargets.add(target.getName());
}
Modules.addOpens(module, pn, target);
}
}
if (!exportTargets.isEmpty()) {
exported.add(String.format("%s/%s=%s", module.getName(), pn, String.join(",", exportTargets)));
}
if (!openTargets.isEmpty()) {
opened.add(String.format("%s/%s=%s", module.getName(), pn, String.join(",", openTargets)));
}
}
}
}
}
} else {
out.printf("%s: Ignoring specification not matching <module>/<package>[=<target-module>(,<target-module>)*] pattern: %s%n", context, spec);
}
}
private static void gatherSupertypes(Class<?> cls, Set<Class<?>> supertypes) {
if (!supertypes.contains(cls)) {
supertypes.add(cls);
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
gatherSupertypes(superclass, supertypes);
}
for (Class<?> iface : cls.getInterfaces()) {
gatherSupertypes(iface, supertypes);
}
}
}
private static <T> Optional<T> getElement(String name, Class<T> type, Annotation annotation) {
Class<? extends Annotation> annotationType = annotation.annotationType();
Method valueAccessor;
try {
valueAccessor = annotationType.getMethod(name);
if (!valueAccessor.getReturnType().equals(type)) {
throw new AssertionError(String.format("Element %s of %s is of type %s, not %s ", name, annotationType.getName(), valueAccessor.getReturnType().getName(), type.getName()));
}
} catch (NoSuchMethodException e) {
return Optional.empty();
}
try {
return Optional.of(type.cast(valueAccessor.invoke(annotation)));
} catch (Exception e) {
throw new AssertionError(String.format("Could not read %s element from %s", name, annotation), e);
}
}
}