Package org.eclipse.ui.internal.decorators

Source Code of org.eclipse.ui.internal.decorators.DecorationScheduler

/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* 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.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.decorators;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.DecorationContext;
import org.eclipse.jface.viewers.IDecorationContext;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.progress.WorkbenchJob;

/**
* The DecorationScheduler is the class that handles the decoration of elements
* using a background thread.
*/
public class DecorationScheduler {

  static final ILabelProviderListener[] EMPTY_LISTENER_LIST = new ILabelProviderListener[0];

  // When decorations are computed they are added to this cache via
  // decorated() method
  Map resultCache = new HashMap();

  // Objects that need an icon and text computed for display to the user
  List awaitingDecoration = new ArrayList();

  // Objects that are awaiting a label update.
  Set pendingUpdate = new HashSet();

  // Key to lock write access to the pending update set
  Object pendingKey = new Object();

  Map awaitingDecorationValues = new HashMap();

  DecoratorManager decoratorManager;

  boolean shutdown = false;

  Job decorationJob;

  UIJob updateJob;

  private Collection removedListeners = Collections
      .synchronizedSet(new HashSet());

  private Job clearJob;

  // Static used for the updates to indicate an update is required
  static final int NEEDS_INIT = -1;

  /** Amount of time to delay the update notification when max reached. */
  static final int UPDATE_DELAY = 100;

  /**
   * Return a new instance of the receiver configured for the supplied
   * DecoratorManager.
   *
   * @param manager
   */
  DecorationScheduler(DecoratorManager manager) {
    decoratorManager = manager;
    createDecorationJob();
  }

  /**
   * Decorate the text for the receiver. If it has already been done then
   * return the result, otherwise queue it for decoration.
   *
   * @return String
   * @param text
   * @param element
   * @param adaptedElement
   *            The adapted value of element. May be null.
   * @param context
   *            the decoration context
   */

  public String decorateWithText(String text, Object element,
      Object adaptedElement, IDecorationContext context) {

    DecorationResult decoration = getResult(element, adaptedElement,
        context);

    if (decoration == null) {
      return text;
    }

    return decoration.decorateWithText(text);

  }

  /**
   * Queue the element and its adapted value if it has not been already.
   *
   * @param element
   * @param adaptedElement
   *            The adapted value of element. May be null.
   * @param forceUpdate
   *            If true then a labelProviderChanged is fired whether
   *            decoration occurred or not.
   * @param undecoratedText
   *            The original text for the element if it is known.
   * @param context
   *            The decoration context
   */

  synchronized void queueForDecoration(Object element, Object adaptedElement,
      boolean forceUpdate, String undecoratedText,
      IDecorationContext context) {

    DecorationReference reference = (DecorationReference) awaitingDecorationValues
        .get(element);
    if (reference != null) {
      if (forceUpdate) {// Make sure we don't loose a force
        reference.setForceUpdate(forceUpdate);
      }
      reference.addContext(context);
    } else {
      reference = new DecorationReference(element, adaptedElement,
          context);
      reference.setForceUpdate(forceUpdate);
      reference.setUndecoratedText(undecoratedText);
      awaitingDecorationValues.put(element, reference);
      awaitingDecoration.add(element);
      if (shutdown) {
        return;
      }
      if (decorationJob.getState() == Job.SLEEPING) {
        decorationJob.wakeUp();
      }
      decorationJob.schedule();
    }

  }

  /**
   * Decorate the supplied image, element and its adapted value.
   *
   * @return Image
   * @param image
   * @param element
   * @param adaptedElement
   *            The adapted value of element. May be null.
   * @param context
   *            the decoration context
   *
   */
  public Image decorateWithOverlays(Image image, Object element,
      Object adaptedElement, IDecorationContext context) {

    DecorationResult decoration = getResult(element, adaptedElement,
        context);

    if (decoration == null) {
      return image;
    }
    return decoration.decorateWithOverlays(image, decoratorManager
        .getLightweightManager().getOverlayCache());
  }

