001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.ui;
016:
017: import java.util.ArrayList;
018: import java.util.Collection;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.ListIterator;
023: import java.util.Map;
024:
025: import net.refractions.udig.core.IProvider;
026: import net.refractions.udig.internal.ui.Trace;
027: import net.refractions.udig.internal.ui.UiPlugin;
028: import net.refractions.udig.ui.internal.Messages;
029:
030: import org.eclipse.core.runtime.IProgressMonitor;
031: import org.eclipse.core.runtime.ISafeRunnable;
032: import org.eclipse.core.runtime.NullProgressMonitor;
033: import org.eclipse.jface.viewers.ILazyContentProvider;
034: import org.eclipse.jface.viewers.TableViewer;
035: import org.eclipse.jface.viewers.Viewer;
036: import org.eclipse.swt.SWT;
037: import org.eclipse.swt.widgets.Display;
038: import org.eclipse.swt.widgets.Scrollable;
039: import org.eclipse.swt.widgets.Table;
040: import org.eclipse.ui.PlatformUI;
041: import org.geotools.feature.CollectionEvent;
042: import org.geotools.feature.CollectionListener;
043: import org.geotools.feature.Feature;
044: import org.geotools.feature.FeatureCollection;
045: import org.geotools.feature.FeatureCollections;
046: import org.geotools.feature.FeatureIterator;
047: import org.geotools.feature.FeatureType;
048:
049: class FeatureTableContentProvider implements ILazyContentProvider,
050: IProvider<Collection<Feature>> {
051:
052: private static final IProgressMonitor NULL = new NullProgressMonitor();
053: /** FeatureContentProvider owningFeatureTableControl field */
054: private final FeatureTableControl owningFeatureTableControl;
055:
056: private volatile IProgressMonitor monitor = NULL;
057: private IProvider<IProgressMonitor> progressMonitorProvider;
058:
059: public FeatureTableContentProvider(FeatureTableControl control,
060: IProvider<IProgressMonitor> progressMonitorProvider) {
061: owningFeatureTableControl = control;
062: this .progressMonitorProvider = progressMonitorProvider;
063: }
064:
065: private CollectionListener listener = new CollectionListener() {
066: public void collectionChanged(CollectionEvent event) {
067: if (listener == null)
068: event.getCollection().removeListener(this );
069: Feature changed[] = event.getFeatures();
070: TableViewer viewer = FeatureTableContentProvider.this .owningFeatureTableControl
071: .getViewer();
072:
073: switch (event.getEventType()) {
074: case CollectionEvent.FEATURES_ADDED:
075: for (int i = 0; i < changed.length; i++) {
076: features.add(changed[i]);
077: }
078: viewer.setItemCount(features.size());
079: viewer.getTable().clearAll();
080: break;
081: case CollectionEvent.FEATURES_REMOVED:
082: for (int i = 0; i < changed.length; i++) {
083: for (Iterator<Feature> iter = features.iterator(); iter
084: .hasNext();) {
085: if (iter.next().getID().equals(
086: changed[i].getID())) {
087: iter.remove();
088: break;
089: }
090:
091: }
092: viewer.setItemCount(features.size());
093: viewer.getTable().clearAll();
094: }
095: break;
096: case CollectionEvent.FEATURES_CHANGED:
097: for (int i = 0; i < changed.length; i++) {
098: int j = 0;
099: for (ListIterator<Feature> iter = features
100: .listIterator(); iter.hasNext();) {
101: j++;
102: if (iter.next().getID().equals(
103: changed[i].getID())) {
104: iter.set(changed[i]);
105: break;
106: }
107: }
108: }
109: viewer.getTable().clearAll();
110: break;
111:
112: default:
113: break;
114: }
115: }
116: };
117:
118: // Memory bound cache of features for table
119: // May be sorted according to FID or any of the attributes so don't rely on any given order because
120: // its liable to change. User Lookup instead for quickly locating a features
121: List<Feature> features = new ArrayList<Feature>();
122:
123: /**
124: * Contains same features as Features but sorted by id
125: */
126: Map<String, Feature> lookup = new HashMap<String, Feature>();
127: /**
128: * If true then an edit has occurred and the table is being updated.
129: */
130: private volatile boolean updating = false;
131: private boolean disposed = false;
132:
133: /**
134: * Does nothing.
135: *
136: * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
137: * java.lang.Object, java.lang.Object)
138: * @param viewer
139: * @param oldInput
140: * @param newInput
141: */
142: public void inputChanged(Viewer viewer, Object oldInput,
143: final Object newInput) {
144:
145: synchronized (this ) {
146: if (monitor != NULL) {
147: monitor.setCanceled(true);
148:
149: UiPlugin.trace(Trace.FEATURE_TABLE,
150: FeatureTableContentProvider.class,
151: "#inputChanged(): cancelled monitor", null); //$NON-NLS-1$
152: try {
153: PlatformGIS.wait(500, -1, new WaitCondition() {
154:
155: public boolean isTrue() {
156: return monitor == NULL;
157: }
158:
159: }, this );
160: } catch (InterruptedException e) {
161: UiPlugin.log("Interrupted", e); //$NON-NLS-1$
162: return;
163: }
164: }
165: features.clear();
166:
167: if (oldInput != null) {
168: FeatureCollection old = ((FeatureCollection) oldInput);
169: old.removeListener(listener);
170: }
171: if (newInput != null) {
172: FeatureCollection input = ((FeatureCollection) newInput);
173: input.addListener(listener);
174: }
175:
176: if (newInput == null)
177: return;
178:
179: monitor = progressMonitorProvider.get();
180: monitor.setCanceled(false);
181: owningFeatureTableControl.message(null);
182: owningFeatureTableControl
183: .notifyLoadingListeners(new LoadingEvent(false,
184: monitor, true));
185:
186: final FeatureCollection input = (FeatureCollection) newInput;
187: Display display = Display.getCurrent();
188: owningFeatureTableControl.message(
189: Messages.FeatureTableContentProvider_loading,
190: display.getSystemColor(SWT.COLOR_INFO_BACKGROUND),
191: display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
192: PlatformGIS.run(new ContentLoader(input));
193: }
194: }
195:
196: public void dispose() {
197: synchronized (this ) {
198: if (disposed)
199: return;
200:
201: disposed = true;
202: }
203: features.clear();
204:
205: if (monitor != NULL) {
206: monitor.setCanceled(true);
207: try {
208: PlatformGIS.wait(200, -1, new WaitCondition() {
209:
210: public boolean isTrue() {
211: return monitor == NULL;
212: }
213:
214: }, this );
215: } catch (InterruptedException e) {
216: UiPlugin.log("Interrupted", e); //$NON-NLS-1$
217: return;
218: }
219: }
220: }
221:
222: public Collection<Feature> get() {
223: return features;
224: }
225:
226: public void updateElement(int index) {
227: if (index >= features.size()) {
228: owningFeatureTableControl.getViewer().replace("", index); //$NON-NLS-1$
229: } else if (monitor != NULL && index == 0 && !updating) {
230: owningFeatureTableControl.getViewer().replace(
231: FeatureTableControl.LOADING, 0);
232: } else {
233: int resolvedIndex = index;
234: if (owningFeatureTableControl.getViewer().getTable()
235: .getSortDirection() == SWT.UP)
236: resolvedIndex = features.size() - index - 1;
237: Feature feature = features.get(resolvedIndex);
238: owningFeatureTableControl.getViewer().replace(feature,
239: index);
240: }
241: }
242:
243: private class ContentLoader implements ISafeRunnable {
244:
245: private final FeatureCollection input;
246:
247: public ContentLoader(FeatureCollection input) {
248: this .input = input;
249: }
250:
251: public void handleException(Throwable exception) {
252: UiPlugin.log("Error loading features", exception); //$NON-NLS-1$
253: }
254:
255: public void run() throws Exception {
256: if (cancel())
257: return;
258: UiPlugin.trace(Trace.FEATURE_TABLE,
259: FeatureTableContentProvider.class,
260: "Starting ContentLoader", null); //$NON-NLS-1$
261: setEnabled(false);
262: int i = 0;
263: final int[] monitorUpdate = new int[1];
264: monitorUpdate[0] = 0;
265: boolean updated = false;
266: long start = System.currentTimeMillis();
267: FeatureIterator iterator = null;
268: try {
269: iterator = input.features();
270: while (iterator.hasNext()) {
271: if (System.currentTimeMillis() - start > 500) {
272: if (!updated) {
273: updated = true;
274: updateTable(input, false);
275: }
276: start = System.currentTimeMillis();
277: updateMonitor(i, monitorUpdate);
278: }
279: if (cancel())
280: return;
281: Feature next = iterator.next();
282: features.add(next);
283: lookup.put(next.getID(), next);
284: i++;
285: }
286: } catch (OutOfMemoryError error) {
287: error(
288: input,
289: i
290: + " " + Messages.FeatureTableContentProvider_outOfMemory, true); //$NON-NLS-1$
291: UiPlugin
292: .log("Out of memory error in table view", error); //$NON-NLS-1$
293: return;
294: } catch (Throwable t) {
295: error(
296: input,
297: Messages.FeatureTableContentProvider_unexpectedErro
298: + t.getLocalizedMessage(), false);
299: UiPlugin.log("error loading features in table view", t); //$NON-NLS-1$
300: return;
301: } finally {
302: if (iterator != null)
303: iterator.close();
304: UiPlugin
305: .trace(
306: Trace.FEATURE_TABLE,
307: FeatureTableContentProvider.class,
308: "Ending ContentLoader, Cancel state is:" + monitor.isCanceled(), null); //$NON-NLS-1$
309: }
310: if (!cancel()) {
311: updateTable(input, true);
312: setEnabled(true);
313: }
314: }
315:
316: /**
317: * will setenable and set an error message on the feature table control.
318: */
319: private void error(final FeatureCollection input,
320: final String string, final boolean clearFeatures) {
321: UiPlugin
322: .trace(
323: Trace.FEATURE_TABLE,
324: FeatureTableContentProvider.class,
325: "ContentLoader#error: Error occurred in ContentLoader:\n" + string, null); //$NON-NLS-1$
326:
327: final Display display = owningFeatureTableControl
328: .getViewer().getControl().getDisplay();
329: display.asyncExec(new Runnable() {
330: public void run() {
331: monitor.setCanceled(true);
332: }
333: });
334: done();
335: display.asyncExec(new Runnable() {
336: public void run() {
337: owningFeatureTableControl
338: .message(
339: string,
340: display
341: .getSystemColor(SWT.COLOR_INFO_BACKGROUND),
342: display
343: .getSystemColor(SWT.COLOR_INFO_FOREGROUND));
344: }
345: });
346: }
347:
348: private void updateMonitor(int i, final int[] monitorUpdate) {
349: final int j = i;
350: owningFeatureTableControl.getViewer().getControl()
351: .getDisplay().asyncExec(new Runnable() {
352: public void run() {
353: monitor
354: .subTask(Messages.FeatureTableContentProvider_loadedFeatures
355: + j);
356: monitor.worked(j - monitorUpdate[0]);
357: monitorUpdate[0] = j;
358: }
359: });
360: }
361:
362: /**
363: * If enabled it will finish the {@link IProgressMonitor}, null it out, notify listeners
364: * and enable the table control. if not enabled it will begin the progress task and disable
365: * the Table control
366: *
367: * @param enabled
368: */
369: private void setEnabled(final boolean enabled) {
370:
371: final Scrollable control = owningFeatureTableControl
372: .getViewer().getTable();
373: final int size;
374: if (!enabled)
375: size = input.size();
376: else
377: size = IProgressMonitor.UNKNOWN;
378:
379: control.getDisplay().asyncExec(new Runnable() {
380: public void run() {
381:
382: UiPlugin
383: .trace(
384: Trace.FEATURE_TABLE,
385: FeatureTableContentProvider.class,
386: "ContentLoader#setEnabled():" + enabled, null); //$NON-NLS-1$
387: if (enabled) {
388: done();
389: } else {
390: monitor
391: .beginTask(
392: Messages.FeatureTableControl_loading1
393: + input.getSchema()
394: .getTypeName()
395: + Messages.FeatureTableControl_loading2,
396: size + 1);
397: monitor.worked(1);
398: }
399: }
400: });
401: }
402:
403: private void done() {
404: final Table control = owningFeatureTableControl.getViewer()
405: .getTable();
406: Runnable runnable = new Runnable() {
407: public void run() {
408:
409: UiPlugin.trace(Trace.FEATURE_TABLE,
410: FeatureTableContentProvider.class,
411: "ContentLoader#done()|run():", null); //$NON-NLS-1$
412:
413: monitor.done();
414: synchronized (FeatureTableContentProvider.this ) {
415: monitor = NULL;
416: FeatureTableContentProvider.this .notifyAll();
417: if (!control.isDisposed())
418: control.getVerticalBar().setEnabled(true);
419: if (control.getItemCount() > 0) {
420: owningFeatureTableControl.getViewer()
421: .replace(features.get(0), 0);
422: }
423: owningFeatureTableControl
424: .notifyLoadingListeners(new LoadingEvent(
425: monitor.isCanceled(), null,
426: false));
427: }
428: }
429: };
430: if (Display.getCurrent() != control.getDisplay()) {
431: control.getDisplay().asyncExec(runnable);
432: } else {
433: runnable.run();
434: }
435: }
436:
437: private void updateTable(final FeatureCollection newInput,
438: final boolean done) {
439: final Table table = owningFeatureTableControl.getViewer()
440: .getTable();
441: table.getDisplay().asyncExec(new Runnable() {
442: public void run() {
443:
444: UiPlugin
445: .trace(
446: Trace.FEATURE_TABLE,
447: FeatureTableContentProvider.class,
448: "ContentLoader#updateTable(): done=" + done, null); //$NON-NLS-1$
449:
450: owningFeatureTableControl.message(null);
451: int size = features.size();
452: owningFeatureTableControl.getViewer().setItemCount(
453: size);
454: if (!done && !table.isDisposed())
455: table.getVerticalBar().setEnabled(false);
456: }
457: });
458: }
459:
460: private boolean cancel() {
461: synchronized (FeatureTableContentProvider.this ) {
462: if (monitor.isCanceled()
463: || PlatformUI.getWorkbench().isClosing()) {
464: done();
465: return true;
466: }
467: return false;
468: }
469: }
470:
471: }
472:
473: /**
474: * Updates the features that have the same feature ID to match the new feature or adds the features if they are not part of the
475: * current collection.
476: *
477: * @param features2 the feature collection that contains the modified or new features.
478: */
479: public void update(FeatureCollection features2)
480: throws IllegalArgumentException {
481: if (features == null)
482: return;
483:
484: if (!owningFeatureTableControl.features.getSchema().equals(
485: features2.getSchema()))
486: throw new IllegalArgumentException(
487: "The feature type of the Feature Collection passed as a parameter does not have the same" + //$NON-NLS-1$
488: " feature type as the features in the table so it cannot be used to update the features."); //$NON-NLS-1$
489:
490: ContentUpdater updater = new ContentUpdater(features2);
491: PlatformGIS.run(updater);
492: }
493:
494: Feature findFeature(String featureId) {
495: return lookup.get(featureId);
496: }
497:
498: private class ContentUpdater implements ISafeRunnable {
499:
500: private FeatureCollection newFeatures;
501: private int loaded = 0;
502:
503: public ContentUpdater(FeatureCollection features2) {
504: this .newFeatures = features2;
505: }
506:
507: public void handleException(Throwable exception) {
508: UiPlugin
509: .log(
510: "Exception while updating the features in the FeatureTableControl", exception); //$NON-NLS-1$
511: }
512:
513: public void run() throws Exception {
514: synchronized (FeatureTableContentProvider.this ) {
515: updating = true;
516: if (monitor != NULL) {
517: // wait until finished loading
518: try {
519: PlatformGIS.wait(500, -1, new WaitCondition() {
520:
521: public boolean isTrue() {
522: return monitor == NULL;
523: }
524:
525: }, FeatureTableContentProvider.this );
526: } catch (InterruptedException e) {
527: UiPlugin.log("Interrupted", e); //$NON-NLS-1$
528: return;
529: }
530: }
531:
532: startLoading();
533:
534: FeatureType schema = newFeatures.getSchema();
535: FeatureIterator iter = newFeatures.features();
536: try {
537: boolean featuresWereAdded = false;
538: while (iter.hasNext()) {
539: if (monitor.isCanceled())
540: break;
541: Feature newValue = iter.next();
542: Feature oldValue = findFeature(newValue.getID());
543: if (oldValue == null) {
544: featuresWereAdded = true;
545: features.add(newValue);
546: lookup.put(newValue.getID(), newValue);
547: } else {
548: for (int i = 0; i < schema
549: .getAttributeCount(); i++) {
550: oldValue.setAttribute(i, newValue
551: .getAttribute(i));
552: }
553: }
554: loaded++;
555: updateMonitor(loaded
556: + Messages.FeatureTableContentProvider_updatingFeatures);
557: }
558:
559: updateTable(featuresWereAdded);
560: } finally {
561: iter.close();
562: }
563:
564: }
565: }
566:
567: private void updateTable(final boolean featuresWereAdded) {
568: final Table table = owningFeatureTableControl.getViewer()
569: .getTable();
570: table.getDisplay().asyncExec(new Runnable() {
571: public void run() {
572: if (featuresWereAdded) {
573: updateMonitor(Messages.FeatureTableContentProvider_sortTable);
574: owningFeatureTableControl.sort(false);
575: owningFeatureTableControl.getViewer()
576: .setItemCount(features.size());
577: } else {
578: table.clearAll();
579: }
580: monitor.done();
581: boolean cancelled = monitor.isCanceled();
582: monitor = NULL;
583: updating = false;
584: synchronized (FeatureTableContentProvider.this ) {
585: FeatureTableContentProvider.this .notifyAll();
586: }
587: owningFeatureTableControl
588: .notifyLoadingListeners(new LoadingEvent(
589: cancelled, null, false));
590: }
591: });
592: }
593:
594: private void updateMonitor(final String subTask) {
595: Display display = owningFeatureTableControl.getControl()
596: .getDisplay();
597: display.asyncExec(new Runnable() {
598: public void run() {
599: monitor.subTask(subTask);
600: monitor.worked(1);
601: }
602: });
603: }
604:
605: private void startLoading() {
606: Display display = owningFeatureTableControl.getControl()
607: .getDisplay();
608: display.asyncExec(new Runnable() {
609: public void run() {
610: owningFeatureTableControl
611: .notifyLoadingListeners(new LoadingEvent(
612: false, monitor, true));
613: monitor = progressMonitorProvider.get();
614: monitor.setCanceled(false);
615: monitor
616: .beginTask(
617: Messages.FeatureTableContentProvider_updateTaskName,
618: IProgressMonitor.UNKNOWN);
619: }
620: });
621: }
622: }
623:
624: /**
625: * Checks the lookup table and the feature list to ensure that they have the same number of features and the same features.
626: * An exception will be thrown otherwise.
627: */
628: public void assertInternallyConsistent() {
629: if (features.size() != lookup.size())
630: throw new AssertionError(
631: "lookup table has " + lookup.size() + " features while feature list has " + features.size() + " features"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
632:
633: for (Feature feature : features) {
634: Feature lookupFeature = lookup.get(feature.getID());
635: if (lookup == null) {
636: throw new AssertionError(
637: "Lookup table is missing " + feature); //$NON-NLS-1$
638: }
639: if (lookupFeature != feature)
640: throw new AssertionError(
641: "Lookup table contains: " + lookupFeature + " while feature list contains" + feature + ". They are" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
642: " not the same instance"); //$NON-NLS-1$
643: }
644: }
645:
646: /**
647: * Removes the selected features (the features selected by the owning {@link FeatureTableControl}).
648: * @return returns a collection of the deleted features
649: */
650: public FeatureCollection deleteSelection() {
651: final FeatureCollection deletedFeatures = FeatureCollections
652: .newCollection();
653: Runnable updateTable = new Runnable() {
654: @SuppressWarnings("unchecked")
655: public void run() {
656: Collection<String> selectionFids = owningFeatureTableControl
657: .getSelectionProvider().getSelectionFids();
658: for (Iterator<Feature> iter = features.iterator(); iter
659: .hasNext();) {
660: Feature feature = iter.next();
661: if (selectionFids.contains(feature.getID())) {
662: deletedFeatures.add(feature);
663: iter.remove();
664: lookup.remove(feature.getID());
665: }
666: }
667:
668: selectionFids.clear();
669: owningFeatureTableControl.getViewer().getTable()
670: .clearAll();
671: }
672: };
673:
674: if (Display.getCurrent() == null) {
675: PlatformGIS.syncInDisplayThread(owningFeatureTableControl
676: .getControl().getDisplay(), updateTable);
677: } else {
678: updateTable.run();
679: }
680:
681: return deletedFeatures;
682: }
683: }
|