elharo commented on code in PR #218: URL: https://github.com/apache/maven-script-interpreter/pull/218#discussion_r4132462904
########## src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptOutputRedirect.java: ########## @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.shared.scriptinterpreter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Locale; + +/** + * Routes {@code System.out} and {@code System.err} of the current thread to a script's log while that script runs, + * without taking the streams away from the rest of the JVM. + * <p> + * The process-wide streams are replaced once, while at least one redirect is active, by streams that dispatch each + * write to the redirect registered for the calling thread (and threads it spawns), or to the original stream when + * there is none. When the last redirect ends the original streams are put back. + */ +final class ScriptOutputRedirect implements AutoCloseable { + + private static final Object LOCK = new Object(); + + private static final InheritableThreadLocal<PrintStream> CURRENT = new InheritableThreadLocal<>(); + + private static int active; + + private static PrintStream originalOut; + + private static PrintStream originalErr; + + private final PrintStream previous; + + private ScriptOutputRedirect(PrintStream target) { + synchronized (LOCK) { + if (active++ == 0) { + originalOut = System.out; + originalErr = System.err; + System.setOut(new DispatchingPrintStream(originalOut)); + System.setErr(new DispatchingPrintStream(originalErr)); + } + } + previous = CURRENT.get(); + CURRENT.set(target); + } + + /** + * Starts routing this thread's standard output and error to {@code target}; {@link #close()} ends it. + * + * @param target where the script's output goes, must not be {@code null} + * @return the redirect to close when the script is done + */ + static ScriptOutputRedirect to(PrintStream target) { + return new ScriptOutputRedirect(target); + } + + @Override + public void close() { + if (previous != null) { + CURRENT.set(previous); + } else { + CURRENT.remove(); + } + synchronized (LOCK) { + if (--active == 0) { Review Comment: relying on prefix vs postfix -- is very bug-prone. consider splitting the increment from the comparison such that this doesn't matter. ########## src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptOutputRedirect.java: ########## @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.shared.scriptinterpreter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Locale; + +/** + * Routes {@code System.out} and {@code System.err} of the current thread to a script's log while that script runs, + * without taking the streams away from the rest of the JVM. + * <p> + * The process-wide streams are replaced once, while at least one redirect is active, by streams that dispatch each + * write to the redirect registered for the calling thread (and threads it spawns), or to the original stream when + * there is none. When the last redirect ends the original streams are put back. + */ +final class ScriptOutputRedirect implements AutoCloseable { + + private static final Object LOCK = new Object(); + + private static final InheritableThreadLocal<PrintStream> CURRENT = new InheritableThreadLocal<>(); + + private static int active; + + private static PrintStream originalOut; + + private static PrintStream originalErr; + + private final PrintStream previous; + + private ScriptOutputRedirect(PrintStream target) { + synchronized (LOCK) { + if (active++ == 0) { + originalOut = System.out; + originalErr = System.err; + System.setOut(new DispatchingPrintStream(originalOut)); + System.setErr(new DispatchingPrintStream(originalErr)); + } + } + previous = CURRENT.get(); + CURRENT.set(target); + } + + /** + * Starts routing this thread's standard output and error to {@code target}; {@link #close()} ends it. + * + * @param target where the script's output goes, must not be {@code null} + * @return the redirect to close when the script is done + */ + static ScriptOutputRedirect to(PrintStream target) { + return new ScriptOutputRedirect(target); + } + + @Override + public void close() { + if (previous != null) { + CURRENT.set(previous); + } else { + CURRENT.remove(); + } + synchronized (LOCK) { + if (--active == 0) { + System.setOut(originalOut); + System.setErr(originalErr); + originalOut = null; + originalErr = null; + } + } + } + + /** + * A print stream that hands every call to the redirect of the current thread, or to the stream it replaced. + * Calls are forwarded whole (a {@code println} stays one {@code println}) so a target that mirrors lines on + * flush, such as {@link FileLogger}, sees the same line boundaries as before. + */ + private static final class DispatchingPrintStream extends PrintStream { + private final PrintStream fallback; + + DispatchingPrintStream(PrintStream fallback) { + super(new OutputStream() { + @Override + public void write(int b) { + throw new IllegalStateException("all writes are dispatched"); + } + }); + this.fallback = fallback; + } + + private PrintStream target() { + PrintStream current = CURRENT.get(); + return current != null ? current : fallback; + } + + @Override + public void write(int b) { Review Comment: do we really need all these overrides? I would have expected most of them to work with just overriding one or two write methods. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
