/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.gdb.server; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.plugin.gdb.server.parser.GdbOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import static java.lang.Math.min; /** * @author Anatoliy Bazko */ public abstract class GdbProcess { private static final Logger LOG = LoggerFactory.getLogger(GdbProcess.class); private static final int MAX_CAPACITY = 1000; private static final int MAX_OUTPUT = 4096; protected int pid; protected final Process process; protected final String outputSeparator; protected final BlockingQueue<GdbOutput> outputs; protected final Thread outputReader; public GdbProcess(String outputSeparator, String... commands) throws IOException { this.outputSeparator = outputSeparator; this.outputs = new ArrayBlockingQueue<>(MAX_CAPACITY); ProcessBuilder processBuilder = new ProcessBuilder(commands); process = processBuilder.start(); outputReader = new OutputReader(commands[0] + " output reader"); outputReader.setDaemon(true); outputReader.start(); try { Field pidField = Thread.currentThread().getContextClassLoader().loadClass("java.lang.UNIXProcess").getDeclaredField("pid"); pidField.setAccessible(true); pid = ((Number)pidField.get(process)).intValue(); } catch (Exception e) { pid = -1; LOG.error(e.getMessage(), e); } } /** * Stops process. */ protected void stop() { outputReader.interrupt(); outputs.clear(); process.destroy(); } /** * Continuously reads process output and store in the {@code #outputs}. */ private class OutputReader extends Thread { public OutputReader(String name) { super(name); } @Override public void run() { StringBuilder buf = new StringBuilder(); while (!isInterrupted()) { if (!process.isAlive()) { outputs.add(GdbOutput.of(buf.toString(), true)); break; } try { InputStream in = getInput(); if (in != null) { String data = read(in); if (!data.isEmpty()) { buf.append(data); if (buf.length() > MAX_OUTPUT) { buf.delete(0, buf.length() - MAX_OUTPUT); } } } } catch (IOException e) { LOG.error(e.getMessage(), e); } if (buf.length() > 0) { extractOutput(buf); } try { Thread.sleep(100); } catch (InterruptedException e) { break; } } LOG.debug(getName() + " has been stopped"); } private InputStream getInput() throws IOException { return hasError() ? process.getErrorStream() : (hasInput() ? (hasError() ? process.getErrorStream() : process.getInputStream()) : null); } private void extractOutput(StringBuilder buf) { int indexOf; while ((indexOf = buf.indexOf(outputSeparator)) >= 0) { GdbOutput gdbOutput = GdbOutput.of(buf.substring(0, indexOf)); outputs.add(gdbOutput); LOG.debug(gdbOutput.getOutput()); buf.delete(0, indexOf + outputSeparator.length()); } } private boolean hasError() throws IOException { return process.getErrorStream().available() != 0; } private boolean hasInput() throws IOException { return process.getInputStream().available() != 0; } @Nullable private String read(InputStream in) throws IOException { int available = min(in.available(), MAX_OUTPUT); byte[] buf = new byte[available]; int read = in.read(buf, 0, available); return new String(buf, 0, read, StandardCharsets.UTF_8); } } }