001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.decorators;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.HashSet;
017: import java.util.List;
018: import java.util.Map;
019: import java.util.Set;
020:
021: import org.eclipse.core.runtime.IProgressMonitor;
022: import org.eclipse.core.runtime.IStatus;
023: import org.eclipse.core.runtime.Status;
024: import org.eclipse.core.runtime.jobs.Job;
025: import org.eclipse.jface.viewers.DecorationContext;
026: import org.eclipse.jface.viewers.IDecorationContext;
027: import org.eclipse.jface.viewers.ILabelProviderListener;
028: import org.eclipse.jface.viewers.LabelProviderChangedEvent;
029: import org.eclipse.swt.graphics.Color;
030: import org.eclipse.swt.graphics.Font;
031: import org.eclipse.swt.graphics.Image;
032: import org.eclipse.ui.PlatformUI;
033: import org.eclipse.ui.internal.WorkbenchMessages;
034: import org.eclipse.ui.progress.UIJob;
035: import org.eclipse.ui.progress.WorkbenchJob;
036:
037: /**
038: * The DecorationScheduler is the class that handles the decoration of elements
039: * using a background thread.
040: */
041: public class DecorationScheduler {
042:
043: static final ILabelProviderListener[] EMPTY_LISTENER_LIST = new ILabelProviderListener[0];
044:
045: // When decorations are computed they are added to this cache via
046: // decorated() method
047: Map resultCache = new HashMap();
048:
049: // Objects that need an icon and text computed for display to the user
050: List awaitingDecoration = new ArrayList();
051:
052: // Objects that are awaiting a label update.
053: Set pendingUpdate = new HashSet();
054:
055: // Key to lock write access to the pending update set
056: Object pendingKey = new Object();
057:
058: Map awaitingDecorationValues = new HashMap();
059:
060: DecoratorManager decoratorManager;
061:
062: boolean shutdown = false;
063:
064: Job decorationJob;
065:
066: UIJob updateJob;
067:
068: private Collection removedListeners = Collections
069: .synchronizedSet(new HashSet());
070:
071: private Job clearJob;
072:
073: // Static used for the updates to indicate an update is required
074: static final int NEEDS_INIT = -1;
075:
076: /** Amount of time to delay the update notification when max reached. */
077: static final int UPDATE_DELAY = 100;
078:
079: /**
080: * Return a new instance of the receiver configured for the supplied
081: * DecoratorManager.
082: *
083: * @param manager
084: */
085: DecorationScheduler(DecoratorManager manager) {
086: decoratorManager = manager;
087: createDecorationJob();
088: }
089:
090: /**
091: * Decorate the text for the receiver. If it has already been done then
092: * return the result, otherwise queue it for decoration.
093: *
094: * @return String
095: * @param text
096: * @param element
097: * @param adaptedElement
098: * The adapted value of element. May be null.
099: * @param context
100: * the decoration context
101: */
102:
103: public String decorateWithText(String text, Object element,
104: Object adaptedElement, IDecorationContext context) {
105:
106: DecorationResult decoration = getResult(element,
107: adaptedElement, context);
108:
109: if (decoration == null) {
110: return text;
111: }
112:
113: return decoration.decorateWithText(text);
114:
115: }
116:
117: /**
118: * Queue the element and its adapted value if it has not been already.
119: *
120: * @param element
121: * @param adaptedElement
122: * The adapted value of element. May be null.
123: * @param forceUpdate
124: * If true then a labelProviderChanged is fired whether
125: * decoration occurred or not.
126: * @param undecoratedText
127: * The original text for the element if it is known.
128: * @param context
129: * The decoration context
130: */
131:
132: synchronized void queueForDecoration(Object element,
133: Object adaptedElement, boolean forceUpdate,
134: String undecoratedText, IDecorationContext context) {
135:
136: DecorationReference reference = (DecorationReference) awaitingDecorationValues
137: .get(element);
138: if (reference != null) {
139: if (forceUpdate) {// Make sure we don't loose a force
140: reference.setForceUpdate(forceUpdate);
141: }
142: reference.addContext(context);
143: } else {
144: reference = new DecorationReference(element,
145: adaptedElement, context);
146: reference.setForceUpdate(forceUpdate);
147: reference.setUndecoratedText(undecoratedText);
148: awaitingDecorationValues.put(element, reference);
149: awaitingDecoration.add(element);
150: if (shutdown) {
151: return;
152: }
153: if (decorationJob.getState() == Job.SLEEPING) {
154: decorationJob.wakeUp();
155: }
156: decorationJob.schedule();
157: }
158:
159: }
160:
161: /**
162: * Decorate the supplied image, element and its adapted value.
163: *
164: * @return Image
165: * @param image
166: * @param element
167: * @param adaptedElement
168: * The adapted value of element. May be null.
169: * @param context
170: * the decoration context
171: *
172: */
173: public Image decorateWithOverlays(Image image, Object element,
174: Object adaptedElement, IDecorationContext context) {
175:
176: DecorationResult decoration = getResult(element,
177: adaptedElement, context);
178:
179: if (decoration == null) {
180: return image;
181: }
182: return decoration.decorateWithOverlays(image, decoratorManager
183: .getLightweightManager().getOverlayCache());
184: }
185:
186: /**
187: * Return the DecorationResult for element. If there isn't one queue for
188: * decoration and return <code>null</code>.
189: *
190: * @param element
191: * The element to be decorated. If it is <code>null</code>
192: * return <code>null</code>.
193: * @param adaptedElement
194: * It's adapted value.
195: * @param context
196: * The deocration context
197: * @return DecorationResult or <code>null</code>
198: */
199: private DecorationResult getResult(Object element,
200: Object adaptedElement, IDecorationContext context) {
201:
202: // We do not support decoration of null
203: if (element == null) {
204: return null;
205: }
206:
207: DecorationResult decoration = internalGetResult(element,
208: context);
209:
210: if (decoration == null) {
211: queueForDecoration(element, adaptedElement, false, null,
212: context);
213: return null;
214: }
215: return decoration;
216:
217: }
218:
219: private DecorationResult internalGetResult(Object element,
220: IDecorationContext context) {
221: Map results = (Map) resultCache.get(context);
222: if (results != null) {
223: return (DecorationResult) results.get(element);
224: }
225: return null;
226: }
227:
228: protected void internalPutResult(Object element,
229: IDecorationContext context, DecorationResult result) {
230: Map results = (Map) resultCache.get(context);
231: if (results == null) {
232: results = new HashMap();
233: resultCache.put(context, results);
234: }
235: results.put(element, result);
236: }
237:
238: /**
239: * Execute a label update using the pending decorations.
240: */
241: synchronized void decorated() {
242:
243: // Don't bother if we are shutdown now
244: if (shutdown) {
245: return;
246: }
247:
248: // Lazy initialize the job
249: if (updateJob == null) {
250: updateJob = getUpdateJob();
251: }
252:
253: // Give it a bit of a lag for other updates to occur
254: updateJob.schedule(UPDATE_DELAY);
255: }
256:
257: /**
258: * Shutdown the decoration.
259: */
260: synchronized void shutdown() {
261: shutdown = true;
262: }
263:
264: /**
265: * Get the next resource to be decorated.
266: *
267: * @return IResource
268: */
269: synchronized DecorationReference nextElement() {
270:
271: if (shutdown || awaitingDecoration.isEmpty()) {
272: return null;
273: }
274: Object element = awaitingDecoration.remove(0);
275:
276: return (DecorationReference) awaitingDecorationValues
277: .remove(element);
278: }
279:
280: /**
281: * Create the Thread used for running decoration.
282: */
283: private void createDecorationJob() {
284: decorationJob = new Job(
285: WorkbenchMessages.DecorationScheduler_CalculationJobName) {
286: /*
287: * (non-Javadoc)
288: *
289: * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
290: */
291: public IStatus run(IProgressMonitor monitor) {
292:
293: synchronized (DecorationScheduler.this ) {
294: if (shutdown) {
295: return Status.CANCEL_STATUS;
296: }
297: }
298:
299: while (updatesPending()) {
300:
301: try {
302: Thread.sleep(100);
303: } catch (InterruptedException e) {
304: // Cancel and try again if there was an error
305: schedule();
306: return Status.CANCEL_STATUS;
307: }
308: }
309:
310: monitor
311: .beginTask(
312: WorkbenchMessages.DecorationScheduler_CalculatingTask,
313: 100);
314: // will block if there are no resources to be decorated
315: DecorationReference reference;
316: monitor.worked(5);
317: int workCount = 5;
318: while ((reference = nextElement()) != null) {
319:
320: // Count up to 90 to give the appearance of updating
321: if (workCount < 90) {
322: monitor.worked(1);
323: workCount++;
324: }
325:
326: monitor.subTask(reference.getSubTask());
327: Object element = reference.getElement();
328: boolean force = reference.shouldForceUpdate();
329: IDecorationContext[] contexts = reference
330: .getContexts();
331: for (int i = 0; i < contexts.length; i++) {
332: IDecorationContext context = contexts[i];
333: ensureResultCached(element, force, context);
334: }
335:
336: // Only notify listeners when we have exhausted the
337: // queue of decoration requests.
338: synchronized (DecorationScheduler.this ) {
339: if (awaitingDecoration.isEmpty()) {
340: decorated();
341: }
342: }
343: }
344: monitor.worked(100 - workCount);
345: monitor.done();
346: return Status.OK_STATUS;
347: }
348:
349: /**
350: * Ensure that a result is cached for the given element and context
351: *
352: * @param element
353: * the elements
354: * @param force
355: * whether an update should be forced
356: * @param context
357: * the decoration context
358: */
359: private void ensureResultCached(Object element,
360: boolean force, IDecorationContext context) {
361: boolean elementIsCached = internalGetResult(element,
362: context) != null;
363: if (elementIsCached) {
364: synchronized (pendingKey) {
365: pendingUpdate.add(element);
366: }
367:
368: }
369:
370: if (!elementIsCached) {
371: DecorationBuilder cacheResult = new DecorationBuilder(
372: context);
373: // Calculate the decoration
374: decoratorManager.getLightweightManager()
375: .getDecorations(element, cacheResult);
376:
377: // If we should update regardless then put a result
378: // anyways
379: if (cacheResult.hasValue() || force) {
380:
381: // Synchronize on the result lock as we want to
382: // be sure that we do not try and decorate during
383: // label update servicing.
384: // Note: resultCache and pendingUpdate modifications
385: // must be done atomically.
386:
387: // Add the decoration even if it's empty in
388: // order to indicate that the decoration is
389: // ready
390: internalPutResult(element, context, cacheResult
391: .createResult());
392:
393: // Add an update for only the original element
394: // to
395: // prevent multiple updates and clear the cache.
396: pendingUpdate.add(element);
397:
398: }
399: }
400: }
401:
402: /*
403: * (non-Javadoc)
404: *
405: * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
406: */
407: public boolean belongsTo(Object family) {
408: return DecoratorManager.FAMILY_DECORATE == family;
409: }
410:
411: /*
412: * (non-Javadoc)
413: *
414: * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
415: */
416: public boolean shouldRun() {
417: return PlatformUI.isWorkbenchRunning();
418: }
419: };
420:
421: decorationJob.setSystem(true);
422: decorationJob.setPriority(Job.DECORATE);
423: decorationJob.schedule();
424: }
425:
426: /**
427: * Return whether or not we are waiting on updated
428: *
429: * @return <code>true</code> if there are updates waiting to be served
430: */
431: protected boolean updatesPending() {
432: if (updateJob != null && updateJob.getState() != Job.NONE) {
433: return true;
434: }
435: if (clearJob != null && clearJob.getState() != Job.NONE) {
436: return true;
437: }
438: return false;
439: }
440:
441: /**
442: * An external update request has been made. Clear the results as they are
443: * likely obsolete now.
444: */
445: void clearResults() {
446: if (clearJob == null) {
447: clearJob = getClearJob();
448: }
449: clearJob.schedule();
450: }
451:
452: private Job getClearJob() {
453: Job clear = new Job(
454: WorkbenchMessages.DecorationScheduler_ClearResultsJob) {
455:
456: /*
457: * (non-Javadoc)
458: *
459: * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
460: */
461: protected IStatus run(IProgressMonitor monitor) {
462: resultCache.clear();
463: return Status.OK_STATUS;
464: }
465:
466: /*
467: * (non-Javadoc)
468: *
469: * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
470: */
471: public boolean shouldRun() {
472: return PlatformUI.isWorkbenchRunning();
473: }
474:
475: };
476: clear.setSystem(true);
477:
478: return clear;
479: }
480:
481: /**
482: * Get the update WorkbenchJob.
483: *
484: * @return WorkbenchJob
485: */
486: private WorkbenchJob getUpdateJob() {
487: WorkbenchJob job = new WorkbenchJob(
488: WorkbenchMessages.DecorationScheduler_UpdateJobName) {
489:
490: int currentIndex = NEEDS_INIT;
491:
492: LabelProviderChangedEvent labelProviderChangedEvent;
493:
494: ILabelProviderListener[] listeners;
495:
496: public IStatus runInUIThread(IProgressMonitor monitor) {
497:
498: synchronized (DecorationScheduler.this ) {
499: if (shutdown) {
500: return Status.CANCEL_STATUS;
501: }
502: }
503:
504: // If this is the first one check again in case
505: // someone has already cleared it out.
506: if (currentIndex == NEEDS_INIT) {
507: if (hasPendingUpdates()) {
508: // If the removal came in while we were waiting clear it
509: // anyways
510: removedListeners.clear();
511: return Status.OK_STATUS;
512: }
513: setUpUpdates();
514: }
515:
516: if (listeners.length == 0) {
517: return Status.OK_STATUS;
518: }
519:
520: monitor
521: .beginTask(
522: WorkbenchMessages.DecorationScheduler_UpdatingTask,
523: IProgressMonitor.UNKNOWN);
524:
525: long startTime = System.currentTimeMillis();
526: while (currentIndex < listeners.length) {
527: ILabelProviderListener listener = listeners[currentIndex];
528: currentIndex++;
529:
530: // If it was removed in the meantime then skip it.
531: if (!removedListeners.contains(listener)) {
532: decoratorManager.fireListener(
533: labelProviderChangedEvent, listener);
534: }
535:
536: // If it is taking long enough for the user to notice then
537: // cancel the
538: // updates.
539: if ((System.currentTimeMillis() - startTime) >= UPDATE_DELAY / 2) {
540: break;
541: }
542: }
543:
544: monitor.done();
545:
546: if (currentIndex >= listeners.length) {
547: // Other decoration requests may have occurred due to
548: // updates or we may have timed out updating listeners.
549: // Only clear the results if there are none pending.
550: if (awaitingDecoration.isEmpty()) {
551: resultCache.clear();
552: }
553:
554: if (!hasPendingUpdates()) {
555: decorated();
556: }
557: currentIndex = NEEDS_INIT;// Reset
558: labelProviderChangedEvent = null;
559: removedListeners.clear();
560: listeners = EMPTY_LISTENER_LIST;
561: } else {
562: schedule(UPDATE_DELAY);// Reschedule if we are not done
563: }
564: return Status.OK_STATUS;
565: }
566:
567: private void setUpUpdates() {
568: // Get the elements awaiting update and then
569: // clear the list
570: removedListeners.clear();
571: currentIndex = 0;
572: synchronized (pendingKey) {
573: Object[] elements = pendingUpdate
574: .toArray(new Object[pendingUpdate.size()]);
575: pendingUpdate.clear();
576: labelProviderChangedEvent = new LabelProviderChangedEvent(
577: decoratorManager, elements);
578: }
579: listeners = decoratorManager.getListeners();
580: }
581:
582: /*
583: * (non-Javadoc)
584: *
585: * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
586: */
587: public boolean belongsTo(Object family) {
588: return DecoratorManager.FAMILY_DECORATE == family;
589: }
590:
591: /*
592: * (non-Javadoc)
593: *
594: * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
595: */
596: public boolean shouldRun() {
597: return PlatformUI.isWorkbenchRunning();
598: }
599: };
600:
601: job.setSystem(true);
602: return job;
603: }
604:
605: /**
606: * Return whether or not there is a decoration for this element ready.
607: *
608: * @param element
609: * @param context
610: * The decoration context
611: * @return boolean true if the element is ready.
612: */
613: public boolean isDecorationReady(Object element,
614: IDecorationContext context) {
615: return internalGetResult(element, context) != null;
616: }
617:
618: /**
619: * Return the background Color for element. If there is no result cue for
620: * decoration and return null, otherwise return the value in the result.
621: *
622: * @param element
623: * The Object to be decorated
624: * @param adaptedElement
625: * @return Color or <code>null</code> if there is no value or if it is has
626: * not been decorated yet.
627: */
628: public Color getBackgroundColor(Object element,
629: Object adaptedElement) {
630: DecorationResult decoration = getResult(element,
631: adaptedElement, DecorationContext.DEFAULT_CONTEXT);
632:
633: if (decoration == null) {
634: return null;
635: }
636: return decoration.getBackgroundColor();
637: }
638:
639: /**
640: * Return the font for element. If there is no result cue for decoration and
641: * return null, otherwise return the value in the result.
642: *
643: * @param element
644: * The Object to be decorated
645: * @param adaptedElement
646: * @return Font or <code>null</code> if there is no value or if it is has
647: * not been decorated yet.
648: */
649: public Font getFont(Object element, Object adaptedElement) {
650: DecorationResult decoration = getResult(element,
651: adaptedElement, DecorationContext.DEFAULT_CONTEXT);
652:
653: if (decoration == null) {
654: return null;
655: }
656: return decoration.getFont();
657: }
658:
659: /**
660: * Return the foreground Color for element. If there is no result cue for
661: * decoration and return null, otherwise return the value in the result.
662: *
663: * @param element
664: * The Object to be decorated
665: * @param adaptedElement
666: * @return Color or <code>null</code> if there is no value or if it is has
667: * not been decorated yet.
668: */
669: public Color getForegroundColor(Object element,
670: Object adaptedElement) {
671: DecorationResult decoration = getResult(element,
672: adaptedElement, DecorationContext.DEFAULT_CONTEXT);
673:
674: if (decoration == null) {
675: return null;
676: }
677: return decoration.getForegroundColor();
678: }
679:
680: /**
681: * Return whether or not any updates are being processed/
682: *
683: * @return boolean
684: */
685: public boolean processingUpdates() {
686: return !hasPendingUpdates() && !awaitingDecoration.isEmpty();
687: }
688:
689: /**
690: * A listener has been removed. If we are updating then skip it.
691: *
692: * @param listener
693: */
694: void listenerRemoved(ILabelProviderListener listener) {
695: if (updatesPending()) {// Only keep track of them if there are updates
696: // pending
697: removedListeners.add(listener);
698: }
699: if (!updatesPending()) {
700: removedListeners.remove(listener);
701: }
702:
703: }
704:
705: /**
706: * Return whether or not there are any updates pending.
707: *
708: * @return
709: */
710: boolean hasPendingUpdates() {
711: synchronized (pendingKey) {
712: return pendingUpdate.isEmpty();
713: }
714:
715: }
716: }
|