Ticket #9446: 9446_v2.patch
File 9446_v2.patch, 32.3 KB (added by , 18 months ago) |
---|
-
src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
64 64 /** 65 65 * Dialog that pops up when the user closes a layer with modified data. 66 66 * 67 * It asks for confirmation that all modification should be discarded and offers67 * It asks for confirmation that all modifications should be discarded and offer 68 68 * to save the layers to file or upload to server, depending on the type of layer. 69 69 */ 70 70 public class SaveLayersDialog extends JDialog implements TableModelListener { … … 556 556 for (final SaveLayerInfo layerInfo: toUpload) { 557 557 AbstractModifiableLayer layer = layerInfo.getLayer(); 558 558 if (canceled) { 559 model.setUploadState(layer, UploadOrSaveState.CANCELED);559 GuiHelper.runInEDTAndWait(() -> model.setUploadState(layer, UploadOrSaveState.CANCELED)); 560 560 continue; 561 561 } 562 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));562 GuiHelper.runInEDTAndWait(() -> monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()))); 563 563 564 // checkPreUploadConditions must not be run in the EDT to avoid deadlocks 564 565 if (!UploadAction.checkPreUploadConditions(layer)) { 565 model.setUploadState(layer, UploadOrSaveState.FAILED);566 GuiHelper.runInEDTAndWait(() -> model.setUploadState(layer, UploadOrSaveState.FAILED)); 566 567 continue; 567 568 } 568 569 569 AbstractUploadDialog dialog = layer.getUploadDialog(); 570 if (dialog != null) { 571 dialog.setVisible(true); 572 if (dialog.isCanceled()) { 573 model.setUploadState(layer, UploadOrSaveState.CANCELED); 574 continue; 575 } 576 dialog.rememberUserInput(); 577 } 570 GuiHelper.runInEDTAndWait(() -> uploadLayersUploadModelStateOnFinish(layer)); 571 currentTask = null; 572 } 573 } 578 574 579 currentTask = layer.createUploadTask(monitor); 580 if (currentTask == null) { 581 model.setUploadState(layer, UploadOrSaveState.FAILED); 582 continue; 583 } 584 Future<?> currentFuture = worker.submit(currentTask); 585 try { 586 // wait for the asynchronous task to complete 587 currentFuture.get(); 588 } catch (CancellationException e) { 589 Logging.trace(e); 575 /** 576 * Update the {@link #model} state on upload finish 577 * @param layer The layer that has been saved 578 */ 579 private void uploadLayersUploadModelStateOnFinish(AbstractModifiableLayer layer) { 580 AbstractUploadDialog dialog = layer.getUploadDialog(); 581 if (dialog != null) { 582 dialog.setVisible(true); 583 if (dialog.isCanceled()) { 590 584 model.setUploadState(layer, UploadOrSaveState.CANCELED); 591 } catch (InterruptedException | ExecutionException e) { 592 Logging.error(e); 593 model.setUploadState(layer, UploadOrSaveState.FAILED); 594 ExceptionDialogUtil.explainException(e); 585 return; 595 586 } 596 if (currentTask.isCanceled()) { 597 model.setUploadState(layer, UploadOrSaveState.CANCELED); 598 } else if (currentTask.isFailed()) { 599 Logging.error(currentTask.getLastException()); 600 ExceptionDialogUtil.explainException(currentTask.getLastException()); 601 model.setUploadState(layer, UploadOrSaveState.FAILED); 602 } else { 603 model.setUploadState(layer, UploadOrSaveState.OK); 604 } 605 currentTask = null; 587 dialog.rememberUserInput(); 606 588 } 589 590 currentTask = layer.createUploadTask(monitor); 591 if (currentTask == null) { 592 model.setUploadState(layer, UploadOrSaveState.FAILED); 593 return; 594 } 595 Future<?> currentFuture = worker.submit(currentTask); 596 try { 597 // wait for the asynchronous task to complete 598 currentFuture.get(); 599 } catch (CancellationException e) { 600 Logging.trace(e); 601 model.setUploadState(layer, UploadOrSaveState.CANCELED); 602 } catch (InterruptedException | ExecutionException e) { 603 Logging.error(e); 604 model.setUploadState(layer, UploadOrSaveState.FAILED); 605 ExceptionDialogUtil.explainException(e); 606 } 607 if (currentTask.isCanceled()) { 608 model.setUploadState(layer, UploadOrSaveState.CANCELED); 609 } else if (currentTask.isFailed()) { 610 Logging.error(currentTask.getLastException()); 611 ExceptionDialogUtil.explainException(currentTask.getLastException()); 612 model.setUploadState(layer, UploadOrSaveState.FAILED); 613 } else { 614 model.setUploadState(layer, UploadOrSaveState.OK); 615 } 607 616 } 608 617 609 618 protected void saveLayers(List<SaveLayerInfo> toSave) { … … 672 681 673 682 @Override 674 683 public void run() { 684 GuiHelper.runInEDTAndWait(() -> model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING)); 685 // We very specifically do not want to block the EDT or the worker thread when validating 686 List<SaveLayerInfo> toUpload = model.getLayersToUpload(); 687 if (!toUpload.isEmpty()) { 688 uploadLayers(toUpload); 689 } 675 690 GuiHelper.runInEDTAndWait(() -> { 676 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);677 List<SaveLayerInfo> toUpload = model.getLayersToUpload();678 if (!toUpload.isEmpty()) {679 uploadLayers(toUpload);680 }681 691 List<SaveLayerInfo> toSave = model.getLayersToSave(); 682 692 if (!toSave.isEmpty()) { 683 693 saveLayers(toSave); -
src/org/openstreetmap/josm/actions/UploadAction.java
11 11 import java.util.List; 12 12 import java.util.Map; 13 13 import java.util.Optional; 14 import java.util.concurrent.CompletableFuture; 15 import java.util.concurrent.ExecutionException; 16 import java.util.concurrent.Future; 17 import java.util.function.Consumer; 14 18 15 19 import javax.swing.JOptionPane; 16 20 … … 36 40 import org.openstreetmap.josm.io.UploadStrategySpecification; 37 41 import org.openstreetmap.josm.spi.preferences.Config; 38 42 import org.openstreetmap.josm.tools.ImageProvider; 43 import org.openstreetmap.josm.tools.JosmRuntimeException; 39 44 import org.openstreetmap.josm.tools.Logging; 40 45 import org.openstreetmap.josm.tools.Shortcut; 41 46 import org.openstreetmap.josm.tools.Utils; … … 42 47 43 48 /** 44 49 * Action that opens a connection to the osm server and uploads all changes. 45 * 50 * <p> 46 51 * A dialog is displayed asking the user to specify a rectangle to grab. 47 52 * The url and account settings from the preferences are used. 53 * <p> 54 * If the upload fails, this action offers various options to resolve conflicts. 48 55 * 49 * If the upload fails this action offers various options to resolve conflicts.50 *51 56 * @author imi 52 57 */ 53 58 public class UploadAction extends AbstractUploadAction { … … 55 60 * The list of upload hooks. These hooks will be called one after the other 56 61 * when the user wants to upload data. Plugins can insert their own hooks here 57 62 * if they want to be able to veto an upload. 58 * 63 * <p> 59 64 * Be default, the standard upload dialog is the only element in the list. 60 65 * Plugins should normally insert their code before that, so that the upload 61 66 * dialog is the last thing shown before upload really starts; on occasion … … 67 72 private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload"; 68 73 69 74 static { 70 /** 71 * Calls validator before upload. 72 */ 75 /** Calls validator before upload. */ 73 76 UPLOAD_HOOKS.add(new ValidateUploadHook()); 74 77 75 /** 76 * Fixes database errors 77 */ 78 /** Fixes database errors */ 78 79 UPLOAD_HOOKS.add(new FixDataHook()); 79 80 80 /** 81 * Checks server capabilities before upload. 82 */ 81 /** Checks server capabilities before upload. */ 83 82 UPLOAD_HOOKS.add(new ApiPreconditionCheckerHook()); 84 83 85 /** 86 * Adjusts the upload order of new relations 87 */ 84 /** Adjusts the upload order of new relations */ 88 85 UPLOAD_HOOKS.add(new RelationUploadOrderHook()); 89 86 90 /** 91 * Removes discardable tags like created_by on modified objects 92 */ 87 /** Removes discardable tags like created_by on modified objects */ 93 88 LATE_UPLOAD_HOOKS.add(new DiscardTagsHook()); 94 89 } 95 90 … … 194 189 } 195 190 196 191 /** 197 * Check whether the preconditions are met to upload data in <code>apiData</code>.198 * Makes sure upload is allowed, primitives in <code>apiData</code>don't participate in conflicts and192 * Check whether the preconditions are met to upload data in {@code apiData}. 193 * Makes sure upload is allowed, primitives in {@code apiData} don't participate in conflicts and 199 194 * runs the installed {@link UploadHook}s. 200 195 * 201 196 * @param layer the source layer of the data to be uploaded … … 203 198 * @return true, if the preconditions are met; false, otherwise 204 199 */ 205 200 public static boolean checkPreUploadConditions(AbstractModifiableLayer layer, APIDataSet apiData) { 201 try { 202 return checkPreUploadConditionsAsync(layer, apiData, null).get(); 203 } catch (InterruptedException e) { 204 Thread.currentThread().interrupt(); 205 throw new JosmRuntimeException(e); 206 } catch (ExecutionException e) { 207 throw new JosmRuntimeException(e); 208 } 209 } 210 211 /** 212 * Check whether the preconditions are met to upload data in {@code apiData}. 213 * Makes sure upload is allowed, primitives in {@code apiData} don't participate in conflicts and 214 * runs the installed {@link UploadHook}s. 215 * 216 * @param layer the source layer of the data to be uploaded 217 * @param apiData the data to be uploaded 218 * @param onFinish {@code true} if the preconditions are met; {@code false}, otherwise 219 * @return A future that completes when the checks are finished 220 */ 221 public static Future<Boolean> checkPreUploadConditionsAsync(AbstractModifiableLayer layer, APIDataSet apiData, Consumer<Boolean> onFinish) { 222 final CompletableFuture<Boolean> future = new CompletableFuture<>(); 223 if (onFinish != null) { 224 future.thenAccept(onFinish); 225 } 206 226 if (layer.isUploadDiscouraged() && warnUploadDiscouraged(layer)) { 207 return false;227 future.complete(false); 208 228 } 209 229 if (layer instanceof OsmDataLayer) { 210 230 OsmDataLayer osmLayer = (OsmDataLayer) layer; … … 211 231 ConflictCollection conflicts = osmLayer.getConflicts(); 212 232 if (apiData.participatesInConflict(conflicts)) { 213 233 alertUnresolvedConflicts(osmLayer); 214 return false; 234 if (!future.isDone()) { 235 future.complete(false); 236 } 215 237 } 216 238 } 217 239 // Call all upload hooks in sequence. 218 // FIXME: this should become an asynchronous task 219 // 220 if (apiData != null) { 221 return UPLOAD_HOOKS.stream().allMatch(hook -> hook.checkUpload(apiData)); 240 if (!future.isDone()) { 241 MainApplication.worker.execute(() -> { 242 boolean hooks = true; 243 if (apiData != null) { 244 hooks = UPLOAD_HOOKS.stream().allMatch(hook -> hook.checkUpload(apiData)); 245 } 246 future.complete(hooks); 247 }); 222 248 } 223 224 return true; 249 return future; 225 250 } 226 251 227 252 /** … … 235 260 new Notification(tr("No changes to upload.")).show(); 236 261 return; 237 262 } 238 if (!checkPreUploadConditions(layer, apiData)) 239 return; 263 checkPreUploadConditionsAsync(layer, apiData, passed -> GuiHelper.runInEDT(() -> { 264 if (Boolean.TRUE.equals(passed)) { 265 realUploadData(layer, apiData); 266 } else { 267 new Notification(tr("One of the upload verification processes failed")).show(); 268 } 269 })); 270 } 240 271 272 /** 273 * Uploads data to the OSM API. 274 * 275 * @param layer the source layer for the data to upload 276 * @param apiData the primitives to be added, updated, or deleted 277 */ 278 private static void realUploadData(final OsmDataLayer layer, final APIDataSet apiData) { 279 241 280 ChangesetUpdater.check(); 242 281 243 282 final UploadDialog dialog = UploadDialog.getUploadDialog(); … … 283 322 Optional<AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask( 284 323 uploadStrategySpecification, layer, apiData, cs); 285 324 286 if (asyncUploadTask.isPresent()) { 287 MainApplication.worker.execute(asyncUploadTask.get()); 288 } 325 asyncUploadTask.ifPresent(MainApplication.worker::execute); 289 326 } else { 290 327 MainApplication.worker.execute(new UploadPrimitivesTask(uploadStrategySpecification, layer, apiData, cs)); 291 328 } -
src/org/openstreetmap/josm/actions/upload/ValidateUploadHook.java
5 5 6 6 import java.awt.Dimension; 7 7 import java.awt.GridBagLayout; 8 import java.util.ArrayList;9 8 import java.util.Collection; 10 9 import java.util.List; 10 import java.util.concurrent.atomic.AtomicBoolean; 11 11 12 12 import javax.swing.JPanel; 13 13 import javax.swing.JScrollPane; … … 14 14 15 15 import org.openstreetmap.josm.data.APIDataSet; 16 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;18 17 import org.openstreetmap.josm.data.validation.OsmValidator; 19 import org.openstreetmap.josm.data.validation.Severity;20 import org.openstreetmap.josm.data.validation.Test;21 18 import org.openstreetmap.josm.data.validation.TestError; 19 import org.openstreetmap.josm.data.validation.ValidationTask; 22 20 import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor; 23 21 import org.openstreetmap.josm.gui.ExtendedDialog; 24 22 import org.openstreetmap.josm.gui.MainApplication; 25 import org.openstreetmap.josm.gui.MapFrame;26 23 import org.openstreetmap.josm.gui.dialogs.validator.ValidatorTreePanel; 27 import org.openstreetmap.josm.gui.layer.OsmDataLayer;28 24 import org.openstreetmap.josm.gui.layer.ValidatorLayer; 29 25 import org.openstreetmap.josm.gui.util.GuiHelper; 30 26 import org.openstreetmap.josm.gui.widgets.HtmlPanel; … … 33 29 /** 34 30 * The action that does the validate thing. 35 31 * <p> 36 * This action iterates through all active tests and give them the data, so that32 * This action iterates through all active tests and gives them the data, so that 37 33 * each one can test it. 38 34 * 39 35 * @author frsantos … … 44 40 /** 45 41 * Validate the modified data before uploading 46 42 * @param apiDataSet contains primitives to be uploaded 47 * @return trueif upload should continue, else false43 * @return {@code true} if upload should continue, else false 48 44 */ 49 45 @Override 50 46 public boolean checkUpload(APIDataSet apiDataSet) { 51 52 OsmValidator.initializeTests(); 53 Collection<Test> tests = OsmValidator.getEnabledTests(true); 54 if (tests.isEmpty()) 55 return true; 56 47 AtomicBoolean returnCode = new AtomicBoolean(); 57 48 AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor(); 58 49 v.visit(apiDataSet.getPrimitivesToAdd()); 59 Collection<OsmPrimitive> selection = v.visit(apiDataSet.getPrimitivesToUpdate()); 60 61 List<TestError> errors = new ArrayList<>(30); 62 for (Test test : tests) { 63 test.setBeforeUpload(true); 64 test.setPartialSelection(true); 65 test.startTest(null); 66 test.visit(selection); 67 test.endTest(); 68 if (ValidatorPrefHelper.PREF_OTHER.get() && ValidatorPrefHelper.PREF_OTHER_UPLOAD.get()) { 69 errors.addAll(test.getErrors()); 50 Collection<OsmPrimitive> visited = v.visit(apiDataSet.getPrimitivesToUpdate()); 51 OsmValidator.initializeTests(); 52 new ValidationTask(errors -> { 53 if (errors.stream().allMatch(TestError::isIgnored)) { 54 returnCode.set(true); 70 55 } else { 71 for (TestError e : test.getErrors()) { 72 if (e.getSeverity() != Severity.OTHER) { 73 errors.add(e); 74 } 75 } 56 // Unfortunately, the progress monitor is not "finished" until after `finish` is called, so we will 57 // have a ProgressMonitor open behind the error screen. Fortunately, the error screen appears in front 58 // of the progress monitor. 59 GuiHelper.runInEDTAndWait(() -> returnCode.set(displayErrorScreen(errors))); 76 60 } 77 test.clear(); 78 test.setBeforeUpload(false); 79 } 61 }, null, OsmValidator.getEnabledTests(true), visited, null, true).run(); 80 62 81 if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) { 82 errors.forEach(TestError::updateIgnored); 83 } 84 85 OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer(); 86 if (editLayer != null) { 87 editLayer.validationErrors.clear(); 88 editLayer.validationErrors.addAll(errors); 89 } 90 MapFrame map = MainApplication.getMap(); 91 if (map != null) { 92 map.validatorDialog.tree.setErrors(errors); 93 } 94 if (errors.stream().allMatch(TestError::isIgnored)) 95 return true; 96 97 return displayErrorScreen(errors); 63 return returnCode.get(); 98 64 } 99 65 100 66 /** … … 101 67 * Displays a screen where the actions that would be taken are displayed and 102 68 * give the user the possibility to cancel the upload. 103 69 * @param errors The errors displayed in the screen 104 * @return <code>true</code>, if the upload should continue. <code>false</code>105 * if the user requested cancel.70 * @return {@code true}, if the upload should continue.<br> 71 * {@code false}, if the user requested cancel. 106 72 */ 107 73 private static boolean displayErrorScreen(List<TestError> errors) { 108 74 JPanel p = new JPanel(new GridBagLayout()); -
src/org/openstreetmap/josm/data/validation/ValidationTask.java
3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.GraphicsEnvironment; 6 7 import java.util.ArrayList; 7 8 import java.util.Collection; 8 9 import java.util.List; 10 import java.util.function.BiConsumer; 11 import java.util.function.Consumer; 9 12 10 13 import javax.swing.JOptionPane; 11 14 … … 25 28 * Asynchronous task for running a collection of tests against a collection of primitives 26 29 */ 27 30 public class ValidationTask extends PleaseWaitRunnable { 31 private final Consumer<List<TestError>> onFinish; 28 32 private Collection<Test> tests; 29 33 private final Collection<OsmPrimitive> validatedPrimitives; 30 34 private final Collection<OsmPrimitive> formerValidatedPrimitives; 35 private final boolean beforeUpload; 31 36 private boolean canceled; 32 37 private List<TestError> errors; 38 private BiConsumer<ValidationTask, Test> testConsumer; 33 39 34 40 /** 35 41 * Constructs a new {@code ValidationTask} … … 44 50 this(new PleaseWaitProgressMonitor(tr("Validating")), tests, validatedPrimitives, formerValidatedPrimitives); 45 51 } 46 52 47 protected ValidationTask(ProgressMonitor progressMonitor, 48 Collection<Test> tests, 49 Collection<OsmPrimitive> validatedPrimitives, 50 Collection<OsmPrimitive> formerValidatedPrimitives) { 51 super(tr("Validating"), progressMonitor, false /*don't ignore exceptions */); 53 /** 54 * Constructs a new {@code ValidationTask} 55 * 56 * @param onFinish called when the tests are finished 57 * @param progressMonitor the progress monitor to update with test progress 58 * @param tests the tests to run 59 * @param validatedPrimitives the collection of primitives to validate. 60 * @param formerValidatedPrimitives the last collection of primitives being validates. May be null. 61 * @param beforeUpload {@code true} if this is being run prior to upload 62 * @since xxx 63 */ 64 public ValidationTask(Consumer<List<TestError>> onFinish, 65 ProgressMonitor progressMonitor, 66 Collection<Test> tests, 67 Collection<OsmPrimitive> validatedPrimitives, 68 Collection<OsmPrimitive> formerValidatedPrimitives, 69 boolean beforeUpload) { 70 super(tr("Validating"), 71 progressMonitor != null ? progressMonitor : new PleaseWaitProgressMonitor(tr("Validating")), 72 false /*don't ignore exceptions */); 73 this.onFinish = onFinish; 52 74 this.validatedPrimitives = validatedPrimitives; 53 75 this.formerValidatedPrimitives = formerValidatedPrimitives; 54 76 this.tests = tests; 77 this.beforeUpload = beforeUpload; 55 78 } 56 79 80 protected ValidationTask(ProgressMonitor progressMonitor, 81 Collection<Test> tests, 82 Collection<OsmPrimitive> validatedPrimitives, 83 Collection<OsmPrimitive> formerValidatedPrimitives) { 84 this(null, progressMonitor, tests, validatedPrimitives, formerValidatedPrimitives, false); 85 } 86 57 87 @Override 58 88 protected void cancel() { 59 89 this.canceled = true; … … 63 93 protected void finish() { 64 94 if (canceled) return; 65 95 66 // update GUI on Swing EDT 67 GuiHelper.runInEDT(() -> { 68 MapFrame map = MainApplication.getMap(); 69 map.validatorDialog.unfurlDialog(); 70 map.validatorDialog.tree.setErrors(errors); 71 //FIXME: nicer way to find / invalidate the corresponding error layer 72 MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); 73 if (!errors.isEmpty()) { 74 OsmValidator.initializeErrorLayer(); 75 } 76 }); 96 if (!GraphicsEnvironment.isHeadless() && MainApplication.getMap() != null) { 97 // update GUI on Swing EDT 98 GuiHelper.runInEDT(() -> { 99 MapFrame map = MainApplication.getMap(); 100 map.validatorDialog.unfurlDialog(); 101 map.validatorDialog.tree.setErrors(errors); 102 //FIXME: nicer way to find / invalidate the corresponding error layer 103 MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); 104 if (!errors.isEmpty()) { 105 OsmValidator.initializeErrorLayer(); 106 } 107 }); 108 } 109 if (this.onFinish != null) { 110 this.onFinish.accept(this.errors); 111 } 77 112 } 78 113 79 114 @Override … … 88 123 return; 89 124 testCounter++; 90 125 getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName())); 91 test.setBeforeUpload( false);126 test.setBeforeUpload(this.beforeUpload); 92 127 test.setPartialSelection(formerValidatedPrimitives != null); 93 128 test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false)); 94 129 test.visit(validatedPrimitives); 95 130 test.endTest(); 96 131 errors.addAll(test.getErrors()); 132 if (this.testConsumer != null) { 133 this.testConsumer.accept(this, test); 134 } 97 135 test.clear(); 136 test.setBeforeUpload(false); 98 137 } 99 138 tests = null; 100 139 if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) { … … 124 163 public List<TestError> getErrors() { 125 164 return errors; 126 165 } 166 167 /** 168 * A test consumer to avoid filling up memory. A test consumer <i>may</i> remove tests it has consumed. 169 * @param testConsumer The consumer which takes a {@link ValidationTask} ({@code this}) and the test that finished. 170 * @since xxx 171 */ 172 public void setTestConsumer(BiConsumer<ValidationTask, Test> testConsumer) { 173 this.testConsumer = testConsumer; 174 } 127 175 } -
src/org/openstreetmap/josm/data/validation/ValidatorCLI.java
10 10 import java.io.OutputStream; 11 11 import java.nio.charset.StandardCharsets; 12 12 import java.nio.file.Files; 13 import java.nio.file.Path; 13 14 import java.nio.file.Paths; 14 15 import java.util.ArrayList; 15 16 import java.util.Arrays; … … 25 26 import java.util.logging.Level; 26 27 import java.util.stream.Collectors; 27 28 29 import jakarta.json.JsonObject; 28 30 import org.apache.commons.compress.utils.FileNameUtils; 29 31 import org.openstreetmap.josm.actions.ExtensionFileFilter; 30 32 import org.openstreetmap.josm.cli.CLIModule; … … 176 178 fileMonitor.beginTask(tr("Processing files..."), this.input.size()); 177 179 for (String inputFile : this.input) { 178 180 if (inputFile.endsWith(".validator.mapcss")) { 179 this.processValidatorFile(inputFile);181 processValidatorFile(inputFile); 180 182 } else if (inputFile.endsWith(".mapcss")) { 181 this.processMapcssFile(inputFile);183 processMapcssFile(inputFile); 182 184 } else { 183 185 this.processFile(inputFile); 184 186 } … … 197 199 * @param inputFile The mapcss file to validate 198 200 * @throws ParseException if the file does not match the mapcss syntax 199 201 */ 200 private void processMapcssFile(final String inputFile) throws ParseException {202 private static void processMapcssFile(final String inputFile) throws ParseException { 201 203 final MapCSSStyleSource styleSource = new MapCSSStyleSource(new File(inputFile).toURI().getPath(), inputFile, inputFile); 202 204 styleSource.loadStyleSource(); 203 205 if (!styleSource.getErrors().isEmpty()) { … … 214 216 * @throws IOException if there is a problem reading the file 215 217 * @throws ParseException if the file does not match the validator mapcss syntax 216 218 */ 217 private void processValidatorFile(final String inputFile) throws ParseException, IOException {219 private static void processValidatorFile(final String inputFile) throws ParseException, IOException { 218 220 // Check asserts 219 221 Config.getPref().putBoolean("validator.check_assert_local_rules", true); 220 222 final MapCSSTagChecker mapCSSTagChecker = new MapCSSTagChecker(); … … 256 258 OsmDataLayer dataLayer = null; 257 259 try { 258 260 Logging.info(task); 259 OsmValidator.initializeTests();260 261 dataLayer = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class) 261 262 .stream().filter(layer -> inputFileFile.equals(layer.getAssociatedFile())) 262 263 .findFirst().orElseThrow(() -> new JosmRuntimeException(tr("Could not find a layer for {0}", inputFile))); … … 269 270 } 270 271 } 271 272 } 272 Collection<Test> tests = OsmValidator.getEnabledTests(false);273 if ( Files.isRegularFile(Paths.get(outputFile)) && !Files.deleteIfExists(Paths.get(outputFile))) {273 Path path = Paths.get(outputFile); 274 if (path.toFile().isFile() && !Files.deleteIfExists(path)) { 274 275 Logging.error("Could not delete {0}, attempting to append", outputFile); 275 276 } 276 277 GeoJSONMapRouletteWriter geoJSONMapRouletteWriter = new GeoJSONMapRouletteWriter(dataSet); 277 try (OutputStream fileOutputStream = Files.newOutputStream(Paths.get(outputFile))) { 278 tests.parallelStream().forEach(test -> runTest(test, geoJSONMapRouletteWriter, fileOutputStream, dataSet)); 278 OsmValidator.initializeTests(); 279 280 try (OutputStream fileOutputStream = Files.newOutputStream(path)) { 281 // The first writeErrors catches anything that was written, for whatever reason. This is probably never 282 // going to be called. 283 ValidationTask validationTask = new ValidationTask(errors -> writeErrors(geoJSONMapRouletteWriter, fileOutputStream, errors), 284 progressMonitorFactory.get(), OsmValidator.getEnabledTests(false), 285 dataSet.allPrimitives(), Collections.emptyList(), false); 286 // This avoids keeping errors in memory 287 validationTask.setTestConsumer((t, test) -> { 288 writeErrors(geoJSONMapRouletteWriter, fileOutputStream, test.getErrors()); 289 t.getErrors().removeIf(test.getErrors()::contains); 290 }); 291 validationTask.run(); 279 292 } 280 293 } finally { 281 294 if (dataLayer != null) { … … 285 298 } 286 299 } 287 300 301 private void writeErrors(GeoJSONMapRouletteWriter geoJSONMapRouletteWriter, OutputStream fileOutputStream, 302 Collection<TestError> errors) { 303 for (TestError error : errors) { 304 Optional<JsonObject> object = geoJSONMapRouletteWriter.write(error); 305 if (object.isPresent()) { 306 try { 307 writeToFile(fileOutputStream, object.get().toString().getBytes(StandardCharsets.UTF_8)); 308 } catch (IOException e) { 309 throw new JosmRuntimeException(e); 310 } 311 } 312 } 313 } 314 288 315 /** 289 316 * Get the default output name 290 317 * @param inputString The input file … … 302 329 } 303 330 304 331 /** 305 * Run a test306 * @param test The test to run307 * @param geoJSONMapRouletteWriter The object to use to create challenges308 * @param fileOutputStream The location to write data to309 * @param dataSet The dataset to check310 */311 private void runTest(final Test test, final GeoJSONMapRouletteWriter geoJSONMapRouletteWriter,312 final OutputStream fileOutputStream, DataSet dataSet) {313 test.startTest(progressMonitorFactory.get());314 test.visit(dataSet.allPrimitives());315 test.endTest();316 test.getErrors().stream().map(geoJSONMapRouletteWriter::write)317 .filter(Optional::isPresent).map(Optional::get)318 .map(jsonObject -> jsonObject.toString().getBytes(StandardCharsets.UTF_8)).forEach(bytes -> {319 try {320 writeToFile(fileOutputStream, bytes);321 } catch (IOException e) {322 throw new JosmRuntimeException(e);323 }324 });325 test.clear();326 }327 328 /**329 332 * Write to a file. Synchronized to avoid writing to the same file in different threads. 330 333 * 331 334 * @param fileOutputStream The file output stream to read