001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package net.refractions.udig.catalog.ui;
018:
019: import java.io.IOException;
020: import java.util.IdentityHashMap;
021: import java.util.List;
022: import java.util.Map;
023:
024: import net.refractions.udig.catalog.CatalogPlugin;
025: import net.refractions.udig.catalog.ICatalog;
026: import net.refractions.udig.catalog.IGeoResource;
027: import net.refractions.udig.catalog.IResolve;
028: import net.refractions.udig.catalog.IResolveChangeEvent;
029: import net.refractions.udig.catalog.IResolveChangeListener;
030: import net.refractions.udig.catalog.IService;
031: import net.refractions.udig.catalog.ui.internal.Messages;
032:
033: import org.eclipse.core.runtime.Platform;
034: import org.eclipse.jface.viewers.ITreeContentProvider;
035: import org.eclipse.jface.viewers.TreeViewer;
036: import org.eclipse.jface.viewers.Viewer;
037: import org.eclipse.swt.widgets.Display;
038: import org.eclipse.ui.PlatformUI;
039:
040: /**
041: * Provides a threaded Tree content provider for IResolve.
042: * <p>
043: * Responsibilities:
044: * <ul>
045: * <li>Rooted by a catalog
046: * <li>Ensure that calls to members are dispatched in a non ui thread
047: * </ul>
048: * </p>
049: *
050: * @author jgarnett
051: * @since 0.6.0
052: */
053: public class ResolveContentProvider implements ITreeContentProvider,
054: IResolveChangeListener {
055: /**
056: * Map of threads used to resolve associated IResolves.
057: * <p>
058: * Waking up the thread will cause the associated IResolve to be refreshed.
059: * </p>
060: * <p>
061: * Here an {@link IdentityHashMap} is used because the containsKey otherwise doesn't look deep
062: * enough in the object to correctly deal with multilevel services.
063: * </p>
064: */
065: Map<IResolve, Thread> threadFarm = new IdentityHashMap<IResolve, Thread>();
066:
067: /**
068: * Captures parent child relationships.
069: * <p>
070: * Here an {@link IdentityHashMap} is used because the containsKey otherwise doesn't look deep
071: * enough in the object to correctly deal with multilevel services.
072: * </p>
073: */
074: Map<IResolve, List<IResolve>> structure = new IdentityHashMap<IResolve, List<IResolve>>();
075:
076: /**
077: * Root of this tree, often a ICatalog.
078: */
079: private ICatalog catalog;
080: private List<IResolve> list;
081:
082: private Viewer viewer;
083:
084: /**
085: * Catalog has changed!
086: * <p>
087: * Will only start up a thread to update the structure if:
088: * <ul>
089: * <li>We care about this resolve
090: * <li>We are not already running a thread
091: * </ul>
092: * </p>
093: * This will allow us to "ignore" events generated by the process up inspecting the resolve
094: * being updated.
095: * </p>
096: *
097: * @param event
098: */
099: public void changed(IResolveChangeEvent event) {
100: if (event.getType() != IResolveChangeEvent.Type.POST_CHANGE) {
101: return;
102: }
103: IResolve resolve = event.getResolve();
104: if (threadFarm.containsKey(resolve)) {
105: update(resolve);
106: }
107: }
108:
109: /**
110: * Called by thread to client code to get the content refreshed.
111: * <p>
112: * This call will not update structure, just appearance.
113: * </p>
114: *
115: * @param resolve
116: */
117: public void refresh(final IResolve resolve) {
118: if (PlatformUI.getWorkbench().isClosing())
119: return;
120: if (Display.getCurrent() != null) {
121: if (viewer instanceof TreeViewer) {
122: ((TreeViewer) viewer).refresh(resolve, true);
123: } else {
124: viewer.refresh();
125: }
126: } else {
127: Display.getDefault().asyncExec(new Runnable() {
128: public void run() {
129: if (viewer instanceof TreeViewer) {
130: ((TreeViewer) viewer).refresh(resolve, true);
131: } else {
132: viewer.refresh();
133: }
134: }
135: });
136: }
137: }
138:
139: /**
140: * Update appearance and structure.
141: * <p>
142: * Note: this will spawn a thread to fetch the required information.
143: * </p>
144: *
145: * @param resolve
146: */
147: public void update(final IResolve resolve) {
148: if (resolve == null) {
149: // go away
150: return;
151: }
152: if (resolve.getIdentifier() == null) {
153: // go away x 2
154: // System.out.println( "Got an resolve with out an id "+ resolve);
155: }
156: // run the thread, unless it is already running...
157: // (this will cause any change events generated by the thread
158: // to be ignored).
159: if (threadFarm.containsKey(resolve)) {
160: Thread update = threadFarm.get(resolve);
161: if (update == null) {
162: // This IResolve is not suitable for member resolution
163: //
164: }
165: if (update.isAlive()) {
166: // thread will already report back to structure
167: // Note: thread should end with a async update to the
168: // assoicated element
169: } else {
170: // We had a thread but it stopped - must be do to an error?
171: //
172: update.interrupt();
173:
174: update = new Thread(new Update(resolve),
175: "Update " + resolve.getIdentifier()); //$NON-NLS-1$
176: threadFarm.put(resolve, update);
177: update.setPriority(Thread.MIN_PRIORITY);
178: update.start();
179: }
180: } else {
181: // request a structure update
182: Thread update = new Thread(new Update(resolve),
183: "Update " + resolve.getIdentifier()); //$NON-NLS-1$
184: threadFarm.put(resolve, update);
185: update.setPriority(Thread.MIN_PRIORITY);
186: update.start();
187: }
188: }
189:
190: /**
191: * Thread for updating structure
192: * <p>
193: * Note: thread notify the system that the element has requires an update
194: */
195: class Update implements Runnable {
196: IResolve resolve;
197:
198: Update(IResolve target) {
199: resolve = target;
200: }
201:
202: /**
203: * Update strucuture, Thread will be notified if more updates are required.
204: * <p>
205: * Note: We also need to let ourselves be interrupted
206: */
207: @SuppressWarnings("unchecked")
208: public void run() {
209: try {
210: try {
211: List members = resolve.members(null);
212: structure.put(resolve, members);
213: } catch (IOException io) {
214: // could not get children
215: // System.out.println( resolve.getIdentifier()+": "+ io );
216: io.printStackTrace();
217: structure.put(resolve, null);
218: // return; // don't even try again
219: }
220: refresh(resolve);
221: } catch (Throwable t) {
222: // System.out.println( resolve.getIdentifier()+": "+t );
223: }
224: }
225: }
226:
227: /**
228: * Returns the child elements of the given parent element.
229: * <p>
230: * The difference between this method and <code>IStructuredContentProvider.getElements</code>
231: * is that <code>getElements</code> is called to obtain the tree viewer's root elements,
232: * whereas <code>getChildren</code> is used to obtain the children of a given parent element
233: * in the tree (including a root).
234: * </p>
235: * The result is not modified by the viewer.
236: * </p>
237: *
238: * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
239: * @param parent the parent element
240: * @return an array of child elements
241: */
242: public Object[] getChildren(Object parent) {
243: if (parent == null)
244: return null;
245: if (parent instanceof List)
246: return ((List) parent).toArray();
247: if (parent instanceof String)
248: return new Object[0];
249: if (!(parent instanceof IResolve))
250: return null;
251:
252: IResolve resolve = (IResolve) parent;
253: if (structure.containsKey(resolve)) {
254: List<IResolve> members = structure.get(resolve);
255: return members != null ? members.toArray() : null;
256: } else {
257: update(resolve); // calculate
258: return new Object[] { Messages.ResolveContentProvider_searching };
259: }
260: }
261:
262: /**
263: * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
264: */
265: public Object getParent(Object element) {
266: if (!(element instanceof IResolve))
267: return null;
268: IResolve resolve = (IResolve) element;
269: try {
270: // assume find parent is cheap
271: if (element instanceof IService) {
272: if (list != null && list.contains(resolve))
273: return list;
274: if (catalog != null)
275: return catalog;
276: return null; // service is probably the parent
277: }
278: if (element instanceof IGeoResource) {
279: return ((IGeoResource) element).resolve(IService.class,
280: null);
281: }
282: } catch (Throwable t) {
283: t.printStackTrace();
284: }
285: return null;
286: }
287:
288: /**
289: * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
290: */
291: public boolean hasChildren(Object element) {
292: try {
293: if (element == null)
294: return false;
295: if (element instanceof IResolve)
296: return true;
297: return ((element instanceof List) && !((List) element)
298: .isEmpty());
299: } catch (Throwable t) {
300: t.printStackTrace();
301: }
302: return false;
303: }
304:
305: /**
306: * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
307: */
308: public Object[] getElements(Object inputElement) {
309: return getChildren(inputElement);
310: }
311:
312: /**
313: * Notifies this content provider that the given viewer's input has been switched to a different
314: * element.
315: * <p>
316: * A typical use for this method is registering the content provider as a listener to changes on
317: * the new input (using model-specific means), and deregistering the viewer from the old input.
318: * In response to these change notifications, the content provider should update the viewer (see
319: * the add, remove, update and refresh methods on the viewers).
320: * </p>
321: * <p>
322: * The viewer should not be updated during this call, as it might be in the process of being
323: * disposed.
324: * </p>
325: *
326: * @param viewer the viewer
327: * @param oldInput the old input element, or <code>null</code> if the viewer did not
328: * previously have an input
329: * @param newInput the new input element, or <code>null</code> if the viewer does not have an
330: * input
331: */
332: public void inputChanged(Viewer newViewer, Object oldInput,
333: Object newInput) {
334: if (oldInput == newInput) {
335: return;
336: }
337: viewer = newViewer;
338: if (catalog != null || list != null) {
339: CatalogPlugin.removeListener(this );
340: }
341: catalog = newInput instanceof ICatalog ? (ICatalog) newInput
342: : null;
343: list = newInput instanceof List ? (List<IResolve>) newInput
344: : null;
345: if (catalog != null || list != null) {
346: CatalogPlugin.addListener(this );
347: }
348: }
349:
350: /*
351: * @see org.eclipse.jface.viewers.IContentProvider#dispose()
352: */
353: public void dispose() {
354: if (threadFarm != null) {
355: for (IResolve resolve : threadFarm.keySet()) {
356: Thread thread = threadFarm.get(resolve);
357: if (thread != null && thread.isAlive()) {
358: thread.interrupt();
359: }
360: }
361: threadFarm.clear();
362: threadFarm = null;
363: }
364: if (structure != null) {
365: for (IResolve resolve : structure.keySet()) {
366: List<IResolve> children = structure.get(resolve);
367: if (children != null)
368: children.clear();
369: }
370: structure.clear();
371: }
372: }
373: }
|