  /**
   * Return the DecorationResult for element. If there isn't one queue for
   * decoration and return <code>null</code>.
   *
   * @param element
   *            The element to be decorated. If it is <code>null</code>
   *            return <code>null</code>.
   * @param adaptedElement
   *            It's adapted value.
   * @param context
   *            The deocration context
   * @return DecorationResult or <code>null</code>
   */
  private DecorationResult getResult(Object element, Object adaptedElement,
      IDecorationContext context) {

    // We do not support decoration of null
    if (element == null) {
      return null;
    }

    DecorationResult decoration = internalGetResult(element, context);

    if (decoration == null) {
      queueForDecoration(element, adaptedElement, false, null, context);
      return null;
    }
    return decoration;

  }

  private DecorationResult internalGetResult(Object element,
      IDecorationContext context) {
    Map results = (Map) resultCache.get(context);
    if (results != null) {
      return (DecorationResult) results.get(element);
    }
    return null;
  }

  protected void internalPutResult(Object element,
      IDecorationContext context, DecorationResult result) {
    Map results = (Map) resultCache.get(context);
    if (results == null) {
      results = new HashMap();
      resultCache.put(context, results);
    }
    results.put(element, result);
  }

  /**
   * Execute a label update using the pending decorations.
   */
  synchronized void decorated() {

    // Don't bother if we are shutdown now
    if (shutdown) {
      return;
    }

    // Lazy initialize the job
    if (updateJob == null) {
      updateJob = getUpdateJob();
    }

    // Give it a bit of a lag for other updates to occur
    updateJob.schedule(UPDATE_DELAY);
  }

  /**
   * Shutdown the decoration.
   */
  synchronized void shutdown() {
    shutdown = true;
  }

  /**
   * Get the next resource to be decorated.
   *
   * @return IResource
   */
  synchronized DecorationReference nextElement() {

    if (shutdown || awaitingDecoration.isEmpty()) {
      return null;
    }
    Object element = awaitingDecoration.remove(0);

    return (DecorationReference) awaitingDecorationValues.remove(element);
  }

