Package org.rssowl.core.util

Source Code of org.rssowl.core.util.JobQueue

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2009 RSSOwl Development Team                                  **
**   http://www.rssowl.org/                                                 **
**                                                                          **
**   All rights reserved                                                    **
**                                                                          **
**   This program and the accompanying materials are made available under   **
**   the terms of the Eclipse Public License v1.0 which accompanies this    **
**   distribution, and is available at:                                     **
**   http://www.rssowl.org/legal/epl-v10.html                               **
**                                                                          **
**   A copy is found in the file epl-v10.html and important notices to the  **
**   license from the team is found in the textfile LICENSE.txt distributed **
**   in this package.                                                       **
**                                                                          **
**   This copyright notice MUST APPEAR in all copies of the file!           **
**                                                                          **
**   Contributors:                                                          **
**     RSSOwl Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.core.util;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.osgi.util.NLS;
import org.rssowl.core.internal.Activator;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
* This class allows to add <code>Runnables</code> into a Queue to process them
* in Jobs up to a certain amount of allowed parallel Jobs.
*
* @author bpasero
*/
public class JobQueue {

  /* Helper for the Progress Monitor */
  private static final double TOTAL_TASK_WORK_LOAD = 9900;
  private static final double TOTAL_PROGRESS_WORK_LOAD = 10000;

  /* Delay in ms for the Progress Job to update the Monitor */
  private static final int PROGRESS_UPDATE_DELAY = 300;

  /** This was copied from IProgressConstants to avoid UI dependancy */
  public static final QualifiedName NO_IMMEDIATE_ERROR_PROMPT_PROPERTY = new QualifiedName("org.eclipse.ui.workbench.progress", "delayErrorPrompt"); //$NON-NLS-1$ //$NON-NLS-2$

  private final Job fProgressJob;
  private final int fMaxConcurrentJobs;
  private final int fProgressDelay;
  private final String fName;
  private String fTaskPrefix;
  private final boolean fShowProgress;
  private boolean fIsUnknownProgress;
  private final ListenerList fListeners = new ListenerList();
  private boolean fIsSealed;

  /* These fields are accessed from N Jobs concurrently */
  private volatile boolean fProgressJobScheduled;
  private volatile String fCurrentTask = ""; //$NON-NLS-1$
  private final AtomicInteger fTotalWork = new AtomicInteger(0); // Number of Tasks in Total
  private final AtomicInteger fWorkDone = new AtomicInteger(0); // Number of finished Tasks
  private final AtomicInteger fProgressShown = new AtomicInteger(0); // Number of Progress Shown
  private final AtomicInteger fProgressBuf = new AtomicInteger(0); // Buffer for the Progress Monitor
  private final AtomicInteger fScheduledJobs = new AtomicInteger(0); // Count number of running Jobs
  private final BlockingQueue<ITask> fOpenTasksQueue;

  /**
   * Creates an instance of <code>JobQueue</code> that allows to add
   * <code>Runnables</code> into a Queue to process them in Jobs up to a certain
   * amount of allowed parallel Jobs.
   *
   * @param name A human-readable name that is displayed in the Progress-View
   * while the Queue is processed.
   * @param maxConcurrentJobs The maximum number of concurrent running Tasks.
   * @param maxQueueSize The maximum number of tasks that this queue will accept
   * before blocking.
   * @param showProgress If TRUE, show Progress of Jobs running from Queue.
   * @param progressDelay The time in milliseconds to wait before showing any
   * progress. This is useful in case the Tasks finish very quickly. Setting it
   * to 0 will show Progress instantly with no delay.
   */
  public JobQueue(String name, int maxConcurrentJobs, int maxQueueSize, boolean showProgress, int progressDelay) {
    this(name, name, maxConcurrentJobs, maxQueueSize, showProgress, progressDelay);
  }

