001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.gui.dialogs.validator; 003 004 import java.awt.event.MouseEvent; 005 import java.util.ArrayList; 006 import java.util.Collections; 007 import java.util.Enumeration; 008 import java.util.HashMap; 009 import java.util.HashSet; 010 import java.util.LinkedHashSet; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Set; 014 import java.util.Map.Entry; 015 016 import javax.swing.JTree; 017 import javax.swing.ToolTipManager; 018 import javax.swing.tree.DefaultMutableTreeNode; 019 import javax.swing.tree.DefaultTreeModel; 020 import javax.swing.tree.TreePath; 021 import javax.swing.tree.TreeSelectionModel; 022 023 import org.openstreetmap.josm.data.osm.OsmPrimitive; 024 import org.openstreetmap.josm.data.validation.Severity; 025 import org.openstreetmap.josm.data.validation.TestError; 026 import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor; 027 import org.openstreetmap.josm.gui.preferences.ValidatorPreference; 028 import org.openstreetmap.josm.tools.MultiMap; 029 import org.openstreetmap.josm.Main; 030 031 /** 032 * A panel that displays the error tree. The selection manager 033 * respects clicks into the selection list. Ctrl-click will remove entries from 034 * the list while single click will make the clicked entry the only selection. 035 * 036 * @author frsantos 037 */ 038 public class ValidatorTreePanel extends JTree { 039 /** Serializable ID */ 040 private static final long serialVersionUID = 2952292777351992696L; 041 042 /** 043 * The validation data. 044 */ 045 protected DefaultTreeModel valTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); 046 047 /** The list of errors shown in the tree */ 048 private List<TestError> errors = new ArrayList<TestError>(); 049 050 /** 051 * If {@link #filter} is not <code>null</code> only errors are displayed 052 * that refer to one of the primitives in the filter. 053 */ 054 private Set<OsmPrimitive> filter = null; 055 056 private int updateCount; 057 058 /** 059 * Constructor 060 * @param errors The list of errors 061 */ 062 public ValidatorTreePanel(List<TestError> errors) { 063 ToolTipManager.sharedInstance().registerComponent(this); 064 this.setModel(valTreeModel); 065 this.setRootVisible(false); 066 this.setShowsRootHandles(true); 067 this.expandRow(0); 068 this.setVisibleRowCount(8); 069 this.setCellRenderer(new ValidatorTreeRenderer()); 070 this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); 071 setErrorList(errors); 072 } 073 074 @Override 075 public String getToolTipText(MouseEvent e) { 076 String res = null; 077 TreePath path = getPathForLocation(e.getX(), e.getY()); 078 if (path != null) { 079 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 080 Object nodeInfo = node.getUserObject(); 081 082 if (nodeInfo instanceof TestError) { 083 TestError error = (TestError) nodeInfo; 084 MultipleNameVisitor v = new MultipleNameVisitor(); 085 v.visit(error.getPrimitives()); 086 res = "<html>" + v.getText() + "<br>" + error.getMessage(); 087 String d = error.getDescription(); 088 if (d != null) 089 res += "<br>" + d; 090 res += "</html>"; 091 } else { 092 res = node.toString(); 093 } 094 } 095 return res; 096 } 097 098 /** Constructor */ 099 public ValidatorTreePanel() { 100 this(null); 101 } 102 103 @Override 104 public void setVisible(boolean v) { 105 if (v) { 106 buildTree(); 107 } else { 108 valTreeModel.setRoot(new DefaultMutableTreeNode()); 109 } 110 super.setVisible(v); 111 } 112 113 /** 114 * Builds the errors tree 115 */ 116 public void buildTree() { 117 updateCount++; 118 DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); 119 120 if (errors == null || errors.isEmpty()) { 121 valTreeModel.setRoot(rootNode); 122 return; 123 } 124 125 // Remember the currently expanded rows 126 Set<Object> oldSelectedRows = new HashSet<Object>(); 127 Enumeration<TreePath> expanded = getExpandedDescendants(new TreePath(getRoot())); 128 if (expanded != null) { 129 while (expanded.hasMoreElements()) { 130 TreePath path = expanded.nextElement(); 131 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 132 Object userObject = node.getUserObject(); 133 if (userObject instanceof Severity) { 134 oldSelectedRows.add(userObject); 135 } else if (userObject instanceof String) { 136 String msg = (String) userObject; 137 msg = msg.substring(0, msg.lastIndexOf(" (")); 138 oldSelectedRows.add(msg); 139 } 140 } 141 } 142 143 Map<Severity, MultiMap<String, TestError>> errorTree = new HashMap<Severity, MultiMap<String, TestError>>(); 144 Map<Severity, HashMap<String, MultiMap<String, TestError>>> errorTreeDeep = new HashMap<Severity, HashMap<String, MultiMap<String, TestError>>>(); 145 for (Severity s : Severity.values()) { 146 errorTree.put(s, new MultiMap<String, TestError>(20)); 147 errorTreeDeep.put(s, new HashMap<String, MultiMap<String, TestError>>()); 148 } 149 150 boolean other = Main.pref.getBoolean(ValidatorPreference.PREF_OTHER, false); 151 for (TestError e : errors) { 152 if (e.getIgnored()) { 153 continue; 154 } 155 Severity s = e.getSeverity(); 156 if(!other && s == Severity.OTHER) { 157 continue; 158 } 159 String d = e.getDescription(); 160 String m = e.getMessage(); 161 if (filter != null) { 162 boolean found = false; 163 for (OsmPrimitive p : e.getPrimitives()) { 164 if (filter.contains(p)) { 165 found = true; 166 break; 167 } 168 } 169 if (!found) { 170 continue; 171 } 172 } 173 if (d != null) { 174 MultiMap<String, TestError> b = errorTreeDeep.get(s).get(m); 175 if (b == null) { 176 b = new MultiMap<String, TestError>(20); 177 errorTreeDeep.get(s).put(m, b); 178 } 179 b.put(d, e); 180 } else { 181 errorTree.get(s).put(m, e); 182 } 183 } 184 185 List<TreePath> expandedPaths = new ArrayList<TreePath>(); 186 for (Severity s : Severity.values()) { 187 MultiMap<String, TestError> severityErrors = errorTree.get(s); 188 Map<String, MultiMap<String, TestError>> severityErrorsDeep = errorTreeDeep.get(s); 189 if (severityErrors.isEmpty() && severityErrorsDeep.isEmpty()) { 190 continue; 191 } 192 193 // Severity node 194 DefaultMutableTreeNode severityNode = new DefaultMutableTreeNode(s); 195 rootNode.add(severityNode); 196 197 if (oldSelectedRows.contains(s)) { 198 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode })); 199 } 200 201 for (Entry<String, LinkedHashSet<TestError>> msgErrors : severityErrors.entrySet()) { 202 // Message node 203 Set<TestError> errs = msgErrors.getValue(); 204 String msg = msgErrors.getKey() + " (" + errs.size() + ")"; 205 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg); 206 severityNode.add(messageNode); 207 208 if (oldSelectedRows.contains(msgErrors.getKey())) { 209 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode })); 210 } 211 212 for (TestError error : errs) { 213 // Error node 214 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error); 215 messageNode.add(errorNode); 216 } 217 } 218 for (Entry<String, MultiMap<String, TestError>> bag : severityErrorsDeep.entrySet()) { 219 // Group node 220 MultiMap<String, TestError> errorlist = bag.getValue(); 221 DefaultMutableTreeNode groupNode = null; 222 if (errorlist.size() > 1) { 223 String nmsg = bag.getKey() + " (" + errorlist.size() + ")"; 224 groupNode = new DefaultMutableTreeNode(nmsg); 225 severityNode.add(groupNode); 226 if (oldSelectedRows.contains(bag.getKey())) { 227 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode })); 228 } 229 } 230 231 for (Entry<String, LinkedHashSet<TestError>> msgErrors : errorlist.entrySet()) { 232 // Message node 233 Set<TestError> errs = msgErrors.getValue(); 234 String msg; 235 if (groupNode != null) { 236 msg = msgErrors.getKey() + " (" + errs.size() + ")"; 237 } else { 238 msg = msgErrors.getKey() + " - " + bag.getKey() + " (" + errs.size() + ")"; 239 } 240 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg); 241 if (groupNode != null) { 242 groupNode.add(messageNode); 243 } else { 244 severityNode.add(messageNode); 245 } 246 247 if (oldSelectedRows.contains(msgErrors.getKey())) { 248 if (groupNode != null) { 249 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode, 250 messageNode })); 251 } else { 252 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode })); 253 } 254 } 255 256 for (TestError error : errs) { 257 // Error node 258 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error); 259 messageNode.add(errorNode); 260 } 261 } 262 } 263 } 264 265 valTreeModel.setRoot(rootNode); 266 for (TreePath path : expandedPaths) { 267 this.expandPath(path); 268 } 269 } 270 271 /** 272 * Sets the errors list used by a data layer 273 * @param errors The error list that is used by a data layer 274 */ 275 public void setErrorList(List<TestError> errors) { 276 this.errors = errors; 277 if (isVisible()) { 278 buildTree(); 279 } 280 } 281 282 /** 283 * Clears the current error list and adds these errors to it 284 * @param errors The validation errors 285 */ 286 public void setErrors(List<TestError> newerrors) { 287 if (errors == null) 288 return; 289 errors.clear(); 290 for (TestError error : newerrors) { 291 if (!error.getIgnored()) { 292 errors.add(error); 293 } 294 } 295 if (isVisible()) { 296 buildTree(); 297 } 298 } 299 300 /** 301 * Returns the errors of the tree 302 * @return the errors of the tree 303 */ 304 public List<TestError> getErrors() { 305 return errors != null ? errors : Collections.<TestError> emptyList(); 306 } 307 308 public Set<OsmPrimitive> getFilter() { 309 return filter; 310 } 311 312 public void setFilter(Set<OsmPrimitive> filter) { 313 if (filter != null && filter.isEmpty()) { 314 this.filter = null; 315 } else { 316 this.filter = filter; 317 } 318 if (isVisible()) { 319 buildTree(); 320 } 321 } 322 323 /** 324 * Updates the current errors list 325 * @param errors The validation errors 326 */ 327 public void resetErrors() { 328 List<TestError> e = new ArrayList<TestError>(errors); 329 setErrors(e); 330 } 331 332 /** 333 * Expands all tree 334 */ 335 @SuppressWarnings("unchecked") 336 public void expandAll() { 337 DefaultMutableTreeNode root = getRoot(); 338 339 int row = 0; 340 Enumeration<DefaultMutableTreeNode> children = root.breadthFirstEnumeration(); 341 while (children.hasMoreElements()) { 342 children.nextElement(); 343 expandRow(row++); 344 } 345 } 346 347 /** 348 * Returns the root node model. 349 * @return The root node model 350 */ 351 public DefaultMutableTreeNode getRoot() { 352 return (DefaultMutableTreeNode) valTreeModel.getRoot(); 353 } 354 355 public int getUpdateCount() { 356 return updateCount; 357 } 358 }