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