  /**
   * Create the Thread used for running decoration.
   */
  private void createDecorationJob() {
    decorationJob = new Job(
        WorkbenchMessages.DecorationScheduler_CalculationJobName) {
      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
       */
      public IStatus run(IProgressMonitor monitor) {

        synchronized (DecorationScheduler.this) {
          if (shutdown) {
            return Status.CANCEL_STATUS;
          }
        }

        while (updatesPending()) {

          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
            // Cancel and try again if there was an error
            schedule();
            return Status.CANCEL_STATUS;
          }
        }

        monitor.beginTask(
            WorkbenchMessages.DecorationScheduler_CalculatingTask,
            100);
        // will block if there are no resources to be decorated
        DecorationReference reference;
        monitor.worked(5);
        int workCount = 5;
        while ((reference = nextElement()) != null) {

          // Count up to 90 to give the appearance of updating
          if (workCount < 90) {
            monitor.worked(1);
            workCount++;
          }

          monitor.subTask(reference.getSubTask());
          Object element = reference.getElement();
          boolean force = reference.shouldForceUpdate();
          IDecorationContext[] contexts = reference.getContexts();
          for (int i = 0; i < contexts.length; i++) {
            IDecorationContext context = contexts[i];
            ensureResultCached(element, force, context);
          }

          // Only notify listeners when we have exhausted the
          // queue of decoration requests.
          synchronized (DecorationScheduler.this) {
            if (awaitingDecoration.isEmpty()) {
              decorated();
            }
          }
        }
        monitor.worked(100 - workCount);
        monitor.done();
        return Status.OK_STATUS;
      }

      /**
       * Ensure that a result is cached for the given element and context
       *
       * @param element
       *            the elements
       * @param force
       *            whether an update should be forced
       * @param context
       *            the decoration context
       */
      private void ensureResultCached(Object element, boolean force,
          IDecorationContext context) {
        boolean elementIsCached = internalGetResult(element, context) != null;
        if (elementIsCached) {
          synchronized (pendingKey) {
            pendingUpdate.add(element);
          }

        }

        if (!elementIsCached) {
          DecorationBuilder cacheResult = new DecorationBuilder(
              context);
          // Calculate the decoration
          decoratorManager.getLightweightManager().getDecorations(
              element, cacheResult);

          // If we should update regardless then put a result
          // anyways
          if (cacheResult.hasValue() || force) {

            // Synchronize on the result lock as we want to
            // be sure that we do not try and decorate during
            // label update servicing.
            // Note: resultCache and pendingUpdate modifications
            // must be done atomically.

            // Add the decoration even if it's empty in
            // order to indicate that the decoration is
            // ready
            internalPutResult(element, context, cacheResult
                .createResult());

            // Add an update for only the original element
            // to
            // prevent multiple updates and clear the cache.
            pendingUpdate.add(element);

          }
        }
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
       */
      public boolean belongsTo(Object family) {
        return DecoratorManager.FAMILY_DECORATE == family;
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
       */
      public boolean shouldRun() {
        return PlatformUI.isWorkbenchRunning();
      }
    };

    decorationJob.setSystem(true);
    decorationJob.setPriority(Job.DECORATE);
    decorationJob.schedule();
  }

  /**
   * Return whether or not we are waiting on updated
   *
   * @return <code>true</code> if there are updates waiting to be served
   */
  protected boolean updatesPending() {
    if (updateJob != null && updateJob.getState() != Job.NONE) {
      return true;
    }
    if (clearJob != null && clearJob.getState() != Job.NONE) {
      return true;
    }
    return false;
  }

  /**
   * An external update request has been made. Clear the results as they are
   * likely obsolete now.
   */
  void clearResults() {
    if (clearJob == null) {
      clearJob = getClearJob();
    }
    clearJob.schedule();
  }

  private Job getClearJob() {
    Job clear = new Job(
        WorkbenchMessages.DecorationScheduler_ClearResultsJob) {

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
       */
      protected IStatus run(IProgressMonitor monitor) {
        resultCache.clear();
        return Status.OK_STATUS;
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
       */
      public boolean shouldRun() {
        return PlatformUI.isWorkbenchRunning();
      }

    };
    clear.setSystem(true);

    return clear;
  }

  /**
   * Get the update WorkbenchJob.
   *
   * @return WorkbenchJob
   */
  private WorkbenchJob getUpdateJob() {
    WorkbenchJob job = new WorkbenchJob(
        WorkbenchMessages.DecorationScheduler_UpdateJobName) {

      int currentIndex = NEEDS_INIT;

      LabelProviderChangedEvent labelProviderChangedEvent;

      ILabelProviderListener[] listeners;

      public IStatus runInUIThread(IProgressMonitor monitor) {

        synchronized (DecorationScheduler.this) {
          if (shutdown) {
            return Status.CANCEL_STATUS;
          }
        }

        // If this is the first one check again in case
        // someone has already cleared it out.
        if (currentIndex == NEEDS_INIT) {
          if (hasPendingUpdates()) {
            // If the removal came in while we were waiting clear it
            // anyways
            removedListeners.clear();
            return Status.OK_STATUS;
          }
          setUpUpdates();
        }

        if (listeners.length == 0) {
          return Status.OK_STATUS;
        }

        monitor.beginTask(
            WorkbenchMessages.DecorationScheduler_UpdatingTask,
            IProgressMonitor.UNKNOWN);

        long startTime = System.currentTimeMillis();
        while (currentIndex < listeners.length) {
          ILabelProviderListener listener = listeners[currentIndex];
          currentIndex++;

          // If it was removed in the meantime then skip it.
          if (!removedListeners.contains(listener)) {
            decoratorManager.fireListener(
                labelProviderChangedEvent, listener);
          }

          // If it is taking long enough for the user to notice then
          // cancel the
          // updates.
          if ((System.currentTimeMillis() - startTime) >= UPDATE_DELAY / 2) {
            break;
          }
        }

        monitor.done();

        if (currentIndex >= listeners.length) {
          // Other decoration requests may have occurred due to
          // updates or we may have timed out updating listeners.
          // Only clear the results if there are none pending.
          if (awaitingDecoration.isEmpty()) {
            resultCache.clear();
          }

          if (!hasPendingUpdates()) {
            decorated();
          }
          currentIndex = NEEDS_INIT;// Reset
          labelProviderChangedEvent = null;
          removedListeners.clear();
          listeners = EMPTY_LISTENER_LIST;
        } else {
          schedule(UPDATE_DELAY);// Reschedule if we are not done
        }
        return Status.OK_STATUS;
      }

      private void setUpUpdates() {
        // Get the elements awaiting update and then
        // clear the list
        removedListeners.clear();
        currentIndex = 0;
        synchronized (pendingKey) {
          Object[] elements = pendingUpdate
              .toArray(new Object[pendingUpdate.size()]);
          pendingUpdate.clear();
          labelProviderChangedEvent = new LabelProviderChangedEvent(
              decoratorManager, elements);
        }
        listeners = decoratorManager.getListeners();
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
       */
      public boolean belongsTo(Object family) {
        return DecoratorManager.FAMILY_DECORATE == family;
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
       */
      public boolean shouldRun() {
        return PlatformUI.isWorkbenchRunning();
      }
    };

    job.setSystem(true);
    return job;
  }

  /**
   * Return whether or not there is a decoration for this element ready.
   *
   * @param element
   * @param context
   *            The decoration context
   * @return boolean true if the element is ready.
   */
  public boolean isDecorationReady(Object element, IDecorationContext context) {
    return internalGetResult(element, context) != null;
  }

  /**
   * Return the background Color for element. If there is no result cue for
   * decoration and return null, otherwise return the value in the result.
   *
   * @param element
   *            The Object to be decorated
   * @param adaptedElement
   * @return Color or <code>null</code> if there is no value or if it is has
   *         not been decorated yet.
   */
  public Color getBackgroundColor(Object element, Object adaptedElement) {
    DecorationResult decoration = getResult(element, adaptedElement,
        DecorationContext.DEFAULT_CONTEXT);

    if (decoration == null) {
      return null;
    }
    return decoration.getBackgroundColor();
  }

  /**
   * Return the font for element. If there is no result cue for decoration and
   * return null, otherwise return the value in the result.
   *
   * @param element
   *            The Object to be decorated
   * @param adaptedElement
   * @return Font or <code>null</code> if there is no value or if it is has
   *         not been decorated yet.
   */
  public Font getFont(Object element, Object adaptedElement) {
    DecorationResult decoration = getResult(element, adaptedElement,
        DecorationContext.DEFAULT_CONTEXT);

    if (decoration == null) {
      return null;
    }
    return decoration.getFont();
  }

  /**
   * Return the foreground Color for element. If there is no result cue for
   * decoration and return null, otherwise return the value in the result.
   *
   * @param element
   *            The Object to be decorated
   * @param adaptedElement
   * @return Color or <code>null</code> if there is no value or if it is has
   *         not been decorated yet.
   */
  public Color getForegroundColor(Object element, Object adaptedElement) {
    DecorationResult decoration = getResult(element, adaptedElement,
        DecorationContext.DEFAULT_CONTEXT);

    if (decoration == null) {
      return null;
    }
    return decoration.getForegroundColor();
  }

  /**
   * Return whether or not any updates are being processed/
   *
   * @return boolean
   */
  public boolean processingUpdates() {
    return !hasPendingUpdates() && !awaitingDecoration.isEmpty();
  }

  /**
   * A listener has been removed. If we are updating then skip it.
   *
   * @param listener
   */
  void listenerRemoved(ILabelProviderListener listener) {
    if (updatesPending()) {// Only keep track of them if there are updates
      // pending
      removedListeners.add(listener);
    }
    if (!updatesPending()) {
      removedListeners.remove(listener);
    }

  }

  /**
   * Return whether or not there are any updates pending.
   *
   * @return
   */
  boolean hasPendingUpdates() {
    synchronized (pendingKey) {
      return pendingUpdate.isEmpty();
    }

  }
}
TOP

Related Classes of org.eclipse.ui.internal.decorators.DecorationScheduler

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.