View Javadoc
1   //******************************************************************************
2   //
3   // File:    Job.java
4   // Package: edu.rit.pj.job
5   // Unit:    Class edu.rit.pj.job.Job
6   //
7   // This Java source file is copyright (C) 2010 by Alan Kaminsky. All rights
8   // reserved. For further information, contact the author, Alan Kaminsky, at
9   // ark@cs.rit.edu.
10  //
11  // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
12  // software; you can redistribute it and/or modify it under the terms of the GNU
13  // General Public License as published by the Free Software Foundation; either
14  // version 3 of the License, or (at your option) any later version.
15  //
16  // PJ is distributed in the hope that it will be useful, but WITHOUT ANY
17  // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18  // A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19  //
20  // Linking this library statically or dynamically with other modules is making a
21  // combined work based on this library. Thus, the terms and conditions of the GNU
22  // General Public License cover the whole combination.
23  //
24  // As a special exception, the copyright holders of this library give you
25  // permission to link this library with independent modules to produce an
26  // executable, regardless of the license terms of these independent modules, and
27  // to copy and distribute the resulting executable under terms of your choice,
28  // provided that you also meet, for each linked independent module, the terms
29  // and conditions of the license of that module. An independent module is a module
30  // which is not derived from or based on this library. If you modify this library,
31  // you may extend this exception to your version of the library, but you are not
32  // obligated to do so. If you do not wish to do so, delete this exception
33  // statement from your version.
34  //
35  // A copy of the GNU General Public License is provided in the file gpl.txt. You
36  // may also obtain a copy of the GNU General Public License on the World Wide
37  // Web at http://www.gnu.org/licenses/gpl.html.
38  //
39  //******************************************************************************
40  package edu.rit.pj.job;
41  
42  import java.io.*;
43  import java.lang.reflect.Method;
44  import java.lang.reflect.Modifier;
45  import java.util.ArrayList;
46  
47  import edu.rit.io.Stdio;
48  
49  /**
50   * Class Job encapsulates a job and its attributes. A job is typically created
51   * by a {@linkplain JobGenerator}. The job's attributes are:
52   * <UL>
53   *
54   * <LI>
55   * Job number. A JobGenerator assigns a unique job number to each job it
56   * creates.
57   *
58   * <LI>
59   * Description. An arbitrary string. The {@linkplain Runner} program prints the
60   * job number and description whenever a job is started.
61   *
62   * <LI>
63   * Fully-qualified name of the main program class. To run a job, the main
64   * program class's <code>public static void main(String[])</code> method is called.
65   *
66   * <LI>
67   * List of argument strings for the <code>main()</code> method.
68   *
69   * <LI>
70   * Standard input redirection. The standard input may be redirected from a file.
71   * The job redirects the per-thread standard input stream in class {@linkplain
72   * edu.rit.io.Stdio Stdio}. For redirection to work, the <code>main()</code> method
73   * must use <code>Stdio.in()</code> for the standard input.
74   *
75   * <LI>
76   * Standard output redirection. The standard output may be redirected to a file,
77   * either overwriting or appending to the file. The job redirects the per-thread
78   * standard output stream in class {@linkplain edu.rit.io.Stdio Stdio}. For
79   * redirection to work, the <code>main()</code> method must use <code>Stdio.out()</code>
80   * for the standard output.
81   *
82   * <LI>
83   * Standard error redirection. The standard error may be redirected to a file,
84   * either overwriting or appending to the file. The standard error may also be
85   * redirected to the same place as the standard output. The job redirects the
86   * per-thread standard error stream in class {@linkplain edu.rit.io.Stdio
87   * Stdio}. For redirection to work, the <code>main()</code> method must use
88   * <code>Stdio.err()</code> for the standard error.
89   * </UL>
90   *
91   * @author Alan Kaminsky
92   * @version 09-Oct-2010
93   */
94  public class Job
95          implements Runnable, Externalizable {
96  
97      @Serial
98      private static final long serialVersionUID = 1L;
99  
100 // Hidden data members.
101     //private static final long serialVersionUID = 717404081766242374L;
102     private int myJobNumber;
103     private String myDescription;
104     private String myMainClassName;
105     private final ArrayList<String> myArguments = new ArrayList<>();
106     private Stdin myStdinRedirection = Stdin.NONE;
107     private Stdout myStdoutRedirection = Stdout.NONE;
108     private Stderr myStderrRedirection = Stderr.NONE;
109     private File myStdinFile = null;
110     private File myStdoutFile = null;
111     private File myStderrFile = null;
112 
113 // Exported constructors.
114     /**
115      * Construct a new uninitialized job. This constructor is for use only by
116      * object deserialization.
117      */
118     public Job() {
119     }
120 
121     /**
122      * Construct a new job. The job has the given job number and description.
123      * The job will run the <code>public static void main(String[])</code> method in
124      * the given class.
125      *
126      * @param theJobNumber Job number.
127      * @param theDescription Job description.
128      * @param theMainClassName Fully qualified name of main class.
129      * @exception NullPointerException (unchecked exception) Thrown if
130      * <code>theMainClassName</code> is null.
131      */
132     public Job(int theJobNumber,
133             String theDescription,
134             String theMainClassName) {
135         if (theMainClassName == null) {
136             throw new NullPointerException("Job(): Main class name is null");
137         }
138         myJobNumber = theJobNumber;
139         myDescription = theDescription;
140         myMainClassName = theMainClassName;
141     }
142 
143 // Exported operations.
144     /**
145      * Returns this job's number.
146      *
147      * @return Job number.
148      */
149     public int getJobNumber() {
150         return myJobNumber;
151     }
152 
153     /**
154      * Returns this job's description.
155      *
156      * @return Job description.
157      */
158     public String getDescription() {
159         return myDescription;
160     }
161 
162     /**
163      * Add the given argument string to this job.
164      *
165      * @param arg Argument string.
166      * @exception NullPointerException (unchecked exception) Thrown if
167      * <code>arg</code> is null.
168      */
169     public void addArgument(String arg) {
170         if (arg == null) {
171             throw new NullPointerException("Job.addArgument(): Argument string is null");
172         }
173         myArguments.add(arg);
174     }
175 
176     /**
177      * Read this job's standard input from the given file. It is an error if the
178      * file does not exist.
179      *
180      * @param file File.
181      * @exception NullPointerException (unchecked exception) Thrown if
182      * <code>file</code> is null.
183      */
184     public void stdinFromFile(File file) {
185         if (file == null) {
186             throw new NullPointerException("Job.stdinFromFile(): File is null");
187         }
188         myStdinRedirection = Stdin.FILE;
189         myStdinFile = file;
190     }
191 
192     /**
193      * Store this job's standard output in the given file. The file is created
194      * if it does not exist. The file is overwritten if it does exist.
195      *
196      * @param file File.
197      * @exception NullPointerException (unchecked exception) Thrown if
198      * <code>file</code> is null.
199      */
200     public void stdoutToFile(File file) {
201         if (file == null) {
202             throw new NullPointerException("Job.stdoutToFile(): File is null");
203         }
204         myStdoutRedirection = Stdout.FILE;
205         myStdoutFile = file;
206     }
207 
208     /**
209      * Append this job's standard output to the end of the given file. The file
210      * is created if it does not exist.
211      *
212      * @param file File.
213      * @exception NullPointerException (unchecked exception) Thrown if
214      * <code>file</code> is null.
215      */
216     public void stdoutAppendToFile(File file) {
217         if (file == null) {
218             throw new NullPointerException("Job.stdoutAppendToFile(): File is null");
219         }
220         myStdoutRedirection = Stdout.FILE_APPEND;
221         myStdoutFile = file;
222     }
223 
224     /**
225      * Store this job's standard error in the given file. The file is created if
226      * it does not exist. The file is overwritten if it does exist.
227      *
228      * @param file File.
229      * @exception NullPointerException (unchecked exception) Thrown if
230      * <code>file</code> is null.
231      */
232     public void stderrToFile(File file) {
233         if (file == null) {
234             throw new NullPointerException("Job.stderrToFile(): File is null");
235         }
236         myStderrRedirection = Stderr.FILE;
237         myStderrFile = file;
238     }
239 
240     /**
241      * Append this job's standard error to the end of the given file. The file
242      * is created if it does not exist.
243      *
244      * @param file File.
245      * @exception NullPointerException (unchecked exception) Thrown if
246      * <code>file</code> is null.
247      */
248     public void stderrAppendToFile(File file) {
249         if (file == null) {
250             throw new NullPointerException("Job.stderrAppendToFile(): File is null");
251         }
252         myStderrRedirection = Stderr.FILE_APPEND;
253         myStderrFile = file;
254     }
255 
256     /**
257      * Redirect this job's standard error to the same place as this job's
258      * standard output.
259      */
260     public void stderrToStdout() {
261         myStderrRedirection = Stderr.STDOUT;
262         myStderrFile = null;
263     }
264 
265     /**
266      * Run this job.
267      * <OL TYPE=1>
268      *
269      * <LI>
270      * The per-thread standard input, standard output, and standard error in
271      * class {@linkplain edu.rit.io.Stdio Stdio} are redirected as necessary.
272      *
273      * <LI>
274      * This job's main class is found, using the calling thread's context class
275      * loader.
276      *
277      * <LI>
278      * The main class's <code>public static void main(String[])</code> method is
279      * found.
280      *
281      * <LI>
282      * The <code>main()</code> method is called, passing in this job's arguments.
283      * </OL>
284      * <P>
285      * If an I/O error occurs during Step 1, a RuntimeException wrapping an
286      * IOException is thrown. If any exception is thrown during Steps 2 through
287      * 4, an exception stack trace is printed on the per-thread standard error
288      * (which may have been redirected), but the exception is not propagated.
289      */
290     public void run() {
291         // Redirect standard input, standard output, and standard error.
292         try {
293             switch (myStdinRedirection) {
294                 case FILE:
295                     Stdio.in(new FileInputStream(myStdinFile));
296                     break;
297             }
298             switch (myStdoutRedirection) {
299                 case FILE:
300                     Stdio.out(new PrintStream(new FileOutputStream(myStdoutFile, false),
301                             true));
302                     break;
303                 case FILE_APPEND:
304                     Stdio.out(new PrintStream(new FileOutputStream(myStdoutFile, true),
305                             true));
306                     break;
307             }
308             switch (myStderrRedirection) {
309                 case FILE:
310                     Stdio.err(new PrintStream(new FileOutputStream(myStderrFile, false),
311                             true));
312                     break;
313                 case FILE_APPEND:
314                     Stdio.err(new PrintStream(new FileOutputStream(myStderrFile, true),
315                             true));
316                     break;
317                 case STDOUT:
318                     Stdio.err(Stdio.out());
319                     break;
320             }
321         } catch (IOException exc) {
322             throw new RuntimeException(exc);
323         }
324 
325         try {
326             // Find main class.
327             Class<?> mainClass
328                     = Class.forName(myMainClassName,
329                             true,
330                             Thread.currentThread().getContextClassLoader());
331 
332             // Find public static void main(String[]) method.
333             Method mainMethod = mainClass.getMethod("main", String[].class);
334             int modifiers = mainMethod.getModifiers();
335             if (!Modifier.isStatic(mainMethod.getModifiers())) {
336                 throw new NoSuchMethodException("Job.run(): Class " + mainClass.getName()
337                         + " main() method is not static");
338             }
339             if (mainMethod.getReturnType() != Void.TYPE) {
340                 throw new NoSuchMethodException("Job.run(): Class " + mainClass.getName()
341                         + " main() method does not return void");
342             }
343 
344             // Call main() method with arguments.
345             String[] args = myArguments.toArray(new String[0]);
346             mainMethod.invoke(null, (Object) args);
347         } catch (Throwable exc) {
348             exc.printStackTrace(Stdio.err());
349         }
350     }
351 
352     /**
353      * {@inheritDoc}
354      *
355      * Write this job to the given object output stream.
356      * @exception IOException Thrown if an I/O error occurred.
357      */
358     public void writeExternal(ObjectOutput out)
359             throws IOException {
360         out.writeInt(myJobNumber);
361         out.writeUTF(myDescription);
362         out.writeUTF(myMainClassName);
363         out.writeInt(myArguments.size());
364         for (String arg : myArguments) {
365             out.writeUTF(arg);
366         }
367         out.writeObject(myStdinRedirection);
368         out.writeObject(myStdoutRedirection);
369         out.writeObject(myStderrRedirection);
370         out.writeObject(myStdinFile);
371         out.writeObject(myStdoutFile);
372         out.writeObject(myStderrFile);
373     }
374 
375     /**
376      * {@inheritDoc}
377      *
378      * Read this job from the given object input stream.
379      * @exception IOException Thrown if an I/O error occurred.
380      * @exception ClassNotFoundException Thrown if an object's class could not
381      * be found.
382      */
383     public void readExternal(ObjectInput in)
384             throws IOException, ClassNotFoundException {
385         int n;
386         myJobNumber = in.readInt();
387         myDescription = in.readUTF();
388         myMainClassName = in.readUTF();
389         myArguments.clear();
390         n = in.readInt();
391         for (int i = 0; i < n; ++i) {
392             myArguments.add(in.readUTF());
393         }
394         myStdinRedirection = (Stdin) in.readObject();
395         myStdoutRedirection = (Stdout) in.readObject();
396         myStderrRedirection = (Stderr) in.readObject();
397         myStdinFile = (File) in.readObject();
398         myStdoutFile = (File) in.readObject();
399         myStderrFile = (File) in.readObject();
400     }
401 
402 }