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 }