Java tutorial
/* uDig - User Friendly Desktop Internet GIS client * http://udig.refractions.net * (C) 2004, Refractions Research Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package net.refractions.udig.tools.edit; import java.awt.geom.AffineTransform; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import net.refractions.udig.project.ILayer; import net.refractions.udig.project.ILayerListener; import net.refractions.udig.project.IMap; import net.refractions.udig.project.Interaction; import net.refractions.udig.project.LayerEvent; import net.refractions.udig.project.command.UndoableComposite; import net.refractions.udig.project.command.UndoableMapCommand; import net.refractions.udig.project.internal.EditManager; import net.refractions.udig.project.internal.Layer; import net.refractions.udig.project.internal.impl.EditManagerImpl; import net.refractions.udig.project.internal.render.RenderPackage; import net.refractions.udig.project.internal.render.ViewportModel; import net.refractions.udig.project.render.displayAdapter.IMapDisplayListener; import net.refractions.udig.project.render.displayAdapter.MapDisplayEvent; import net.refractions.udig.project.ui.ApplicationGIS; import net.refractions.udig.project.ui.tool.IToolContext; import net.refractions.udig.project.ui.tool.Tool; import net.refractions.udig.tool.edit.internal.Messages; import net.refractions.udig.tools.edit.support.EditBlackboard; import net.refractions.udig.tools.edit.support.EditGeom; import net.refractions.udig.tools.edit.support.PrimitiveShape; import net.refractions.udig.ui.PlatformGIS; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.CommandEvent; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.ICommandListener; import org.eclipse.core.commands.IHandler; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.geotools.data.FeatureEvent; import org.geotools.data.FeatureSource; import org.geotools.data.FeatureStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.GeoTools; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.Id; import org.opengis.filter.identity.Identifier; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; /** * To delete soon EditManager will have an editblackboard. * * @author jones * @since 1.1.0 */ public class EditBlackboardUtil { private static final MathTransform IDENTITY; static { MathTransform tmp = null; try { tmp = CRS.findMathTransform(DefaultGeographicCRS.WGS84, DefaultGeographicCRS.WGS84); } catch (FactoryException e) { // can't happen } IDENTITY = tmp; } private volatile static ViewportModelListener listener; public static final EditBlackboard EMPTY_BLACKBOARD = new EditBlackboard(0, 0, AffineTransform.getTranslateInstance(0, 0), IDENTITY); public static final String EDIT_BLACKBOARD_KEY = "EDIT_BLACKBOARD_KEY_839834"; //$NON-NLS-1$ private static Lock blackboardLock = new ReentrantLock(); public static EditBlackboard getEditBlackboard(IToolContext context, ILayer layer2) { if (layer2 == null || !ApplicationGIS.getOpenMaps().contains(layer2.getMap())) return EMPTY_BLACKBOARD; ILayer layer = layer2; EditBlackboard editBlackBoard; blackboardLock.lock(); try { EditManager editManager = ((EditManager) context.getEditManager()); if (editManager.getEditLayer() != null && editManager.isEditLayerLocked()) { layer = context.getEditManager().getEditLayer(); } editBlackBoard = getEditBlackBoardFromLayer(layer); if (editBlackBoard == null) { MathTransform layerToMapTransform; try { layerToMapTransform = layer.layerToMapTransform(); } catch (IOException e) { EditPlugin.log("", e); //$NON-NLS-1$ layerToMapTransform = IDENTITY; } editBlackBoard = new EditBlackboard(context.getMapDisplay().getWidth(), context.getMapDisplay().getHeight(), context.worldToScreenTransform(), layerToMapTransform); final EditBlackboard bb = editBlackBoard; context.getViewportPane().addPaneListener(new IMapDisplayListener() { public void sizeChanged(MapDisplayEvent event) { if (event.getOldSize() != null && event.getOldSize().width != event.getSize().width) bb.setWidth(event.getSize().width); if (event.getOldSize() != null && event.getOldSize().height != event.getSize().height) bb.setHeight(event.getSize().height); } }); layer.getBlackboard().put(EDIT_BLACKBOARD_KEY, editBlackBoard); } enableViewportListener((ViewportModel) context.getViewportModel()); //Vitalus: moved to EditToolHandler.enableListeners(). // enableClearBlackboardCommand(context); // disabled until I fix the events // enableLayerChangeEventListener(layer, editBlackBoard); } finally { blackboardLock.unlock(); } if (dirtyAreas.get(layer) != null) { openDataChangedDialog(layer, dirtyAreas.get(layer)); } editBlackBoard.setToScreenTransform(context.worldToScreenTransform()); return editBlackBoard; } private static Map<ILayer, ILayerListener> layerListenerMap = new HashMap<ILayer, ILayerListener>(); private static Map<ILayer, Envelope> dirtyAreas = new HashMap<ILayer, Envelope>(); /** * Adds a listener so that the editblackboards can be notified when the data in the feature * store are modified. */ private static synchronized void enableLayerChangeEventListener(final ILayer layer, final EditBlackboard editBlackboard) { ILayerListener listener = layerListenerMap.get(layer); if (listener == null) { listener = new ILayerListener() { public void refresh(LayerEvent event) { if (event.getSource() != layer) { layer.removeListener(this); return; } if (event.getType() != LayerEvent.EventType.EDIT_EVENT) return; EditManager editManager = (EditManager) layer.getMap().getEditManager(); ILayer editlayer = editManager.getSelectedLayer(); if (editManager.getEditLayer() != null && editManager.isEditLayerLocked()) { editlayer = editManager.getEditLayer(); } EditState currentEditState = (EditState) layer.getMap().getBlackboard() .get(EditToolHandler.EDITSTATE); if (editlayer == layer && (currentEditState == EditState.COMMITTING || EditManagerListener.committing == editManager)) return; FeatureEvent editEvent = (FeatureEvent) event.getNewValue(); if (editEvent == null) return; Envelope dirtyArea = dirtyAreas.get(editlayer); if (dirtyArea == null) { dirtyArea = editEvent.getBounds(); } else { dirtyArea.expandToInclude(editEvent.getBounds()); } dirtyAreas.put(editlayer, dirtyArea); if (editlayer == layer && layer.getMap() == ApplicationGIS.getActiveMap()) { openDataChangedDialog(editlayer, dirtyArea); } } }; layerListenerMap.put(layer, listener); } layer.addListener(listener); } static volatile Dialog dialog; public static void openDataChangedDialog(final ILayer layer, final Envelope dirtyArea) { Display d = Display.getCurrent(); if (d == null) d = Display.getDefault(); Condition condition = blackboardLock.newCondition(); try { blackboardLock.lock(); if (dialog != null) { // we're in the display thread, which means that the viewport is repainting since the dialog has any other input blocked. // so just return if (Display.getCurrent() != null) return; // the issue is being resolved we should wait for it to be resolved. while (dialog != null) { try { condition.await(500, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return; } } return; } } finally { blackboardLock.unlock(); } PlatformGIS.syncInDisplayThread(d, new Runnable() { public void run() { try { blackboardLock.lock(); if (dirtyAreas.get(layer) == null) return; dialog = new Dialog(Display.getCurrent().getActiveShell()) { private final int UPDATE = 1; private final int CLEAR = 2; private final int IGNORE = 3; public Control createDialogArea(Composite parent) { Composite comp = new Composite(parent, SWT.NONE); comp.setLayout(new GridLayout(2, false)); Display display = Display.getCurrent(); Label label = new Label(comp, SWT.NONE); label.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); label.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); label.setImage(Dialog.getImage(Dialog.DLG_IMG_WARNING)); label.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); Text text = new Text(comp, SWT.WRAP | SWT.READ_ONLY); text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); text.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); text.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); text.setText(Messages.EditBlackboardUtil_data_changed); return comp; } public void createButtonsForButtonBar(Composite composite) { createButton(composite, UPDATE, Messages.EditBlackboardUtil_update, false); createButton(composite, CLEAR, Messages.EditBlackboardUtil_clear, true); createButton(composite, IGNORE, Messages.EditBlackboardUtil_ignore, false); } public void buttonPressed(int button) { switch (button) { case UPDATE: { boolean ok = MessageDialog.openConfirm(getParentShell(), Messages.EditBlackboardUtil_Update_Selection, Messages.EditBlackboardUtil_update_selection_confirmation); if (ok) { try { ProgressMonitorDialog d = new ProgressMonitorDialog(this.getParentShell()); blackboardLock.unlock(); d.run(false, false, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) { updateFeatures(layer, monitor, dirtyArea); } }); okPressed(); } catch (Exception e) { EditPlugin.log("", e); //$NON-NLS-1$ } } break; } case CLEAR: { boolean ok = MessageDialog.openConfirm(getParentShell(), Messages.EditBlackboardUtil_clear_selection, Messages.EditBlackboardUtil_changes_will_be_lost); if (ok) { blackboardLock.unlock(); (getEditBlackBoardFromLayer(layer)).clear(); layer.getMap().getBlackboard().put(EditToolHandler.CURRENT_SHAPE, null); ((EditManager) layer.getMap().getEditManager()).setEditFeature(null, null); okPressed(); } break; } case IGNORE: boolean ok = MessageDialog.openConfirm(getParentShell(), Messages.EditBlackboardUtil_ignore_change, Messages.EditBlackboardUtil_changes_will_be_overwritten); if (ok) { okPressed(); } break; default: cancelPressed(); break; } } public void okPressed() { dirtyAreas.remove(layer); super.okPressed(); } }; dialog.setBlockOnOpen(true); dialog.open(); } finally { blackboardLock.unlock(); dialog = null; } } }); } /** * Updates the features in the "dirty Area" so that the {@link EditGeom}s reflect the actual state of the stored features. * Any changes to the {@link EditGeom} will be lost. * * @param layer that needs to be updated. * @param monitor progress monitor * @param dirtyArea area that needs to be updated. */ public static void updateFeatures(ILayer layer, IProgressMonitor monitor, Envelope dirtyArea) { EditBlackboard bb = getEditBlackBoardFromLayer(layer); List<EditGeom> geoms = bb.getGeoms(); monitor.beginTask(Messages.EditBlackboardUtil_updating_selected_features, geoms.size()); PrimitiveShape shape = (PrimitiveShape) layer.getMap().getBlackboard().get(EditToolHandler.CURRENT_SHAPE); EditManager editManager = (EditManager) layer.getMap().getEditManager(); FilterFactory factory = CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints()); Set<Identifier> ids = new HashSet<Identifier>(); for (EditGeom geom : geoms) { ids.add(factory.featureId(geom.getFeatureIDRef().get())); } Id fidFilter = factory.id(ids); Filter filter = factory.and(fidFilter, layer.createBBoxFilter(dirtyArea, new NullProgressMonitor())); try { FeatureSource<SimpleFeatureType, SimpleFeature> fs = layer.getResource(FeatureSource.class, monitor); FeatureCollection<SimpleFeatureType, SimpleFeature> results = fs.getFeatures(filter); FeatureIterator<SimpleFeature> reader = results.features(); try { int read = 0; boolean selectedFound = false; List<EditGeom> toRemove = new ArrayList<EditGeom>(); while (reader.hasNext()) { int count = geoms.size() - read; monitor.setTaskName(MessageFormat.format(Messages.EditBlackboardUtil_count_remaining, new Object[] { count })); read++; SimpleFeature feature = reader.next(); for (EditGeom geom : geoms) { if (feature.getID().equals(geom.getFeatureIDRef().get())) { toRemove.add(geom); } } Map<Geometry, EditGeom> mapping = bb.addGeometry((Geometry) feature.getDefaultGeometry(), feature.getID()); if (feature.getID().equals(shape.getEditGeom().getFeatureIDRef().get())) { editManager.setEditFeature(feature, (Layer) layer); layer.getMap().getBlackboard().put(EditToolHandler.CURRENT_SHAPE, mapping.values().iterator().next().getShell()); selectedFound = true; } monitor.worked(1); } if (!selectedFound) { layer.getMap().getBlackboard().put(EditToolHandler.CURRENT_SHAPE, null); editManager.setEditFeature(null, null); } bb.removeGeometries(toRemove); } finally { reader.close(); monitor.done(); } } catch (IOException e) { EditPlugin.log("", e); //$NON-NLS-1$ } } /** * Returns the Editblackboard from the layer if it is on the layer or null if it hasn't been created yet. * {@link #getEditBlackboard(IToolContext, ILayer)} will create and initialize the blackbaord * * @param layer * @return */ private static EditBlackboard getEditBlackBoardFromLayer(ILayer layer) { blackboardLock.lock(); try { return (EditBlackboard) layer.getBlackboard().get(EDIT_BLACKBOARD_KEY); } finally { blackboardLock.unlock(); } } /** * Command handler for the command "net.refractions.udig.tool.edit.clearAction" * to clear EditBlackboard. */ static IHandler clearEditBlackboardHandler; /** * Listener for "net.refractions.udig.tool.edit.clearAction" command. */ static ICommandListener clearEditBlackboardCommandListener; /** * Sets the command handler for the "ESC" button. The handler clears current edit blackboard. * <p> * Called from <code>EditToolHandler.enableListeners()</code>. * * @param context */ static synchronized void enableClearBlackboardCommand(final IToolContext context) { if (clearEditBlackboardHandler == null) { clearEditBlackboardHandler = new AbstractHandler() { public Object execute(ExecutionEvent event) throws ExecutionException { Tool tool = ApplicationGIS.getToolManager().getActiveTool(); if (tool instanceof AbstractEditTool) { EditToolHandler editToolHandler = ((AbstractEditTool) tool).getHandler(); List<Behaviour> behaviours = editToolHandler.getCancelBehaviours(); UndoableComposite compositeCommand = new UndoableComposite(); for (Behaviour behaviour : behaviours) { if (behaviour.isValid(editToolHandler)) { UndoableMapCommand command = behaviour.getCommand(editToolHandler); if (command != null) compositeCommand.getCommands().add(command); } } if (!compositeCommand.getCommands().isEmpty()) editToolHandler.getContext().sendASyncCommand(compositeCommand); } return null; } }; } ICommandService service = (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); Command command = service.getCommand("net.refractions.udig.tool.edit.clearAction"); //$NON-NLS-1$ command.setHandler(clearEditBlackboardHandler); if (clearEditBlackboardCommandListener == null) { clearEditBlackboardCommandListener = new ICommandListener() { public void commandChanged(CommandEvent commandEvent) { if (commandEvent.isHandledChanged()) { commandEvent.getCommand().removeCommandListener(this); clearEditBlackboardCommandListener = null; IMap map = ApplicationGIS.getActiveMap(); resetBlackboards(map); } } }; command.addCommandListener(clearEditBlackboardCommandListener); } } /** * Removes a command handler for the "ESC" button. * <p> * Called from <code>EditToolHandler.disableListeners()</code>. */ static synchronized void disableClearBlackboardCommand() { ICommandService service = (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); Command command = service.getCommand("net.refractions.udig.tool.edit.clearAction"); //$NON-NLS-1$ if (clearEditBlackboardCommandListener != null) { command.removeCommandListener(clearEditBlackboardCommandListener); clearEditBlackboardCommandListener = null; } command.setHandler(null); } /** * Disables listeners so that they will not get events. * Should be called when tool is disabled or when a blackboard is no longer required. * * @see #getEditBlackboard(IToolContext, ILayer) (it enables listeners). */ public static void doneListening() { disableViewportListener(); // disableLayerEvents(); } /** * Disables the listeners listening to layers for edit events. * @see #enableLayerChangeEventListener(ILayer, EditBlackboard) */ public static synchronized void disableLayerEvents() { Collection<Map.Entry<ILayer, ILayerListener>> entries = layerListenerMap.entrySet(); for (Map.Entry<ILayer, ILayerListener> entry : entries) { entry.getKey().removeListener(entry.getValue()); } layerListenerMap.clear(); } /** * Returns the selectedLayer or if it is not editable or not visible then the edit layer will be * * @return */ public static ILayer findEditLayer(IToolContext context) { ILayer layer = null; IMap map = context.getMap(); // The selected layer will become the edit layer! If it's a featureStore and editable of // course. ILayer selectedLayer = ((EditManagerImpl) map.getEditManager()).getSelectedLayer(); if (isEditable(selectedLayer)) { return selectedLayer; } // Otherwise we'll fall back to the current editLayer if (isEditable(map.getEditManager().getEditLayer())) return map.getEditManager().getEditLayer(); // If all else fails we'll iterate through the layer and find the first layer eligable for // editing. // We should really never get here though. for (Iterator<ILayer> iter = map.getMapLayers().iterator(); iter.hasNext();) { layer = iter.next(); if (isEditable(layer)) break; } return layer; } /** * Checks that a layer has a FeatureStore, is editable and is visible. */ private static boolean isEditable(ILayer layer) { return layer != null && layer.hasResource(FeatureStore.class) && layer.getInteraction(Interaction.EDIT) && layer.isVisible(); //$NON-NLS-1$ } @SuppressWarnings("unchecked") private synchronized static void enableViewportListener(ViewportModel model) { disableViewportListener(); listener = new ViewportModelListener(model); listener.model.eAdapters().add(listener); } private synchronized static void disableViewportListener() { if (listener != null) listener.model.eAdapters().remove(listener); listener = null; } /** * Listens for changes to the viewport model and transforms the edit blackboards to the new CRS or bounds * */ static class ViewportModelListener extends AdapterImpl { ViewportModel model; private ILayer layer; ViewportModelListener(ViewportModel model) { this.model = model; this.layer = model.getMap().getEditManager().getSelectedLayer(); } @Override public void notifyChanged(Notification msg) { if (listener == null) { model.eAdapters().remove(listener); listener = this; return; } switch (msg.getFeatureID(ViewportModel.class)) { case RenderPackage.VIEWPORT_MODEL__BOUNDS: { EditBlackboard editBlackBoard = getEditBlackBoardFromLayer(layer); if (editBlackBoard == null) { return; // cannot notify edit blackboard } editBlackBoard.setToScreenTransform(model.worldToScreenTransform()); break; } case RenderPackage.VIEWPORT_MODEL__CRS: { EditBlackboard editBlackBoard = getEditBlackBoardFromLayer(layer); if (editBlackBoard == null) { return; // cannot notify edit blackboard } try { editBlackBoard.setMapLayerTransform(layer.mapToLayerTransform()); } catch (IOException e) { EditPlugin.log("", e); //$NON-NLS-1$ } editBlackBoard.setToScreenTransform(model.worldToScreenTransform()); break; } } } } /** * clears edit blackboard for all layers in a given map... Critical to prevent memory leaks */ public static void resetBlackboards(IMap map) { if (map != null) { blackboardLock.lock(); try { List<ILayer> layers = map.getMapLayers(); for (ILayer layer : layers) { EditBlackboard blackboard = getEditBlackBoardFromLayer(layer); if (blackboard != null) { blackboard.clear(); layer.getBlackboard().put(EDIT_BLACKBOARD_KEY, null); } } } finally { blackboardLock.unlock(); } } } }