/*
* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.catalog.ui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.locationtech.udig.catalog.CatalogPlugin;
import org.locationtech.udig.catalog.ICatalog;
import org.locationtech.udig.catalog.IResolve;
import org.locationtech.udig.catalog.IResolveChangeEvent;
import org.locationtech.udig.catalog.IResolveChangeListener;
import org.locationtech.udig.ui.PlatformGIS;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.ui.PlatformUI;
/**
* Provides a threaded Tree content provider for IResolve.
* <p>
* Responsibilities:
* <ul>
* <li>Rooted by a catalog
* <li>Ensure that calls to members are dispatched in a non ui thread
* </ul>
* </p>
*
* @author jgarnett
* @since 0.6.0
*/
public class AbstractResolveContentProvider implements IResolveChangeListener {
/**
* Map of threads used to resolve associated IResolves.
* <p>
* Waking up the thread will cause the associated IResolve to be refreshed.
* </p>
* <p>
* Here an {@link IdentityHashMap} is used because the containsKey otherwise doesn't look deep
* enough in the object to correctly deal with multilevel services.
* </p>
*/
Map<IResolve, Thread> threadFarm = new IdentityHashMap<IResolve, Thread>();
/**
* Captures parent child relationships.
* <p>
* Here an {@link IdentityHashMap} is used because the containsKey otherwise doesn't look deep
* enough in the object to correctly deal with multilevel services.
* </p>
*/
protected final Map<IResolve, List<IResolve>> structure = new IdentityHashMap<IResolve, List<IResolve>>();
/**
* Root of this tree, often a ICatalog.
*/
protected ICatalog catalog;
protected List<IResolve> list;
protected Viewer viewer;
public AbstractResolveContentProvider() {
super();
}
/**
* Catalog has changed!
* <p>
* Will only start up a thread to update the structure if:
* <ul>
* <li>We care about this resolve
* <li>We are not already running a thread
* </ul>
* </p>
* This will allow us to "ignore" events generated by the process up inspecting the resolve
* being updated.
* </p>
*
* @param event
*/
public void changed( IResolveChangeEvent event ) {
if( threadFarm==null ){
// we are disposed
if( catalog!=null ){
catalog.removeCatalogListener(this);
}
return;
}
if ( event.getType() != IResolveChangeEvent.Type.POST_CHANGE) {
return;
}
IResolve resolve = event.getResolve();
if (threadFarm.containsKey(resolve)) {
update(resolve);
}
}
/**
* Called by thread to client code to get the content refreshed.
* <p>
* This call will not update structure, just appearance.
* </p>
*
* @param resolve
*/
public void refresh( final IResolve resolve ) {
if( PlatformUI.getWorkbench().isClosing() )
return;
Runnable object = new Runnable(){
public void run() {
if (viewer instanceof TreeViewer) {
TreeViewer treeViewer = (TreeViewer) viewer;
treeViewer.refresh(resolve, true);
}else{
viewer.refresh();
}
}
};
PlatformGIS.asyncInDisplayThread(object, true);
}
/**
* Update appearance and structure.
* <p>
* Note: this will spawn a thread to fetch the required information.
* </p>
*
* @param resolve
*/
public void update( final IResolve resolve ) {
if (resolve == null) {
// go away
return;
}
if (resolve.getIdentifier() == null) {
// go away x 2
// System.out.println( "Got an resolve with out an id "+ resolve);
}
// run the thread, unless it is already running...
// (this will cause any change events generated by the thread
// to be ignored).
if (threadFarm.containsKey(resolve)) {
Thread update = threadFarm.get(resolve);
if (update == null) {
// This IResolve is not suitable for member resolution
//
}
if (update.isAlive()) {
// thread will already report back to structure
// Note: thread should end with a async update to the
// assoicated element
} else {
// We had a thread but it stopped - must be do to an error?
//
update.interrupt();
update = new Thread(new Update(resolve), "Update " + resolve.getIdentifier()); //$NON-NLS-1$
threadFarm.put(resolve, update);
update.setPriority(Thread.MIN_PRIORITY);
update.start();
}
} else {
// request a structure update
Thread update = new Thread(new Update(resolve), "Update " + resolve.getIdentifier()); //$NON-NLS-1$
threadFarm.put(resolve, update);
update.setPriority(Thread.MIN_PRIORITY);
update.start();
}
}
/**
* Notifies this content provider that the given viewer's input has been switched to a different
* element.
* <p>
* A typical use for this method is registering the content provider as a listener to changes on
* the new input (using model-specific means), and deregistering the viewer from the old input.
* In response to these change notifications, the content provider should update the viewer (see
* the add, remove, update and refresh methods on the viewers).
* </p>
* <p>
* The viewer should not be updated during this call, as it might be in the process of being
* disposed.
* </p>
*
* @param viewer the viewer
* @param oldInput the old input element, or <code>null</code> if the viewer did not
* previously have an input
* @param newInput the new input element, or <code>null</code> if the viewer does not have an
* input
*/
@SuppressWarnings("unchecked")
public void inputChanged( Viewer newViewer, Object oldInput, Object newInput ) {
if (oldInput == newInput) {
return;
}
viewer = newViewer;
if (catalog != null || list != null) {
CatalogPlugin.removeListener(this);
}
catalog = newInput instanceof ICatalog ? (ICatalog) newInput : null;
list = newInput instanceof List ? (List<IResolve>) newInput : null;
if (catalog != null || list != null) {
CatalogPlugin.addListener(this);
}
}
public void dispose() {
if (threadFarm != null) {
for( IResolve resolve : threadFarm.keySet() ) {
Thread thread = threadFarm.get(resolve);
if (thread!=null && thread.isAlive()) {
thread.interrupt();
}
}
if(catalog!=null)
catalog.removeCatalogListener(this);
CatalogPlugin.getDefault().getLocalCatalog().removeCatalogListener(this);
threadFarm.clear();
threadFarm = null;
}
if (structure != null) {
for( IResolve resolve : structure.keySet() ) {
List<IResolve> children = structure.get(resolve);
if( children!=null )
children.clear();
}
structure.clear();
}
}
/**
* Thread for updating structure
* <p>
* Note: thread notify the system that the element has requires an update
*/
class Update implements Runnable {
IResolve resolve;
Update( IResolve target ) {
resolve = target;
}
/**
* Update strucuture, Thread will be notified if more updates are required.
* <p>
* Note: We also need to let ourselves be interrupted
*/
@SuppressWarnings("unchecked")
public void run() {
try {
try {
List<IResolve> members = new ArrayList<IResolve>(resolve.members(null));
structure.put(resolve, members);
} catch (IOException io) {
// could not get children
// System.out.println( resolve.getIdentifier()+": "+ io );
io.printStackTrace();
structure.put(resolve, null);
// return; // don't even try again
}
refresh(resolve);
} catch (Throwable t) {
CatalogUIPlugin.trace( resolve.getIdentifier()+": "+t, t );
}
}
}
}