001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.event.ActionEvent; 007import java.awt.event.KeyEvent; 008import java.io.IOException; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.List; 012import java.util.Optional; 013 014import org.openstreetmap.josm.data.osm.DataSet; 015import org.openstreetmap.josm.data.osm.OsmPrimitive; 016import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 017import org.openstreetmap.josm.data.validation.OsmValidator; 018import org.openstreetmap.josm.data.validation.Test; 019import org.openstreetmap.josm.data.validation.TestError; 020import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor; 021import org.openstreetmap.josm.gui.MainApplication; 022import org.openstreetmap.josm.gui.MapFrame; 023import org.openstreetmap.josm.gui.PleaseWaitRunnable; 024import org.openstreetmap.josm.gui.layer.ValidatorLayer; 025import org.openstreetmap.josm.gui.util.GuiHelper; 026import org.openstreetmap.josm.io.OsmTransferException; 027import org.openstreetmap.josm.tools.Shortcut; 028import org.xml.sax.SAXException; 029 030/** 031 * The action that does the validate thing. 032 * <p> 033 * This action iterates through all active tests and give them the data, so that 034 * each one can test it. 035 * 036 * @author frsantos 037 */ 038public class ValidateAction extends JosmAction { 039 040 /** Last selection used to validate */ 041 private transient Collection<OsmPrimitive> lastSelection; 042 043 /** 044 * Constructor 045 */ 046 public ValidateAction() { 047 super(tr("Validation"), "dialogs/validator", tr("Performs the data validation"), 048 Shortcut.registerShortcut("tools:validate", tr("Tool: {0}", tr("Validation")), 049 KeyEvent.VK_V, Shortcut.SHIFT), true); 050 } 051 052 @Override 053 public void actionPerformed(ActionEvent ev) { 054 doValidate(true); 055 } 056 057 /** 058 * Does the validation. 059 * <p> 060 * If getSelectedItems is true, the selected items (or all items, if no one 061 * is selected) are validated. If it is false, last selected items are revalidated 062 * 063 * @param getSelectedItems If selected or last selected items must be validated 064 */ 065 public void doValidate(boolean getSelectedItems) { 066 MapFrame map = MainApplication.getMap(); 067 if (map == null || !map.isVisible()) 068 return; 069 070 OsmValidator.initializeTests(); 071 OsmValidator.initializeErrorLayer(); 072 073 Collection<Test> tests = OsmValidator.getEnabledTests(false); 074 if (tests.isEmpty()) 075 return; 076 077 Collection<OsmPrimitive> selection; 078 if (getSelectedItems) { 079 selection = getLayerManager().getActiveDataSet().getAllSelected(); 080 if (selection.isEmpty()) { 081 selection = getLayerManager().getActiveDataSet().allNonDeletedPrimitives(); 082 lastSelection = null; 083 } else { 084 AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor(); 085 selection = v.visit(selection); 086 lastSelection = selection; 087 } 088 } else { 089 selection = Optional.ofNullable(lastSelection).orElseGet( 090 () -> getLayerManager().getActiveDataSet().allNonDeletedPrimitives()); 091 } 092 093 MainApplication.worker.submit(new ValidationTask(tests, selection, lastSelection)); 094 } 095 096 @Override 097 public void updateEnabledState() { 098 DataSet ds = getLayerManager().getActiveDataSet(); 099 setEnabled(ds != null && !ds.isEmpty()); 100 } 101 102 @Override 103 public void destroy() { 104 // Hack - this action should stay forever because it could be added to toolbar 105 // Do not call super.destroy() here 106 lastSelection = null; 107 } 108 109 /** 110 * Asynchronous task for running a collection of tests against a collection of primitives 111 */ 112 static class ValidationTask extends PleaseWaitRunnable { 113 private Collection<Test> tests; 114 private final Collection<OsmPrimitive> validatedPrimitives; 115 private final Collection<OsmPrimitive> formerValidatedPrimitives; 116 private boolean canceled; 117 private List<TestError> errors; 118 119 /** 120 * Constructs a new {@code ValidationTask} 121 * @param tests the tests to run 122 * @param validatedPrimitives the collection of primitives to validate. 123 * @param formerValidatedPrimitives the last collection of primitives being validates. May be null. 124 */ 125 ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives, 126 Collection<OsmPrimitive> formerValidatedPrimitives) { 127 super(tr("Validating"), false /*don't ignore exceptions */); 128 this.validatedPrimitives = validatedPrimitives; 129 this.formerValidatedPrimitives = formerValidatedPrimitives; 130 this.tests = tests; 131 } 132 133 @Override 134 protected void cancel() { 135 this.canceled = true; 136 } 137 138 @Override 139 protected void finish() { 140 if (canceled) return; 141 142 // update GUI on Swing EDT 143 // 144 GuiHelper.runInEDT(() -> { 145 MapFrame map = MainApplication.getMap(); 146 map.validatorDialog.unfurlDialog(); 147 map.validatorDialog.tree.setErrors(errors); 148 //FIXME: nicer way to find / invalidate the corresponding error layer 149 MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); 150 }); 151 } 152 153 @Override 154 protected void realRun() throws SAXException, IOException, 155 OsmTransferException { 156 if (tests == null || tests.isEmpty()) 157 return; 158 errors = new ArrayList<>(200); 159 getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size()); 160 int testCounter = 0; 161 for (Test test : tests) { 162 if (canceled) 163 return; 164 testCounter++; 165 getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName())); 166 test.setPartialSelection(formerValidatedPrimitives != null); 167 test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false)); 168 test.visit(validatedPrimitives); 169 test.endTest(); 170 errors.addAll(test.getErrors()); 171 test.clear(); 172 } 173 tests = null; 174 if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) { 175 getProgressMonitor().setCustomText(""); 176 getProgressMonitor().subTask(tr("Updating ignored errors ...")); 177 for (TestError error : errors) { 178 if (canceled) return; 179 error.updateIgnored(); 180 } 181 } 182 } 183 } 184}