/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: Job.java
*
* Copyright (c) 2003 Sun Microsystems and Static Free Software
*
* Electric(tm) 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.
*
* Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.Environment;
import com.sun.electric.database.Snapshot;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.IdManager;
import com.sun.electric.database.id.IdReader;
import com.sun.electric.database.id.IdWriter;
import com.sun.electric.database.id.LibId;
import com.sun.electric.database.id.TechId;
import com.sun.electric.database.text.Pref;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.user.ActivityLogger;
import com.sun.electric.tool.user.CantEditException;
import com.sun.electric.tool.user.ErrorLogger;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
/**
* Jobs are processes that will run in the background, such as
* DRC, NCC, Netlisters, etc. Each Job gets placed in a Job
* window, and reports its status. A job can be cancelled.
*
* <p>To start a new job, do:
* <p>Job job = new Job(name);
* <p>job.start();
*
* <p>Job subclass must implement "doIt" method, it may override "terminateOK" method.
* <p>Job subclass is activated by "Job.startJob()". In case of exception in constructor it is not activated.
* Hence "doIt" and "terminateOK" method are not called.
* <p>Job subclass must be serializable for CHANGE and REMOTE_EXAMINE mode.
* Serialization occurs at the moment when "Job.startJob()" is called.
* Fields that are not needed on the server are escaped from serialization by "transient" keyword.
* "doIt" is executed on serverDatabase for CHANGE and REMOTE_EXAMINE mode.
* "doIt" is executed on clientDatabase for EXAMINE mode, Job subclass need not be serializable.
* <p>"doIt" may return true, may return false, may throw JobException or any other Exception/Error.
* Return true is considered normal termination.
* Return false and throwing any Exception/Throwable are failure terminations.
* <p>On normal termination in CHANGE or REMOTE_EXAMINE mode "fieldVariableChanged" variables are serialized.
* In case of REMOTE_EXAMINE they are serialized on read-only database state.
* In case of CHANGE they are serialized on database state after Constraint propagation.
* In case of EXAMINE they are not serialized, but they are checked for valid field names.
* Some time later the changed variables are deserialized on client database.
* If serialization on server and deserialization on client was OK then terminateOK method is called on client database for
* all three modes CHANGE, REMOTE_EXAMINE, EXAMINE.
* If serialization/deserialization failed then terminateOK is not called, error message is issued.
* <p>In case of failure termination no terminateOK is called, error message is issued,
*
* <p>The extendig class may override getProgress(),
* which returns a string indicating the current status.
* Job also contains boolean abort, which gets set when the user decides
* to abort the Job. The extending class' code should check abort when/where
* applicable.
*
* <p>Note that if your Job calls methods outside of this thread
* that access shared data, those called methods should be synchronized.
*
* @author gainsley
*/
public abstract class Job implements Serializable {
private static boolean GLOBALDEBUG = false;
static final int PROTOCOL_VERSION = 19; // Apr 17
public static boolean LOCALDEBUGFLAG; // Gilda's case
// private static final String CLASS_NAME = Job.class.getName();
static final Logger logger = Logger.getLogger("com.sun.electric.tool.job");
/**
* Method to tell whether Electric is running in "debug" mode.
* If the program is started with the "-debug" switch, debug mode is enabled.
* @return true if running in debug mode.
*/
public static boolean getDebug() { return GLOBALDEBUG; }
public static void setDebug(boolean f) { GLOBALDEBUG = f; }
// ---------------------------- public methods ---------------------------
/**
* Type is a typesafe enum class that describes the type of job (CHANGE or EXAMINE).
*/
public static enum Type {
/** Describes a server database change. */ CHANGE,
/** Describes a server database undo/redo. */ UNDO,
/** Describes a server database examination. */ SERVER_EXAMINE,
/** Describes a client database examination. */ CLIENT_EXAMINE;
public boolean isExamine() {
return this == Job.Type.CLIENT_EXAMINE || this == Job.Type.SERVER_EXAMINE;
}
}
/**
* Priority is a typesafe enum class that describes the priority of a job.
*/
public static enum Priority {
/** The highest priority: from the user. */ USER,
/** Next lower priority: visible changes. */ VISCHANGES,
/** Next lower priority: invisible changes. */ INVISCHANGES,
/** Lowest priority: analysis. */ ANALYSIS;
}
/** default execution time in milis */ /*private*/ public static final int MIN_NUM_SECONDS = 60000;
/** job manager */ /*private*/ static ServerJobManager serverJobManager;
/** job manager */ /*private*/ static ClientJobManager clientJobManager;
static AbstractUserInterface currentUI;
static Thread clientThread;
/** delete when done if true */ /*private*/ boolean deleteWhenDone;
// Job Status
/** job start time */ public /*protected*/ long startTime;
/** job end time */ public /*protected*/ long endTime;
/** was job started? */ /*private*/boolean started;
/** is job finished? */ /*private*/ public boolean finished;
/** thread aborted? */ /*private*/ boolean aborted;
/** schedule thread to abort */ /*private*/ boolean scheduledToAbort;
/** report execution time regardless MIN_NUM_SECONDS */
/*private*/ public boolean reportExecution = false;
/** tool running the job */ /*private*/ Tool tool;
/** current technology */ final TechId curTechId;
/** current library */ final LibId curLibId;
/** current Cell */ final CellId curCellId;
// /** priority of job */ private Priority priority;
// /** bottom of "up-tree" of cells affected */private Cell upCell;
// /** top of "down-tree" of cells affected */ private Cell downCell;
// /** status */ private String status = null;
transient EJob ejob;
transient EDatabase database;
public static void initJobManager(int numThreads, String loggingFilePath, int socketPort, AbstractUserInterface ui, Job initDatabaseJob) {
currentUI = ui;
serverJobManager = new ServerJobManager(numThreads, loggingFilePath, false, socketPort);
serverJobManager.runLoop(initDatabaseJob);
}
public static void pipeServer(int numThreads, String loggingFilePath, int socketPort) {
Pref.forbidPreferences();
EDatabase.setServerDatabase(new EDatabase(IdManager.stdIdManager.getInitialSnapshot()));
Tool.initAllTools();
serverJobManager = new ServerJobManager(numThreads, loggingFilePath, true, socketPort);
}
public static void socketClient(String serverMachineName, int socketPort, AbstractUserInterface ui, Job initDatabaseJob) {
currentUI = ui;
try {
clientJobManager = new ClientJobManager(serverMachineName, socketPort);
clientJobManager.runLoop(initDatabaseJob);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void pipeClient(Process process, AbstractUserInterface ui, Job initDatabaseJob, boolean skipOneLine) {
currentUI = ui;
try {
clientJobManager = new ClientJobManager(process, skipOneLine);
clientJobManager.runLoop(initDatabaseJob);
} catch (IOException e) {
e.printStackTrace();
}
}
// public static Mode getRunMode() { return threadMode; }
// public static int getNumThreads() { return recommendedNumThreads; }
/**
* Constructor creates a new instance of Job.
* @param jobName a string that describes this Job.
* @param tool the Tool that originated this Job.
* @param jobType the Type of this Job (EXAMINE or CHANGE).
* @param upCell the Cell at the bottom of a hierarchical "up cone" of change.
* If this and "downCell" are null, the entire database is presumed.
* @param downCell the Cell at the top of a hierarchical "down tree" of changes/examinations.
* If this and "upCell" are null, the entire database is presumed.
* @param priority the priority of this Job.
*/
public Job(String jobName, Tool tool, Type jobType, Cell upCell, Cell downCell, Priority priority) {
ejob = new EJob(this, jobType, jobName, EditingPreferences.getThreadEditingPreferences());
UserInterface ui = getUserInterface();
database = ui != null ? ui.getDatabase() : EDatabase.clientDatabase();
this.tool = tool;
this.deleteWhenDone = true;
startTime = endTime = 0;
Technology curTech = ui != null ? ui.getCurrentTechnology() : null;
curTechId = curTech != null ? curTech.getId() : null;
Library curLib = ui != null ? ui.getCurrentLibrary() : null;
curLibId = curLib != null ? curLib.getId() : null;
Cell curCell = ui != null ? ui.getCurrentCell() : null;
curCellId = curCell != null ? curCell.getId() : null;
// started = finished = aborted = scheduledToAbort = false;
// thread = null;
}
/**
* Start a job. By default displays Job on Job List UI, and
* delete Job when done. Jobs that have state the user would like to use
* after the Job finishes (like DRC, NCC, etc) should call
* <code>startJob(true, true)</code>.
*/
public void startJob()
{
startJob(true);
}
/**
* Start a job on snapshot obtained at the end of current job. By default displays Job on Job List UI, and
* delete Job when done.
*/
public void startJobOnMyResult()
{
startJob(true, true);
}
/**
* Start the job by placing it on the JobThread queue.
* If <code>deleteWhenDone</code> is true, Job will be deleted
* after it is done (frees all data and references it stores/created)
* @param deleteWhenDone delete when job is done if true, otherwise leave it around
*/
public void startJob(boolean deleteWhenDone) {
startJob(deleteWhenDone, false);
}
/**
* Start the job by placing it on the JobThread queue.
* If <code>deleteWhenDone</code> is true, Job will be deleted
* after it is done (frees all data and references it stores/created)
* @param deleteWhenDone delete when job is done if true, otherwise leave it around
*/
private void startJob(boolean deleteWhenDone, boolean onMySnapshot)
{
this.deleteWhenDone = deleteWhenDone;
UserInterface ui = getUserInterface();
Job.Key curJobKey = ui.getJobKey();
boolean startedByServer = curJobKey.doItOnServer;
boolean doItOnServer = ejob.jobType != Job.Type.CLIENT_EXAMINE;
if (startedByServer) {
assert doItOnServer;
ejob.client = Job.serverJobManager.serverConnections.get(curJobKey.clientId);
ejob.jobKey = ejob.client.newJobId(startedByServer, doItOnServer);
ejob.serverJob.startTime = System.currentTimeMillis();
ejob.serialize(EDatabase.serverDatabase());
ejob.clientJob = null;
Job.serverJobManager.addJob(ejob, onMySnapshot);
} else {
ejob.client = Job.currentUI;
ejob.jobKey = ejob.client.newJobId(startedByServer, doItOnServer);
ejob.clientJob.startTime = System.currentTimeMillis();
ejob.serverJob = null;
if (doItOnServer) {
ejob.serialize(EDatabase.clientDatabase());
Job.currentUI.putProcessingEJob(ejob, onMySnapshot);
if (serverJobManager != null) {
serverJobManager.addJob(ejob, onMySnapshot);
} else {
assert SwingUtilities.isEventDispatchThread();
try {
clientJobManager.writeEJob(ejob);
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
Job.currentUI.putProcessingEJob(ejob, onMySnapshot);
}
}
}
/**
* Method to remember that a field variable of the Job has been changed by the doIt() method.
* @param variableName the name of the variable that changed.
*/
protected void fieldVariableChanged(String variableName) { ejob.fieldVariableChanged(variableName); }
//--------------------------ABSTRACT METHODS--------------------------
/** This is the main work method. This method should
* perform all needed tasks.
* @throws JobException TODO
*/
public abstract boolean doIt() throws JobException;
/**
* This method executes in the Client side after termination of doIt method.
* This method should perform all needed termination actions.
* @param jobException null if doIt terminated normally, otherwise exception thrown by doIt.
*/
public void terminateIt(Throwable jobException) {
if (jobException == null)
terminateOK();
else
terminateFail(jobException);
}
/**
* This method executes in the Client side after normal termination of doIt method.
* This method should perform all needed termination actions.
*/
public void terminateOK() {}
/**
* This method executes in the Client side after exceptional termination of doIt method.
* @param jobException null exception thrown by doIt.
*/
public void terminateFail(Throwable jobException) {
if (jobException instanceof CantEditException) {
((CantEditException)jobException).presentProblem();
} else if (jobException instanceof JobException) {
String message = jobException.getMessage();
if (message == null)
message = "Job " + ejob.jobName + " failed";
System.out.println(message);
} else {
ActivityLogger.logException(jobException);
}
}
//--------------------------PRIVATE JOB METHODS--------------------------
//--------------------------PROTECTED JOB METHODS-----------------------
/** Set reportExecution flag on/off */
protected void setReportExecutionFlag(boolean flag)
{
reportExecution = flag;
}
//--------------------------PUBLIC JOB METHODS--------------------------
// /**
// * Method to end the current batch of changes and start another.
// * Besides starting a new batch, it cleans up the constraint system, and
// */
// protected void flushBatch()
// {
// if (jobType != Type.CHANGE) return;
// Undo.endChanges();
// Undo.startChanges(tool, jobName, /*upCell,*/ savedHighlights, savedHighlightsOffset);
// }
protected synchronized void setProgress(String progress) {
if (serverJobManager != null)
serverJobManager.setProgress(ejob, progress);
else
ejob.progress = progress;
}
private synchronized String getProgress() { return ejob.progress; }
/** Return run status */
public boolean isFinished() { return finished; }
/** Tell thread to abort. Extending class should check
* abort when/where applicable
*/
public synchronized void abort() {
// if (ejob.jobType != Job.Type.EXAMINE) return;
if (aborted) {
System.out.println("Job already aborted: "+getStatus());
return;
}
scheduledToAbort = true;
if (ejob.jobType != Job.Type.CLIENT_EXAMINE && ejob.serverJob != null)
ejob.serverJob.scheduledToAbort = true;
// Job.getUserInterface().wantToRedoJobTree();
}
/** Confirmation that thread is aborted */
private synchronized void setAborted() { aborted = true; /*Job.getUserInterface().wantToRedoJobTree();*/ }
/** get scheduled to abort status */
protected synchronized boolean getScheduledToAbort() { return scheduledToAbort; }
/**
* Method to get abort status. Please leave this function as private.
* To retrieve abort status from another class, use checkAbort which also
* checks if job is scheduled to be aborted.
* @return
*/
private synchronized boolean getAborted() { return aborted; }
/** get deleteWhenDone status */
public boolean getDeleteWhenDone() { return deleteWhenDone; }
/**
* Check if we are scheduled to abort. If so, print msg if non null
* and return true.
* This is because setAbort and getScheduledToAbort
* are protected in Job.
* @return true on abort, false otherwise. If job is scheduled for abort or aborted.
* and it will report it to std output
*/
public boolean checkAbort()
{
// if (ejob.jobType != Job.Type.EXAMINE) return false;
if (getAborted()) return (true);
boolean scheduledAbort = getScheduledToAbort();
if (scheduledAbort)
{
System.out.println(this + ": aborted"); // should call Job.toString()
setReportExecutionFlag(true); // Force reporting
setAborted(); // Job has been aborted
}
return scheduledAbort;
}
/** get all jobs iterator */
public static Iterator<Job> getAllJobs() {
return serverJobManager != null ? serverJobManager.getAllJobs() : currentUI.getAllJobs().iterator();
}
/**
* If this current thread is a EThread running a Job return the Job.
* Return null otherwise.
* @return a running Job or null
*/
public static Job getRunningJob() {
Thread thread = Thread.currentThread();
return thread instanceof EThread ? ((EThread)thread).getRunningJob() : null;
}
/**
* Returns true if this current thread is a EThread running a server Job.
* @return true if this current thread is a EThread running a server Job.
*/
public static boolean inServerThread() {
Thread thread = Thread.currentThread();
return thread instanceof EThread && ((EThread)thread).isServerThread;
}
public static void setCurrentLibraryInJob(Library lib) {
EThread thread = (EThread)Thread.currentThread();
assert thread.ejob.jobType == Type.CHANGE;
thread.userInterface.curLibId = lib != null ? lib.getId() : null;
}
/** get status */
public String getStatus() {
switch (ejob.state) {
// case CLIENT_WAITING: return "cwaiting";
case WAITING:
return "waiting";
case RUNNING:
return getProgress() == null ? "running" : getProgress();
case SERVER_DONE:
return getProgress() == null ? "done" : getProgress();
case CLIENT_DONE:
return "cdone";
}
if (!started) return "waiting";
if (aborted) return "aborted";
if (finished) return "done";
if (scheduledToAbort) return "scheduled to abort";
if (getProgress() == null) return "running";
return getProgress();
}
/** Remove job from Job list if it is done */
public boolean remove() {
if (!finished && !aborted) {
//System.out.println("Cannot delete running jobs. Wait till finished or abort");
return false;
}
if (serverJobManager != null)
serverJobManager.removeJob(this);
else
currentUI.removeProcessingEJob(getKey());
return true;
}
private static final ThreadLocal<UserInterface> threadUserInterface = new ThreadLocal<UserInterface>() {
// @Override
// protected UserInterface initialValue() {
// throw new IllegalStateException();
// }
};
public static UserInterface getUserInterface() {
Thread currentThread = Thread.currentThread();
if (currentThread instanceof EThread)
return ((EThread)currentThread).getUserInterface();
else if (currentThread == clientThread)
return currentUI;
else
return threadUserInterface.get();
}
public static void setUserInterface(UserInterface ui) {
Thread currentThread = Thread.currentThread();
assert !(currentThread instanceof EThread) && currentThread != clientThread;
if (ui == null)
throw new UnsupportedOperationException();
threadUserInterface.set(ui);
}
/**
* Low-level method.
*/
public static AbstractUserInterface getExtendedUserInterface() {
// if (!isClientThread())
// ActivityLogger.logException(new IllegalStateException());
return currentUI;
}
public static boolean isClientThread() {
return Thread.currentThread() == clientThread;
}
public EDatabase getDatabase() {
return database;
}
public Environment getEnvironment() {
return database.getEnvironment();
}
public TechPool getTechPool() {
return database.getTechPool();
}
public Tool getTool() {
return tool;
}
public EditingPreferences getEditingPreferences() {
return ejob.editingPreferences;
}
public static void updateNetworkErrors(Cell cell, List<ErrorLogger.MessageLog> errors) {
if (currentUI != null)
currentUI.updateNetworkErrors(cell, errors);
}
public static void updateIncrementalDRCErrors(Cell cell, List<ErrorLogger.MessageLog> newErrors,
List<ErrorLogger.MessageLog> delErrors) {
currentUI.updateIncrementalDRCErrors(cell, newErrors, delErrors);
}
/**
* Find some valid snapshot in cache.
* @return some valid snapshot
*/
public static Snapshot findValidSnapshot() {
return EThread.findValidSnapshot();
}
//-------------------------------JOB UI--------------------------------
public String toString() { return ejob.jobName+" ("+getStatus()+")"; }
/**
* Print a message, dump a stack trace, and throw a RuntimeException if
* errorHasOccurred argument is true.
*
* @param errorHasOccurred indicates a runtime error has been detected
* @param msg the message to print when an error occurs
* @throws RuntimeException if errorHasOccurred is true
*/
public static void error(boolean errorHasOccurred, String msg) {
if (!errorHasOccurred) return;
RuntimeException e = new RuntimeException(msg);
// The following prints a stack trace on the console
ActivityLogger.logException(e);
// The following prints a stack trace in the Electric messages window
throw e;
}
/** Get info on Job */
public String getInfo() {
StringBuffer buf = new StringBuffer();
buf.append("Job "+toString());
//buf.append(" start time: "+start+"\n");
if (finished) {
// Date end = new Date(endTime);
//buf.append(" end time: "+end+"\n");
long time = endTime - startTime;
buf.append(" took: "+TextUtils.getElapsedTime(time));
Date start = new Date(startTime);
buf.append(" (started at "+start+")");
} else if (getProgress() == null) {
long time = System.currentTimeMillis()-startTime;
buf.append(" has not finished. Current running time: " + TextUtils.getElapsedTime(time));
} else {
buf.append(" did not successfully finish.");
}
return buf.toString();
}
// static void runTerminate(EJob ejob) {
// Throwable jobException = ejob.deserializeResult();
// Job job = ejob.clientJob;
// try {
// job.terminateIt(jobException);
// } catch (Throwable e) {
// System.out.println("Exception executing terminateIt");
// e.printStackTrace(System.out);
// }
// job.endTime = System.currentTimeMillis();
// job.finished = true; // is this redundant with Thread.isAlive()?
//
// // say something if it took more than a minute by default
// if (job.reportExecution || (job.endTime - job.startTime) >= MIN_NUM_SECONDS) {
// if (User.isBeepAfterLongJobs()) {
// Toolkit.getDefaultToolkit().beep();
// }
// System.out.println(job.getInfo());
// }
// }
public Key getKey() {
return ejob.jobKey;
}
public Inform getInform() {
return new Inform(this);
}
/**
* Identifies a Job in a given Electric client/server session.
* Job obtains its Key in startJob method.
* Also can identify Jobless context (for example Client's Gui)
*/
public static class Key implements Serializable {
/**
* Client which launched the Job
*/
public final int clientId;
/**
* Job id.
* 0 - Jobless context
* positive - Job started from server side
* negative - Job started from client side
*/
public final int jobId;
public final boolean doItOnServer;
Key(int clientId, int jobId, boolean doItOnServer) {
this.clientId = clientId;
this.jobId = jobId;
this.doItOnServer = doItOnServer;
}
Key(Client client, int jobId, boolean doItOnServer) {
this(client.connectionId, jobId, doItOnServer);
}
public boolean startedByServer() {
return jobId > 0;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof Key) {
Key that = (Key)o;
if (this.clientId == that.clientId && this.jobId == that.jobId) {
assert this.doItOnServer == that.doItOnServer;
return true;
}
}
return false;
}
@Override
public int hashCode() {
return jobId;
}
public void write(IdWriter writer) throws IOException {
writer.writeInt(clientId);
writer.writeInt(jobId);
writer.writeBoolean(doItOnServer);
}
public static Key read(IdReader reader) throws IOException {
int clientId = reader.readInt();
int jobId = reader.readInt();
boolean doItOnServer = reader.readBoolean();
return new Key(clientId, jobId, doItOnServer);
}
}
public static class Inform implements Serializable {
private final Key jobKey;
private final boolean isChange;
private final String toString;
private final long startTime;
private final long endTime;
private final int finished;
Inform(Job job) {
jobKey = job.getKey();
isChange = (job.ejob.jobType == Type.CHANGE) || (job.ejob.jobType == Type.UNDO);
toString = job.toString();
startTime = job.startTime;
endTime = job.endTime;
if (job.finished)
finished = 1;
else if (job.getProgress() == null)
finished = 0;
else
finished = -1;
}
Inform(Key jobKey, boolean isChange, String toString, long startTime, long endTime, int finished) {
this.jobKey = jobKey;
this.isChange = isChange;
this.toString = toString;
this.startTime = startTime;
this.endTime = endTime;
this.finished = finished;
}
public void abort() {
for (Iterator<Job> it = getAllJobs(); it.hasNext(); ) {
Job job = it.next();
if (job.getKey().equals(jobKey)) {
job.abort();
break;
}
}
}
public boolean remove() { return false; /*return job.remove();*/ }
public Key getKey() { return jobKey; }
@Override
public String toString() { return toString; }
public String getInfo() {
StringBuilder buf = new StringBuilder();
buf.append("Job "+toString());
//buf.append(" start time: "+start+"\n");
if (finished == 1) {
// Date end = new Date(endTime);
//buf.append(" end time: "+end+"\n");
long time = endTime - startTime;
buf.append(" took: "+TextUtils.getElapsedTime(time));
Date start = new Date(startTime);
buf.append(" (started at "+start+")");
} else if (finished == 0) {
long time = System.currentTimeMillis()-startTime;
buf.append(" has not finished. Current running time: " + TextUtils.getElapsedTime(time));
} else {
buf.append(" did not successfully finish.");
}
return buf.toString();
}
public boolean isChangeJobQueuedOrRunning() {
return finished != 1 && isChange;
}
public void write(IdWriter writer) throws IOException {
jobKey.write(writer);
writer.writeBoolean(isChange);
writer.writeString(toString);
writer.writeLong(startTime);
writer.writeLong(endTime);
writer.writeInt(finished);
}
public static Inform read(IdReader reader) throws IOException {
Key jobKey = Key.read(reader);
boolean isChange = reader.readBoolean();
String toString = reader.readString();
long startTime = reader.readLong();
long endTime = reader.readLong();
int finished = reader.readInt();
return new Inform(jobKey, isChange, toString, startTime, endTime, finished);
}
}
}