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 }