  /**
   * Creates an instance of <code>JobQueue</code> that allows to add
   * <code>Runnables</code> into a Queue to process them in Jobs up to a certain
   * amount of allowed parallel Jobs.
   *
   * @param globalName A human-readable name that is displayed in the
   * Progress-View while the Queue is processed.
   * @param taskPrefix A human-readable prefix that is shown before the name of
   * a task that is currently processed.
   * @param maxConcurrentJobs The maximum number of concurrent running Tasks.
   * @param maxQueueSize The maximum number of tasks that this queue will accept
   * before blocking.
   * @param showProgress If TRUE, show Progress of Jobs running from Queue.
   * @param progressDelay The time in milliseconds to wait before showing any
   * progress. This is useful in case the Tasks finish very quickly. Setting it
   * to 0 will show Progress instantly with no delay.
   */
  public JobQueue(String globalName, String taskPrefix, int maxConcurrentJobs, int maxQueueSize, boolean showProgress, int progressDelay) {
    Assert.isNotNull(globalName);
    Assert.isNotNull(taskPrefix);
    Assert.isLegal(progressDelay >= 0, "JobQueue Progress delay is negative"); //$NON-NLS-1$
    fName = globalName;
    fTaskPrefix = taskPrefix;
    fMaxConcurrentJobs = maxConcurrentJobs;
    fShowProgress = showProgress;
    fProgressDelay = progressDelay;
    fOpenTasksQueue = new LinkedBlockingQueue<ITask>(maxQueueSize);

    /* Eagerly create the Progress-Job if we need one */
    if (showProgress)
      fProgressJob = createProgressJob();
    else
      fProgressJob = null;
  }

  /**
   * @param isUnknownProgress Sets the progress reporting of the progress Job
   * used for this {@link JobQueue} to {@link IProgressMonitor#UNKNOWN} if
   * <code>true</code>.
   */
  public void setUnknownProgress(boolean isUnknownProgress) {
    fIsUnknownProgress = isUnknownProgress;
  }

