|  | // 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 com.android.tools.r8.keepanno.annotations.KeepForApi; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.origin.PathOrigin; | 
|  | import com.android.tools.r8.utils.ExceptionDiagnostic; | 
|  | import java.io.IOException; | 
|  | import java.io.Writer; | 
|  | import java.nio.charset.Charset; | 
|  | import java.nio.charset.StandardCharsets; | 
|  | import java.nio.file.Files; | 
|  | import java.nio.file.Path; | 
|  |  | 
|  | /** Interface for receiving String resource. */ | 
|  | @KeepForApi | 
|  | public interface StringConsumer extends Finishable { | 
|  |  | 
|  | /** | 
|  | * Callback to receive part of a string resource. | 
|  | * | 
|  | * <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. | 
|  | * | 
|  | * <p>Note: prior to the addition of 'finished' consumers could expect all content to be reported | 
|  | * in one call to accept. That is no longer guaranteed. | 
|  | * | 
|  | * @param string Part of the string resource. | 
|  | * @param handler Diagnostics handler for reporting. | 
|  | */ | 
|  | void accept(String string, DiagnosticsHandler handler); | 
|  |  | 
|  | static EmptyConsumer emptyConsumer() { | 
|  | return EmptyConsumer.EMPTY_CONSUMER; | 
|  | } | 
|  |  | 
|  | /** Empty consumer to request the production of the resource but ignore its value. */ | 
|  | @KeepForApi | 
|  | class EmptyConsumer implements StringConsumer { | 
|  |  | 
|  | private static final EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer(); | 
|  |  | 
|  | @Override | 
|  | public void accept(String string, DiagnosticsHandler handler) { | 
|  | // Ignore content. | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) { | 
|  | // No content so, nothing to do. | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Forwarding consumer to delegate to an optional existing consumer. */ | 
|  | @KeepForApi | 
|  | class ForwardingConsumer implements StringConsumer { | 
|  |  | 
|  | private final StringConsumer consumer; | 
|  |  | 
|  | /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */ | 
|  | public ForwardingConsumer(StringConsumer consumer) { | 
|  | this.consumer = consumer; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void accept(String string, DiagnosticsHandler handler) { | 
|  | if (consumer != null) { | 
|  | consumer.accept(string, handler); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) { | 
|  | if (consumer != null) { | 
|  | consumer.finished(handler); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** File consumer to write contents to a file-system file. */ | 
|  | @KeepForApi | 
|  | class FileConsumer extends ForwardingConsumer { | 
|  |  | 
|  | private final Path outputPath; | 
|  | private Charset encoding = StandardCharsets.UTF_8; | 
|  | private WriterConsumer delegate = null; | 
|  | private boolean failedToCreateDelegate = false; | 
|  |  | 
|  | /** Consumer that writes to {@param outputPath}. */ | 
|  | public FileConsumer(Path outputPath) { | 
|  | this(outputPath, null); | 
|  | } | 
|  |  | 
|  | /** Consumer that forwards to {@param consumer} and also writes to {@param outputPath}. */ | 
|  | public FileConsumer(Path outputPath, StringConsumer consumer) { | 
|  | super(consumer); | 
|  | this.outputPath = outputPath; | 
|  | } | 
|  |  | 
|  | /** Get the output path that the consumer will write to. */ | 
|  | public Path getOutputPath() { | 
|  | return outputPath; | 
|  | } | 
|  |  | 
|  | /** Set the output encoding. Defaults to UTF8. */ | 
|  | public void setEncoding(Charset encoding) { | 
|  | assert encoding != null; | 
|  | if (delegate != null) { | 
|  | throw new IllegalStateException("Invalid call to set encoding after file stream is opened"); | 
|  | } | 
|  | this.encoding = encoding; | 
|  | } | 
|  |  | 
|  | /** Get the output encoding. Defaults to UTF8. */ | 
|  | public Charset getEncoding() { | 
|  | return encoding; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void accept(String string, DiagnosticsHandler handler) { | 
|  | super.accept(string, handler); | 
|  | if (failedToCreateDelegate) { | 
|  | return; | 
|  | } | 
|  | ensureDelegate(handler); | 
|  | if (delegate != null) { | 
|  | delegate.accept(string, handler); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) { | 
|  | super.finished(handler); | 
|  | if (failedToCreateDelegate) { | 
|  | return; | 
|  | } | 
|  | ensureDelegate(handler); | 
|  | if (delegate != null) { | 
|  | delegate.finished(handler); | 
|  | delegate = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | private void ensureDelegate(DiagnosticsHandler handler) { | 
|  | if (delegate != null) { | 
|  | return; | 
|  | } | 
|  | PathOrigin origin = new PathOrigin(outputPath); | 
|  | try { | 
|  | Path parent = outputPath.getParent(); | 
|  | if (parent != null && !parent.toFile().exists()) { | 
|  | Files.createDirectories(parent); | 
|  | } | 
|  | delegate = new WriterConsumer(origin, Files.newBufferedWriter(outputPath, encoding)); | 
|  | } catch (IOException e) { | 
|  | failedToCreateDelegate = true; | 
|  | handler.error(new ExceptionDiagnostic(e, origin)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * String consumer to write contents to a Writer. | 
|  | * | 
|  | * <p>Note: The writer is closed when the consumer receives its 'finished' callback. | 
|  | */ | 
|  | class WriterConsumer extends ForwardingConsumer { | 
|  |  | 
|  | private final Origin origin; | 
|  | private Writer writer; | 
|  |  | 
|  | /** Consumer that writes to {@param writer}. */ | 
|  | public WriterConsumer(Origin origin, Writer writer) { | 
|  | this(origin, writer, null); | 
|  | } | 
|  |  | 
|  | /** Consumer that forwards to {@param consumer} and also writes to {@param writer}. */ | 
|  | public WriterConsumer(Origin origin, Writer writer, StringConsumer consumer) { | 
|  | super(consumer); | 
|  | this.origin = origin; | 
|  | this.writer = writer; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void accept(String string, DiagnosticsHandler handler) { | 
|  | super.accept(string, handler); | 
|  | try { | 
|  | writer.write(string); | 
|  | } catch (IOException e) { | 
|  | handler.error(new ExceptionDiagnostic(e, origin)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) { | 
|  | super.finished(handler); | 
|  | try { | 
|  | writer.close(); | 
|  | } catch (IOException e) { | 
|  | handler.error(new ExceptionDiagnostic(e, origin)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |