OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
16
import java.io.BufferedReader;
17
import java.io.IOException;
18
import java.io.InputStream;
19
import java.io.InputStreamReader;
93 ilm 20
import java.io.OutputStream;
17 ilm 21
import java.io.PrintStream;
156 ilm 22
import java.lang.ProcessBuilder.Redirect;
17 ilm 23
import java.util.concurrent.Callable;
182 ilm 24
import java.util.concurrent.CancellationException;
25
import java.util.concurrent.CompletableFuture;
26
import java.util.concurrent.ExecutionException;
17 ilm 27
import java.util.concurrent.ExecutorService;
28
import java.util.concurrent.Executors;
29
import java.util.concurrent.Future;
182 ilm 30
import java.util.concurrent.TimeUnit;
31
import java.util.concurrent.TimeoutException;
180 ilm 32
import java.util.function.Supplier;
17 ilm 33
 
34
/**
35
 * Redirect streams of a process to System.out and System.err.
182 ilm 36
 *
17 ilm 37
 * @author Sylvain
38
 */
182 ilm 39
public class ProcessStreams implements AutoCloseable {
17 ilm 40
 
182 ilm 41
    // Don't set too low, this is a maximum, it would only fail on slow computers.
42
    private static final int MAX_SHUTTING_DOWN_DELAY = 500;
80 ilm 43
 
177 ilm 44
    // Added to Java 9
45
    public static final Redirect DISCARD = Redirect.to(StreamUtils.NULL_FILE);
46
 
180 ilm 47
    static public final ProcessBuilder redirect(final ProcessBuilder pb) {
182 ilm 48
        // ATTN don't use redirectErrorStream(true) as this would merge the error and output of pb
49
        // into the VM output.
50
        return pb.redirectError(Redirect.INHERIT).redirectOutput(Redirect.INHERIT);
156 ilm 51
    }
52
 
182 ilm 53
    /**
54
     * Consume all streams. Needed since some programs might fail (e.g. route on FreeBSD) if the
55
     * streams are just closed.
56
     *
57
     * @param proc the process.
58
     * @return the passed process.
59
     */
60
    static public final Process consume(final Process proc) {
61
        try (final ProcessStreams streams = new ProcessStreams(proc)) {
62
            streams.start(StreamUtils.NULL_OS, StreamUtils.NULL_OS);
63
            streams.awaitTermination();
64
        } catch (Exception e) {
65
            throw new IllegalStateException("Couldn't consume output of " + proc, e);
80 ilm 66
        }
182 ilm 67
        return proc;
80 ilm 68
    }
69
 
17 ilm 70
    private final ExecutorService exec = Executors.newFixedThreadPool(2);
182 ilm 71
    private final Process process;
72
    private Future<?> out;
73
    private Future<?> err;
17 ilm 74
 
93 ilm 75
    /**
76
     * Create a new instance and start reading from the passed process. If a passed
77
     * {@link OutputStream} is <code>null</code>, then the corresponding {@link InputStream} is not
78
     * used at all, so the caller should handle it. If the output must be discarded, use
79
     * {@link StreamUtils#NULL_OS}.
182 ilm 80
     *
93 ilm 81
     * @param p the process to read from.
82
     * @param out where to write the {@link Process#getInputStream() standard output}.
83
     * @param err where to write the {@link Process#getErrorStream() standard error}.
84
     */
182 ilm 85
    public ProcessStreams(final Process p) {
86
        this.process = p;
87
    }
88
 
89
    public final ProcessStreams start(final OutputStream out, final OutputStream err) {
90
        if (this.out != null)
91
            throw new IllegalStateException("Already started");
92
        this.out = writeToAsync(this.process::getInputStream, out);
93
        this.err = writeToAsync(this.process::getErrorStream, err);
94
        assert this.out != null && this.err != null;
80 ilm 95
        this.exec.submit(new Runnable() {
180 ilm 96
            @Override
17 ilm 97
            public void run() {
98
                try {
182 ilm 99
                    // OK even if the future is cancelled before having started
100
                    try {
101
                        ProcessStreams.this.out.get();
102
                    } catch (Exception e) {
103
                    }
104
                    try {
105
                        ProcessStreams.this.err.get();
106
                    } catch (Exception e) {
107
                    }
17 ilm 108
                } finally {
109
                    ProcessStreams.this.exec.shutdown();
110
                }
111
            }
80 ilm 112
        });
182 ilm 113
        return this;
17 ilm 114
    }
115
 
116
    protected final void stopOut() {
117
        this.stop(this.out);
118
    }
119
 
120
    protected final void stopErr() {
121
        this.stop(this.err);
122
    }
123
 
180 ilm 124
    private final void stop(final Future<?> f) {
93 ilm 125
        if (f == null)
182 ilm 126
            throw new IllegalStateException("Not started");
127
        // ATTN Process returns InputStream which are not interruptible.
128
        f.cancel(true);
17 ilm 129
    }
130
 
182 ilm 131
    // From AutoCloseable : close() shouldn't throw InterruptedException
132
    @Override
133
    public void close() {
134
        this.exec.shutdownNow();
135
    }
136
 
137
    public final void awaitTermination() throws InterruptedException, ExecutionException {
138
        this.out.get();
139
        this.err.get();
140
        if (!this.exec.awaitTermination(MAX_SHUTTING_DOWN_DELAY, TimeUnit.MILLISECONDS))
141
            throw new IllegalStateException("Executor still not terminated after " + MAX_SHUTTING_DOWN_DELAY);
142
    }
143
 
144
    public final void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
145
        try {
146
            this.out.get(timeout, unit);
147
        } catch (CancellationException e) {
148
            // OK
149
        }
150
        try {
151
            this.err.get(timeout, unit);
152
        } catch (CancellationException e) {
153
            // OK
154
        }
155
        if (!this.exec.awaitTermination(MAX_SHUTTING_DOWN_DELAY, TimeUnit.MILLISECONDS))
156
            throw new TimeoutException("Executor still not terminated after " + MAX_SHUTTING_DOWN_DELAY);
157
    }
158
 
180 ilm 159
    private final Future<?> writeToAsync(final Supplier<InputStream> insSupplier, final Object outs) {
93 ilm 160
        if (outs == null) {
182 ilm 161
            return CompletableFuture.completedFuture(null);
93 ilm 162
        }
17 ilm 163
        return this.exec.submit(new Callable<Object>() {
180 ilm 164
            @Override
165
            public Void call() throws InterruptedException, IOException {
166
                try (final InputStream ins = insSupplier.get()) {
93 ilm 167
                    // PrintStream is also an OutputStream
168
                    if (outs instanceof PrintStream)
169
                        writeTo(ins, (PrintStream) outs);
170
                    else
171
                        StreamUtils.copy(ins, (OutputStream) outs);
17 ilm 172
                    return null;
173
                }
174
            }
175
        });
176
    }
177
 
178
    /**
179
     * Copy ins to outs, line by line.
182 ilm 180
     *
17 ilm 181
     * @param ins the source.
182
     * @param outs the destination.
183
     * @throws InterruptedException if current thread is interrupted.
93 ilm 184
     * @throws IOException if I/O error.
17 ilm 185
     */
180 ilm 186
    public static final void writeTo(final InputStream ins, final PrintStream outs) throws InterruptedException, IOException {
17 ilm 187
        final BufferedReader r = new BufferedReader(new InputStreamReader(ins));
188
        String encodedName;
189
        while ((encodedName = r.readLine()) != null) {
190
            if (Thread.currentThread().isInterrupted()) {
191
                throw new InterruptedException();
192
            }
193
            outs.println(encodedName);
194
        }
195
    }
196
 
197
}