All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.knowledgelinks.cicd.semantic.versioning.commandline.ShellRunner Maven / Gradle / Ivy

package io.knowledgelinks.cicd.semantic.versioning.commandline;

/*-
 * #%L
 * Semantic Versioning
 * %%
 * Copyright (C) 2022 - 2023 Knowledgelinks
 * %%
 * Licensed 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.
 * #L%
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Container for running shell commands. It stores the error code and collects the data and error
 * streams for retrieval.
 * 
 * @author mstabile75
 *
 */
public class ShellRunner implements Runnable {
  private String[] command;

  private Map environment;

  List data = new ArrayList<>();

  List errors = new ArrayList<>();

  int exitCode;

  private File directory = null;

  public ShellRunner(String... command) {
    this(null, command);
  }

  public ShellRunner(Map environment, String... command) {
    this.command = command.clone();
    this.environment = environment;
  }

  @Override
  public void run() {
    ProcessBuilder builder = new ProcessBuilder();
    builder.command(command);
    builder.directory(directory);
    if (environment != null) {
      Map builderEnv = builder.environment();
      builderEnv.putAll(environment);
    }

    Process process = null;
    ExecutorService service = null;
    Thread dataStreamConsumer = null;
    Thread errorStreamConsumer = null;
    try {
      process = builder.start();
      dataStreamConsumer = new Thread(new StreamConsumer(process.getInputStream(), data::add));
      errorStreamConsumer = new Thread(new StreamConsumer(process.getErrorStream(), errors::add));
      dataStreamConsumer.start();
      errorStreamConsumer.start();

      service = Executors.newSingleThreadExecutor();
      exitCode = process.waitFor();

    } catch (IOException | InterruptedException e) {
      exitCode = 1;
      errors.add(0, String.format("%s: %s | running command: %s", e.getClass().getName(),
          e.getMessage(), String.join(" ", command)));
    } finally {
      // ensure all streams, services and processes are closed
      // all errors thrown here are ignored
      try {
        dataStreamConsumer.join();
      } catch (NullPointerException | InterruptedException e) {
        // ignore error
      }
      try {
        errorStreamConsumer.join();
      } catch (NullPointerException | InterruptedException e) {
        // ignore error
      }
      try {
        service.shutdown();
      } catch (NullPointerException e) {
        // ignore error
      }
      try {
        process.destroy();
      } catch (NullPointerException e) {
        // ignore error
      }

    }

    if (exitCode != 0) {
      errors.add(0, "exitCode=" + exitCode);
    }
  }

  public Map getEnvironment() {
    return environment;
  }

  /**
   * Set additional environment variables that will be added to the runtime.
   * 

* Note: this does not replace the environment that was in place when the JVM started. * * @param environment the environment to use in the shell call */ public void setEnvironment(Map environment) { this.environment = environment; } public String[] getCommand() { return command.clone(); } /** * Retrieve the data from the execution. * * @return the list of data */ public List getData() { return data; } /** * Retrieve the error output, if there was any. *

* Note: Occasionally, this may contain data even if the program ran without an error. Use a * combination of the errorCode and this field to determine if there was an error encountered for * the specific command that was executed. * * @return the list of errors */ public List getErrors() { return errors; } /** * Returns the error code for the executed command. This is program dependent, however 0 typically * indicates no errors were encountered. * * @return the exitCode from the shell command */ public int getExitCode() { return exitCode; } public File getDirectory() { return directory; } /** * Set the working directory for the shell command to run. * * @param directory the directory to run the command */ public void setDirectory(File directory) { this.directory = directory; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy