source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java@ 10611

Last change on this file since 10611 was 10611, checked in by Don-vip, 8 years ago

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

  • Property svn:eol-style set to native
File size: 15.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.validator;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.KeyListener;
7import java.awt.event.MouseEvent;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.EnumMap;
12import java.util.Enumeration;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Set;
19
20import javax.swing.JTree;
21import javax.swing.ToolTipManager;
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.DefaultTreeModel;
24import javax.swing.tree.TreeNode;
25import javax.swing.tree.TreePath;
26import javax.swing.tree.TreeSelectionModel;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.validation.Severity;
32import org.openstreetmap.josm.data.validation.TestError;
33import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
34import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
35import org.openstreetmap.josm.gui.util.GuiHelper;
36import org.openstreetmap.josm.tools.Destroyable;
37import org.openstreetmap.josm.tools.MultiMap;
38import org.openstreetmap.josm.tools.Predicate;
39import org.openstreetmap.josm.tools.Predicates;
40import org.openstreetmap.josm.tools.Utils;
41
42/**
43 * A panel that displays the error tree. The selection manager
44 * respects clicks into the selection list. Ctrl-click will remove entries from
45 * the list while single click will make the clicked entry the only selection.
46 *
47 * @author frsantos
48 */
49public class ValidatorTreePanel extends JTree implements Destroyable {
50
51 private static final class GroupTreeNode extends DefaultMutableTreeNode {
52
53 GroupTreeNode(Object userObject) {
54 super(userObject);
55 }
56
57 @Override
58 public String toString() {
59 return tr("{0} ({1})", super.toString(), getLeafCount());
60 }
61 }
62
63 /**
64 * The validation data.
65 */
66 protected DefaultTreeModel valTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
67
68 /** The list of errors shown in the tree */
69 private transient List<TestError> errors = new ArrayList<>();
70
71 /**
72 * If {@link #filter} is not <code>null</code> only errors are displayed
73 * that refer to one of the primitives in the filter.
74 */
75 private transient Set<? extends OsmPrimitive> filter;
76
77 /** a counter to check if tree has been rebuild */
78 private int updateCount;
79
80 /**
81 * Constructor
82 * @param errors The list of errors
83 */
84 public ValidatorTreePanel(List<TestError> errors) {
85 ToolTipManager.sharedInstance().registerComponent(this);
86 this.setModel(valTreeModel);
87 this.setRootVisible(false);
88 this.setShowsRootHandles(true);
89 this.expandRow(0);
90 this.setVisibleRowCount(8);
91 this.setCellRenderer(new ValidatorTreeRenderer());
92 this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
93 setErrorList(errors);
94 for (KeyListener keyListener : getKeyListeners()) {
95 // Fix #3596 - Remove default keyListener to avoid conflicts with JOSM commands
96 if ("javax.swing.plaf.basic.BasicTreeUI$Handler".equals(keyListener.getClass().getName())) {
97 removeKeyListener(keyListener);
98 }
99 }
100 }
101
102 @Override
103 public String getToolTipText(MouseEvent e) {
104 String res = null;
105 TreePath path = getPathForLocation(e.getX(), e.getY());
106 if (path != null) {
107 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
108 Object nodeInfo = node.getUserObject();
109
110 if (nodeInfo instanceof TestError) {
111 TestError error = (TestError) nodeInfo;
112 MultipleNameVisitor v = new MultipleNameVisitor();
113 v.visit(error.getPrimitives());
114 res = "<html>" + v.getText() + "<br>" + error.getMessage();
115 String d = error.getDescription();
116 if (d != null)
117 res += "<br>" + d;
118 res += "</html>";
119 } else {
120 res = node.toString();
121 }
122 }
123 return res;
124 }
125
126 /** Constructor */
127 public ValidatorTreePanel() {
128 this(null);
129 }
130
131 @Override
132 public void setVisible(boolean v) {
133 if (v) {
134 buildTree();
135 } else {
136 valTreeModel.setRoot(new DefaultMutableTreeNode());
137 }
138 super.setVisible(v);
139 }
140
141 /**
142 * Builds the errors tree
143 */
144 public void buildTree() {
145 updateCount++;
146 final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
147
148 if (errors == null || errors.isEmpty()) {
149 GuiHelper.runInEDTAndWait(() -> valTreeModel.setRoot(rootNode));
150 return;
151 }
152 // Sort validation errors - #8517
153 Collections.sort(errors);
154
155 // Remember the currently expanded rows
156 Set<Object> oldSelectedRows = new HashSet<>();
157 Enumeration<TreePath> expanded = getExpandedDescendants(new TreePath(getRoot()));
158 if (expanded != null) {
159 while (expanded.hasMoreElements()) {
160 TreePath path = expanded.nextElement();
161 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
162 Object userObject = node.getUserObject();
163 if (userObject instanceof Severity) {
164 oldSelectedRows.add(userObject);
165 } else if (userObject instanceof String) {
166 String msg = (String) userObject;
167 int index = msg.lastIndexOf(" (");
168 if (index > 0) {
169 msg = msg.substring(0, index);
170 }
171 oldSelectedRows.add(msg);
172 }
173 }
174 }
175
176 Map<Severity, MultiMap<String, TestError>> errorTree = new EnumMap<>(Severity.class);
177 Map<Severity, HashMap<String, MultiMap<String, TestError>>> errorTreeDeep = new EnumMap<>(Severity.class);
178 for (Severity s : Severity.values()) {
179 errorTree.put(s, new MultiMap<String, TestError>(20));
180 errorTreeDeep.put(s, new HashMap<String, MultiMap<String, TestError>>());
181 }
182
183 final Boolean other = ValidatorPreference.PREF_OTHER.get();
184 for (TestError e : errors) {
185 if (e.isIgnored()) {
186 continue;
187 }
188 Severity s = e.getSeverity();
189 if (!other && s == Severity.OTHER) {
190 continue;
191 }
192 String d = e.getDescription();
193 String m = e.getMessage();
194 if (filter != null) {
195 boolean found = false;
196 for (OsmPrimitive p : e.getPrimitives()) {
197 if (filter.contains(p)) {
198 found = true;
199 break;
200 }
201 }
202 if (!found) {
203 continue;
204 }
205 }
206 if (d != null) {
207 MultiMap<String, TestError> b = errorTreeDeep.get(s).get(m);
208 if (b == null) {
209 b = new MultiMap<>(20);
210 errorTreeDeep.get(s).put(m, b);
211 }
212 b.put(d, e);
213 } else {
214 errorTree.get(s).put(m, e);
215 }
216 }
217
218 List<TreePath> expandedPaths = new ArrayList<>();
219 for (Severity s : Severity.values()) {
220 MultiMap<String, TestError> severityErrors = errorTree.get(s);
221 Map<String, MultiMap<String, TestError>> severityErrorsDeep = errorTreeDeep.get(s);
222 if (severityErrors.isEmpty() && severityErrorsDeep.isEmpty()) {
223 continue;
224 }
225
226 // Severity node
227 DefaultMutableTreeNode severityNode = new GroupTreeNode(s);
228 rootNode.add(severityNode);
229
230 if (oldSelectedRows.contains(s)) {
231 expandedPaths.add(new TreePath(new Object[] {rootNode, severityNode}));
232 }
233
234 for (Entry<String, Set<TestError>> msgErrors : severityErrors.entrySet()) {
235 // Message node
236 Set<TestError> errs = msgErrors.getValue();
237 String msg = tr("{0} ({1})", msgErrors.getKey(), errs.size());
238 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
239 severityNode.add(messageNode);
240
241 if (oldSelectedRows.contains(msgErrors.getKey())) {
242 expandedPaths.add(new TreePath(new Object[] {rootNode, severityNode, messageNode}));
243 }
244
245 for (TestError error : errs) {
246 // Error node
247 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
248 messageNode.add(errorNode);
249 }
250 }
251 for (Entry<String, MultiMap<String, TestError>> bag : severityErrorsDeep.entrySet()) {
252 // Group node
253 MultiMap<String, TestError> errorlist = bag.getValue();
254 DefaultMutableTreeNode groupNode = null;
255 if (errorlist.size() > 1) {
256 groupNode = new GroupTreeNode(bag.getKey());
257 severityNode.add(groupNode);
258 if (oldSelectedRows.contains(bag.getKey())) {
259 expandedPaths.add(new TreePath(new Object[] {rootNode, severityNode, groupNode}));
260 }
261 }
262
263 for (Entry<String, Set<TestError>> msgErrors : errorlist.entrySet()) {
264 // Message node
265 Set<TestError> errs = msgErrors.getValue();
266 String msg;
267 if (groupNode != null) {
268 msg = tr("{0} ({1})", msgErrors.getKey(), errs.size());
269 } else {
270 msg = tr("{0} - {1} ({2})", msgErrors.getKey(), bag.getKey(), errs.size());
271 }
272 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
273 if (groupNode != null) {
274 groupNode.add(messageNode);
275 } else {
276 severityNode.add(messageNode);
277 }
278
279 if (oldSelectedRows.contains(msgErrors.getKey())) {
280 if (groupNode != null) {
281 expandedPaths.add(new TreePath(new Object[] {rootNode, severityNode, groupNode, messageNode}));
282 } else {
283 expandedPaths.add(new TreePath(new Object[] {rootNode, severityNode, messageNode}));
284 }
285 }
286
287 for (TestError error : errs) {
288 // Error node
289 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
290 messageNode.add(errorNode);
291 }
292 }
293 }
294 }
295
296 valTreeModel.setRoot(rootNode);
297 for (TreePath path : expandedPaths) {
298 this.expandPath(path);
299 }
300 }
301
302 /**
303 * Sets the errors list used by a data layer
304 * @param errors The error list that is used by a data layer
305 */
306 public final void setErrorList(List<TestError> errors) {
307 this.errors = errors;
308 if (isVisible()) {
309 buildTree();
310 }
311 }
312
313 /**
314 * Clears the current error list and adds these errors to it
315 * @param newerrors The validation errors
316 */
317 public void setErrors(List<TestError> newerrors) {
318 if (errors == null)
319 return;
320 clearErrors();
321 DataSet ds = Main.getLayerManager().getEditDataSet();
322 for (TestError error : newerrors) {
323 if (!error.isIgnored()) {
324 errors.add(error);
325 if (ds != null) {
326 ds.addDataSetListener(error);
327 }
328 }
329 }
330 if (isVisible()) {
331 buildTree();
332 }
333 }
334
335 /**
336 * Returns the errors of the tree
337 * @return the errors of the tree
338 */
339 public List<TestError> getErrors() {
340 return errors != null ? errors : Collections.<TestError>emptyList();
341 }
342
343 /**
344 * Selects all errors related to the specified {@code primitives}, i.e. where {@link TestError#getPrimitives()}
345 * returns a primitive present in {@code primitives}.
346 * @param primitives collection of primitives
347 */
348 public void selectRelatedErrors(final Collection<OsmPrimitive> primitives) {
349 final Collection<TreePath> paths = new ArrayList<>();
350 walkAndSelectRelatedErrors(new TreePath(getRoot()), Predicates.inCollection(new HashSet<>(primitives)), paths);
351 getSelectionModel().clearSelection();
352 for (TreePath path : paths) {
353 expandPath(path);
354 getSelectionModel().addSelectionPath(path);
355 }
356 }
357
358 private void walkAndSelectRelatedErrors(final TreePath p, final Predicate<OsmPrimitive> isRelevant, final Collection<TreePath> paths) {
359 final int count = getModel().getChildCount(p.getLastPathComponent());
360 for (int i = 0; i < count; i++) {
361 final Object child = getModel().getChild(p.getLastPathComponent(), i);
362 if (getModel().isLeaf(child) && child instanceof DefaultMutableTreeNode
363 && ((DefaultMutableTreeNode) child).getUserObject() instanceof TestError) {
364 final TestError error = (TestError) ((DefaultMutableTreeNode) child).getUserObject();
365 if (error.getPrimitives() != null) {
366 if (Utils.exists(error.getPrimitives(), isRelevant)) {
367 paths.add(p.pathByAddingChild(child));
368 }
369 }
370 } else {
371 walkAndSelectRelatedErrors(p.pathByAddingChild(child), isRelevant, paths);
372 }
373 }
374 }
375
376 /**
377 * Returns the filter list
378 * @return the list of primitives used for filtering
379 */
380 public Set<? extends OsmPrimitive> getFilter() {
381 return filter;
382 }
383
384 /**
385 * Set the filter list to a set of primitives
386 * @param filter the list of primitives used for filtering
387 */
388 public void setFilter(Set<? extends OsmPrimitive> filter) {
389 if (filter != null && filter.isEmpty()) {
390 this.filter = null;
391 } else {
392 this.filter = filter;
393 }
394 if (isVisible()) {
395 buildTree();
396 }
397 }
398
399 /**
400 * Updates the current errors list
401 */
402 public void resetErrors() {
403 List<TestError> e = new ArrayList<>(errors);
404 setErrors(e);
405 }
406
407 /**
408 * Expands complete tree
409 */
410 @SuppressWarnings("unchecked")
411 public void expandAll() {
412 DefaultMutableTreeNode root = getRoot();
413
414 int row = 0;
415 Enumeration<TreeNode> children = root.breadthFirstEnumeration();
416 while (children.hasMoreElements()) {
417 children.nextElement();
418 expandRow(row++);
419 }
420 }
421
422 /**
423 * Returns the root node model.
424 * @return The root node model
425 */
426 public DefaultMutableTreeNode getRoot() {
427 return (DefaultMutableTreeNode) valTreeModel.getRoot();
428 }
429
430 /**
431 * Returns a value to check if tree has been rebuild
432 * @return the current counter
433 */
434 public int getUpdateCount() {
435 return updateCount;
436 }
437
438 private void clearErrors() {
439 if (errors != null) {
440 DataSet ds = Main.getLayerManager().getEditDataSet();
441 if (ds != null) {
442 for (TestError e : errors) {
443 ds.removeDataSetListener(e);
444 }
445 }
446 errors.clear();
447 }
448 }
449
450 @Override
451 public void destroy() {
452 clearErrors();
453 }
454}
Note: See TracBrowser for help on using the repository browser.