Ticket #19199: 19199.threading.2.patch
| File 19199.threading.2.patch, 20.5 KB (added by , 5 years ago) |
|---|
-
.project
diff --git a/.project b/.project index c82979ddf..844e2ad74 100644
a b 1 1 <?xml version="1.0" encoding="UTF-8"?> 2 2 <projectDescription> 3 <name>JOSM </name>3 <name>JOSM_clean</name> 4 4 <comment></comment> 5 5 <projects> 6 6 </projects> -
src/org/openstreetmap/josm/actions/SimplifyWayAction.java
diff --git a/src/org/openstreetmap/josm/actions/SimplifyWayAction.java b/src/org/openstreetmap/josm/actions/SimplifyWayAction.java index de0c78790..66cb6bffb 100644
a b import java.util.LinkedList; 17 17 import java.util.List; 18 18 import java.util.Objects; 19 19 import java.util.Set; 20 import java.util.concurrent.ForkJoinTask; 21 import java.util.concurrent.atomic.AtomicBoolean; 22 import java.util.concurrent.atomic.AtomicReference; 20 23 import java.util.stream.Collectors; 24 import java.util.stream.Stream; 21 25 22 26 import javax.swing.BorderFactory; 23 27 import javax.swing.JCheckBox; … … import org.openstreetmap.josm.gui.HelpAwareOptionPane; 46 50 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 47 51 import org.openstreetmap.josm.gui.MainApplication; 48 52 import org.openstreetmap.josm.gui.Notification; 53 import org.openstreetmap.josm.gui.util.GuiHelper; 49 54 import org.openstreetmap.josm.spi.preferences.Config; 50 55 import org.openstreetmap.josm.spi.preferences.IPreferences; 51 56 import org.openstreetmap.josm.tools.GBC; 52 57 import org.openstreetmap.josm.tools.ImageProvider; 58 import org.openstreetmap.josm.tools.Logging; 53 59 import org.openstreetmap.josm.tools.Shortcut; 54 60 import org.openstreetmap.josm.tools.StreamUtils; 55 61 … … import org.openstreetmap.josm.tools.StreamUtils; 58 64 * @since 2575 59 65 */ 60 66 public class SimplifyWayAction extends JosmAction { 67 private static final int MAX_WAYS_NO_ASK = 10; 61 68 62 69 /** 63 70 * Constructs a new {@code SimplifyWayAction}. … … public class SimplifyWayAction extends JosmAction { 120 127 } 121 128 122 129 /** 123 * Asks the user for max-err value used to simplify ways, if not remembered before 130 * Asks the user for max-err value used to simplify ways, if not remembered before. This is not asynchronous. 124 131 * @param ways the ways that are being simplified (to show estimated number of nodes to be removed) 125 132 * @param text the text being shown 126 133 * @param auto whether it's called automatically (conversion) or by the user 127 134 * @return the max-err value or -1 if canceled 135 * @see #askSimplifyWays(Collection, String, boolean, SimplifyWayActionListener...) 128 136 * @since 16566 129 137 */ 130 138 public static double askSimplifyWays(List<Way> ways, String text, boolean auto) { 139 AtomicReference<Double> returnVar = new AtomicReference<>((double) -1); 140 AtomicBoolean hasRun = new AtomicBoolean(); 141 askSimplifyWays(ways, text, auto, result -> { 142 synchronized (returnVar) { 143 returnVar.set(result); 144 returnVar.notifyAll(); 145 hasRun.set(true); 146 } 147 }); 148 if (!SwingUtilities.isEventDispatchThread()) { 149 synchronized (returnVar) { 150 while (!hasRun.get()) { 151 try { 152 // Wait 2 seconds 153 returnVar.wait(2000); 154 } catch (InterruptedException e) { 155 Logging.error(e); 156 Thread.currentThread().interrupt(); 157 } 158 } 159 } 160 } 161 return returnVar.get(); 162 } 163 164 /** 165 * Asks the user for max-err value used to simplify ways, if not remembered before 166 * @param waysCollection the ways that are being simplified (to show estimated number of nodes to be removed) 167 * @param text the text being shown 168 * @param auto whether it's called automatically (conversion) or by the user 169 * @param listeners Listeners to call when the max error is calculated 170 * @since xxx 171 */ 172 public static void askSimplifyWays(Collection<Way> waysCollection, String text, boolean auto, SimplifyWayActionListener... listeners) { 173 final Collection<SimplifyWayActionListener> realListeners = Stream.of(listeners).filter(Objects::nonNull).collect(Collectors.toList()); 174 final List<Way> ways; 175 if (waysCollection instanceof List) { 176 ways = (List<Way>) waysCollection; 177 } else { 178 ways = new ArrayList<>(waysCollection); 179 } 131 180 IPreferences s = Config.getPref(); 132 181 String key = "simplify-way." + (auto ? "auto." : ""); 133 182 String keyRemember = key + "remember"; … … public class SimplifyWayAction extends JosmAction { 135 184 136 185 String r = s.get(keyRemember, "ask"); 137 186 if (auto && "no".equals(r)) { 138 return -1; 187 realListeners.forEach(listener -> listener.maxErrorListener(-1)); 188 return; 139 189 } else if ("yes".equals(r)) { 140 return s.getDouble(keyError, 3.0); 190 realListeners.forEach(listener -> listener.maxErrorListener(s.getDouble(keyError, 3.0))); 191 return; 141 192 } 142 193 143 194 JPanel p = new JPanel(new GridBagLayout()); … … public class SimplifyWayAction extends JosmAction { 178 229 } else { 179 230 ed.setButtonIcons("ok", "cancel"); 180 231 } 181 182 int ret = ed.showDialog().getValue(); 183 double val = (double) n.getValue(); 184 if (l.lastCommand != null && l.lastCommand.equals(UndoRedoHandler.getInstance().getLastCommand())) { 185 UndoRedoHandler.getInstance().undo(); 186 l.lastCommand = null; 187 } 188 if (ret == 1) { 189 s.putDouble(keyError, val); 190 if (c.isSelected()) { 191 s.put(keyRemember, "yes"); 232 ed.addResultListener(ret -> { 233 final double result; 234 double val = (double) n.getValue(); 235 if (l.lastCommand != null && l.lastCommand.equals(UndoRedoHandler.getInstance().getLastCommand())) { 236 UndoRedoHandler.getInstance().undo(); 237 l.lastCommand = null; 192 238 } 193 return val; 194 } else { 195 if (auto && c.isSelected()) { //do not remember cancel for manual simplify, otherwise nothing would happen 196 s.put(keyRemember, "no"); 239 if (ret == 1) { 240 s.putDouble(keyError, val); 241 if (c.isSelected()) { 242 s.put(keyRemember, "yes"); 243 } 244 result = val; 245 } else { 246 if (auto && c.isSelected()) { //do not remember cancel for manual simplify, otherwise nothing would happen 247 s.put(keyRemember, "no"); 248 } 249 result = -1; 197 250 } 198 return -1; 199 } 251 realListeners.forEach(listener -> listener.maxErrorListener(result)); 252 }); 253 GuiHelper.runInEDT(ed::showDialog); 200 254 } 201 255 202 256 @Override 203 257 public void actionPerformed(ActionEvent e) { 204 258 DataSet ds = getLayerManager().getEditDataSet(); 205 ds.update(() -> { 206 List<Way> ways = ds.getSelectedWays().stream() 207 .filter(p -> !p.isIncomplete()) 208 .collect(Collectors.toList()); 209 if (ways.isEmpty()) { 210 alertSelectAtLeastOneWay(); 211 return; 212 } else if (!confirmWayWithNodesOutsideBoundingBox(ways) || (ways.size() > 10 && !confirmSimplifyManyWays(ways.size()))) { 213 return; 214 } 259 List<Way> ways = ds.getSelectedWays().stream() 260 .filter(p -> !p.isIncomplete()) 261 .collect(Collectors.toList()); 262 if (ways.isEmpty()) { 263 alertSelectAtLeastOneWay(); 264 return; 265 } else if (!confirmWayWithNodesOutsideBoundingBox(ways) || (ways.size() > MAX_WAYS_NO_ASK && !confirmSimplifyManyWays(ways.size()))) { 266 return; 267 } 215 268 216 String lengthstr = SystemOfMeasurement.getSystemOfMeasurement().getDistText(217 ways.stream().mapToDouble(Way::getLength).sum());269 String lengthstr = SystemOfMeasurement.getSystemOfMeasurement().getDistText( 270 ways.stream().mapToDouble(Way::getLength).sum()); 218 271 219 double err = askSimplifyWays(ways, trn(220 "You are about to simplify {0} way with a total length of {1}.",221 "You are about to simplify {0} ways with a total length of {1}.",222 ways.size(), ways.size(), lengthstr), false);272 double err = askSimplifyWays(ways, trn( 273 "You are about to simplify {0} way with a total length of {1}.", 274 "You are about to simplify {0} ways with a total length of {1}.", 275 ways.size(), ways.size(), lengthstr), false); 223 276 224 if (err > 0) { 225 simplifyWays(ways, err); 226 } 227 }); 277 if (err > 0) { 278 simplifyWays(ways, err); 279 } 228 280 } 229 281 230 282 /** … … public class SimplifyWayAction extends JosmAction { 308 360 * @since 16566 (private) 309 361 */ 310 362 private static SequenceCommand buildSimplifyWaysCommand(List<Way> ways, double threshold) { 311 Collection<Command> allCommands = ways.stream() 363 // Use `parallelStream` since this can significantly decrease the amount of time taken for large numbers of ways 364 Collection<Command> allCommands = ways.parallelStream() 312 365 .map(way -> createSimplifyCommand(way, threshold)) 313 366 .filter(Objects::nonNull) 314 367 .collect(StreamUtils.toUnmodifiableList()); … … public class SimplifyWayAction extends JosmAction { 499 552 private final JLabel nodesToRemove; 500 553 private final SpinnerNumberModel errorModel; 501 554 private final List<Way> ways; 555 private ForkJoinTask<?> futureNodesToRemove; 502 556 503 557 SimplifyChangeListener(JLabel nodesToRemove, SpinnerNumberModel errorModel, List<Way> ways) { 504 558 this.nodesToRemove = nodesToRemove; … … public class SimplifyWayAction extends JosmAction { 508 562 509 563 @Override 510 564 public void stateChanged(ChangeEvent e) { 511 if (Objects.equals(UndoRedoHandler.getInstance().getLastCommand(), lastCommand)) { 512 UndoRedoHandler.getInstance().undo(); 565 MainApplication.worker.execute(() -> { 566 synchronized (errorModel) { 567 double threshold = errorModel.getNumber().doubleValue(); 568 if (futureNodesToRemove != null) { 569 futureNodesToRemove.cancel(false); 570 } 571 futureNodesToRemove = MainApplication.getForkJoinPool().submit(new ForkJoinTask<Command>() { 572 private Command result; 573 574 @Override 575 public Command getRawResult() { 576 return result; 577 } 578 579 @Override 580 protected void setRawResult(Command value) { 581 this.result = value; 582 } 583 584 @Override 585 protected boolean exec() { 586 synchronized (SimplifyChangeListener.this) { 587 if (!this.isCancelled()) { 588 result = updateNodesToRemove(ways, threshold); 589 return true; 590 } 591 } 592 return false; 593 } 594 }); 595 } 596 }); 597 } 598 599 private Command updateNodesToRemove(List<Way> ways, double threshold) { 600 // This must come first in order for everything else to be accurate 601 if (lastCommand != null && Objects.equals(lastCommand, UndoRedoHandler.getInstance().getLastCommand())) { 602 // We need to wait to avoid cases where we cannot undo due threading issues. 603 GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().undo()); 513 604 } 514 double threshold = errorModel.getNumber().doubleValue(); 515 int removeNodes = simplifyWaysCountNodesRemoved(ways, threshold); 516 nodesToRemove.setText(trn( 517 "(about {0} node to remove)", 518 "(about {0} nodes to remove)", removeNodes, removeNodes)); 519 lastCommand = SimplifyWayAction.buildSimplifyWaysCommand(ways, threshold); 520 if (lastCommand != null) { 521 UndoRedoHandler.getInstance().add(lastCommand); 605 final Command command = buildSimplifyWaysCommand(ways, threshold); 606 // This is the same code from simplifyWaysCountNodesRemoved, but is duplicated for performance reasons 607 // (this avoids running the code to calculate the command twice). 608 int removeNodes = command == null ? 0 : (int) command.getParticipatingPrimitives().stream() 609 .filter(Node.class::isInstance) 610 .count(); 611 int totalNodes = ways.parallelStream().mapToInt(Way::getNodesCount).sum(); 612 int percent = (int) Math.round(100 * removeNodes / (double) totalNodes); 613 GuiHelper.runInEDTAndWait(() -> 614 nodesToRemove.setText(trn( 615 "(about {0} node to remove ({1}%))", 616 "(about {0} nodes to remove ({1}%))", removeNodes, removeNodes, percent)) 617 ); 618 lastCommand = command; 619 if (lastCommand != null && ways.size() < MAX_WAYS_NO_ASK) { 620 GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().add(lastCommand)); 522 621 } 622 return lastCommand; 523 623 } 524 624 } 625 626 627 /** 628 * A listener that is called when the {@link #askSimplifyWays} method is called. 629 * @since xxx 630 */ 631 @FunctionalInterface 632 public interface SimplifyWayActionListener { 633 /** 634 * Called when the max error is set 635 * @param maxError The max error 636 */ 637 void maxErrorListener(double maxError); 638 } 525 639 } -
src/org/openstreetmap/josm/gui/ExtendedDialog.java
diff --git a/src/org/openstreetmap/josm/gui/ExtendedDialog.java b/src/org/openstreetmap/josm/gui/ExtendedDialog.java index a11295f58..fcc616a4d 100644
a b import org.openstreetmap.josm.tools.GBC; 42 42 import org.openstreetmap.josm.tools.ImageProvider; 43 43 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 44 44 import org.openstreetmap.josm.tools.InputMapUtils; 45 import org.openstreetmap.josm.tools.ListenerList; 45 46 import org.openstreetmap.josm.tools.Logging; 46 47 import org.openstreetmap.josm.tools.Utils; 47 48 … … public class ExtendedDialog extends JDialog implements IExtendedDialog { 95 96 private transient Icon icon; 96 97 private final boolean modal; 97 98 private boolean focusOnDefaultButton; 99 private final ListenerList<ExtendedDialogResultListener> resultListeners = ListenerList.create(); 98 100 99 101 /** true, if the dialog should include a help button */ 100 102 private boolean showHelpButton; … … public class ExtendedDialog extends JDialog implements IExtendedDialog { 241 243 // Check if the user has set the dialog to not be shown again 242 244 if (toggleCheckState()) { 243 245 result = toggleValue; 246 this.finishResult(); 244 247 return this; 245 248 } 246 249 … … public class ExtendedDialog extends JDialog implements IExtendedDialog { 388 391 protected void buttonAction(int buttonIndex, ActionEvent evt) { 389 392 result = buttonIndex+1; 390 393 setVisible(false); 394 this.finishResult(); 391 395 } 392 396 393 397 /** … … public class ExtendedDialog extends JDialog implements IExtendedDialog { 419 423 getClass().getName(), actionEvent, new Exception().getStackTrace()[1]); 420 424 } 421 425 setVisible(false); 426 ExtendedDialog.this.finishResult(); 422 427 } 423 428 }; 424 429 … … public class ExtendedDialog extends JDialog implements IExtendedDialog { 561 566 HelpBrowser.setUrlForHelpTopic(helpTopic); 562 567 } 563 568 } 569 570 /** 571 * Add a listener for the results. This makes this dialog non-modal, and stops blocking the EDT. 572 * @param listener The listener to add 573 * @since xxx 574 */ 575 public void addResultListener(ExtendedDialogResultListener listener) { 576 if (this.isModal()) { 577 this.setModal(false); 578 } 579 this.resultListeners.addListener(listener); 580 } 581 582 /** 583 * Remove a result listener. If there are no more result listeners, this dialog will become modal, and will block the EDT. 584 * @param listener The listener to remove 585 * @since xxx 586 */ 587 public void removeResultListener(ExtendedDialogResultListener listener) { 588 this.resultListeners.removeListener(listener); 589 if (!this.resultListeners.hasListeners()) { 590 this.setModal(true); 591 } 592 } 593 594 /** 595 * Call when the result has been calculated. There is no point in keeping the listeners, so remove them all. 596 */ 597 private void finishResult() { 598 this.resultListeners.fireEvent(listener -> listener.parseResult(result)); 599 final List<ExtendedDialogResultListener> listeners = new ArrayList<>(); 600 this.resultListeners.fireEvent(listeners::add); 601 for (ExtendedDialogResultListener listener : listeners) { 602 this.removeResultListener(listener); 603 } 604 } 605 606 /** 607 * A listener for results 608 * @since xxx 609 */ 610 public interface ExtendedDialogResultListener { 611 /** 612 * Do something with a result 613 * @param result The result when the dialog is finished 614 */ 615 void parseResult(int result); 616 } 564 617 } -
src/org/openstreetmap/josm/gui/MainApplication.java
diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java index 3609f845c..bba626e49 100644
a b import java.util.Set; 44 44 import java.util.TreeSet; 45 45 import java.util.concurrent.ExecutorService; 46 46 import java.util.concurrent.Executors; 47 import java.util.concurrent.ForkJoinPool; 47 48 import java.util.concurrent.Future; 48 49 import java.util.logging.Level; 49 50 import java.util.stream.Collectors; … … public class MainApplication { 212 213 */ 213 214 public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY); 214 215 216 /** 217 * This class literally exists to ensure that consumers cannot shut the pool down. 218 * @since xxx 219 */ 220 private static class ForkJoinPoolAlways extends ForkJoinPool { 221 ForkJoinPoolAlways() { 222 // Leave a processor for the UI. 223 super(Math.max(1, Runtime.getRuntime().availableProcessors() - 1), ForkJoinPool.defaultForkJoinWorkerThreadFactory, 224 new BugReportExceptionHandler(), false); 225 } 226 227 @Override 228 public void shutdown() { 229 // Do nothing. This is functionally the same as for the commonPool. 230 } 231 232 @Override 233 public List<Runnable> shutdownNow() { 234 // Do nothing. This is functionally the same as for the commonPool. 235 return Collections.emptyList(); 236 } 237 } 238 239 // If there is a security manager, ForkJoinPool.commonPool uses an "InnocuousForkJoinWorkerThreadFactory", which can kill stuff calling 240 // repaint. Otherwise, the common pool is better from (most) perspectives. 241 private static final ForkJoinPool forkJoinPool = System.getSecurityManager() != null ? new ForkJoinPoolAlways() : ForkJoinPool.commonPool(); 242 243 /** 244 * Get a generic {@link ForkJoinPool}. 245 * @return A ForkJoinPool to use. Calling {@link ForkJoinPool#shutdown()} or {@link ForkJoinPool#shutdownNow()} has no effect. 246 * @since xxx 247 */ 248 public static final ForkJoinPool getForkJoinPool() { 249 return forkJoinPool; 250 } 251 215 252 /** 216 253 * Provides access to the layers displayed in the main view. 217 254 */ … … public class MainApplication { 283 320 * Listener that sets the enabled state of undo/redo menu entries. 284 321 */ 285 322 final CommandQueueListener redoUndoListener = (queueSize, redoSize) -> { 286 menu.undo.setEnabled(queueSize > 0);287 menu.redo.setEnabled(redoSize > 0);323 GuiHelper.runInEDT(() -> menu.undo.setEnabled(queueSize > 0)); 324 GuiHelper.runInEDT(() -> menu.redo.setEnabled(redoSize > 0)); 288 325 }; 289 326 290 327 /**
