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 }