/*******************************************************************************
* Copyright (c) 2011, 2012 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.ui.trees;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.tcf.te.core.utils.Ancestor;
import org.eclipse.tcf.te.ui.interfaces.ISchedulableEvent;
/**
* CommonViewerListener listens to the property change event from the
* tree and update the viewer accordingly.
*/
public class CommonViewerListener extends Ancestor<Object> implements PropertyChangeListener, IPropertyChangeListener {
// The timer that process the property events periodically.
private static Timer viewerTimer;
static {
viewerTimer = new Timer("Viewer_Refresher", true); //$NON-NLS-1$
}
// The interval of the refreshing timer.
private static final long INTERVAL = 333;
// Maximum delay before immediate refreshing.
private static final long MAX_DELAY = 1000;
// The NULL object stands for refreshing the whole tree.
private static final Object NULL = new Object();
// The tree viewer
private TreeViewer viewer;
// The current queued property event sources.
private List<EventObject> queue;
// The timer task to process the property events periodically.
private TimerTask task;
// The content provider
ITreeContentProvider contentProvider;
// The time of last run.
long lastRun;
/***
* Create an instance for the specified tree content provider.
*
* @param viewer The tree content provider.
*/
public CommonViewerListener(TreeViewer viewer, ITreeContentProvider contentProvider) {
Assert.isNotNull(viewer);
this.viewer = viewer;
this.contentProvider = contentProvider;
this.task = new TimerTask(){
@Override
public void run() {
handleEvent(true);
}};
viewerTimer.schedule(this.task, INTERVAL, INTERVAL);
this.queue = Collections.synchronizedList(new ArrayList<EventObject>());
}
/*
* (non-Javadoc)
* @see org.eclipse.tcf.te.core.utils.Ancestor#getParent(java.lang.Object)
*/
@Override
protected Object getParent(Object element) {
return contentProvider.getParent(element);
}
/*
* (non-Javadoc)
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
processEvent(event);
}
/**
* Adding the event object into the queue and trigger the scheduling.
*
* @param event The event object.
*/
private void processEvent(EventObject event) {
if(!(event instanceof ISchedulableEvent) || ((ISchedulableEvent)event).isApplicable(viewer)) {
queue.add(event);
if(event instanceof ISchedulableEvent) {
((ISchedulableEvent)event).eventQueued();
}
viewerTimer.schedule(new TimerTask(){
@Override
public void run() {
handleEvent(false);
}}, 0);
}
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
@Override
public void propertyChange(org.eclipse.jface.util.PropertyChangeEvent event) {
processEvent(event);
}
/**
* Get and empty the queued objects.
*
* @return The objects in current queue.
*/
Object[] emptyQueue() {
synchronized (queue) {
Iterator<EventObject> iterator = queue.iterator();
List<Object> objects = new ArrayList<Object>();
while(iterator.hasNext()) {
EventObject event = iterator.next();
if(event instanceof ISchedulableEvent && !((ISchedulableEvent)event).isSchedulable()) {
continue;
}
objects.add(event.getSource());
iterator.remove();
}
return objects.toArray();
}
}
/**
* Check if it is ready for next run. If the time
* has expired, then mark last run time and return true.
*
* @param scheduled if this processing is scheduled
* @return true if it is time.
*/
synchronized boolean checkReady(boolean scheduled) {
if (scheduled || System.currentTimeMillis() - lastRun > MAX_DELAY) {
lastRun = System.currentTimeMillis();
return true;
}
return false;
}
/**
* Handle the current events in the event queue.
*
* @param scheduled if this handling is scheduled.
*/
void handleEvent(boolean scheduled) {
if (checkReady(scheduled)) {
Object[] objects = emptyQueue();
if (objects.length > 0) {
List<Object> list = mergeObjects(objects);
Object object = getRefreshRoot(list);
processObject(object);
}
}
}
/**
* Get the refreshing root for the object list.
*
* @param objects The objects to be refreshed.
* @return The root of these objects.
*/
private Object getRefreshRoot(List<Object> objects) {
if (objects.isEmpty()) {
return NULL;
}
else if (objects.size() == 1) {
Object object = objects.get(0);
if (contentProvider.getParent(object) == null) {
return NULL;
}
return object;
}
else {
// If there are multiple root nodes, then select NULL as the final root.
Object object = getAncestor(objects);
if (object == null) {
return NULL;
}
return object;
}
}
/**
* Merge the current objects into an ancestor object.
*
* @param objects The objects to be merged.
* @return NULL or a list presenting the top objects.
*/
private List<Object> mergeObjects(Object[] objects) {
for (Object object : objects) {
if (object == NULL) {
// If one object is NULL, then return NULL
List<Object> result = new ArrayList<Object>();
result.add(NULL);
return result;
}
}
// Remove duplicates.
List<Object> list = Arrays.asList(objects);
Set<Object> set = new HashSet<Object>(list);
objects = set.toArray();
list = Arrays.asList(objects);
return getAncestors(list);
}
/**
* Process the object node.
*
* @param object The object to be processed.
*/
void processObject(final Object object) {
Assert.isNotNull(object);
Tree tree = viewer.getTree();
if (!tree.isDisposed()) {
Display display = tree.getDisplay();
if (display.getThread() == Thread.currentThread()) {
if (object != NULL) {
viewer.refresh(object);
}
else {
viewer.refresh();
}
}
else {
display.asyncExec(new Runnable() {
@Override
public void run() {
processObject(object);
}
});
}
}
}
/**
* Cancel the current task and the current timer.
*/
public void cancel() {
task.cancel();
}
}