private boolean isJobInFamilyRunning(Object family) {
   Job[] jobs = Job.getJobManager().find(family);
   if (jobs != null && jobs.length > 0) {
     for (int i = 0; i < jobs.length; i++) {
       Job job = jobs[i];
       if (job.getState() != Job.NONE) {
         return true;
       }
     }
   }
   return false;
 }
  private void initialize(final IRefreshSubscriberListener listener) {
    final GotoActionWrapper actionWrapper = new GotoActionWrapper();

    IProgressMonitor group = Job.getJobManager().createProgressGroup();
    group.beginTask(taskName, 100);
    setProgressGroup(group, 80);
    handleProgressGroupSet(group, 20);
    setProperty(IProgressConstants.ICON_PROPERTY, participant.getImageDescriptor());
    setProperty(IProgressConstants.ACTION_PROPERTY, actionWrapper);
    setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(!isJobModal()));
    // Listener delegate
    IRefreshSubscriberListener autoListener =
        new IRefreshSubscriberListener() {
          @Override
          public void refreshStarted(IRefreshEvent event) {
            if (listener != null) {
              listener.refreshStarted(event);
            }
          }

          @Override
          public ActionFactory.IWorkbenchAction refreshDone(IRefreshEvent event) {
            if (listener != null) {
              boolean isModal = isJobModal();
              event.setIsLink(!isModal);
              final ActionFactory.IWorkbenchAction runnable = listener.refreshDone(event);
              if (runnable != null) {
                // If the job is being run modally then simply prompt the user immediately
                if (isModal) {
                  if (runnable != null) {
                    Job update = new UIJob("") { // $NON-NLS-1$
                          @Override
                          public IStatus runInUIThread(IProgressMonitor monitor) {
                            runnable.run();
                            return Status.OK_STATUS;
                          }
                        };
                    update.setSystem(true);
                    update.schedule();
                  }
                } else {
                  // If the job is being run in the background, don't interrupt the user and simply
                  // update the goto action
                  // to perform the results.
                  actionWrapper.setGotoAction(runnable);
                }
              }
              RefreshParticipantJob.removeRefreshListener(this);
            }
            return null;
          }
        };

    if (listener != null) {
      RefreshParticipantJob.addRefreshListener(autoListener);
    }
  }
/**
 * Job to refresh a {@link Subscriber} in the background. The job can be configured to be
 * re-scheduled and run at a specified interval.
 *
 * <p>The job supports a basic work flow for modal/non-modal usage. If the job is run in the
 * foreground (e.g. in a modal progress dialog) the refresh listeners action is invoked immediately
 * after the refresh is completed. Otherwise the refresh listeners action is associated to the job
 * as a <i>goto</i> action. This will allow the user to select the action in the progress view and
 * run it when they choose.
 *
 * @since 3.0
 */
public abstract class RefreshParticipantJob extends Job {

  /** Uniquely identifies this type of job. This is used for cancellation. */
  private static final Object FAMILY_ID = new Object();

  /** If true this job will be restarted when it completes */
  private boolean reschedule = false;

  /** If true a rescheduled refresh job should be restarted when canceled */
  private boolean restartOnCancel = true;

  /** The schedule delay used when rescheduling a completed job */
  private static long scheduleDelay;

  /** The participant that is being refreshed. */
  private ISynchronizeParticipant participant;

  /** The task name for this refresh. This is usually more descriptive than the job name. */
  private String taskName;

  /** Refresh started/completed listener for every refresh */
  private static List listeners = new ArrayList(1);

  private static final int STARTED = 1;
  private static final int DONE = 2;

  /*
   * Lock used to sequence refresh jobs
   */
  private static final ILock lock = Job.getJobManager().newLock();

  /*
   * Constant used for postponement
   */
  private static final IStatus POSTPONED =
      new Status(
          IStatus.CANCEL,
          TeamUIPlugin.ID,
          0,
          "Scheduled refresh postponed due to conflicting operation",
          null); //$NON-NLS-1$

  /*
   * Action wrapper which allows the goto action
   * to be set later. It also handles errors
   * that have occurred during the refresh
   */
  private final class GotoActionWrapper extends WorkbenchAction {
    private ActionFactory.IWorkbenchAction gotoAction;
    private IStatus status;

