CompilationTarget.java
/*-
* #%L
* io.earcam.instrumental.compile
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.instrumental.compile;
import static java.util.stream.Collectors.toMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardJavaFileManager;
import io.earcam.instrumental.reflect.Names;
/**
* An extendible interface for compilation output
*
* @param <T>
*/
public interface CompilationTarget<T> extends Supplier<T> {
/**
* <p>
* configureOutputFileManager.
* </p>
*
* @param manager a {@link JavaFileManager} instance.
* @return a {@link javax.tools.JavaFileManager} object.
*/
public abstract JavaFileManager configureOutputFileManager(StandardJavaFileManager manager);
/**
* <p>
* Returns a builder, where you must explicitly set, as a minimum, the class output directory.
* </p>
*
* @return a builder for the filesystem compilation target
* @see CompilationTarget#toFileSystem(Path)
*/
public static FilesystemCompilationTargetBuilder toFileSystem()
{
return new FilesystemCompilationTargetBuilder();
}
/**
* <p>
* Compile to a filesystem. The returned {@link CompilationTarget} is an instance of
* {@link FilesystemCompilationTarget},
* which allows additional output locations to be set (generated sources and native headers)
* </p>
*
* @param classOutputDirectory the path to write classes to.
* @return a {@link FilesystemCompilationTarget} instance, which may have further calls to set output locations
* @see CompilationTarget#toFileSystem()
*/
public static FilesystemCompilationTarget toFileSystem(Path classOutputDirectory)
{
return new FilesystemCompilationTarget(classOutputDirectory);
}
/**
* @return an in-memory {@link CompilationTarget}.
*/
public static CompilationTarget<Map<String, byte[]>> toByteArrays()
{
return new CompilationTarget<Map<String, byte[]>>() {
List<ByteArrayJavaFileObject> output = new ArrayList<>();
@Override
public JavaFileManager configureOutputFileManager(StandardJavaFileManager manager)
{
return new StandardForwardingJavaFileManager(manager) {
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException
{
ByteArrayJavaFileObject javaBytes = new ByteArrayJavaFileObject(className, kind);
output.add(javaBytes);
return javaBytes;
}
@Override
public boolean isSameFile(FileObject a, FileObject b)
{
return a.getName().equals(b.getName());
}
@Override
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException
{
Kind kind = EnumSet.allOf(Kind.class).stream()
.filter(k -> relativeName.endsWith(k.extension))
.findFirst()
.orElse(Kind.OTHER);
ByteArrayJavaFileObject fileBytes = new ByteArrayJavaFileObject(packageName + '/' + relativeName, kind);
output.add(fileBytes);
return fileBytes;
}
};
}
@Override
public Map<String, byte[]> get()
{
return output.stream()
.collect(toMap(b -> b.getName().substring(1), ByteArrayJavaFileObject::getBytes));
}
};
}
/**
* <p>
* toClassLoader.
* </p>
*
* @return a {@link CompilationTarget} object.
*/
public static CompilationTarget<ClassLoader> toClassLoader()
{
return toClassLoader(ClassLoader.getSystemClassLoader());
}
/**
* <p>
* toClassLoader.
* </p>
*
* @param parent a {@link java.lang.ClassLoader} object.
* @return a {@link CompilationTarget} object.
*/
public static CompilationTarget<ClassLoader> toClassLoader(ClassLoader parent)
{
CompilationTarget<Map<String, byte[]>> underlying = toByteArrays();
return new CompilationTarget<ClassLoader>() {
@Override
public JavaFileManager configureOutputFileManager(StandardJavaFileManager manager)
{
return underlying.configureOutputFileManager(manager);
}
@Override
public ClassLoader get()
{
Map<String, byte[]> map = underlying.get();
return new ClassLoader(parent) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
byte[] bytes = map.get(Names.typeToResourceName(name));
return (bytes == null) ? super.loadClass(name) : defineClass(null, bytes, 0, bytes.length, null);
}
};
}
};
}
/**
* Useful if only interested in annotation processing
*
* @return a NOOP {@link CompilationTarget}
*/
public static CompilationTarget<Void> toBlackhole()
{
CompilationTarget<Map<String, byte[]>> map = toByteArrays();
return new CompilationTarget<Void>() {
@Override
public Void get()
{
return null;
}
@Override
public JavaFileManager configureOutputFileManager(StandardJavaFileManager manager)
{
return map.configureOutputFileManager(manager);
}
};
}
}