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

com.googlecode.kevinarpe.papaya.process.AbstractProcessSettings Maven / Gradle / Ivy

package com.googlecode.kevinarpe.papaya.process;

/*
 * #%L
 * This file is part of Papaya.
 * %%
 * Copyright (C) 2013 - 2014 Kevin Connor ARPE ([email protected])
 * %%
 * Papaya is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GPL Classpath Exception:
 * This project is subject to the "Classpath" exception as provided in
 * the LICENSE file that accompanied this code.
 * 
 * Papaya is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Papaya.  If not, see .
 * #L%
 */

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.googlecode.kevinarpe.papaya.ObjectUtils;
import com.googlecode.kevinarpe.papaya.annotation.FullyTested;
import com.googlecode.kevinarpe.papaya.argument.ObjectArgs;

/**
 * This is the shared base class for {@link ProcessBuilder2} and {@link Process2}.
 * 
 * @author Kevin Connor ARPE ([email protected])
 */
@FullyTested
public abstract class AbstractProcessSettings {
    
    /**
     * Used by the
     * {@linkplain AbstractProcessSettings#AbstractProcessSettings(AbstractProcessSettings, ChildProcessState)
     * copy constructor} to distinguish between copies of {@link ProcessBuilder2}, where the child
     * process has not started, and {@link Process2}, where the child process has
     * started.
     * 
     * @author Kevin Connor ARPE ([email protected])
     * 
     * @see AbstractProcessSettings#AbstractProcessSettings(AbstractProcessSettings, ChildProcessState)
     */
    public static enum ChildProcessState {
        
        /**
         * The child process has not started.  Used when creating an instance of
         * {@link ProcessBuilder2}.
         */
        HAS_NOT_STARTED,
        
        /**
         * The child process has started.  Use when creating an instance of
         * {@link Process2}.
         */
        HAS_STARTED;
    }
    
    private final ChildProcessState _childProcessState;
    private final ProcessOutputStreamSettings _stdoutSettings;
    private final ProcessOutputStreamSettings _stderrSettings;

    protected AbstractProcessSettings() {
        _childProcessState = ChildProcessState.HAS_NOT_STARTED;
        _stdoutSettings = new ProcessOutputStreamSettings();
        _stderrSettings = new ProcessOutputStreamSettings();
    }
    
