System.out.println(""); System.err.println("");

package further.util;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
/**
* 控制台同步输出(项目扩展,与 {@link java.lang.System} 不同包名,避免替 JDK 引导类)。
* <p>
* <strong>顺序:</strong>Eclipse 等对 stdout / stderr <strong>两条管道分别缓冲</strong>,即使 JVM 里加锁、逐行 flush,
* 控制台仍常出现「先整块黑字、再整块红字」,与源码交替调用顺序不一致------这在 IDE 侧无法仅靠 Java 根治。
* 要保证<strong>显示顺序与调用顺序一致</strong>,只能<strong>合并为一条管道</strong>(只写 {@link FileDescriptor#out}),见 {@link #installOrderedConsole()}。
* </p>
* <p>
* <strong>红色:</strong>IDE 的红字来自<strong>真实 stderr(fd2)</strong>,见 {@link #installDualFileDescriptorConsole()};
* 与「合并管道保顺序」二者在 Eclipse 内置控制台里<strong>不可兼得</strong>。合并模式下 {@link Kind#ERR} 时默认加
* {@code [stderr] } 前缀区分错误语义;若控制台支持 ANSI,可加 {@code -Dsyncconsole.ansiErr=true} 尝试红色转义(部分 Eclipse 版本仍不会着色)。
* </p>
*
* @author ZengWenFeng
* @mobile 13805029595
* @email 117791303@qq.com
* @date 2026.05.08
*/
public final class System2
{
/**
* 打印目标:标准输出 / 标准错误(语义),用于 {@link #println(Kind, String)} 第一个参数。
*/
public enum Kind
{
/** 标准输出 */
OUT,
/** 标准错误 */
ERR
}
/** 与 {@link Kind#OUT} 相同,便于书写 {@code System2.println(System2.OUT, ...)} */
public static final Kind OUT = Kind.OUT;
/** 与 {@link Kind#ERR} 相同 */
public static final Kind ERR = Kind.ERR;
private static final Object CONSOLE_LOCK = new Object();
private static final String STDERR_PREFIX = "[stderr] ";
private static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_RESET = "\u001B[0m";
private static volatile boolean installed;
/** true:合并管道模式;false:双 fd(JDK 式)。 */
private static volatile boolean mergedStdoutMode;
private static volatile PrintStream outStream;
private static volatile PrintStream errStream;
private static volatile PrintStream unifiedStream;
private static volatile boolean ansiErrRed = Boolean.parseBoolean(java.lang.System.getProperty("syncconsole.ansiErr", "false"));
private System2()
{
}
/**
* <strong>默认推荐:</strong>只使用标准输出 fd,{@link java.lang.System#out} 与 {@link java.lang.System#err} 指向同一 {@link PrintStream},
* 控制台收到的字节顺序与 {@link #println(Kind, String)} 调用顺序一致。{@link Kind#ERR} 时默认带 {@code [stderr] } 前缀;
* 需要 ANSI 红字时可 {@code -Dsyncconsole.ansiErr=true}(视控制台是否解析转义而定)。
*/
public static void installOrderedConsole()
{
installMergedStdoutInternal();
}
/**
* 与 {@link #installOrderedConsole()} 相同实现(合并管道、顺序优先)。
*/
public static void installMergedStdoutStrictOrder()
{
installMergedStdoutInternal();
}
private static void installMergedStdoutInternal()
{
synchronized (CONSOLE_LOCK)
{
if (installed)
{
return;
}
try
{
OutputStream raw = new FileOutputStream(FileDescriptor.out);
OutputStream gated = new GatedOutputStream(raw);
PrintStream ps = new PrintStream(gated, true, "UTF-8");
unifiedStream = ps;
java.lang.System.setOut(ps);
java.lang.System.setErr(ps);
mergedStdoutMode = true;
installed = true;
}
catch (UnsupportedEncodingException e)
{
throw new IllegalStateException("UTF-8 must be supported", e);
}
}
}
/**
* JDK 式双 fd:{@link java.lang.System#err} 走真实 stderr,在 Eclipse 中一般为红色,但<strong>不要期望</strong>与 {@link Kind#OUT} 交替时
* 显示顺序与源码一致(IDE 分管道缓冲)。
*/
public static void installDualFileDescriptorConsole()
{
synchronized (CONSOLE_LOCK)
{
if (installed)
{
return;
}
try
{
FileOutputStream fosOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fosErr = new FileOutputStream(FileDescriptor.err);
outStream = new PrintStream(fosOut, true, "UTF-8");
errStream = new PrintStream(fosErr, true, "UTF-8");
java.lang.System.setOut(outStream);
java.lang.System.setErr(errStream);
mergedStdoutMode = false;
installed = true;
}
catch (UnsupportedEncodingException e)
{
throw new IllegalStateException("UTF-8 must be supported", e);
}
}
}
/**
* @deprecated 语义同 {@link #installDualFileDescriptorConsole()}
*/
@Deprecated
public static void installPrintStreamsLikeSystemClass()
{
installDualFileDescriptorConsole();
}
public static void setAnsiErrRedEnabled(boolean enabled)
{
ansiErrRed = enabled;
}
public static boolean isAnsiErrRedEnabled()
{
return ansiErrRed;
}
/**
* 按通道输出一行(换行)。示例:{@code System2.println(System2.OUT, "")}、{@code System2.println(System2.ERR, "msg")}。
*
* @param kind {@link #OUT} 或 {@link #ERR},不可为 {@code null}
* @param message 文本;{@code null} 时与 {@link PrintStream#println(String)} 一致
*/
public static void println(Kind kind, String message)
{
if (kind == null)
{
throw new IllegalArgumentException("kind is null");
}
if (kind == Kind.OUT)
{
printlnOut(message);
}
else
{
printlnErr(message);
}
}
/**
* @param kind {@link #OUT} 或 {@link #ERR}
* @param obj 将 {@link String#valueOf(Object)} 再写出
*/
public static void println(Kind kind, Object obj)
{
println(kind, String.valueOf(obj));
}
/**
* @param kind {@link #OUT} 或 {@link #ERR}
* @param message 不换行;{@code null} 当作 ""
*/
public static void print(Kind kind, String message)
{
if (kind == null)
{
throw new IllegalArgumentException("kind is null");
}
if (kind == Kind.OUT)
{
printOut(message);
}
else
{
printErr(message);
}
}
private static void printlnOut(String message)
{
ensureInstalled();
synchronized (CONSOLE_LOCK)
{
if (mergedStdoutMode)
{
unifiedStream.println(message);
}
else
{
outStream.println(message);
outStream.flush();
}
}
}
private static void printlnErr(String message)
{
ensureInstalled();
synchronized (CONSOLE_LOCK)
{
if (mergedStdoutMode)
{
if (ansiErrRed)
{
unifiedStream.print(ANSI_RED);
unifiedStream.println(message);
unifiedStream.print(ANSI_RESET);
}
else
{
unifiedStream.print(STDERR_PREFIX);
unifiedStream.println(message);
}
}
else
{
errStream.println(message);
errStream.flush();
}
}
}
private static void printOut(String message)
{
ensureInstalled();
synchronized (CONSOLE_LOCK)
{
if (mergedStdoutMode)
{
unifiedStream.print(message == null ? "" : message);
}
else
{
outStream.print(message == null ? "" : message);
outStream.flush();
}
}
}
private static void printErr(String message)
{
ensureInstalled();
synchronized (CONSOLE_LOCK)
{
if (mergedStdoutMode)
{
if (ansiErrRed)
{
unifiedStream.print(ANSI_RED);
unifiedStream.print(message == null ? "" : message);
unifiedStream.print(ANSI_RESET);
}
else
{
unifiedStream.print(STDERR_PREFIX);
unifiedStream.print(message == null ? "" : message);
}
}
else
{
errStream.print(message == null ? "" : message);
errStream.flush();
}
}
}
public static void flush()
{
synchronized (CONSOLE_LOCK)
{
if (!installed)
{
return;
}
if (mergedStdoutMode)
{
if (unifiedStream != null)
{
unifiedStream.flush();
}
}
else
{
if (outStream != null)
{
outStream.flush();
}
if (errStream != null)
{
errStream.flush();
}
}
}
}
private static void ensureInstalled()
{
if (!installed)
{
throw new IllegalStateException("Call System2.installOrderedConsole() (or installMergedStdoutStrictOrder()) first");
}
}
private static final class GatedOutputStream extends OutputStream
{
private final OutputStream delegate;
GatedOutputStream(OutputStream delegate)
{
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException
{
synchronized (CONSOLE_LOCK)
{
delegate.write(b);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
synchronized (CONSOLE_LOCK)
{
delegate.write(b, off, len);
}
}
@Override
public void flush() throws IOException
{
synchronized (CONSOLE_LOCK)
{
delegate.flush();
}
}
}
}