001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.log4j.lf5.viewer.categoryexplorer; 018 019import java.awt.AWTEventMulticaster; 020import java.awt.event.ActionEvent; 021import java.awt.event.ActionListener; 022import java.util.Enumeration; 023 024import javax.swing.SwingUtilities; 025import javax.swing.tree.DefaultTreeModel; 026import javax.swing.tree.TreeNode; 027import javax.swing.tree.TreePath; 028 029import org.apache.log4j.lf5.LogRecord; 030 031/** 032 * CategoryExplorerModel 033 * 034 * @author Michael J. Sikorsky 035 * @author Robert Shaw 036 * @author Brent Sprecher 037 * @author Richard Hurst 038 */ 039 040// Contributed by ThoughtWorks Inc. 041 042public class CategoryExplorerModel extends DefaultTreeModel { 043 private static final long serialVersionUID = -3413887384316015901L; 044 045 //-------------------------------------------------------------------------- 046 // Constants: 047 //-------------------------------------------------------------------------- 048 049 //-------------------------------------------------------------------------- 050 // Protected Variables: 051 //-------------------------------------------------------------------------- 052 053 protected boolean _renderFatal = true; 054 protected ActionListener _listener = null; 055 protected ActionEvent _event = new ActionEvent(this, 056 ActionEvent.ACTION_PERFORMED, 057 "Nodes Selection changed"); 058 059 //-------------------------------------------------------------------------- 060 // Private Variables: 061 //-------------------------------------------------------------------------- 062 063 //-------------------------------------------------------------------------- 064 // Constructors: 065 //-------------------------------------------------------------------------- 066 067 public CategoryExplorerModel(CategoryNode node) { 068 super(node); 069 } 070 //-------------------------------------------------------------------------- 071 // Public Methods: 072 //-------------------------------------------------------------------------- 073 074 public void addLogRecord(LogRecord lr) { 075 CategoryPath path = new CategoryPath(lr.getCategory()); 076 addCategory(path); // create category path if it is new 077 CategoryNode node = getCategoryNode(path); 078 node.addRecord(); // update category node 079 if (_renderFatal && lr.isFatal()) { 080 TreeNode[] nodes = getPathToRoot(node); 081 int len = nodes.length; 082 CategoryNode parent; 083 084 // i = 0 gives root node 085 // skip node and root, loop through "parents" in between 086 for (int i = 1; i < len - 1; i++) { 087 parent = (CategoryNode) nodes[i]; 088 parent.setHasFatalChildren(true); 089 nodeChanged(parent); 090 } 091 node.setHasFatalRecords(true); 092 nodeChanged(node); 093 } 094 } 095 096 public CategoryNode getRootCategoryNode() { 097 return (CategoryNode) getRoot(); 098 } 099 100 public CategoryNode getCategoryNode(String category) { 101 CategoryPath path = new CategoryPath(category); 102 return (getCategoryNode(path)); 103 } 104 105 /** 106 * returns null if no CategoryNode exists. 107 */ 108 public CategoryNode getCategoryNode(CategoryPath path) { 109 CategoryNode root = (CategoryNode) getRoot(); 110 CategoryNode parent = root; // Start condition. 111 112 for (int i = 0; i < path.size(); i++) { 113 CategoryElement element = path.categoryElementAt(i); 114 115 // If the two nodes have matching titles they are considered equal. 116 Enumeration children = parent.children(); 117 118 boolean categoryAlreadyExists = false; 119 while (children.hasMoreElements()) { 120 CategoryNode node = (CategoryNode) children.nextElement(); 121 String title = node.getTitle().toLowerCase(); 122 123 String pathLC = element.getTitle().toLowerCase(); 124 if (title.equals(pathLC)) { 125 categoryAlreadyExists = true; 126 // This is now the new parent node. 127 parent = node; 128 break; // out of the while, and back to the for(). 129 } 130 } 131 132 if (categoryAlreadyExists == false) { 133 return null; // Didn't find the Node. 134 } 135 } 136 137 return (parent); 138 } 139 140 /** 141 * @return true if all the nodes in the specified CategoryPath are 142 * selected. 143 */ 144 public boolean isCategoryPathActive(CategoryPath path) { 145 CategoryNode root = (CategoryNode) getRoot(); 146 CategoryNode parent = root; // Start condition. 147 boolean active = false; 148 149 for (int i = 0; i < path.size(); i++) { 150 CategoryElement element = path.categoryElementAt(i); 151 152 // If the two nodes have matching titles they are considered equal. 153 Enumeration children = parent.children(); 154 155 boolean categoryAlreadyExists = false; 156 active = false; 157 158 while (children.hasMoreElements()) { 159 CategoryNode node = (CategoryNode) children.nextElement(); 160 String title = node.getTitle().toLowerCase(); 161 162 String pathLC = element.getTitle().toLowerCase(); 163 if (title.equals(pathLC)) { 164 categoryAlreadyExists = true; 165 // This is now the new parent node. 166 parent = node; 167 168 if (parent.isSelected()) { 169 active = true; 170 } 171 172 break; // out of the while, and back to the for(). 173 } 174 } 175 176 if (active == false || categoryAlreadyExists == false) { 177 return false; 178 } 179 } 180 181 return (active); 182 } 183 184 185 /** 186 * <p>Method altered by Richard Hurst such that it returns the CategoryNode 187 * corresponding to the CategoryPath</p> 188 * 189 * @param path category path. 190 * @return CategoryNode 191 */ 192 public CategoryNode addCategory(CategoryPath path) { 193 CategoryNode root = (CategoryNode) getRoot(); 194 CategoryNode parent = root; // Start condition. 195 196 for (int i = 0; i < path.size(); i++) { 197 CategoryElement element = path.categoryElementAt(i); 198 199 // If the two nodes have matching titles they are considered equal. 200 Enumeration children = parent.children(); 201 202 boolean categoryAlreadyExists = false; 203 while (children.hasMoreElements()) { 204 CategoryNode node = (CategoryNode) children.nextElement(); 205 String title = node.getTitle().toLowerCase(); 206 207 String pathLC = element.getTitle().toLowerCase(); 208 if (title.equals(pathLC)) { 209 categoryAlreadyExists = true; 210 // This is now the new parent node. 211 parent = node; 212 break; 213 } 214 } 215 216 if (categoryAlreadyExists == false) { 217 // We need to add the node. 218 CategoryNode newNode = new CategoryNode(element.getTitle()); 219 220 //This method of adding a new node cause parent roots to be 221 // collapsed. 222 //parent.add( newNode ); 223 //reload(parent); 224 225 // This doesn't force the nodes to collapse. 226 insertNodeInto(newNode, parent, parent.getChildCount()); 227 refresh(newNode); 228 229 // The newly added node is now the parent. 230 parent = newNode; 231 232 } 233 } 234 235 return parent; 236 } 237 238 public void update(CategoryNode node, boolean selected) { 239 if (node.isSelected() == selected) { 240 return; // nothing was changed, nothing to do 241 } 242 // select parents or deselect children 243 if (selected) { 244 setParentSelection(node, true); 245 } else { 246 setDescendantSelection(node, false); 247 } 248 } 249 250 public void setDescendantSelection(CategoryNode node, boolean selected) { 251 Enumeration descendants = node.depthFirstEnumeration(); 252 CategoryNode current; 253 while (descendants.hasMoreElements()) { 254 current = (CategoryNode) descendants.nextElement(); 255 // does the current node need to be changed? 256 if (current.isSelected() != selected) { 257 current.setSelected(selected); 258 nodeChanged(current); 259 } 260 } 261 notifyActionListeners(); 262 } 263 264 public void setParentSelection(CategoryNode node, boolean selected) { 265 TreeNode[] nodes = getPathToRoot(node); 266 int len = nodes.length; 267 CategoryNode parent; 268 269 // i = 0 gives root node, i=len-1 gives this node 270 // skip the root node 271 for (int i = 1; i < len; i++) { 272 parent = (CategoryNode) nodes[i]; 273 if (parent.isSelected() != selected) { 274 parent.setSelected(selected); 275 nodeChanged(parent); 276 } 277 } 278 notifyActionListeners(); 279 } 280 281 282 public synchronized void addActionListener(ActionListener l) { 283 _listener = AWTEventMulticaster.add(_listener, l); 284 } 285 286 public synchronized void removeActionListener(ActionListener l) { 287 _listener = AWTEventMulticaster.remove(_listener, l); 288 } 289 290 public void resetAllNodeCounts() { 291 Enumeration nodes = getRootCategoryNode().depthFirstEnumeration(); 292 CategoryNode current; 293 while (nodes.hasMoreElements()) { 294 current = (CategoryNode) nodes.nextElement(); 295 current.resetNumberOfContainedRecords(); 296 nodeChanged(current); 297 } 298 } 299 300 /** 301 * <p>Returns the CategoryPath to the specified CategoryNode</p> 302 * 303 * @param node The target CategoryNode 304 * @return CategoryPath 305 */ 306 public TreePath getTreePathToRoot(CategoryNode node) { 307 if (node == null) { 308 return null; 309 } 310 return (new TreePath(getPathToRoot(node))); 311 } 312 313 //-------------------------------------------------------------------------- 314 // Protected Methods: 315 //-------------------------------------------------------------------------- 316 protected void notifyActionListeners() { 317 if (_listener != null) { 318 _listener.actionPerformed(_event); 319 } 320 } 321 322 /** 323 * Fires a nodechanged event on the SwingThread. 324 */ 325 protected void refresh(final CategoryNode node) { 326 SwingUtilities.invokeLater(new Runnable() { 327 public void run() { 328 nodeChanged(node); // remind the tree to render the new node 329 } 330 }); 331 } 332 333 //-------------------------------------------------------------------------- 334 // Private Methods: 335 //-------------------------------------------------------------------------- 336 337 //-------------------------------------------------------------------------- 338 // Nested Top-Level Classes or Interfaces: 339 //-------------------------------------------------------------------------- 340 341} 342 343 344 345 346 347