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.

Dependency Graph

Module Dependency

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)));


Back to top

Version: 0.1.0. Last Published: 2018-10-08.

Earcam Maven Skin.