JavaFileObjects.java
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2019 Arno Unkrig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.commons.compiler.jdk.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import org.codehaus.commons.compiler.io.Readers;
import org.codehaus.commons.compiler.util.resource.Resource;
import org.codehaus.commons.compiler.util.resource.ResourceCreator;
import org.codehaus.commons.nullanalysis.NotNullByDefault;
import org.codehaus.commons.nullanalysis.Nullable;
/**
* Utility methods related to {@link JavaFileObject}s.
*/
public final
class JavaFileObjects {
private JavaFileObjects() {}
/**
* Byte array-based implementation of {@link JavaFileObject}.
*/
public static final
class ResourceJavaFileObject extends SimpleJavaFileObject {
private final Resource resource;
private final Charset charset;
private final String name;
private
ResourceJavaFileObject(Resource resource, String className, Kind kind, Charset charset) {
super(
URI.create("bytearray:///" + className.replace('.', '/') + kind.extension),
kind
);
this.resource = resource;
this.charset = charset;
this.name = "/" + className.replace('.', '/') + kind.extension;
}
@Override public boolean
isNameCompatible(@Nullable String simpleName, @Nullable Kind kind) {
return !"module-info".equals(simpleName);
}
@Override public String
getName() {
return this.name;
}
@Override public InputStream
openInputStream() throws IOException { return this.resource.open(); }
@Override public Reader
openReader(boolean ignoreEncodingErrors) throws IOException {
return new InputStreamReader(this.resource.open(), this.charset);
}
@Override public CharSequence
getCharContent(boolean ignoreEncodingErrors) throws IOException {
Reader r = this.openReader(true);
try {
return Readers.readAll(r);
} finally {
r.close();
}
}
@Override public long
getLastModified() { return this.resource.lastModified(); }
public String
getResourceFileName() { return this.resource.getFileName(); }
}
/**
* Wraps a {@link Resource} as a {@link JavaFileObject}.
*/
public static JavaFileObject
fromResource(Resource resource, String className, Kind kind, Charset charset) {
return new ResourceJavaFileObject(resource, className, kind, charset);
}
/**
* @return The resource designated by the <var>url</var>, wrapped in a {@link JavaFileObject}
*/
public static JavaFileObject
fromUrl(final URL url, final String name, final Kind kind) {
final URI subresourceUri;
try {
subresourceUri = url.toURI();
} catch (URISyntaxException use) {
throw new AssertionError(use);
}
// Cannot use "javax.tools.SimpleJavaFileObject" here, because the constructor requires a URI with a "path",
// and URIs without a ":/" infix don't have a path.
@NotNullByDefault(false) JavaFileObject result = new JavaFileObject() {
@Override public URI
toUri() { return subresourceUri; }
@Override public String
getName() { return name; }
@Override public InputStream
openInputStream() throws IOException { return url.openStream(); }
@Override public Kind
getKind() { return kind; }
@Override public OutputStream openOutputStream() { throw new UnsupportedOperationException(); }
@Override public Reader openReader(boolean ignoreEncodingErrors) { throw new UnsupportedOperationException(); }
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { throw new UnsupportedOperationException(); }
@Override public Writer openWriter() { throw new UnsupportedOperationException(); }
@Override public long getLastModified() { throw new UnsupportedOperationException(); }
@Override public boolean delete() { throw new UnsupportedOperationException(); }
@Override public boolean isNameCompatible(String simpleName, Kind kind) { throw new UnsupportedOperationException(); }
@Override public NestingKind getNestingKind() { throw new UnsupportedOperationException(); }
@Override public Modifier getAccessLevel() { throw new UnsupportedOperationException(); }
@Override public String
toString() { return name + " from " + this.getClass().getSimpleName(); }
};
return result;
}
/**
* @return A {@link JavaFileObject} that stores its data in an internal byte array
*/
public static ByteArrayJavaFileObject
inMemory(final String className, final Kind kind2, final Charset charset) {
class MyJavaFileObject extends SimpleJavaFileObject implements ByteArrayJavaFileObject {
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
MyJavaFileObject() {
super(URI.create("bytearray:///" + className.replace('.', '/') + kind2.extension), kind2);
}
@Override public InputStream
openInputStream() throws IOException { return new ByteArrayInputStream(this.toByteArray()); }
@Override public OutputStream
openOutputStream() throws IOException { return this.buffer; }
@Override public Reader
openReader(boolean ignoreEncodingErrors) throws IOException {
return new InputStreamReader(this.openInputStream(), charset);
}
@Override public Writer
openWriter() throws IOException { return new OutputStreamWriter(this.openOutputStream(), charset); }
/**
* @return The bytes that were previously written to this {@link JavaFileObject}
*/
@Override
public byte[]
toByteArray() { return this.buffer.toByteArray(); }
}
return new MyJavaFileObject();
}
/**
* Byte array-based implementation of {@link JavaFileObject}.
*/
public
interface ByteArrayJavaFileObject extends JavaFileObject {
/**
* @return The bytes that were previously written to this {@link JavaFileObject}
*/
byte[] toByteArray();
}
/**
* @param resourceName E.g. {@code "com/foo/pkg/Bar.class"}
* @return A {@link JavaFileObject} that stores data through the given <var>resourceCreator</var> and
* <var>resourceName</var>
*/
public static JavaFileObject
fromResourceCreator(
final ResourceCreator resourceCreator,
final String resourceName,
Kind kind,
final Charset charset
) {
return new SimpleJavaFileObject(URI.create("bytearray:///" + resourceName), kind) {
@Override public OutputStream
openOutputStream() throws IOException { return resourceCreator.createResource(resourceName); }
@Override public Writer
openWriter() throws IOException { return new OutputStreamWriter(this.openOutputStream(), charset); }
};
}
}