blob: 9a59313a596bad88794364dc1b5c1b2ad85154d1 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.IOExceptionDiagnostic;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.zip.ZipOutputStream;
/**
* Consumer for Java classfile encoded programs.
*
* <p>This consumer can only be provided to R8.
*/
public interface ClassFileConsumer extends ProgramConsumer {
/**
* Callback to receive Java classfile data for a compilation output.
*
* <p>There is no guaranteed order and files might be written concurrently.
*
* <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
* {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
* then the compiler guaranties to exit with an error.
*
* @param data Java class-file encoded data.
* @param descriptor Class descriptor of the class the data pertains to.
* @param handler Diagnostics handler for reporting.
*/
void accept(byte[] data, String descriptor, DiagnosticsHandler handler);
/** Empty consumer to request the production of the resource but ignore its value. */
static ClassFileConsumer emptyConsumer() {
return ForwardingConsumer.EMPTY_CONSUMER;
}
/** Forwarding consumer to delegate to an optional existing consumer. */
class ForwardingConsumer implements ClassFileConsumer {
private static final ClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
private final ClassFileConsumer consumer;
public ForwardingConsumer(ClassFileConsumer consumer) {
this.consumer = consumer;
}
@Override
public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
if (consumer != null) {
consumer.accept(data, descriptor, handler);
}
}
@Override
public void finished(DiagnosticsHandler handler) {
if (consumer != null) {
consumer.finished(handler);
}
}
}
/** Archive consumer to write program resources to a zip archive. */
class ArchiveConsumer extends ForwardingConsumer implements InternalProgramOutputPathConsumer {
private final Path archive;
private final Origin origin;
private ZipOutputStream stream = null;
private boolean closed = false;
public ArchiveConsumer(Path archive) {
this(archive, null);
}
public ArchiveConsumer(Path archive, ClassFileConsumer consumer) {
super(consumer);
this.archive = archive;
origin = new PathOrigin(archive);
}
@Override
public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
super.accept(data, descriptor, handler);
synchronizedWrite(getClassFileName(descriptor), data, handler);
}
@Override
public void finished(DiagnosticsHandler handler) {
super.finished(handler);
assert !closed;
closed = true;
try {
if (stream != null) {
stream.close();
stream = null;
}
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, origin));
}
}
@Override
public Path internalGetOutputPath() {
return archive;
}
private ZipOutputStream getStream(DiagnosticsHandler handler) {
assert !closed;
if (stream == null) {
try {
stream =
new ZipOutputStream(
Files.newOutputStream(
archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, origin));
}
}
return stream;
}
private synchronized void synchronizedWrite(
String entry, byte[] content, DiagnosticsHandler handler) {
try {
ZipUtils.writeToZipStream(getStream(handler), entry, content);
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, origin));
}
}
private static String getClassFileName(String classDescriptor) {
assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
}
public static void writeResources(Path archive, List<ProgramResource> resources)
throws IOException, ResourceException {
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (Closer closer = Closer.create()) {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
for (ProgramResource resource : resources) {
assert resource.getClassDescriptors().size() == 1;
String className = resource.getClassDescriptors().iterator().next();
String entryName = getClassFileName(className);
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
ZipUtils.writeToZipStream(out, entryName, bytes);
}
}
}
}
}
/** Directory consumer to write program resources to a directory. */
class DirectoryConsumer extends ForwardingConsumer implements InternalProgramOutputPathConsumer {
private final Path directory;
public DirectoryConsumer(Path directory) {
this(directory, null);
}
public DirectoryConsumer(Path directory, ClassFileConsumer consumer) {
super(consumer);
this.directory = directory;
}
@Override
public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
super.accept(data, descriptor, handler);
Path target = directory.resolve(ArchiveConsumer.getClassFileName(descriptor));
try {
writeFileFromDescriptor(data, target);
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
}
}
@Override
public void finished(DiagnosticsHandler handler) {
super.finished(handler);
}
@Override
public Path internalGetOutputPath() {
return directory;
}
private static void writeFileFromDescriptor(byte[] contents, Path target) throws IOException {
Files.createDirectories(target.getParent());
FileUtils.writeToFile(target, null, contents);
}
}
}