Skip to content

Content of file DefaultProcessExecutor.java.html

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>DefaultProcessExecutor.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">TheseFoolishThings Examples - Finder Example 1</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-util</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.util.spi</a> &gt; <span class="el_source">DefaultProcessExecutor.java</span></div><h1>DefaultProcessExecutor.java</h1><pre class="source lang-java linenums">/*
 * #%L
 * *********************************************************************************************************************
 *
 * These Foolish Things - Miscellaneous utilities
 * http://thesefoolishthings.tidalwave.it - git clone git@bitbucket.org:tidalwave/thesefoolishthings-src.git
 * %%
 * Copyright (C) 2009 - 2021 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;); 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 &quot;AS IS&quot; 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.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.util.spi;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import it.tidalwave.util.ProcessExecutor;
import lombok.AccessLevel;
import lombok.Cleanup;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 * @since   1.39
 *
 **********************************************************************************************************************/
<span class="nc" id="L65">@ThreadSafe @NoArgsConstructor(access=AccessLevel.PRIVATE) @Slf4j</span>
public class DefaultProcessExecutor implements ProcessExecutor
  {
<span class="nc" id="L68">    private final ExecutorService executorService = Executors.newFixedThreadPool(10);</span>

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
<span class="nc bnc" id="L74" title="All 4 branches missed.">    @RequiredArgsConstructor(access=AccessLevel.PACKAGE)</span>
    public class DefaultConsoleOutput implements ConsoleOutput
      {
        @Nonnull
        private final String name;

        @Nonnull
        private final InputStream input;

<span class="nc" id="L83">        @Getter</span>
<span class="nc" id="L84">        private final List&lt;String&gt; content = Collections.synchronizedList(new ArrayList&lt;String&gt;());</span>

        private volatile String latestLine;

<span class="nc" id="L88">        private volatile int li = 0;</span>

<span class="nc" id="L90">        private final AtomicBoolean started = new AtomicBoolean();</span>
        
<span class="nc" id="L92">        @CheckForNull @Setter @Getter</span>
        private Listener listener;

        /***************************************************************************************************************
         *
         *
         ***************************************************************************************************************/
<span class="nc" id="L99">        private final Runnable reader = new Runnable()</span>
<span class="nc" id="L100">          {</span>
            @Override
            public void run()
              {
                try
                  {
<span class="nc" id="L106">                    read();</span>
                  }
<span class="nc" id="L108">                catch (IOException e)</span>
                  {
<span class="nc" id="L110">                    log.warn(&quot;while reading from &quot; + name, e);</span>
<span class="nc" id="L111">                  }</span>
<span class="nc" id="L112">              }</span>
          };

        /***************************************************************************************************************
         *
         *
         ***************************************************************************************************************/
<span class="nc" id="L119">        private final Runnable logger = new Runnable()</span>
<span class="nc" id="L120">          {</span>
            @Override
            public void run()
              {
<span class="nc" id="L124">                int l = 0;</span>

                for (;;)
                  {
                    try
                      {
<span class="nc bnc" id="L130" title="All 4 branches missed.">                        if ((l != li) &amp;&amp; (latestLine != null))</span>
                          {
<span class="nc" id="L132">                            log.trace(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; {} {}&quot;, name, latestLine);</span>
                          }

<span class="nc" id="L135">                        l = li;</span>
<span class="nc" id="L136">                        Thread.sleep(500);</span>
                      }
<span class="nc" id="L138">                    catch (InterruptedException e)</span>
                      {
<span class="nc" id="L140">                        return;</span>
                      }
<span class="nc" id="L142">                    catch (Throwable e)</span>
                      {
<span class="nc" id="L144">                        return;</span>
<span class="nc" id="L145">                      }</span>
                  }
              }
          };

        /***************************************************************************************************************
         *
         * Should not be used by the programmer.
         *
         ***************************************************************************************************************/
        @Nonnull
        public ConsoleOutput start()
          {
<span class="nc bnc" id="L158" title="All 2 branches missed.">            if (started.getAndSet(true))</span>
              {
<span class="nc" id="L160">                throw new IllegalStateException(&quot;Already started&quot;);</span>
              }

<span class="nc" id="L163">            log.info(&quot;{} - started&quot;, name);</span>
<span class="nc" id="L164">            executorService.submit(reader);</span>
<span class="nc" id="L165">            executorService.submit(logger);</span>
<span class="nc" id="L166">            return this;</span>
          }

        /***************************************************************************************************************
         *
         * {@inheritDoc}
         *
         ***************************************************************************************************************/
        @Override
        public boolean latestLineMatches (final @Nonnull String regexp)
          {
<span class="nc" id="L177">            String s = null;</span>

<span class="nc bnc" id="L179" title="All 2 branches missed.">            if (latestLine != null)</span>
              {
<span class="nc" id="L181">                s = latestLine;</span>
              }
<span class="nc bnc" id="L183" title="All 2 branches missed.">            else if (!content.isEmpty())</span>
              {
<span class="nc" id="L185">                s = content.get(content.size() - 1);</span>
              }

<span class="nc" id="L188">            log.trace(&quot;&gt;&gt;&gt;&gt; testing '{}' for '{}'&quot;, s, regexp);</span>
<span class="nc bnc" id="L189" title="All 2 branches missed.">            return (s == null) ? false : Pattern.compile(regexp).matcher(s).matches();</span>
            // FIXME: sync
          }

        /***************************************************************************************************************
         *
         * {@inheritDoc}
         *
         ***************************************************************************************************************/
        @Override @Nonnull
        public Scanner filteredAndSplitBy (final @Nonnull String filterRegexp, final @Nonnull String delimiterRegexp)
          {
<span class="nc" id="L201">            final String string = filteredBy(filterRegexp).get(0);</span>
<span class="nc" id="L202">            return new Scanner(string).useDelimiter(Pattern.compile(delimiterRegexp));</span>
          }

        /***************************************************************************************************************
         *
         * {@inheritDoc}
         *
         ***************************************************************************************************************/
        @Override @Nonnull
        public List&lt;String&gt; filteredBy (final @Nonnull String regexp)
          {
<span class="nc" id="L213">            final Pattern pattern = Pattern.compile(regexp);</span>
<span class="nc" id="L214">            final List&lt;String&gt; result = new ArrayList&lt;&gt;();</span>
<span class="nc" id="L215">            final ArrayList&lt;String&gt; strings = new ArrayList&lt;&gt;(content);</span>

            // TODO: sync
<span class="nc bnc" id="L218" title="All 2 branches missed.">            if (latestLine != null)</span>
              {
<span class="nc" id="L220">                strings.add(latestLine);</span>
              }

<span class="nc bnc" id="L223" title="All 2 branches missed.">            for (final String s : strings)</span>
              {
//                log.trace(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; matching '{}' with '{}'...&quot;, s, filter);
<span class="nc" id="L226">                final Matcher m = pattern.matcher(s);</span>

<span class="nc bnc" id="L228" title="All 2 branches missed.">                if (m.matches())</span>
                  {
<span class="nc" id="L230">                    result.add(m.group(1));</span>
                  }
<span class="nc" id="L232">              }</span>

<span class="nc" id="L234">            return result;</span>
          }

        /***************************************************************************************************************
         *
         * {@inheritDoc}
         *
         ***************************************************************************************************************/
        @Override @Nonnull
        public ConsoleOutput waitFor (final @Nonnull String regexp)
          throws InterruptedException, IOException
          {
<span class="nc" id="L246">            log.debug(&quot;{} - waitFor({})&quot;, name, regexp);</span>

<span class="nc bnc" id="L248" title="All 2 branches missed.">            while (filteredBy(regexp).isEmpty())</span>
              {
                try
                  {
<span class="nc" id="L252">                    final int exitValue = process.exitValue();</span>
<span class="nc" id="L253">                    throw new IOException(&quot;Process exited with &quot; + exitValue);</span>
                  }
<span class="nc" id="L255">                catch (IllegalThreadStateException e) // ok, process not terminated yet</span>
                  {
<span class="nc" id="L257">                    synchronized (this)</span>
                      {
<span class="nc" id="L259">                        wait(50); // FIXME: polls because it doesn't get notified</span>
<span class="nc" id="L260">                      }</span>
<span class="nc" id="L261">                  }</span>
              }

<span class="nc" id="L264">            return this;</span>
          }

        /***************************************************************************************************************
         *
         * {@inheritDoc}
         *
         ***************************************************************************************************************/
        @Override
        public void clear()
          {
<span class="nc" id="L275">            content.clear();</span>
<span class="nc" id="L276">            latestLine = null;</span>
<span class="nc" id="L277">          }</span>

        /***************************************************************************************************************
         *
         *
         ***************************************************************************************************************/
        private void read()
          throws IOException
          {
<span class="nc bnc" id="L286" title="All 2 branches missed.">            final @Cleanup InputStreamReader is = new InputStreamReader(input);</span>
<span class="nc" id="L287">            StringBuilder l = new StringBuilder();</span>

            for (;;)
              {
<span class="nc" id="L291">                int c = is.read();</span>

<span class="nc bnc" id="L293" title="All 2 branches missed.">                if (c &lt; 0)</span>
                  {
<span class="nc" id="L295">                    break;</span>
                  }

//                if (c == 10)
//                  {
//                    continue;
//                  }

<span class="nc bnc" id="L303" title="All 4 branches missed.">                if ((c == 13) || (c == 10))</span>
                  {
<span class="nc" id="L305">                    latestLine = l.toString();</span>
<span class="nc" id="L306">                    li++;</span>
<span class="nc" id="L307">                    content.add(latestLine);</span>
<span class="nc" id="L308">                    l = new StringBuilder();</span>
<span class="nc" id="L309">                    log.trace(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; {} {}&quot;, name, latestLine);</span>
                    
<span class="nc bnc" id="L311" title="All 2 branches missed.">                    if (listener != null)</span>
                      {
<span class="nc" id="L313">                        listener.onReceived(latestLine);</span>
                      }
                  }
                else
                  {
<span class="nc" id="L318">                    l.append((char)c);</span>
<span class="nc" id="L319">                    latestLine = l.toString();</span>
<span class="nc" id="L320">                    li++;</span>
                  }

<span class="nc" id="L323">                synchronized (this)</span>
                  {
<span class="nc" id="L325">                    notifyAll();</span>
<span class="nc" id="L326">                  }</span>
<span class="nc" id="L327">              }</span>

<span class="nc" id="L329">            log.debug(&quot;&gt;&gt;&gt;&gt;&gt;&gt; {} closed&quot;, name);</span>
<span class="nc" id="L330">            is.close();</span>
<span class="nc" id="L331">          }</span>
      }

<span class="nc" id="L334">    private final List&lt;String&gt; arguments = new ArrayList&lt;&gt;();</span>

    private Process process;

<span class="nc" id="L338">    @Getter</span>
    private ConsoleOutput stdout;

<span class="nc" id="L341">    @Getter</span>
    private ConsoleOutput stderr;

    private PrintWriter stdin;

    /*******************************************************************************************************************
     *
     * Factory method for associating an executable. It returns an intermediate executor that must be configured and
     * later started. Under Windows, the '.exe' suffix is automatically appended to the name of the executable.
     * 
     * @see #start() 
     * 
     * @param       executable      the executable (with the full path)
     * @return                      the executor
     *
     ******************************************************************************************************************/
    @Nonnull
    public static DefaultProcessExecutor forExecutable (final @Nonnull String executable)
      {
<span class="nc" id="L360">        final DefaultProcessExecutor executor = new DefaultProcessExecutor();</span>
<span class="nc bnc" id="L361" title="All 2 branches missed.">        executor.arguments.add(new File(executable + (isWindows() ? &quot;.exe&quot; : &quot;&quot;)).getAbsolutePath());</span>
<span class="nc" id="L362">        return executor;</span>
      }

//    /*******************************************************************************************************************
//     *
//     *
//     ******************************************************************************************************************/
//    @Nonnull
//    private static String findPath (final @Nonnull String executable)
//      throws NotFoundException
//      {
//        for (final String path : System.getenv(&quot;PATH&quot;).split(File.pathSeparator))
//          {
//            final File file = new File(new File(path), executable);
//
//            if (file.canExecute())
//              {
//                return file.getAbsolutePath();
//              }
//          }
//
//        throw new NotFoundException(&quot;Can't find &quot; + executable + &quot; in PATH&quot;);
//      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public DefaultProcessExecutor withArgument (final @Nonnull String argument)
      {
<span class="nc" id="L394">        arguments.add(argument);</span>
<span class="nc" id="L395">        return this;</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public DefaultProcessExecutor withArguments (final @Nonnull String ... arguments)
      {
<span class="nc" id="L406">        this.arguments.addAll(Arrays.asList(arguments));</span>
<span class="nc" id="L407">        return this;</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public DefaultProcessExecutor start()
      throws IOException
      {
<span class="nc" id="L419">        log.info(&quot;&gt;&gt;&gt;&gt; executing {} ...&quot;, arguments);</span>

<span class="nc" id="L421">        final List&lt;String&gt; environment = new ArrayList&lt;&gt;();</span>

<span class="nc bnc" id="L423" title="All 2 branches missed.">        for (final Entry&lt;String, String&gt; e : System.getenv().entrySet())</span>
          {
<span class="nc" id="L425">            environment.add(String.format(&quot;%s=%s&quot;, e.getKey(), e.getValue()));</span>
<span class="nc" id="L426">          }</span>

<span class="nc" id="L428">        log.info(&quot;&gt;&gt;&gt;&gt; environment: {}&quot;, environment);</span>
<span class="nc" id="L429">        process = Runtime.getRuntime().exec(arguments.toArray(new String[0]),</span>
<span class="nc" id="L430">                                            environment.toArray(new String[0]));</span>

<span class="nc" id="L432">        stdout = new DefaultConsoleOutput(&quot;out&quot;, process.getInputStream()).start();</span>
<span class="nc" id="L433">        stderr = new DefaultConsoleOutput(&quot;err&quot;, process.getErrorStream()).start();</span>
<span class="nc" id="L434">        stdin  = new PrintWriter(process.getOutputStream(), true);</span>

<span class="nc" id="L436">        return this;</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void stop()
      {
<span class="nc" id="L447">        log.info(&quot;stop()&quot;);</span>
<span class="nc" id="L448">        process.destroy();</span>
<span class="nc" id="L449">        executorService.shutdownNow();</span>
<span class="nc" id="L450">      }</span>

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public DefaultProcessExecutor waitForCompletion()
      throws IOException, InterruptedException
      {
<span class="nc bnc" id="L461" title="All 2 branches missed.">        if (process.waitFor() != 0)</span>
          {
//            throw new IOException(&quot;Process exited with &quot; + process.exitValue()); FIXME
} <span class="nc" id="L466"> return this;</span> } /******************************************************************************************************************* * * {@inheritDoc} * ******************************************************************************************************************/ @Override @Nonnull public DefaultProcessExecutor send (final @Nonnull String string) throws IOException { <span class="nc" id="L478"> log.debug(&quot;&gt;&gt;&gt;&gt; sending '{}'...&quot;, string.replaceAll(&quot;\n&quot;, &quot;&lt;CR&gt;&quot;));</span> <span class="nc" id="L479"> stdin.print(string);</span> <span class="nc" id="L480"> stdin.flush();</span> <span class="nc" id="L481"> return this;</span> } /******************************************************************************************************************* * * ******************************************************************************************************************/ private static boolean isWindows() { <span class="nc" id="L490"> return System.getProperty (&quot;os.name&quot;).toLowerCase().startsWith(&quot;windows&quot;);</span> } } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.6.202009150832</span></div></body></html>