blob: 8176d9c448f061b2bd7a9df64d9c056480a3f587 [file] [log] [blame]
package org.eclipse.ui.internal.decorators;
/*******************************************************************************
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM - Initial implementation
******************************************************************************/
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.internal.WorkbenchPlugin;
/**
* The DecorationScheduler is the class that handles the
* decoration of elements using a background thread.
*/
public class DecorationScheduler implements IResourceChangeListener {
// When decorations are computed they are added to this cache via decorated() method
private Map resultCache = Collections.synchronizedMap(new HashMap());
// Objects that need an icon and text computed for display to the user
private List awaitingDecoration = new ArrayList();
// Objects that are awaiting a label update.
private List pendingUpdate = new ArrayList();
private Map awaitingDecorationValues = new HashMap();
private DecoratorManager decoratorManager;
private boolean shutdown = false;
private Thread decoratorUpdateThread;
//The number of results to batch before the label changed is sent
private final int NUM_TO_BATCH = 50;
/**
* Return a new instance of the receiver configured for
* the supplied DecoratorManager.
* @param manager
*/
DecorationScheduler(DecoratorManager manager) {
decoratorManager = manager;
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
/**
* 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.
*/
public String decorateWithText(
String text,
Object element,
Object adaptedElement) {
//We do not support decoration of null
if (element == null)
return text;
DecorationResult decoration =
(DecorationResult) resultCache.get(element);
if (decoration == null) {
queueForDecoration(element, adaptedElement);
return text;
} else
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.
*/
private synchronized void queueForDecoration(
Object element,
Object adaptedElement) {
//Lazily create the thread that calculates the decoration for a resource
if (decoratorUpdateThread == null) {
createDecoratorThread();
decoratorUpdateThread.start();
}
if (!awaitingDecorationValues.containsKey(element)) {
DecorationReference reference =
new DecorationReference(element, adaptedElement);
awaitingDecorationValues.put(element, reference);
awaitingDecoration.add(element);
//Notify the receiver as the next method is
//synchronized on the receiver.
notify();
}
}
/**
* 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.
*
*/
public Image decorateWithOverlays(
Image image,
Object element,
Object adaptedElement) {
//We do not support decoration of null
if (element == null)
return image;
DecorationResult decoration =
(DecorationResult) resultCache.get(element);
if (decoration == null) {
queueForDecoration(element, adaptedElement);
return image;
} else
return decoration.decorateWithOverlays(
image,
decoratorManager.getLightweightManager().getOverlayCache());
}
/**
* Execute a label update using the pending decorations.
* @param resources
* @param decorationResults
*/
public void decorated() {
//Don't bother if we are shutdown now
if (!shutdown) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (pendingUpdate.isEmpty())
return;
//Get the elements awaiting update and then
//clear the list
Object[] elements =
pendingUpdate.toArray(new Object[pendingUpdate.size()]);
pendingUpdate.clear();
decoratorManager.labelProviderChanged(
new LabelProviderChangedEvent(
decoratorManager,
elements));
resultCache.clear();
}
});
}
}
/**
* Shutdown the decoration thread.
*/
void shutdown() {
shutdown = true;
// Wake the thread up if it is asleep.
synchronized (this) {
notifyAll();
}
try {
if (decoratorUpdateThread != null)
// Wait for the decorator thread to finish before returning.
decoratorUpdateThread.join();
} catch (InterruptedException e) {
}
}
/**
* @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
final List changedObjects = new ArrayList();
delta.accept(new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta)
throws CoreException {
IResource resource = delta.getResource();
if (resource.getType() == IResource.ROOT) {
// continue with the delta
return true;
}
switch (delta.getKind()) {
case IResourceDelta.REMOVED :
// remove the cached decoration for any removed resource
resultCache.remove(resource);
break;
case IResourceDelta.CHANGED :
// for changed resources remove the result as it will need to
//be recalculated.
resultCache.remove(resource);
}
return true;
}
});
changedObjects.clear();
} catch (CoreException exception) {
WorkbenchPlugin.getDefault().getLog().log(
exception.getStatus());
}
}
}
/**
* Get the next resource to be decorated.
* @return IResource
*/
synchronized DecorationReference next() {
try {
if (shutdown)
return null;
if (awaitingDecoration.isEmpty()) {
wait();
}
// We were awakened.
if (shutdown) {
// The decorator was awakened by the plug-in as it was shutting down.
return null;
}
Object element = awaitingDecoration.remove(0);
return (DecorationReference) awaitingDecorationValues.remove(
element);
} catch (InterruptedException e) {
}
return null;
}
/**
* Create the Thread used for running decoration.
*/
private void createDecoratorThread() {
Runnable decorationRunnable = new Runnable() {
/* @see Runnable#run()
*/
public void run() {
while (true) {
// will block if there are no resources to be decorated
DecorationReference reference = next();
DecorationBuilder cacheResult = new DecorationBuilder();
// if next() returned null, we are done and should shut down.
if (reference == null) {
return;
}
//Don't decorate if there is already a pending result
if (!resultCache.containsKey(reference.getElement())) {
//Just build for the resource first
Object adapted = reference.getAdaptedElement();
if (adapted != null) {
decoratorManager
.getLightweightManager()
.getDecorations(
adapted,
cacheResult);
if (cacheResult.hasValue()) {
resultCache.put(
adapted,
cacheResult.createResult());
}
}
//Now add in the results for the main object
decoratorManager
.getLightweightManager()
.getDecorations(
reference.getElement(),
cacheResult);
if (cacheResult.hasValue()) {
resultCache.put(
reference.getElement(),
cacheResult.createResult());
//Add an update for only the original element to
//prevent multiple updates and clear the cache.
pendingUpdate.add(reference.getElement());
cacheResult.clearContents();
};
}
// notify that decoration is ready
if (awaitingDecoration.isEmpty()) {
decorated();
}
}
};
};
decoratorUpdateThread = new Thread(decorationRunnable, "Decoration");
decoratorUpdateThread.setPriority(Thread.MIN_PRIORITY);
}
}