    /**
     * Copy constructor.
     * 
     * @param other
     *        reference to another instance of this class, perhaps {@link ProcessBuilder2} or
     *        {@link Process}
     * @param state
     * 
    *
  • state of child process -- started or not
  • *
  • To copy {@link ProcessBuilder2}, use {@link ChildProcessState#HAS_NOT_STARTED}
  • *
  • To copy {@link Process2}, use {@link ChildProcessState#HAS_STARTED}
  • *
* * @throws NullPointerException * if {@code other} or {@code state} is {@code null} * @throws IllegalArgumentException * if {@code state} is an unknown value */ protected AbstractProcessSettings(AbstractProcessSettings other, ChildProcessState state) { ObjectArgs.checkNotNull(other, "other"); ObjectArgs.checkNotNull(state, "state"); if (ChildProcessState.HAS_NOT_STARTED == state) { _stdoutSettings = new ProcessOutputStreamSettings(other._stdoutSettings); _stderrSettings = new ProcessOutputStreamSettings(other._stderrSettings); } else if (ChildProcessState.HAS_STARTED == state) { _stdoutSettings = new ProcessOutputStreamSettingsAfterStart(other._stdoutSettings); _stderrSettings = new ProcessOutputStreamSettingsAfterStart(other._stderrSettings); } else { throw new IllegalArgumentException(String.format("Unknown child process state: '%s'", state.name())); } _childProcessState = state; } /** * This value is set on construction and never changes. * * @return state of child process -- started or not * * @see ChildProcessState */ public ChildProcessState childProcessState() { return _childProcessState; } /** * Settings for STDOUT stream of child process. Initially, settings are controlled from * {@link ProcessBuilder2}. When the child process is spawned via * {@link ProcessBuilder2#start()}, the settings are copied by {@link Process2}. Most settings * remain mutable after the child process starts. * * @return *
    *
  • reference to settings for STDOUT stream of child process
  • *
  • If child process has not started, this is a reference to * {@link ProcessOutputStreamSettings}
  • *
  • If child process has started, this is a reference to * {@link ProcessOutputStreamSettingsAfterStart}
  • *
* * @see #stderrSettings() * @see #childProcessState() * @see ProcessOutputStreamSettings * @see ProcessOutputStreamSettingsAfterStart */ public ProcessOutputStreamSettings stdoutSettings() { return _stdoutSettings; } /** * Settings for STDERR stream of child process. Initially, settings are controlled from * {@link ProcessBuilder2}. When the child process is spawned via * {@link ProcessBuilder2#start()}, the settings are copied by {@link Process2}. Most settings * remain mutable after the child process starts. * * @return *
    *
  • reference to settings for STDERR stream of child process
  • *
  • If child process has not started, this is a reference to * {@link ProcessOutputStreamSettings}
  • *
  • If child process has started, this is a reference to * {@link ProcessOutputStreamSettingsAfterStart}
  • *
* * @see #stdoutSettings() * @see #childProcessState() * @see ProcessOutputStreamSettings * @see ProcessOutputStreamSettingsAfterStart */ public ProcessOutputStreamSettings stderrSettings() { return _stderrSettings; } /** * @see ProcessBuilder#command() */ public abstract List command(); /** * @see ProcessBuilder#environment() */ public abstract Map environment(); /** * @see ProcessBuilder#directory() */ public abstract File directory(); /** * @see ProcessBuilder#redirectErrorStream() */ public abstract boolean redirectErrorStream(); /** * Retrieves an array of bytes to be written to the STDIN stream of the child process. *

* Depending upon the implementation, this may or may not create a copy of the byte array. * Generally, {@link ProcessBuilder2} will return a reference to the original array to allow * modifications, but {@link Process2} will return a copy of the original array. * * @return array of bytes to write to child process STDIN stream * * @see #stdinDataRef() */ public abstract byte[] stdinData(); /** * Similar to {@link #stdinData()}, but guarantees the return value is a reference to, not a * copy of, the original byte array. * * @see #stdinData() */ protected abstract byte[] stdinDataRef(); /** * Retrieves a reference to text to be written to STDIN stream of child process. */ public abstract String stdinText(); /** * The most beautiful {@code toString()} you ever saw. *

* {@inheritDoc} */ @Override public String toString() { String commandStr = Joiner.on("', '").join(command()); if (!commandStr.isEmpty()) { commandStr = "'" + commandStr + "'"; } Map envMap = environment(); String envStr = Joiner.on("', '").withKeyValueSeparator(" => ").join(envMap); if (!envStr.isEmpty()) { envStr = "'" + envStr + "'"; } File dirPath = directory(); String x = String.format( "%s [" + "%n\tstdoutSettings()=[%s]" + "%n\tstderrSettings()=[%s]" + "%n\tcommand()=[%s]" + "%n\tenvironment()={%s}" + "%n\tdirectory()=%s" + "%n\tredirectErrorStream()=%s" + "%n\tstdinData()=%s" + "%n\tstdinText()=%s" + "%n\t]", getClass().getSimpleName(), stdoutSettings(), stderrSettings(), commandStr, envStr, (null == dirPath ? "null" : "'" + dirPath.getAbsolutePath() + "'"), redirectErrorStream(), _stdinByteArrToString(), _stdinTextToString()); return x; } private final int MAX_STDIN_TEXT_SAMPLE_LENGTH = 256; private final int MAX_STDIN_BYTE_ARR_SAMPLE_LENGTH = MAX_STDIN_TEXT_SAMPLE_LENGTH / 2; private String _stdinTextToString() { String optStdinText = stdinText(); if (null == optStdinText) { return "null"; } int len = optStdinText.length(); if (len <= MAX_STDIN_TEXT_SAMPLE_LENGTH) { return "'" + optStdinText + "'"; } String sample = String.format("(sample: %d of %d chars) '%s'", MAX_STDIN_TEXT_SAMPLE_LENGTH, len, optStdinText.substring(0, MAX_STDIN_TEXT_SAMPLE_LENGTH)); return sample; } private static final char[] HEX_ARR = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; private String _stdinByteArrToString() { byte[] optStdinByteArr = stdinDataRef(); if (null == optStdinByteArr) { return "null"; } byte[] byteArr = optStdinByteArr; String result = ""; if (optStdinByteArr.length > MAX_STDIN_BYTE_ARR_SAMPLE_LENGTH) { byteArr = Arrays.copyOf(byteArr, MAX_STDIN_BYTE_ARR_SAMPLE_LENGTH); result = String.format("(sample: %d of %d bytes) ", MAX_STDIN_BYTE_ARR_SAMPLE_LENGTH, optStdinByteArr.length); } String[] hexArr = new String[byteArr.length]; char[] hexCharArr = new char[2]; for (int i = 0; i < byteArr.length; ++i) { // Ref: http://stackoverflow.com/a/9855338/257299 int x = byteArr[i] & 0xFF; // safely convert a byte to an integer hexCharArr[0] = HEX_ARR[x >>> 4]; hexCharArr[1] = HEX_ARR[x & 0x0F]; String hex = new String(hexCharArr); hexArr[i] = hex; } result += "[" + Joiner.on(", ").join(hexArr) + "]"; return result; } /** * Since this object is mutable, apply caution when calling this method and interpreting * the result. *

* {@inheritDoc} */ @Override public int hashCode() { // This order closely follows that of equals(). int x = Objects.hashCode( _stdoutSettings, _stderrSettings, redirectErrorStream(), directory(), command(), stdinText(), environment()); x = ObjectUtils.appendHashCodes(x, Arrays.hashCode(stdinDataRef())); return x; } /** * Since this object is mutable, apply caution when calling this method and interpreting * the result. *

* {@inheritDoc} */ @Override public boolean equals(Object obj) { boolean result = (this == obj); // This also handles when obj == null. if (!result && obj instanceof AbstractProcessSettings) { AbstractProcessSettings other = (AbstractProcessSettings) obj; // This long, complicated expression is carefully organized from fastest-to-slowest for // equals testing. Primitives appear first, followed by arrays and collections. result = // Both settings objects consist of many primitives. this._stdoutSettings.equals(other._stdoutSettings) && this._stderrSettings.equals(other._stderrSettings) && Objects.equal(this.redirectErrorStream(), other.redirectErrorStream()) && Objects.equal(this.directory(), other.directory()) && Objects.equal(this.command(), other.command()) && Objects.equal(this.environment(), other.environment()) && Objects.equal(this.stdinText(), other.stdinText()) && Arrays.equals(this.stdinDataRef(), other.stdinDataRef()) ; // if (result) { // // From Javadocs for ProcessBuilder.environment(): // // The returned map and its collection views may not obey the general contract of // // the Object.equals and Object.hashCode methods. // // Thus, we need to make a copy before comparing. This is expensive! // result = result // && Objects.equal( // ImmutableMap.copyOf(this.environment()), // ImmutableMap.copyOf(other.environment())); // } } return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy