module.auto
Bytecode parsing sufficient to enable auto-importing for programmatic modules
Determines required imports - used for JPMS and OSGi auto-import.
ImportsOf does the grunt work, Reader provides a suitable abstraction for package-to-module mappers.
Examples
The examples below use a bit of test support for class and packages:private static String cn(Class<?> type) { return type.getCanonicalName(); } private static String pkg(Class<?> type) { return type.getPackage().getName(); }
Simple example
Given a class:
import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ImportsSlf4jApi { private static final Logger LOG = LoggerFactory.getLogger(ImportsSlf4jApi.class); public static void main(String[] args) { LOG.debug("hello " + Arrays.toString(args)); } }
List all it’s imports:
Map<String, Set<String>> imports = new HashMap<>(); reader() .addImportListener(imports::put) .processClass(Resources.classAsBytes(ImportsSlf4jApi.class)); assertThat(imports, is(aMapWithSize(1))); assertThat(imports, hasEntry(equalTo(cn(ImportsSlf4jApi.class)), containsInAnyOrder( cn(Arrays.class), cn(Logger.class), cn(LoggerFactory.class), cn(Object.class), cn(Class.class), cn(StringBuilder.class), cn(String.class))));
Annotations selectively in/excluded
Given a heavily annotated class:
package io.earcam.acme; import java.io.UncheckedIOException; import java.util.Comparator; import java.util.Objects; import org.hamcrest.Matchers; import org.hamcrest.core.IsAnything; import org.junit.jupiter.api.Test; import org.objectweb.asm.Label; import org.objectweb.asm.signature.SignatureReader; import io.earcam.utilitarian.charstar.CharSequences; @AcmeAnnotation(CharSequences.class) public class Annotated implements Comparable<@AcmeAnnotation(Test.class) Annotated> { @AcmeAnnotation(Label.class) private int num = 42; @AcmeAnnotation(SignatureReader.class) public int getNum() throws @AcmeAnnotation(IllegalStateException.class) OutOfMemoryError { @AcmeAnnotation(Matchers.class) int numnum = num; return new @AcmeAnnotation(IsAnything.class) Integer(numnum).intValue(); } @Override public int compareTo(@AcmeAnnotation(Comparator.class) Annotated o) { return 0; } public static <T extends Comparable<T>> boolean isNaNaNullALul(T instance, Class<@AcmeAnnotation(Objects.class) T> type) { try { return instance == null; } catch(@AcmeAnnotation(UncheckedIOException.class) Throwable t) { return true; } } }
List all imports, including annotations and their elements:
Map<String, Set<String>> imports = new HashMap<>(); reader() .addImportListener(imports::put) .processClass(Resources.classAsBytes(Annotated.class)); assertThat(imports, is(aMapWithSize(1))); assertThat(imports, hasEntry(equalTo(cn(Annotated.class)), containsInAnyOrder( cn(Object.class), cn(Comparable.class), cn(Class.class), cn(Throwable.class), cn(OutOfMemoryError.class), cn(Integer.class), cn(AcmeAnnotation.class), cn(CharSequences.class), cn(Test.class), cn(Label.class), cn(SignatureReader.class), cn(IllegalStateException.class), cn(Matchers.class), cn(IsAnything.class), cn(Comparator.class), cn(Objects.class), cn(UncheckedIOException.class))));
Or don’t (configure Reader to ignore):
reader() .ignoreAnnotations() .addImportListener(imports::put) .processClass(Resources.classAsBytes(Annotated.class)); assertThat(imports, is(aMapWithSize(1))); assertThat(imports, hasEntry(equalTo(cn(Annotated.class)), containsInAnyOrder( cn(Object.class), cn(Comparable.class), cn(Class.class), cn(Throwable.class), cn(OutOfMemoryError.class), cn(Integer.class))));
At a higher level
Map an artifact/archive/module; in this case defining-packages-to-required-packages:
Map<String, Set<String>> imports = new HashMap<>(); InputStream jar = bundle() .add(ImportsSlf4jApi.class) .add("irrelevant/file.txt", new ByteArrayInputStream("hello".getBytes(UTF_8))) .build(); BiConsumer<String, Set<String>> listener = imports::put; reader() .addImportListener(listener) .setImportedTypeReducer(Reader::typeToPackageReducer) .setImportingTypeReducer(Reader::typeToPackageReducer) .processJar(jar); assertThat(imports, is(aMapWithSize(1))); Set<String> expected = new HashSet<>(Arrays.asList( pkg(Arrays.class), pkg(Logger.class), pkg(LoggerFactory.class), pkg(Object.class), pkg(Class.class), pkg(StringBuilder.class), pkg(String.class))); assertThat(imports, hasEntry(equalTo(pkg(ImportsSlf4jApi.class)), equalTo(expected)));