| /* |
| * 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.lifecycle.internal.builder.multithreaded; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.maven.lifecycle.internal.ProjectBuildList; |
| import org.apache.maven.lifecycle.internal.ProjectSegment; |
| |
| /** |
| * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice. |
| * This class in particular may spontaneously self-combust and be replaced by a plexus-compliant thread aware |
| * logger implementation at any time. |
| * |
| * @since 3.0 |
| */ |
| @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"}) |
| public class ThreadOutputMuxer { |
| private final Iterator<ProjectSegment> projects; |
| |
| private final ThreadLocal<ProjectSegment> projectBuildThreadLocal = new ThreadLocal<>(); |
| |
| private final Map<ProjectSegment, ByteArrayOutputStream> streams = new HashMap<>(); |
| |
| private final Map<ProjectSegment, PrintStream> printStreams = new HashMap<>(); |
| |
| private final ByteArrayOutputStream defaultOutputStreamForUnknownData = new ByteArrayOutputStream(); |
| |
| private final PrintStream defaultPrintStream = new PrintStream(defaultOutputStreamForUnknownData); |
| |
| private final Set<ProjectSegment> completedBuilds = Collections.synchronizedSet(new HashSet<>()); |
| |
| private volatile ProjectSegment currentBuild; |
| |
| private final PrintStream originalSystemOUtStream; |
| |
| private final ConsolePrinter printer; |
| |
| /** |
| * A simple but safe solution for printing to the console. |
| */ |
| class ConsolePrinter implements Runnable { |
| private volatile boolean running; |
| |
| private final ProjectBuildList projectBuildList; |
| |
| ConsolePrinter(ProjectBuildList projectBuildList) { |
| this.projectBuildList = projectBuildList; |
| } |
| |
| public void run() { |
| running = true; |
| for (ProjectSegment projectBuild : projectBuildList) { |
| final PrintStream projectStream = printStreams.get(projectBuild); |
| ByteArrayOutputStream projectOs = streams.get(projectBuild); |
| |
| do { |
| synchronized (projectStream) { |
| try { |
| projectStream.wait(100); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| try { |
| projectOs.writeTo(originalSystemOUtStream); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| |
| projectOs.reset(); |
| } |
| } while (!completedBuilds.contains(projectBuild)); |
| } |
| running = false; |
| } |
| |
| /* |
| Wait until we are sure the print-stream thread is running. |
| */ |
| |
| public void waitUntilRunning(boolean expect) { |
| while (!running == expect) { |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |
| |
| public ThreadOutputMuxer(ProjectBuildList segmentChunks, PrintStream originalSystemOut) { |
| projects = segmentChunks.iterator(); |
| for (ProjectSegment segmentChunk : segmentChunks) { |
| final ByteArrayOutputStream value = new ByteArrayOutputStream(); |
| streams.put(segmentChunk, value); |
| printStreams.put(segmentChunk, new PrintStream(value)); |
| } |
| setNext(); |
| this.originalSystemOUtStream = originalSystemOut; |
| System.setOut(new ThreadBoundPrintStream(this.originalSystemOUtStream)); |
| printer = new ConsolePrinter(segmentChunks); |
| new Thread(printer).start(); |
| printer.waitUntilRunning(true); |
| } |
| |
| public void close() { |
| printer.waitUntilRunning(false); |
| System.setOut(this.originalSystemOUtStream); |
| } |
| |
| private void setNext() { |
| currentBuild = projects.hasNext() ? projects.next() : null; |
| } |
| |
| private boolean ownsRealOutputStream(ProjectSegment projectBuild) { |
| return projectBuild.equals(currentBuild); |
| } |
| |
| private PrintStream getThreadBoundPrintStream() { |
| ProjectSegment threadProject = projectBuildThreadLocal.get(); |
| if (threadProject == null) { |
| return defaultPrintStream; |
| } |
| if (ownsRealOutputStream(threadProject)) { |
| return originalSystemOUtStream; |
| } |
| return printStreams.get(threadProject); |
| } |
| |
| public void associateThreadWithProjectSegment(ProjectSegment projectBuild) { |
| projectBuildThreadLocal.set(projectBuild); |
| } |
| |
| public void setThisModuleComplete(ProjectSegment projectBuild) { |
| completedBuilds.add(projectBuild); |
| PrintStream stream = printStreams.get(projectBuild); |
| synchronized (stream) { |
| stream.notifyAll(); |
| } |
| disconnectThreadFromProject(); |
| } |
| |
| private void disconnectThreadFromProject() { |
| projectBuildThreadLocal.remove(); |
| } |
| |
| private class ThreadBoundPrintStream extends PrintStream { |
| |
| ThreadBoundPrintStream(PrintStream systemOutStream) { |
| super(systemOutStream); |
| } |
| |
| private PrintStream getOutputStreamForCurrentThread() { |
| return getThreadBoundPrintStream(); |
| } |
| |
| @Override |
| public void println() { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(char c) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(c); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(char x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(double d) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(d); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(double x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(float f) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(f); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(float x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(int i) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(i); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(int x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(long l) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(l); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(long x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(boolean b) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(b); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(boolean x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(char s[]) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(s); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(char x[]) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(Object obj) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(obj); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(Object x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void print(String s) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.print(s); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void println(String x) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.println(x); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void write(byte b[], int off, int len) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.write(b, off, len); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void close() { |
| getOutputStreamForCurrentThread().close(); |
| } |
| |
| @Override |
| public void flush() { |
| getOutputStreamForCurrentThread().flush(); |
| } |
| |
| @Override |
| public void write(int b) { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.write(b); |
| currentStream.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void write(byte b[]) throws IOException { |
| final PrintStream currentStream = getOutputStreamForCurrentThread(); |
| synchronized (currentStream) { |
| currentStream.write(b); |
| currentStream.notifyAll(); |
| } |
| } |
| } |
| } |