  /**
   * Cancels all Jobs that belong to this Queue. Optionally the caller may
   * decide to join the running Jobs that are not yet done. Note that this will
   * <em>block</em> the calling Thread until all running Tasks have finished so
   * this should only be considered for <em>short-running</em> Tasks.
   *
   * @param joinRunning If <code>TRUE</code>, join the running Jobs that are not
   * yet done.
   * @param seal if <code>true</code> this queue is sealed and no tasks will
   * ever be scheduled anymore.
   */
  public void cancel(boolean joinRunning, boolean seal) {
    synchronized (this) {

      /* Seal */
      if (seal)
        seal();

      /* Clear open tasks */
      fOpenTasksQueue.clear();

      /* Cancel scheduled Jobs */
      Job.getJobManager().cancel(this);

      /* Cancel Progress Job */
      if (fProgressJob != null)
        fProgressJob.cancel();
    }

    /* Join running Jobs if any */
    if (joinRunning) {
      while (Job.getJobManager().find(this).length != 0) {
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
          break;
        }
      }
    }
  }

  /**
   * Seals this queue so that no task can be added anymore.
   */
  public void seal() {
    fIsSealed= true;
  }

  /**
   * Determines whether the given Task is already queued in this Queue. That is,
   * the Task is scheduled and did not yet run to completion.
   *
   * @param task The Task to check for being queued in this Queue.
   * @return <code>TRUE</code> in case the given Task is already queued in this
   * Queue, meaning that it has been scheduled but did not yet complete
   * execution, <code>FALSE</code> otherwise.
   */
  public boolean isQueued(ITask task) {
    return fOpenTasksQueue.contains(task);
  }

  /**
   * Adds the given Task into the Queue waiting if necessary for space to become
   * available. The Task is processed in a <code>Job</code> once the number of
   * parallel processed Tasks is below <code>MAX_SCHEDULED_JOBS</code>.
   *
   * @param task The Task to add into this Queue.
   * @return {@code true} if all the tasks were scheduled or {@code false} if
   * some tasks were not scheduled because the current thread was interrupted.
   */
  public boolean schedule(ITask task) {
    return schedule(Collections.singletonList(task));
  }

  /**
   * Adds the given List of Tasks into the Queue waiting is necessary for space
   * to become available. Each Runnable is processed in a <code>Job</code> once
   * the number of parallel processed Tasks is below
   * <code>MAX_SCHEDULED_JOBS</code>.
   *
   * @param tasks The Tasks to add into this Queue.
   * @return {@code true} if all the tasks were scheduled or {@code false} if
   * some tasks were not scheduled because the current thread was interrupted or
   * this queue is sealed.
   */
  public boolean schedule(List<ITask> tasks) {
    final int tasksSize = tasks.size();

    /* Ignore empty lists */
    if (tasksSize == 0)
      return true;

    /* Return if Queue is Sealed */
    if (fIsSealed)
      return false;

    /* Add into List of open tasks */
    for (ITask task : tasks) {
      try {
        fOpenTasksQueue.put(task);

        /* Adjust Total Work Counter */
        fTotalWork.incrementAndGet();
      } catch (InterruptedException e) {
        return false;
      }
    }

    /* Schedule Job if not yet done */
    if (!fProgressJobScheduled && fShowProgress) {
      fProgressJobScheduled = true;
      fProgressJob.schedule(fProgressDelay);
    }

    /* Optimisation: We are able to release the calling thread without locking. */
    if (fScheduledJobs.get() >= fMaxConcurrentJobs)
      return true;

    /* Start a new Job for each free Slot */
    for (int i = 0; i < tasksSize && !fOpenTasksQueue.isEmpty(); ++i) {

      /* Never exceed max number of allowed concurrent Jobs */
      if (fScheduledJobs.incrementAndGet() > fMaxConcurrentJobs) {
        fScheduledJobs.decrementAndGet();
        break;
      }

      /* Create the Job */
      Job job = createJob();

      /* Listen to Job's Lifecycle */
      job.addJobChangeListener(new JobChangeAdapter() {

        /* Update Fields when a Job is Done */
        @Override
        public void done(IJobChangeEvent event) {

          /* Re-Schedule this Job if there is work left to do */
          if (!fOpenTasksQueue.isEmpty())
            event.getJob().schedule();
          else
            fScheduledJobs.decrementAndGet();
        }
      });

      /* Do not interrupt on any Error */
      job.setProperty(NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);

      /*
       * Workaround: Since we are using our own Job for displaying Progress, we
       * don't want these Jobs show up in the Progress View. There is currently
       * no bug-free solution of aggregating the Progress of N Jobs into a
       * single Monitor.
       */
      job.setSystem(true);

      /* Schedule it immediately */
      job.schedule();
    }
    return true;
  }

  /* Create a Job for a Task to handle */
  private Job createJob() {
    Job job = new Job("") { //$NON-NLS-1$
      @Override
      protected IStatus run(final IProgressMonitor monitor) {

        /* Poll the next Task */
        final ITask task = fOpenTasksQueue.poll();

        /* Queue is empty - so all work is done */
        if (task == null)
          return Status.OK_STATUS;

        /* Perform the Operation if not yet Cancelled */
        if (!monitor.isCanceled()) {
          SafeRunner.run(new LoggingSafeRunnable() {
            public void run() throws Exception {
              fCurrentTask = task.getName();
              IStatus status = task.run(monitor);

              /* Log anything that is an Error or Warning */
              if (status.getSeverity() == IStatus.ERROR || status.getSeverity() == IStatus.WARNING) {
                if (Activator.getDefault() != null)
                  Activator.getDefault().getLog().log(status);
              }
            }
          });

          /* Update Work Fields if not cancelled meanwhile */
          if (!monitor.isCanceled()) {
            fWorkDone.incrementAndGet();

            /* Calculate the Progress */
            int workDifference = fTotalWork.get() - fWorkDone.get();
            if (workDifference > 0 && fProgressJobScheduled) {
              int progress = (int) ((TOTAL_TASK_WORK_LOAD - fProgressShown.get()) / workDifference);
              fProgressShown.addAndGet(progress);
              fProgressBuf.addAndGet(progress);
            }
          }
        }

        /* Inform about cancelation if present */
        return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
      }

      @Override
      public boolean belongsTo(Object family) {
        return family == JobQueue.this;
      }
    };

    return job;
  }

  /**
   * Returns <code>TRUE</code> in case the JobQueue has finished all open Tasks.
   *
   * @return <code>TRUE</code> in case the JobQueue has finished all open Tasks,
   * <code>FALSE</code> otherwise.
   */
  public synchronized boolean isEmpty() {
    return internalIsEmpty();
  }

  private boolean internalIsEmpty() {
    return fTotalWork.get() - fWorkDone.get() == 0;
  }

  /**
   * @param listener The Listener to add to the List of Listeners.
   */
  public void addJobQueueListener(JobQueueListener listener) {
    fListeners.add(listener);
  }

  /**
   * @param listener The Listener to remove from the List of Listeners.
   */
  public void removeJobQueueListener(JobQueueListener listener) {
    fListeners.remove(listener);
  }

  /* Creates the Job for displaying Progress while Tasks are processed */
  private Job createProgressJob() {
    return new Job(fName) {
      private int fLastWorkDone = -1;
      private String fLastTask;

      @Override
      protected IStatus run(IProgressMonitor monitor) {
        boolean interrupted = false;

        /* Indicate Beginning if there is still work to do */
        if (!internalIsEmpty())
          monitor.beginTask(fName, fIsUnknownProgress ? IProgressMonitor.UNKNOWN : (int) TOTAL_PROGRESS_WORK_LOAD);

        /* Update Progress while not Cancelled and not Done */
        while (!monitor.isCanceled() && !internalIsEmpty()) {

          /* Update the Task Label if there was an Update */
          if (fCurrentTask != null && ((fLastWorkDone != fWorkDone.get()) || !StringUtils.isSet(fLastTask))) {
            fLastWorkDone = fWorkDone.get();
            fLastTask = fCurrentTask;
            monitor.setTaskName(formatTask());
          }

          /* Increment Monitor Progress */
          if (fProgressBuf.get() > 0) {
            monitor.worked(fProgressBuf.get());
            fProgressBuf.set(0);
          }

          try {
            Thread.sleep(PROGRESS_UPDATE_DELAY);
          } catch (InterruptedException e) {
            interrupted = true;
            break;
          }
        }

        /* Always call done() even if canceled */
        monitor.done();

        /* Task Processing has been canceled */
        if (monitor.isCanceled())
          Job.getJobManager().cancel(JobQueue.this);

        /* Be ready for the next Tasks */
        synchronized (JobQueue.this) {
          reset();
        }

        fLastWorkDone = -1;
        fLastTask = null;

        notifyWorkDone();

        /* Inform about cancellation if present */
        if (monitor.isCanceled() || interrupted)
          return Status.CANCEL_STATUS;

        return Status.OK_STATUS;
      }

      @Override
      public boolean belongsTo(Object family) {
        return family == JobQueue.this;
      }
    };
  }

  private void notifyWorkDone() {
    Object listeners[] = fListeners.getListeners();
    for (Object element : listeners) {
      final JobQueueListener listener = (JobQueueListener) element;
      SafeRunner.run(new LoggingSafeRunnable() {
        public void run() throws Exception {
          listener.workDone();
        }
      });
    }
  }

  private String formatTask() {
    int workDone = fWorkDone.get();
    Object[] bindings = Arrays.asList(fTaskPrefix, String.valueOf((workDone != 0 ? workDone : 1)), String.valueOf(fTotalWork.get()), fCurrentTask.replaceAll("&", "&&")).toArray(); //$NON-NLS-1$//$NON-NLS-2$

    String str = NLS.bind(Messages.JobQueue_TASK_NAME, bindings);
    return str;
  }

  /* Reset fields and cancel all Jobs of this Family */
  private void reset() {
    fScheduledJobs.set(0);
    fProgressJobScheduled = false;
    fWorkDone.set(0);
    fTotalWork.set(0);
    fProgressBuf.set(0);
    fProgressShown.set(0);
    fCurrentTask = ""; //$NON-NLS-1$
    fOpenTasksQueue.clear();
  }
}
TOP

Related Classes of org.rssowl.core.util.JobQueue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.