001package Torello.Java;
002
003import java.io.BufferedWriter;
004import java.io.OutputStream;
005import java.io.OutputStreamWriter;
006import java.io.InputStream;
007import java.io.IOException;
008
009import java.util.Arrays;
010import java.util.Objects;
011
012/**
013 * Allows a user to pipe data directly from Java-Memory, and into an instance of 
014 * <CODE>java.lang.Process</CODE>.
015 */
016public class OSJavaPipe implements Cloneable
017{
018    // ********************************************************************************************
019    // ********************************************************************************************
020    // Two Private-Fields, Two Inpsection Methods
021    // ********************************************************************************************
022    // ********************************************************************************************
023
024
025    private Object  textOrDataToPipe;
026    private byte    dataType;
027
028    /**
029     * Retrieve the Data-Type that is contained by this instance' internal PIPE-Data contents.
030     * @return The Static-Flag Constant associated with the Data-Type that has been assigned to
031     * this instance.
032     */
033    public byte getDataType()
034    { return this.dataType; }
035
036    /**
037     * Retrieve the actual data, as a {@code java.lang.Object} contained by this instance.
038     * @return This class internal Data-Field
039     */
040    public Object getPipeData()
041    { return this.textOrDataToPipe; }
042
043
044    // ********************************************************************************************
045    // ********************************************************************************************
046    // Static-Flag Constants, representing the Data's Type
047    // ********************************************************************************************
048    // ********************************************************************************************
049
050
051    /**
052     * Constant used to indicate that this class data-contents currently contain a non-null
053     * {@code int[]}-Array.
054     */
055    public static final byte INT_ARR = 1;
056
057    /**
058     * Constant used to indicate that this class data-contents currently contain a non-null
059     * {@code byte[]}-Array.
060     */
061    public static final byte BYTE_ARR = 2;
062
063    /**
064     * Constant used to indicate that this class data-contents currently contain a non-null
065     * {@code char[]}-Array.
066     */
067    public static final byte CHAR_ARR = 3;
068
069    /**
070     * Constant used to indicate that this class data-contents currently contain a non-null
071     * {@code String}.
072     */
073    public static final byte STRING = 4;
074
075    /**
076     * Constant used to indicate that this class data-contents currently contain a non-null
077     * {@code InputStream}.
078     */
079    public static final byte INPUT_STREAM = 5;
080
081
082    // ********************************************************************************************
083    // ********************************************************************************************
084    // Constructors
085    // ********************************************************************************************
086    // ********************************************************************************************
087
088
089    private OSJavaPipe() { }
090
091    /**
092     * Constructs an instance of this class, assigning the input {@code int[]}-Array Parameter to
093     * the internal Data-Field.
094     * 
095     * @param intArrData Any Java {@code int[]} Integer-Array.
096     * @throws NullPointerException if {@code 'intArrData'} is null.
097     */
098    public OSJavaPipe(final int[] intArrData)
099    { this.setData(intArrData); }
100
101    /**
102     * Constructs an instance of this class, assigning the input {@code byte[]}-Array Parameter to
103     * the internal Data-Field.
104     * 
105     * @param byteArrData Any Java {@code byte[]} Byte-Array.
106     * @throws NullPointerException if {@code 'byteArrData'} is null.
107     */
108    public OSJavaPipe(final byte[] byteArrData)
109    { this.setData(byteArrData); }
110
111    /**
112     * Constructs an instance of this class, assigning the input {@code char[]}-Array Parameter to
113     * the internal Data-Field.
114     * 
115     * @param charArrData Any Java {@code char[]} Char-Array.
116     * @throws NullPointerException if {@code 'charArrData'} is null.
117     */
118    public OSJavaPipe(final char[] charArrData)
119    { this.setData(charArrData); }
120
121    /**
122     * Constructs an instance of this class, assigning the input {@code String}-Parameter to the
123     * internal Data-Field.
124     * 
125     * @param strData Any Java {@code String}.
126     * @throws NullPointerException if {@code 'strData'} is null.
127     */
128    public OSJavaPipe(final String strData)
129    { this.setData(strData); }
130
131    /**
132     * Constructs an instance of this class, assigning the {@code InputStream} Parameter to the
133     * internal Data-Field.
134     * 
135     * @param inputStream Any Java Input-Stream.
136     * @throws NullPointerException if {@code 'inputStream'} is null.
137     */
138    public OSJavaPipe(final InputStream inputStream)
139    { this.setData(inputStream); }
140
141
142    // ********************************************************************************************
143    // ********************************************************************************************
144    // This Class Main User-API for assigning some data
145    // ********************************************************************************************
146    // ********************************************************************************************
147
148
149    /**
150     * Sets the data to be used for piping into an Operating-System Process.
151     * @param intArrData Any Java {@code int[]} Integer-Array.
152     * @throws NullPointerException if {@code 'intArrData'} is null.
153     */
154    public synchronized void setData(final int[] intArrData)
155    {
156        Objects.requireNonNull(intArrData, "The int[]-Array passed may not be null");
157
158        this.textOrDataToPipe   = intArrData;
159        this.dataType           = INT_ARR;
160    }
161
162    /**
163     * Sets the data to be used for piping into an Operating-System Process.
164     * @param byteArrData Any Java {@code byte[]} Byte-Array.
165     * @throws NullPointerException if {@code 'byteArrData'} is null.
166     */
167    public synchronized void setData(final byte[] byteArrData)
168    {
169        Objects.requireNonNull(byteArrData, "The byte[]-Array passed may not be null");
170
171        this.textOrDataToPipe   = byteArrData;
172        this.dataType           = BYTE_ARR;
173    }
174
175    /**
176     * Sets the data to be used for piping into an Operating-System Process.
177     * @param charArrData Any Java {@code char[]} Char-Array.
178     * @throws NullPointerException if {@code 'charArrData'} is null.
179     */
180    public synchronized void setData(final char[] charArrData)
181    {
182        Objects.requireNonNull(charArrData, "The char[]-Array passed may not be null");
183
184        this.textOrDataToPipe   = charArrData;
185        this.dataType           = CHAR_ARR;
186    }
187
188    /**
189     * Sets the data to be used for piping into an Operating-System Process.
190     * @param strData Any Java {@code String}.
191     * @throws NullPointerException if {@code 'strData'} is null.
192     */
193    public synchronized void setData(final String strData)
194    {
195        Objects.requireNonNull(strData, "The String-Data passed may not be null");
196
197        this.textOrDataToPipe   = strData;
198        this.dataType           = STRING;
199    }
200
201    /**
202     * Sets the data to be used for piping into an Operating-System Process.
203     * @param inputStream Any Java Input-Stream.
204     * @throws NullPointerException if {@code 'inputStream'} is null.
205     */
206    public synchronized void setData(final InputStream inputStream)
207    {
208        Objects.requireNonNull(inputStream, "The Input-Stream passed may not be null");
209
210        this.textOrDataToPipe   = inputStream;
211        this.dataType           = INPUT_STREAM;
212    }
213
214
215    // ********************************************************************************************
216    // ********************************************************************************************
217    // Package-Private Method for writing this data to an input stream.
218    // ********************************************************************************************
219    // ********************************************************************************************
220
221
222    // Class OSCommands uses this Package-Private method to write the contents of the data-field
223    // inside this class to the "OutputStream".  The OutputStream instance that is provided as 
224    // input to this method is the one that is retrieved by invoking the method:
225    //
226    // Process.getOutputStream(). 
227    // 
228    // This OutputStream pipes the text/data that it receives directly to the Operating-System's
229    // Process.  NOTE - this method is, essentially, the whole entire purpose of this class.  Also,
230    // this class isn't so complicated, it's just a lot of wordy-explanations that help understand
231    // how to use java.lang.Process / Process.Redirect / and all those Input-Output Streams.  It
232    // hopefully makes it a lot easier to use and extend this stuff.  It certainly does for me.
233    // 
234    // Again, this is Package-Private, and only invoked in one place, inside the "OSCommands"
235    // method.
236
237    synchronized void writeToPipe(final OutputStream os) throws IOException
238    {
239        switch (this.dataType)
240        {
241            case INT_ARR :
242                for (int i : (int[]) textOrDataToPipe) os.write(i);
243                break;
244
245            case BYTE_ARR :
246                for (byte b : (byte[]) textOrDataToPipe) os.write(b);
247                break;
248
249            case CHAR_ARR :
250                for (char c : (char[]) textOrDataToPipe) os.write(c);
251                break;
252
253            case STRING :
254                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
255                writer.write((String) textOrDataToPipe);
256                writer.flush();
257                writer.close(); // Important to close the stream to signal end of input
258                break;
259
260            case INPUT_STREAM  :
261                ((InputStream) textOrDataToPipe).transferTo(os);
262                break;
263
264            default: throw new UnreachableError();
265        }
266
267        os.flush();
268        os.close();
269    }
270
271
272    // ********************************************************************************************
273    // ********************************************************************************************
274    // java.lang.Object
275    // ********************************************************************************************
276    // ********************************************************************************************
277
278
279    private static final String NAME = "OSJavaPipe Contents: ";
280
281    /**
282     * Generates a {@code String} this class.  The returned {@code String} merely converts the
283     * contained data to a {@code String}, using Standard Java Methods.
284     *     
285     * @return A simple representation of this class, as a {@code java.lang.String}
286     */
287    public synchronized String toString()
288    {
289        switch (this.dataType)
290        {
291            case INT_ARR : return NAME + "int[]:\n" + StrPrint.abbrev(
292                Arrays.toString((int[]) this.textOrDataToPipe),
293                60, true, null, 120
294            );
295
296            case BYTE_ARR : return NAME + "byte[]:\n" + StrPrint.abbrev(
297                Arrays.toString((byte[]) this.textOrDataToPipe),
298                60, true, null, 120
299            );
300
301            case CHAR_ARR : return NAME + "char[]:\n" + StrPrint.abbrev(
302                Arrays.toString((char[]) this.textOrDataToPipe),
303                60, true, null, 120
304            );
305
306            case STRING : return NAME + "String:\n" + StrPrint.abbrev
307                ((String) this.textOrDataToPipe, 60, true, null, 120);
308
309            case INPUT_STREAM : return NAME + "InputStream: Non-Null Data-Input\n";
310
311            default: throw new UnreachableError();
312        }
313    }
314
315    /**
316     * Checks for equality based on whether two instances have identical references.
317     *
318     * @param other Any Java Object.  Only a valid sub-class of {@code OSJavaPipe} could possibly
319     * produce a {@code TRUE} return value
320     *
321     * @return {@code TRUE} if and only if this class' internal fields are <I>identical
322     * references in both {@code 'this'} and {@code 'other'}</I>.
323     */
324    public synchronized boolean equals(Object other)
325    {
326        if (other == null)                      return false;
327        if (! (other instanceof OSJavaPipe))    return false;
328
329        final OSJavaPipe o = (OSJavaPipe) other;
330
331        return
332                Objects.equals(this.textOrDataToPipe, o.textOrDataToPipe)
333            &&  (this.dataType == o.dataType);
334    }
335
336    /**
337     * Makes a copy of {@code 'this'} instance and returns it.
338     * @return a clone of {@code 'this'} instance
339     */
340    public synchronized Object clone()
341    {
342        // Private-Internal Zero-Argument Constructor
343        OSJavaPipe ret = new OSJavaPipe(); 
344
345        ret.dataType            = this.dataType;
346        ret.textOrDataToPipe    = this.textOrDataToPipe;
347
348        return ret;
349    }
350
351    /**
352     * Produces a Hash-Code that may be used to place this instance-reference into a Hash-Table,
353     * Set or Map.  The code produced is generated using whatever hash-code value is returned 
354     * by this class' internal {@link #textOrDataToPipe} hash-code method.
355     * 
356     * @return a hashcode 
357     */
358    public int hashCode()
359    { return this.textOrDataToPipe.hashCode(); }
360}