ConsoleOutputs.java

package io.jstach.rainbowgum.output;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.function.Supplier;

import io.jstach.rainbowgum.LogOutput;
import io.jstach.rainbowgum.LogProperties;
import io.jstach.rainbowgum.LogProvider;
import io.jstach.rainbowgum.annotation.LogConfigurable;

/*
 * Factories for the standard out and standard err outputs. The generated
 * StdOutOutputBuilder and StdErrOutputBuilder are the public API; everything here stays
 * package private.
 */
final class ConsoleOutputs {

	private ConsoleOutputs() {
	}

	static ConsoleStream parseStream(String value) {
		return ConsoleStream.parse(value);
	}

	/**
	 * Creates a standard out output.
	 * @param name output name, used for property lookup.
	 * @param stream {@link ConsoleStream#CACHED} (default) uses {@link System#out} as it
	 * is when the output is created, {@link ConsoleStream#FOLLOW} reads it on every
	 * write.
	 * @return output provider.
	 */
	@LogConfigurable(name = "StdOutOutputBuilder", prefix = LogProperties.OUTPUT_PREFIX + "stdout.")
	static LogProvider<LogOutput> stdout(@LogConfigurable.KeyParameter String name, @LogConfigurable.DefaultParameter(
			constant = "CACHED") @LogConfigurable.ConvertParameter("parseStream") ConsoleStream stream) {
		return switch (stream) {
			case CACHED -> (n, c) -> new StdOutOutput();
			case FOLLOW -> (n, c) -> new FollowStdOutOutput();
		};
	}

	/**
	 * Creates a standard err output.
	 * @param name output name, used for property lookup.
	 * @param stream {@link ConsoleStream#CACHED} (default) uses {@link System#err} as it
	 * is when the output is created, {@link ConsoleStream#FOLLOW} reads it on every
	 * write.
	 * @return output provider.
	 */
	@LogConfigurable(name = "StdErrOutputBuilder", prefix = LogProperties.OUTPUT_PREFIX + "stderr.")
	static LogProvider<LogOutput> stderr(@LogConfigurable.KeyParameter String name, @LogConfigurable.DefaultParameter(
			constant = "CACHED") @LogConfigurable.ConvertParameter("parseStream") ConsoleStream stream) {
		return switch (stream) {
			case CACHED -> (n, c) -> new StdErrOutput();
			case FOLLOW -> (n, c) -> new FollowStdErrOutput();
		};
	}

}

/*
 * Looks up the target stream on every call, the same way Logback's console target does.
 */
final class FollowingOutputStream extends OutputStream {

	private final Supplier<PrintStream> stream;

	FollowingOutputStream(Supplier<PrintStream> stream) {
		this.stream = stream;
	}

	@Override
	public void write(int b) throws IOException {
		stream.get().write(b);
	}

	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		stream.get().write(b, off, len);
	}

	@Override
	public void flush() throws IOException {
		stream.get().flush();
	}

}

abstract class AbstractConsoleOutput extends LogOutput.AbstractOutputStreamOutput {

	AbstractConsoleOutput(URI uri, OutputStream outputStream) {
		super(uri, outputStream);
	}

	/*
	 * The process owns standard out and err, not the logging system.
	 */
	@Override
	public final void close() {
	}

}

final class StdOutOutput extends AbstractConsoleOutput {

	StdOutOutput() {
		super(LogOutput.STDOUT_URI, System.out);
	}

	@Override
	public OutputType type() {
		return OutputType.CONSOLE_OUT;
	}

}

final class StdErrOutput extends AbstractConsoleOutput {

	StdErrOutput() {
		super(LogOutput.STDERR_URI, System.err);
	}

	@Override
	public OutputType type() {
		return OutputType.CONSOLE_ERR;
	}

}

final class FollowStdOutOutput extends AbstractConsoleOutput {

	FollowStdOutOutput() {
		super(LogOutput.STDOUT_URI, new FollowingOutputStream(() -> System.out));
	}

	@Override
	public OutputType type() {
		return OutputType.CONSOLE_OUT;
	}

}

final class FollowStdErrOutput extends AbstractConsoleOutput {

	FollowStdErrOutput() {
		super(LogOutput.STDERR_URI, new FollowingOutputStream(() -> System.err));
	}

	@Override
	public OutputType type() {
		return OutputType.CONSOLE_ERR;
	}

}