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