    @Override
    public void run() {
      if (status != null && !status.isOK()) {
        ErrorDialog.openError(
            Utils.getShell(null), null, TeamUIMessages.RefreshSubscriberJob_3, status);
      } else if (gotoAction != null) {
        gotoAction.run();
      }
    }

    @Override
    public boolean isEnabled() {
      if (gotoAction != null) {
        return gotoAction.isEnabled();
      }
      return true;
    }

    @Override
    public String getText() {
      if (gotoAction != null) {
        return gotoAction.getText();
      }
      return null;
    }

    @Override
    public String getToolTipText() {
      if (status != null && !status.isOK()) {
        return status.getMessage();
      }
      if (gotoAction != null) {
        return gotoAction.getToolTipText();
      }
      return Utils.shortenText(
          SynchronizeView.MAX_NAME_LENGTH, RefreshParticipantJob.this.getName());
    }

    @Override
    public void dispose() {
      super.dispose();
      if (gotoAction != null) {
        gotoAction.dispose();
      }
    }

    public void setGotoAction(ActionFactory.IWorkbenchAction gotoAction) {
      this.gotoAction = gotoAction;
      setEnabled(isEnabled());
      setToolTipText(getToolTipText());
      gotoAction.addPropertyChangeListener(
          new IPropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent event) {
              if (event.getProperty().equals(IAction.ENABLED)) {
                Boolean bool = (Boolean) event.getNewValue();
                GotoActionWrapper.this.setEnabled(bool.booleanValue());
              }
            }
          });
    }

    public void setStatus(IStatus status) {
      this.status = status;
    }
  }

  /** Notification for safely notifying listeners of refresh lifecycle. */
  private abstract class Notification implements ISafeRunnable {
    private IRefreshSubscriberListener listener;

    @Override
    public void handleException(Throwable exception) {
      // don't log the exception....it is already being logged in Platform#run
    }

    public void run(IRefreshSubscriberListener listener) {
      this.listener = listener;
      SafeRunner.run(this);
    }

    @Override
    public void run() throws Exception {
      notify(listener);
    }
    /**
     * Subclasses override this method to send an event safely to a listener
     *
     * @param listener
     */
    protected abstract void notify(IRefreshSubscriberListener listener);
  }

  /** Monitor wrapper that will indicate that the job is canceled if the job is blocking another. */
  private class NonblockingProgressMonitor extends ProgressMonitorWrapper {
    private final RefreshParticipantJob job;
    private long blockTime;
    private static final int THRESHOLD = 250;
    private boolean wasBlocking = false;

    protected NonblockingProgressMonitor(IProgressMonitor monitor, RefreshParticipantJob job) {
      super(monitor);
      this.job = job;
    }

    @Override
    public boolean isCanceled() {
      if (super.isCanceled()) {
        return true;
      }
      if (job.shouldReschedule() && job.isBlocking()) {
        if (blockTime == 0) {
          blockTime = System.currentTimeMillis();
        } else if (System.currentTimeMillis() - blockTime > THRESHOLD) {
          // We've been blocking for too long
          wasBlocking = true;
          return true;
        }
      } else {
        blockTime = 0;
      }
      wasBlocking = false;
      return false;
    }

    public boolean wasBlocking() {
      return wasBlocking;
    }
  }

  public static interface IChangeDescription {
    int getChangeCount();
  }

  /**
   * Create a job to refresh the specified resources with the subscriber.
   *
   * @param participant the subscriber participant
   * @param jobName
   * @param taskName
   * @param listener
   */
  public RefreshParticipantJob(
      ISynchronizeParticipant participant,
      String jobName,
      String taskName,
      IRefreshSubscriberListener listener) {
    super(jobName);
    Assert.isNotNull(participant);
    this.participant = participant;
    this.taskName = taskName;
    setPriority(Job.DECORATE);
    setRefreshInterval(3600 /* 1 hour */);

    // Handle restarting of job if it is configured as a scheduled refresh job.
    addJobChangeListener(
        new JobChangeAdapter() {
          @Override
          public void done(IJobChangeEvent event) {
            if (shouldReschedule()) {
              IStatus result = event.getResult();
              if (result.getSeverity() == IStatus.CANCEL && !restartOnCancel) {
                return;
              }
              long delay = scheduleDelay;
              if (result == POSTPONED) {
                // Restart in 5 seconds
                delay = 5000;
              }
              RefreshParticipantJob.this.schedule(delay);
              restartOnCancel = true;
            }
          }
        });
    if (listener != null) initialize(listener);
  }

  @Override
  public boolean belongsTo(Object family) {
    if (family instanceof SubscriberParticipant) {
      return family == participant;
    } else {
      return (family == getFamily() || family == ISynchronizeManager.FAMILY_SYNCHRONIZE_OPERATION);
    }
  }

  public static Object getFamily() {
    return FAMILY_ID;
  }

  /**
   * This is run by the job scheduler. A list of subscribers will be refreshed, errors will not stop
   * the job and it will continue to refresh the other subscribers.
   */
  @Override
  public IStatus run(IProgressMonitor monitor) {
    // Perform a pre-check for auto-build or manual build jobs
    // when auto-refreshing
    if (shouldReschedule()
        && (isJobInFamilyRunning(ResourcesPlugin.FAMILY_AUTO_BUILD)
            || isJobInFamilyRunning(ResourcesPlugin.FAMILY_MANUAL_BUILD))) {
      return POSTPONED;
    }
    // Only allow one refresh job at a time
    // NOTE: It would be cleaner if this was done by a scheduling
    // rule but at the time of writing, it is not possible due to
    // the scheduling rule containment rules.
    // Acquiring lock to ensure only one refresh job is running at a particular time
    boolean acquired = false;
    try {
      while (!acquired) {
        try {
          acquired = lock.acquire(1000);
        } catch (InterruptedException e1) {
          acquired = false;
        }
        Policy.checkCanceled(monitor);
      }

      IChangeDescription changeDescription = createChangeDescription();
      RefreshEvent event =
          new RefreshEvent(
              reschedule ? IRefreshEvent.SCHEDULED_REFRESH : IRefreshEvent.USER_REFRESH,
              participant,
              changeDescription);
      IStatus status = null;
      NonblockingProgressMonitor wrappedMonitor = null;
      try {
        event.setStartTime(System.currentTimeMillis());
        if (monitor.isCanceled()) {
          return Status.CANCEL_STATUS;
        }
        // Pre-Notify
        notifyListeners(STARTED, event);
        // Perform the refresh
        monitor.setTaskName(getName());
        wrappedMonitor = new NonblockingProgressMonitor(monitor, this);
        doRefresh(changeDescription, wrappedMonitor);
        // Prepare the results
        setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(!isJobModal()));
      } catch (OperationCanceledException e2) {
        if (monitor.isCanceled()) {
          // The refresh was canceled by the user
          status = Status.CANCEL_STATUS;
        } else {
          // The refresh was canceled due to a blockage or a canceled authentication
          if (wrappedMonitor != null && wrappedMonitor.wasBlocking()) {
            status = POSTPONED;
          } else {
            status = Status.CANCEL_STATUS;
          }
        }
      } catch (CoreException e) {
        // Determine the status to be returned and the GOTO action
        status = e.getStatus();
        if (!isUser()) {
          // Use the GOTO action to show the error and return OK
          Object prop = getProperty(IProgressConstants.ACTION_PROPERTY);
          if (prop instanceof GotoActionWrapper) {
            GotoActionWrapper wrapper = (GotoActionWrapper) prop;
            wrapper.setStatus(e.getStatus());
            status =
                new Status(IStatus.OK, TeamUIPlugin.ID, IStatus.OK, e.getStatus().getMessage(), e);
          }
        }
        if (!isUser() && status.getSeverity() == IStatus.ERROR) {
          // Never prompt for errors on non-user jobs
          setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
        }
      } finally {
        event.setStopTime(System.currentTimeMillis());
      }

      // Post-Notify
      if (status == null) {
        status = calculateStatus(event);
      }
      event.setStatus(status);
      notifyListeners(DONE, event);
      if (event.getChangeDescription().getChangeCount() > 0) {
        if (participant instanceof AbstractSynchronizeParticipant) {
          AbstractSynchronizeParticipant asp = (AbstractSynchronizeParticipant) participant;
          asp.firePropertyChange(
              participant, ISynchronizeParticipant.P_CONTENT, null, event.getChangeDescription());
        }
      }
      return event.getStatus();
    } finally {
      if (acquired) lock.release();
      monitor.done();
    }
  }

  protected abstract void doRefresh(IChangeDescription changeListener, IProgressMonitor monitor)
      throws CoreException;

  /**
   * Return the total number of changes covered by the resources of this job.
   *
   * @return the total number of changes covered by the resources of this job
   */
  protected abstract int getChangeCount();

  protected abstract int getIncomingChangeCount();

  protected abstract int getOutgoingChangeCount();

  private boolean isJobInFamilyRunning(Object family) {
    Job[] jobs = Job.getJobManager().find(family);
    if (jobs != null && jobs.length > 0) {
      for (int i = 0; i < jobs.length; i++) {
        Job job = jobs[i];
        if (job.getState() != Job.NONE) {
          return true;
        }
      }
    }
    return false;
  }

  private IStatus calculateStatus(IRefreshEvent event) {
    StringBuffer text = new StringBuffer();
    int code = IStatus.OK;
    int changeCount = event.getChangeDescription().getChangeCount();
    int numChanges = getChangeCount();
    if (numChanges > 0) {
      code = IRefreshEvent.STATUS_CHANGES;

      int incomingChanges = getIncomingChangeCount();
      String numIncomingChanges =
          incomingChanges == 0
              ? "" //$NON-NLS-1$
              : NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_incomingChanges,
                  Integer.toString(incomingChanges));

      int outgoingChanges = getOutgoingChangeCount();
      String numOutgoingChanges =
          outgoingChanges == 0
              ? "" //$NON-NLS-1$
              : NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_outgoingChanges,
                  Integer.toString(outgoingChanges));

      String sep =
          incomingChanges > 0 && outgoingChanges > 0 ? "; " : ""; // $NON-NLS-1$ //$NON-NLS-2$

      if (changeCount > 0) {
        // New changes found
        code = IRefreshEvent.STATUS_NEW_CHANGES;
        String numNewChanges = Integer.toString(changeCount);
        if (changeCount == 1) {
          text.append(
              NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_newChangesSingular,
                  (new Object[] {
                    getName(), numNewChanges, numIncomingChanges, sep, numOutgoingChanges
                  })));
        } else {
          text.append(
              NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_newChangesPlural,
                  (new Object[] {
                    getName(), numNewChanges, numIncomingChanges, sep, numOutgoingChanges
                  })));
        }
      } else {
        // Refreshed resources contain changes
        if (numChanges == 1) {
          text.append(
              NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_changesSingular,
                  (new Object[] {
                    getName(), new Integer(numChanges), numIncomingChanges, sep, numOutgoingChanges
                  })));
        } else {
          text.append(
              NLS.bind(
                  TeamUIMessages.RefreshCompleteDialog_changesPlural,
                  (new Object[] {
                    getName(), new Integer(numChanges), numIncomingChanges, sep, numOutgoingChanges
                  })));
        }
      }
    } else {
      // No changes found
      code = IRefreshEvent.STATUS_NO_CHANGES;
      text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_6, new String[] {getName()}));
    }
    return new Status(IStatus.OK, TeamUIPlugin.ID, code, text.toString(), null);
  }

  private void initialize(final IRefreshSubscriberListener listener) {
    final GotoActionWrapper actionWrapper = new GotoActionWrapper();

    IProgressMonitor group = Job.getJobManager().createProgressGroup();
    group.beginTask(taskName, 100);
    setProgressGroup(group, 80);
    handleProgressGroupSet(group, 20);
    setProperty(IProgressConstants.ICON_PROPERTY, participant.getImageDescriptor());
    setProperty(IProgressConstants.ACTION_PROPERTY, actionWrapper);
    setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(!isJobModal()));
    // Listener delegate
    IRefreshSubscriberListener autoListener =
        new IRefreshSubscriberListener() {
          @Override
          public void refreshStarted(IRefreshEvent event) {
            if (listener != null) {
              listener.refreshStarted(event);
            }
          }

          @Override
          public ActionFactory.IWorkbenchAction refreshDone(IRefreshEvent event) {
            if (listener != null) {
              boolean isModal = isJobModal();
              event.setIsLink(!isModal);
              final ActionFactory.IWorkbenchAction runnable = listener.refreshDone(event);
              if (runnable != null) {
                // If the job is being run modally then simply prompt the user immediately
                if (isModal) {
                  if (runnable != null) {
                    Job update = new UIJob("") { // $NON-NLS-1$
                          @Override
                          public IStatus runInUIThread(IProgressMonitor monitor) {
                            runnable.run();
                            return Status.OK_STATUS;
                          }
                        };
                    update.setSystem(true);
                    update.schedule();
                  }
                } else {
                  // If the job is being run in the background, don't interrupt the user and simply
                  // update the goto action
                  // to perform the results.
                  actionWrapper.setGotoAction(runnable);
                }
              }
              RefreshParticipantJob.removeRefreshListener(this);
            }
            return null;
          }
        };

    if (listener != null) {
      RefreshParticipantJob.addRefreshListener(autoListener);
    }
  }

  /**
   * The progress group of this job has been set. Any subclasses should assign this group to any
   * additional jobs they use to collect changes from the refresh.
   *
   * @param group a progress group
   * @param ticks the ticks for the change collection job
   */
  protected abstract void handleProgressGroupSet(IProgressMonitor group, int ticks);

  protected abstract IChangeDescription createChangeDescription();

  public long getScheduleDelay() {
    return scheduleDelay;
  }

  protected void start() {
    if (getState() == Job.NONE) {
      if (shouldReschedule()) {
        schedule(getScheduleDelay());
      }
    }
  }

  /**
   * Specify the interval in seconds at which this job is scheduled.
   *
   * @param seconds delay specified in seconds
   */
  public void setRefreshInterval(long seconds) {
    boolean restart = false;
    if (getState() == Job.SLEEPING) {
      restart = true;
      cancel();
    }
    scheduleDelay = seconds * 1000;
    if (restart) {
      start();
    }
  }

  public void setRestartOnCancel(boolean restartOnCancel) {
    this.restartOnCancel = restartOnCancel;
  }

  public void setReschedule(boolean reschedule) {
    this.reschedule = reschedule;
  }

  public boolean shouldReschedule() {
    return reschedule;
  }

  public static void addRefreshListener(IRefreshSubscriberListener listener) {
    synchronized (listeners) {
      if (!listeners.contains(listener)) {
        listeners.add(listener);
      }
    }
  }

  public static void removeRefreshListener(IRefreshSubscriberListener listener) {
    synchronized (listeners) {
      listeners.remove(listener);
    }
  }

  protected void notifyListeners(final int state, final IRefreshEvent event) {
    // Get a snapshot of the listeners so the list doesn't change while we're firing
    IRefreshSubscriberListener[] listenerArray;
    synchronized (listeners) {
      listenerArray =
          (IRefreshSubscriberListener[])
              listeners.toArray(new IRefreshSubscriberListener[listeners.size()]);
    }
    // Notify each listener in a safe manner (i.e. so their exceptions don't kill us)
    for (int i = 0; i < listenerArray.length; i++) {
      IRefreshSubscriberListener listener = listenerArray[i];
      Notification notification =
          new Notification() {
            @Override
            protected void notify(IRefreshSubscriberListener listener) {
              switch (state) {
                case STARTED:
                  listener.refreshStarted(event);
                  break;
                case DONE:
                  listener.refreshDone(event);
                  break;
                default:
                  break;
              }
            }
          };
      notification.run(listener);
    }
  }

  private boolean isJobModal() {
    Boolean isModal = (Boolean) getProperty(IProgressConstants.PROPERTY_IN_DIALOG);
    if (isModal == null) return false;
    return isModal.booleanValue();
  }

  public ISynchronizeParticipant getParticipant() {
    return participant;
  }
}