oburn       02/03/22 23:51:26

  Added:       src/java/org/apache/log4j/chainsaw XMLFileHandler.java
                        MyTableModel.java Main.java LoggingReceiver.java
                        LoadXMLAction.java ExitAction.java
                        EventDetails.java DetailPanel.java
                        ControlPanel.java
  Log:
  First version of Chainsaw based on version 1.1. The changes made were:
  - Change the package name
  - Change the license to Apache
  - Remove the startup sound
  - Remove the test generator class
  - Change the email address for the author.
  
  Revision  Changes    Path
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/XMLFileHandler.java
  
  Index: XMLFileHandler.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.util.StringTokenizer;
  import org.apache.log4j.Priority;
  import org.xml.sax.Attributes;
  import org.xml.sax.SAXException;
  import org.xml.sax.helpers.DefaultHandler;
  
  /**
   * A content handler for document containing Log4J events logged using the
   * XMLLayout class. It will create events and add them to a supplied model.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   * @version 1.0
   */
  class XMLFileHandler
      extends DefaultHandler
  {
      /** represents the event tag **/
      private static final String TAG_EVENT = "log4j:event";
      /** represents the message tag **/
      private static final String TAG_MESSAGE = "log4j:message";
      /** represents the ndc tag **/
      private static final String TAG_NDC = "log4j:NDC";
      /** represents the throwable tag **/
      private static final String TAG_THROWABLE = "log4j:throwable";
      /** represents the location info tag **/
      private static final String TAG_LOCATION_INFO = "log4j:locationInfo";
  
      /** where to put the events **/
      private final MyTableModel mModel;
      /** the number of events in the document **/
      private int mNumEvents;
      /** the current element being parsed **/
      private String mCurrentElement;
  
      /** the time of the event **/
      private long mTimeStamp;
      /** the priority of the event **/
      private Priority mPriority;
      /** the category of the event **/
      private String mCategoryName;
      /** the NDC for the event **/
      private String mNDC;
      /** the thread for the event **/
      private String mThreadName;
      /** the msg for the event **/
      private String mMessage;
      /** the throwable details the event **/
      private String[] mThrowableStrRep;
      /** the location details for the event **/
      private String mLocationDetails;
  
  
      /**
       * Creates a new <code>XMLFileHandler</code> instance.
       *
       * @param aModel where to add the events
       */
      XMLFileHandler(MyTableModel aModel) {
          mModel = aModel;
      }
  
      /** @see DefaultHandler **/
      public void startDocument()
          throws SAXException
      {
          mNumEvents = 0;
      }
  
      /** @see DefaultHandler **/
      public void characters(char[] aChars, int aStart, int aLength) {
          if (mCurrentElement == TAG_NDC) {
              mNDC = new String(aChars, aStart, aLength);
          } else if (mCurrentElement == TAG_MESSAGE) {
              mMessage = new String(aChars, aStart, aLength);
          } else if (mCurrentElement == TAG_THROWABLE) {
              final StringTokenizer st =
                  new StringTokenizer(new String(aChars, aStart, aLength), "\t");
              mThrowableStrRep = new String[st.countTokens()];
              if (mThrowableStrRep.length > 0) {
                  mThrowableStrRep[0] = st.nextToken();
                  for (int i = 1; i < mThrowableStrRep.length; i++) {
                      mThrowableStrRep[i] = "\t" + st.nextToken();
                  }
              }
          }
      }
  
      /** @see DefaultHandler **/
      public void endElement(String aNamespaceURI,
                             String aLocalName,
                             String aQName)
      {
          if (TAG_EVENT.equals(aQName)) {
              addEvent();
              resetData();
          } else if (mCurrentElement != TAG_EVENT) {
              mCurrentElement = TAG_EVENT; // hack - but only thing I care about
          }
      }
  
      /** @see DefaultHandler **/
      public void startElement(String aNamespaceURI,
                               String aLocalName,
                               String aQName,
                               Attributes aAtts)
      {
          if (TAG_EVENT.equals(aQName)) {
              mThreadName = aAtts.getValue("thread");
              mTimeStamp = Long.parseLong(aAtts.getValue("timestamp"));
              mCategoryName = aAtts.getValue("category");
              mPriority = Priority.toPriority(aAtts.getValue("priority"));
          } else if (TAG_LOCATION_INFO.equals(aQName)) {
              mLocationDetails = aAtts.getValue("class") + "."
                  + aAtts.getValue("method")
                  + "(" + aAtts.getValue("file") + ":" + aAtts.getValue("line")
                  + ")";
          } else if (TAG_NDC.equals(aQName)) {
              mCurrentElement = TAG_NDC;
          } else if (TAG_MESSAGE.equals(aQName)) {
              mCurrentElement = TAG_MESSAGE;
          } else if (TAG_THROWABLE.equals(aQName)) {
              mCurrentElement = TAG_THROWABLE;
          }
      }
  
      /** @return the number of events in the document **/
      int getNumEvents() {
          return mNumEvents;
      }
  
      ////////////////////////////////////////////////////////////////////////////
      // Private methods
      ////////////////////////////////////////////////////////////////////////////
  
      /** Add an event to the model **/
      private void addEvent() {
          mModel.addEvent(new EventDetails(mTimeStamp,
                                           mPriority,
                                           mCategoryName,
                                           mNDC,
                                           mThreadName,
                                           mMessage,
                                           mThrowableStrRep,
                                           mLocationDetails));
          mNumEvents++;
      }
  
      /** Reset the data for an event **/
      private void resetData() {
          mTimeStamp = 0;
          mPriority = null;
          mCategoryName = null;
          mNDC = null;
          mThreadName = null;
          mMessage = null;
          mThrowableStrRep = null;
          mLocationDetails = null;
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/MyTableModel.java
  
  Index: MyTableModel.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.text.DateFormat;
  import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.Date;
  import java.util.Iterator;
  import java.util.List;
  import java.util.SortedSet;
  import java.util.TreeSet;
  import javax.swing.table.AbstractTableModel;
  import org.apache.log4j.Priority;
  import org.apache.log4j.Category;
  
  /**
   * Represents a list of <code>EventDetails</code> objects that are sorted on
   * logging time. Methods are provided to filter the events that are visible.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   */
  class MyTableModel
      extends AbstractTableModel
  {
  
      /** used to log messages **/
      private static final Category LOG =
          Category.getInstance(MyTableModel.class);
  
      /** use the compare logging events **/
      private static final Comparator MY_COMP = new Comparator()
      {
          /** @see Comparator **/
          public int compare(Object aObj1, Object aObj2) {
              if ((aObj1 == null) && (aObj2 == null)) {
                  return 0; // treat as equal
              } else if (aObj1 == null) {
                  return -1; // null less than everything
              } else if (aObj2 == null) {
                  return 1; // think about it. :->
              }
  
              // will assume only have LoggingEvent
              final EventDetails le1 = (EventDetails) aObj1;
              final EventDetails le2 = (EventDetails) aObj2;
  
              if (le1.getTimeStamp() < le2.getTimeStamp()) {
                  return 1;
              }
              // assume not two events are logged at exactly the same time
              return -1;
          }
          };
  
      /**
       * Helper that actually processes incoming events.
       * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
       */
      private class Processor
          implements Runnable
      {
          /** loops getting the events **/
          public void run() {
              while (true) {
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      // ignore
                  }
  
                  synchronized (mLock) {
                      if (mPaused) {
                          continue;
                      }
  
                      boolean toHead = true; // were events added to head
                      boolean needUpdate = false;
                      final Iterator it = mPendingEvents.iterator();
                      while (it.hasNext()) {
                          final EventDetails event = (EventDetails) it.next();
                          mAllEvents.add(event);
                          toHead = toHead && (event == mAllEvents.first());
                          needUpdate = needUpdate || matchFilter(event);
                      }
                      mPendingEvents.clear();
  
                      if (needUpdate) {
                          updateFilteredEvents(toHead);
                      }
                  }
              }
  
          }
      }
  
  
      /** names of the columns in the table **/
      private static final String[] COL_NAMES = {
          "Time", "Priority", "Trace", "Category", "NDC", "Message"};
  
      /** definition of an empty list **/
      private static final EventDetails[] EMPTY_LIST =  new EventDetails[] {};
  
      /** used to format dates **/
      private static final DateFormat DATE_FORMATTER =
          DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
  
      /** the lock to control access **/
      private final Object mLock = new Object();
      /** set of all logged events - not filtered **/
      private final SortedSet mAllEvents = new TreeSet(MY_COMP);
      /** events that are visible after filtering **/
      private EventDetails[] mFilteredEvents = EMPTY_LIST;
      /** list of events that are buffered for processing **/
      private final List mPendingEvents = new ArrayList();
      /** indicates whether event collection is paused to the UI **/
      private boolean mPaused = false;
  
      /** filter for the thread **/
      private String mThreadFilter = "";
      /** filter for the message **/
      private String mMessageFilter = "";
      /** filter for the NDC **/
      private String mNDCFilter = "";
      /** filter for the category **/
      private String mCategoryFilter = "";
      /** filter for the priority **/
      private Priority mPriorityFilter = Priority.DEBUG;
  
  
      /**
       * Creates a new <code>MyTableModel</code> instance.
       *
       */
      MyTableModel() {
          final Thread t = new Thread(new Processor());
          t.setDaemon(true);
          t.start();
      }
  
  
      ////////////////////////////////////////////////////////////////////////////
      // Table Methods
      ////////////////////////////////////////////////////////////////////////////
  
      /** @see TableModel **/
      public int getRowCount() {
          synchronized (mLock) {
              return mFilteredEvents.length;
          }
      }
  
      /** @see TableModel **/
      public int getColumnCount() {
          // does not need to be synchronized
          return COL_NAMES.length;
      }
  
      /** @see TableModel **/
      public String getColumnName(int aCol) {
          // does not need to be synchronized
          return COL_NAMES[aCol];
      }
  
      /** @see TableModel **/
      public Class getColumnClass(int aCol) {
          // does not need to be synchronized
          return (aCol == 2) ? Boolean.class : Object.class;
      }
  
      /** @see TableModel **/
      public Object getValueAt(int aRow, int aCol) {
          synchronized (mLock) {
              final EventDetails event = mFilteredEvents[aRow];
  
              if (aCol == 0) {
                  return DATE_FORMATTER.format(new Date(event.getTimeStamp()));
              } else if (aCol == 1) {
                  return event.getPriority();
              } else if (aCol == 2) {
                  return (event.getThrowableStrRep() == null)
                      ? Boolean.FALSE : Boolean.TRUE;
              } else if (aCol == 3) {
                  return event.getCategoryName();
              } else if (aCol == 4) {
                  return event.getNDC();
              }
              return event.getMessage();
          }
      }
  
      ////////////////////////////////////////////////////////////////////////////
      // Public Methods
      ////////////////////////////////////////////////////////////////////////////
  
      /**
       * Sets the priority to filter events on. Only events of equal or higher
       * property are now displayed.
       *
       * @param aPriority the priority to filter on
       */
      public void setPriorityFilter(Priority aPriority) {
          synchronized (mLock) {
              mPriorityFilter = aPriority;
              updateFilteredEvents(false);
          }
      }
  
      /**
       * Set the filter for the thread field.
       *
       * @param aStr the string to match
       */
      public void setThreadFilter(String aStr) {
          synchronized (mLock) {
              mThreadFilter = aStr.trim();
              updateFilteredEvents(false);
          }
      }
  
      /**
       * Set the filter for the message field.
       *
       * @param aStr the string to match
       */
      public void setMessageFilter(String aStr) {
          synchronized (mLock) {
              mMessageFilter = aStr.trim();
              updateFilteredEvents(false);
          }
      }
  
      /**
       * Set the filter for the NDC field.
       *
       * @param aStr the string to match
       */
      public void setNDCFilter(String aStr) {
          synchronized (mLock) {
              mNDCFilter = aStr.trim();
              updateFilteredEvents(false);
          }
      }
  
      /**
       * Set the filter for the category field.
       *
       * @param aStr the string to match
       */
      public void setCategoryFilter(String aStr) {
          synchronized (mLock) {
              mCategoryFilter = aStr.trim();
              updateFilteredEvents(false);
          }
      }
  
      /**
       * Add an event to the list.
       *
       * @param aEvent a <code>EventDetails</code> value
       */
      public void addEvent(EventDetails aEvent) {
          synchronized (mLock) {
              mPendingEvents.add(aEvent);
          }
      }
  
      /**
       * Clear the list of all events.
       */
      public void clear() {
          synchronized (mLock) {
              mAllEvents.clear();
              mFilteredEvents = new EventDetails[0];
              mPendingEvents.clear();
              fireTableDataChanged();
          }
      }
  
      /** Toggle whether collecting events **/
      public void toggle() {
          synchronized (mLock) {
              mPaused = !mPaused;
          }
      }
  
      /** @return whether currently paused collecting events **/
      public boolean isPaused() {
          synchronized (mLock) {
              return mPaused;
          }
      }
  
      /**
       * Get the throwable information at a specified row in the filtered events.
       *
       * @param aRow the row index of the event
       * @return the throwable information
       */
      public EventDetails getEventDetails(int aRow) {
          synchronized (mLock) {
              return mFilteredEvents[aRow];
          }
      }
  
      ////////////////////////////////////////////////////////////////////////////
      // Private methods
      ////////////////////////////////////////////////////////////////////////////
  
      /**
       * Update the filtered events data structure.
       * @param aInsertedToFront indicates whether events were added to front of
       *        the events. If true, then the current first event must still exist
       *        in the list after the filter is applied.
       */
      private void updateFilteredEvents(boolean aInsertedToFront) {
          final List filtered = new ArrayList();
          final Iterator it = mAllEvents.iterator();
          while (it.hasNext()) {
              final EventDetails event = (EventDetails) it.next();
              if (matchFilter(event)) {
                  filtered.add(event);
              }
          }
  
          final EventDetails lastFirst = (mFilteredEvents.length == 0)
              ? null
              : mFilteredEvents[0];
          mFilteredEvents = (EventDetails[]) filtered.toArray(EMPTY_LIST);
  
          if (aInsertedToFront && (lastFirst != null)) {
              final int index = filtered.indexOf(lastFirst);
              if (index < 1) {
                  LOG.warn("In strange state");
                  fireTableDataChanged();
              } else {
                  fireTableRowsInserted(0, index - 1);
              }
          } else {
              fireTableDataChanged();
          }
      }
  
      /**
       * Returns whether an event matches the filters.
       *
       * @param aEvent the event to check for a match
       * @return whether the event matches
       */
      private boolean matchFilter(EventDetails aEvent) {
          if (aEvent.getPriority().isGreaterOrEqual(mPriorityFilter) &&
              (aEvent.getThreadName().indexOf(mThreadFilter) >= 0) &&
              (aEvent.getCategoryName().indexOf(mCategoryFilter) >= 0) &&
              ((mNDCFilter.length() == 0) ||
               ((aEvent.getNDC() != null) &&
                (aEvent.getNDC().indexOf(mNDCFilter) >= 0))))
          {
              final String rm = aEvent.getMessage();
              if (rm == null) {
                  // only match if we have not filtering in place
                  return (mMessageFilter.length() == 0);
              } else {
                  return (rm.indexOf(mMessageFilter) >= 0);
              }
          }
  
          return false; // by default not match
      }
  }
  
  
  
  1.1                  jakarta-log4j/src/java/org/apache/log4j/chainsaw/Main.java
  
  Index: Main.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.awt.BorderLayout;
  import java.awt.Dimension;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.io.IOException;
  import java.util.Properties;
  import javax.swing.BorderFactory;
  import javax.swing.JFrame;
  import javax.swing.JMenu;
  import javax.swing.JMenuBar;
  import javax.swing.JMenuItem;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JSplitPane;
  import javax.swing.JTable;
  import javax.swing.ListSelectionModel;
  import org.apache.log4j.Category;
  import org.apache.log4j.PropertyConfigurator;
  
  /**
   * The main application.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   */
  public class Main
      extends JFrame
  {
      /** the default port number to listen on **/
      private static final int DEFAULT_PORT = 4445;
  
      /** name of property for port name **/
      public static final String PORT_PROP_NAME = "chainsaw.port";
  
      /** use to log messages **/
      private static final Category LOG = Category.getInstance(Main.class);
  
  
      /**
       * Creates a new <code>Main</code> instance.
       */
      private Main() {
          super("CHAINSAW - Log4J Log Viewer");
          // create the all important model
          final MyTableModel model = new MyTableModel();
  
          //Create the menu bar.
          final JMenuBar menuBar = new JMenuBar();
          setJMenuBar(menuBar);
          final JMenu menu = new JMenu("File");
          menuBar.add(menu);
  
          try {
              final LoadXMLAction lxa = new LoadXMLAction(this, model);
              final JMenuItem loadMenuItem = new JMenuItem("Load file...");
              menu.add(loadMenuItem);
              loadMenuItem.addActionListener(lxa);
          } catch (Exception e) {
              LOG.info("Unable to create the action to load XML files", e);
              JOptionPane.showMessageDialog(
                  this,
                  "Unable to create a XML parser - unable to load XML events.",
                  "CHAINSAW",
                  JOptionPane.ERROR_MESSAGE);
          }
  
          final JMenuItem exitMenuItem = new JMenuItem("Exit");
          menu.add(exitMenuItem);
          exitMenuItem.addActionListener(ExitAction.INSTANCE);
  
          // Add control panel
          final ControlPanel cp = new ControlPanel(model);
          getContentPane().add(cp, BorderLayout.NORTH);
  
          // Create the table
          final JTable table = new JTable(model);
          table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          final JScrollPane scrollPane = new JScrollPane(table);
          scrollPane.setBorder(BorderFactory.createTitledBorder("Events: "));
          scrollPane.setPreferredSize(new Dimension(900, 300));
  
          // Create the details
          final JPanel details = new DetailPanel(table, model);
          details.setPreferredSize(new Dimension(900, 300));
  
          // Add the table and stack trace into a splitter
          final JSplitPane jsp =
              new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, details);
          getContentPane().add(jsp, BorderLayout.CENTER);
  
          addWindowListener(new WindowAdapter() {
                  public void windowClosing(WindowEvent aEvent) {
                      ExitAction.INSTANCE.actionPerformed(null);
                  }
              });
  
          pack();
          setVisible(true);
  
          setupReceiver(model);
      }
  
      /**
       * Setup recieving messages.
       *
       * @param aModel a <code>MyTableModel</code> value
       */
      private void setupReceiver(MyTableModel aModel) {
          int port = DEFAULT_PORT;
          final String strRep = System.getProperty(PORT_PROP_NAME);
          if (strRep != null) {
              try {
                  port = Integer.parseInt(strRep);
              } catch (NumberFormatException nfe) {
                  LOG.fatal("Unable to parse " + PORT_PROP_NAME +
                            " property with value " + strRep + ".");
                  JOptionPane.showMessageDialog(
                      this,
                      "Unable to parse port number from '" + strRep +
                      "', quitting.",
                      "CHAINSAW",
                      JOptionPane.ERROR_MESSAGE);
                  System.exit(1);
              }
          }
  
          try {
              final LoggingReceiver lr = new LoggingReceiver(aModel, port);
              lr.start();
          } catch (IOException e) {
              LOG.fatal("Unable to connect to socket server, quiting", e);
              JOptionPane.showMessageDialog(
                  this,
                  "Unable to create socket on port " + port + ", quitting.",
                  "CHAINSAW",
                  JOptionPane.ERROR_MESSAGE);
              System.exit(1);
          }
      }
  
  
      ////////////////////////////////////////////////////////////////////////////
      // static methods
      ////////////////////////////////////////////////////////////////////////////
  
  
      /** initialise log4j **/
      private static void initLog4J() {
          final Properties props = new Properties();
          props.setProperty("log4j.rootCategory", "DEBUG, A1");
          props.setProperty("log4j.appender.A1",
                            "org.apache.log4j.ConsoleAppender");
          props.setProperty("log4j.appender.A1.layout",
                            "org.apache.log4j.TTCCLayout");
          PropertyConfigurator.configure(props);
      }
  
      /**
       * The main method.
       *
       * @param aArgs ignored
       */
      public static void main(String[] aArgs) {
          initLog4J();
          new Main();
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/LoggingReceiver.java
  
  Index: LoggingReceiver.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.io.EOFException;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.net.ServerSocket;
  import java.net.Socket;
  import java.net.SocketException;
  import org.apache.log4j.Category;
  import org.apache.log4j.spi.LoggingEvent;
  
  /**
   * A daemon thread the processes connections from a
   * <code>org.apache.log4j.net.SocketAppender.html</code>.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   */
  class LoggingReceiver
      extends Thread
  {
      /** used to log messages **/
      private static final Category LOG =
          Category.getInstance(LoggingReceiver.class);
  
      /**
       * Helper that actually processes a client connection. It receives events
       * and adds them to the supplied model.
       *
       * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
       */
      private class Slurper
          implements Runnable
      {
          /** socket connection to read events from **/
          private final Socket mClient;
  
          /**
           * Creates a new <code>Slurper</code> instance.
           *
           * @param aClient socket to receive events from
           */
          Slurper(Socket aClient) {
              mClient = aClient;
          }
  
          /** loops getting the events **/
          public void run() {
              LOG.debug("Starting to get data");
              try {
                  final ObjectInputStream ois =
                      new ObjectInputStream(mClient.getInputStream());
                  while (true) {
                      final LoggingEvent event = (LoggingEvent) ois.readObject();
                      mModel.addEvent(new EventDetails(event));
                  }
              } catch (EOFException e) {
                  LOG.info("Reached EOF, closing connection");
              } catch (SocketException e) {
                  LOG.info("Caught SocketException, closing connection");
              } catch (IOException e) {
                  LOG.warn("Got IOException, closing connection", e);
              } catch (ClassNotFoundException e) {
                  LOG.warn("Got ClassNotFoundException, closing connection", e);
              }
  
              try {
                  mClient.close();
              } catch (IOException e) {
                  LOG.warn("Error closing connection", e);
              }
          }
      }
  
      /** where to put the events **/
      private final MyTableModel mModel;
  
      /** server for listening for connections **/
      private final ServerSocket mSvrSock;
  
      /**
       * Creates a new <code>LoggingReceiver</code> instance.
       *
       * @param aModel model to place put received into
       * @param aPort port to listen on
       * @throws IOException if an error occurs
       */
      LoggingReceiver(MyTableModel aModel, int aPort)
          throws IOException
      {
          setDaemon(true);
          mModel = aModel;
          mSvrSock = new ServerSocket(aPort);
      }
  
      /** Listens for client connections **/
      public void run() {
          LOG.info("Thread started");
          try {
              while (true) {
                  LOG.debug("Waiting for a connection");
                  final Socket client = mSvrSock.accept();
                  LOG.debug("Got a connection from " +
                            client.getInetAddress().getHostName());
                  final Thread t = new Thread(new Slurper(client));
                  t.setDaemon(true);
                  t.start();
              }
          } catch (IOException e) {
              LOG.error("Error in accepting connections, stopping.", e);
          }
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/LoadXMLAction.java
  
  Index: LoadXMLAction.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.awt.event.ActionEvent;
  import java.io.File;
  import java.io.IOException;
  import java.io.StringReader;
  import javax.swing.AbstractAction;
  import javax.swing.JFileChooser;
  import javax.swing.JFrame;
  import javax.swing.JOptionPane;
  import javax.xml.parsers.ParserConfigurationException;
  import javax.xml.parsers.SAXParserFactory;
  import org.apache.log4j.Category;
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.XMLReader;
  
  /**
   * Encapsulates the action to load an XML file.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   * @version 1.0
   */
  class LoadXMLAction
      extends AbstractAction
  {
      /** use to log messages **/
      private static final Category LOG =
          Category.getInstance(LoadXMLAction.class);
  
      /** the parent frame **/
      private final JFrame mParent;
  
      /**
       * the file chooser - configured to allow only the selection of a
       * single file.
       */
      private final JFileChooser mChooser = new JFileChooser();
      {
          mChooser.setMultiSelectionEnabled(false);
          mChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      }
  
      /** parser to read XML files **/
      private final XMLReader mParser;
      /** the content handler **/
      private final XMLFileHandler mHandler;
  
  
      /**
       * Creates a new <code>LoadXMLAction</code> instance.
       *
       * @param aParent the parent frame
       * @param aModel the model to add events to
       * @exception SAXException if an error occurs
       * @throws ParserConfigurationException if an error occurs
       */
      LoadXMLAction(JFrame aParent, MyTableModel aModel)
          throws SAXException, ParserConfigurationException
      {
          mParent = aParent;
          mHandler = new XMLFileHandler(aModel);
          mParser = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
          mParser.setContentHandler(mHandler);
      }
  
      /**
       * Prompts the user for a file to load events from.
       * @param aIgnore an <code>ActionEvent</code> value
       */
      public void actionPerformed(ActionEvent aIgnore) {
          LOG.info("load file called");
          if (mChooser.showOpenDialog(mParent) == JFileChooser.APPROVE_OPTION) {
              LOG.info("Need to load a file");
              final File chosen = mChooser.getSelectedFile();
              LOG.info("loading the contents of " + chosen.getAbsolutePath());
              try {
                  final int num = loadFile(chosen.getAbsolutePath());
                  JOptionPane.showMessageDialog(
                      mParent,
                      "Loaded " + num + " events.",
                      "CHAINSAW",
                      JOptionPane.INFORMATION_MESSAGE);
              } catch (Exception e) {
                  LOG.warn("caught an exception loading the file", e);
                  JOptionPane.showMessageDialog(
                      mParent,
                      "Error parsing file - " + e.getMessage(),
                      "CHAINSAW",
                      JOptionPane.ERROR_MESSAGE);
              }
          }
      }
  
      /**
       * Loads the contents of file into the model
       *
       * @param aFile the file to extract events from
       * @return the number of events loaded
       * @throws SAXException if an error occurs
       * @throws IOException if an error occurs
       */
      private int loadFile(String aFile)
          throws SAXException, IOException
      {
          synchronized (mParser) {
              // Create a dummy document to parse the file
              final StringBuffer buf = new StringBuffer();
              buf.append("<?xml version=\"1.0\" standalone=\"yes\"?>\n");
              buf.append("<!DOCTYPE log4j:eventSet ");
              buf.append("[<!ENTITY data SYSTEM \"file:///");
              buf.append(aFile);
              buf.append("\">]>\n");
              buf.append("<log4j:eventSet xmlns:log4j=\"Claira\">\n");
              buf.append("&data;\n");
              buf.append("</log4j:eventSet>\n");
  
              final InputSource is =
                  new InputSource(new StringReader(buf.toString()));
              mParser.parse(is);
              return mHandler.getNumEvents();
          }
      }
  }
  
  
  
  1.1                  jakarta-log4j/src/java/org/apache/log4j/chainsaw/ExitAction.java
  
  Index: ExitAction.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.awt.event.ActionEvent;
  import javax.swing.AbstractAction;
  import org.apache.log4j.Category;
  
  /**
   * Encapsulates the action to exit.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   * @version 1.0
   */
  class ExitAction
      extends AbstractAction
  {
      /** use to log messages **/
      private static final Category LOG = Category.getInstance(ExitAction.class);
      /** The instance to share **/
      public static final ExitAction INSTANCE = new ExitAction();
  
      /** Stop people creating instances **/
      private ExitAction() {}
  
      /**
       * Will shutdown the application.
       * @param aIgnore ignored
       */
      public void actionPerformed(ActionEvent aIgnore) {
          LOG.info("shutting down");
          System.exit(0);
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/EventDetails.java
  
  Index: EventDetails.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import org.apache.log4j.Priority;
  import org.apache.log4j.spi.LoggingEvent;
  
  /**
   * Represents the details of a logging event. It is intended to overcome the
   * problem that a LoggingEvent cannot be constructed with purely fake data.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   * @version 1.0
   */
  class EventDetails {
  
      /** the time of the event **/
      private final long mTimeStamp;
      /** the priority of the event **/
      private final Priority mPriority;
      /** the category of the event **/
      private final String mCategoryName;
      /** the NDC for the event **/
      private final String mNDC;
      /** the thread for the event **/
      private final String mThreadName;
      /** the msg for the event **/
      private final String mMessage;
      /** the throwable details the event **/
      private final String[] mThrowableStrRep;
      /** the location details for the event **/
      private final String mLocationDetails;
  
      /**
       * Creates a new <code>EventDetails</code> instance.
       * @param aTimeStamp a <code>long</code> value
       * @param aPriority a <code>Priority</code> value
       * @param aCategoryName a <code>String</code> value
       * @param aNDC a <code>String</code> value
       * @param aThreadName a <code>String</code> value
       * @param aMessage a <code>String</code> value
       * @param aThrowableStrRep a <code>String[]</code> value
       * @param aLocationDetails a <code>String</code> value
       */
      EventDetails(long aTimeStamp,
                   Priority aPriority,
                   String aCategoryName,
                   String aNDC,
                   String aThreadName,
                   String aMessage,
                   String[] aThrowableStrRep,
                   String aLocationDetails)
      {
          mTimeStamp = aTimeStamp;
          mPriority = aPriority;
          mCategoryName = aCategoryName;
          mNDC = aNDC;
          mThreadName = aThreadName;
          mMessage = aMessage;
          mThrowableStrRep = aThrowableStrRep;
          mLocationDetails = aLocationDetails;
      }
  
      /**
       * Creates a new <code>EventDetails</code> instance.
       *
       * @param aEvent a <code>LoggingEvent</code> value
       */
      EventDetails(LoggingEvent aEvent) {
  
          this(aEvent.timeStamp,
               aEvent.level,
               aEvent.categoryName,
               aEvent.getNDC(),
               aEvent.getThreadName(),
               aEvent.getRenderedMessage(),
               aEvent.getThrowableStrRep(),
               (aEvent.getLocationInformation() == null)
               ? null : aEvent.getLocationInformation().fullInfo);
      }
  
      /** @see #mTimeStamp **/
      long getTimeStamp() {
          return mTimeStamp;
      }
  
      /** @see #mPriority **/
      Priority getPriority() {
          return mPriority;
      }
  
      /** @see #mCategoryName **/
      String getCategoryName() {
          return mCategoryName;
      }
  
      /** @see #mNDC **/
      String getNDC() {
          return mNDC;
      }
  
      /** @see #mThreadName **/
      String getThreadName() {
          return mThreadName;
      }
  
      /** @see #mMessage **/
      String getMessage() {
          return mMessage;
      }
  
      /** @see #mLocationDetails **/
      String getLocationDetails(){
          return mLocationDetails;
      }
  
      /** @see #mThrowableStrRep **/
      String[] getThrowableStrRep() {
          return mThrowableStrRep;
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/DetailPanel.java
  
  Index: DetailPanel.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.awt.BorderLayout;
  import java.text.MessageFormat;
  import java.util.Date;
  import javax.swing.BorderFactory;
  import javax.swing.JEditorPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTable;
  import javax.swing.ListSelectionModel;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import org.apache.log4j.Category;
  
  /**
   * A panel for showing a stack trace.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   */
  class DetailPanel
      extends JPanel
      implements ListSelectionListener
  {
      /** used to log events **/
      private static final Category LOG =
          Category.getInstance(DetailPanel.class);
  
      /** used to format the logging event **/
      private static final MessageFormat FORMATTER = new MessageFormat(
          "<b>Time:</b> <code>{0,time,medium}</code>" +
          "&nbsp;&nbsp;<b>Priority:</b> <code>{1}</code>" +
          "&nbsp;&nbsp;<b>Thread:</b> <code>{2}</code>" +
          "&nbsp;&nbsp;<b>NDC:</b> <code>{3}</code>" +
          "<br><b>Category:</b> <code>{4}</code>" +
          "<br><b>Location:</b> <code>{5}</code>" +
          "<br><b>Message:</b>" +
          "<pre>{6}</pre>" +
          "<b>Throwable:</b>" +
          "<pre>{7}</pre>");
  
      /** the model for the data to render **/
      private final MyTableModel mModel;
      /** pane for rendering detail **/
      private final JEditorPane mDetails;
  
      /**
       * Creates a new <code>DetailPanel</code> instance.
       *
       * @param aTable the table to listen for selections on
       * @param aModel the model backing the table
       */
      DetailPanel(JTable aTable, final MyTableModel aModel) {
          mModel = aModel;
          setLayout(new BorderLayout());
          setBorder(BorderFactory.createTitledBorder("Details: "));
  
          mDetails = new JEditorPane();
          mDetails.setEditable(false);
          mDetails.setContentType("text/html");
          add(new JScrollPane(mDetails), BorderLayout.CENTER);
  
          final ListSelectionModel rowSM = aTable.getSelectionModel();
          rowSM.addListSelectionListener(this);
      }
  
      /** @see ListSelectionListener **/
      public void valueChanged(ListSelectionEvent aEvent) {
          //Ignore extra messages.
          if (aEvent.getValueIsAdjusting()) {
              return;
          }
  
          final ListSelectionModel lsm = (ListSelectionModel) aEvent.getSource();
          if (lsm.isSelectionEmpty()) {
              mDetails.setText("Nothing selected");
          } else {
              final int selectedRow = lsm.getMinSelectionIndex();
              final EventDetails e = mModel.getEventDetails(selectedRow);
              final Object[] args =
              {
                  new Date(e.getTimeStamp()),
                  e.getPriority(),
                  escape(e.getThreadName()),
                  escape(e.getNDC()),
                  escape(e.getCategoryName()),
                  escape(e.getLocationDetails()),
                  escape(e.getMessage()),
                  escape(getThrowableStrRep(e))
              };
              mDetails.setText(FORMATTER.format(args));
              mDetails.setCaretPosition(0);
          }
      }
  
      ////////////////////////////////////////////////////////////////////////////
      // Private methods
      ////////////////////////////////////////////////////////////////////////////
  
      /**
       * Returns a string representation of a throwable.
       *
       * @param aEvent contains the throwable information
       * @return a <code>String</code> value
       */
      private static String getThrowableStrRep(EventDetails aEvent) {
          final String[] strs = aEvent.getThrowableStrRep();
          if (strs == null) {
              return null;
          }
  
          final StringBuffer sb = new StringBuffer();
          for (int i = 0; i < strs.length; i++) {
              sb.append(strs[i]).append("\n");
          }
  
          return sb.toString();
      }
  
      /**
       * Escape &lt;, &gt; &amp; and &quot; as their entities. It is very
       * dumb about &amp; handling.
       * @param aStr the String to escape.
       * @return the escaped String
       */
      private String escape(String aStr) {
          if (aStr == null) {
              return null;
          }
  
          final StringBuffer buf = new StringBuffer();
          for (int i = 0; i < aStr.length(); i++) {
              char c = aStr.charAt(i);
              switch (c) {
              case '<':
                  buf.append("&lt;");
                  break;
              case '>':
                  buf.append("&gt;");
                  break;
              case '\"':
                  buf.append("&quot;");
                  break;
              case '&':
                  buf.append("&amp;");
                  break;
              default:
                  buf.append(c);
                  break;
              }
          }
          return buf.toString();
      }
  }
  
  
  
  1.1                  
jakarta-log4j/src/java/org/apache/log4j/chainsaw/ControlPanel.java
  
  Index: ControlPanel.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software
   * License version 1.1, a copy of which has been included with this
   * distribution in the LICENSE.txt file.  */
  package org.apache.log4j.chainsaw;
  
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import javax.swing.BorderFactory;
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JTextField;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  import org.apache.log4j.Category;
  import org.apache.log4j.Priority;
  
  /**
   * Represents the controls for filtering, pausing, exiting, etc.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Oliver Burn</a>
   */
  class ControlPanel
      extends JPanel
  {
      /** use the log messages **/
      private static final Category LOG =
          Category.getInstance(ControlPanel.class);
  
      /**
       * Creates a new <code>ControlPanel</code> instance.
       *
       * @param aModel the model to control
       */
      ControlPanel(final MyTableModel aModel) {
          setBorder(BorderFactory.createTitledBorder("Controls: "));
          final GridBagLayout gridbag = new GridBagLayout();
          final GridBagConstraints c = new GridBagConstraints();
          setLayout(gridbag);
  
          // Pad everything
          c.ipadx = 5;
          c.ipady = 5;
  
          // Add the 1st column of labels
          c.gridx = 0;
          c.anchor = GridBagConstraints.EAST;
  
          c.gridy = 0;
          JLabel label = new JLabel("Filter Level:");
          gridbag.setConstraints(label, c);
          add(label);
  
          c.gridy++;
          label = new JLabel("Filter Thread:");
          gridbag.setConstraints(label, c);
          add(label);
  
          c.gridy++;
          label = new JLabel("Filter Category:");
          gridbag.setConstraints(label, c);
          add(label);
  
          c.gridy++;
          label = new JLabel("Filter NDC:");
          gridbag.setConstraints(label, c);
          add(label);
  
          c.gridy++;
          label = new JLabel("Filter Message:");
          gridbag.setConstraints(label, c);
          add(label);
  
          // Add the 2nd column of filters
          c.weightx = 1;
          //c.weighty = 1;
          c.gridx = 1;
          c.anchor = GridBagConstraints.WEST;
  
          c.gridy = 0;
          final Priority[] allPriorities = Priority.getAllPossiblePriorities();
          final JComboBox priorities = new JComboBox(allPriorities);
          final Priority lowest = allPriorities[allPriorities.length - 1];
          priorities.setSelectedItem(lowest);
          aModel.setPriorityFilter(lowest);
          gridbag.setConstraints(priorities, c);
          add(priorities);
          priorities.setEditable(false);
          priorities.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent aEvent) {
                      aModel.setPriorityFilter(
                          (Priority) priorities.getSelectedItem());
                  }
              });
  
  
          c.fill = GridBagConstraints.HORIZONTAL;
          c.gridy++;
          final JTextField threadField = new JTextField("");
          threadField.getDocument().addDocumentListener(new DocumentListener () {
                  public void insertUpdate(DocumentEvent aEvent) {
                      aModel.setThreadFilter(threadField.getText());
                  }
                  public void removeUpdate(DocumentEvent aEvente) {
                      aModel.setThreadFilter(threadField.getText());
                  }
                  public void changedUpdate(DocumentEvent aEvent) {
                      aModel.setThreadFilter(threadField.getText());
                  }
              });
          gridbag.setConstraints(threadField, c);
          add(threadField);
  
          c.gridy++;
          final JTextField catField = new JTextField("");
          catField.getDocument().addDocumentListener(new DocumentListener () {
                  public void insertUpdate(DocumentEvent aEvent) {
                      aModel.setCategoryFilter(catField.getText());
                  }
                  public void removeUpdate(DocumentEvent aEvent) {
                      aModel.setCategoryFilter(catField.getText());
                  }
                  public void changedUpdate(DocumentEvent aEvent) {
                      aModel.setCategoryFilter(catField.getText());
                  }
              });
          gridbag.setConstraints(catField, c);
          add(catField);
  
          c.gridy++;
          final JTextField ndcField = new JTextField("");
          ndcField.getDocument().addDocumentListener(new DocumentListener () {
                  public void insertUpdate(DocumentEvent aEvent) {
                      aModel.setNDCFilter(ndcField.getText());
                  }
                  public void removeUpdate(DocumentEvent aEvent) {
                      aModel.setNDCFilter(ndcField.getText());
                  }
                  public void changedUpdate(DocumentEvent aEvent) {
                      aModel.setNDCFilter(ndcField.getText());
                  }
              });
          gridbag.setConstraints(ndcField, c);
          add(ndcField);
  
          c.gridy++;
          final JTextField msgField = new JTextField("");
          msgField.getDocument().addDocumentListener(new DocumentListener () {
                  public void insertUpdate(DocumentEvent aEvent) {
                      aModel.setMessageFilter(msgField.getText());
                  }
                  public void removeUpdate(DocumentEvent aEvent) {
                      aModel.setMessageFilter(msgField.getText());
                  }
                  public void changedUpdate(DocumentEvent aEvent) {
                      aModel.setMessageFilter(msgField.getText());
                  }
              });
  
  
          gridbag.setConstraints(msgField, c);
          add(msgField);
  
          // Add the 3rd column of buttons
          c.weightx = 0;
          c.fill = GridBagConstraints.HORIZONTAL;
          c.anchor = GridBagConstraints.EAST;
          c.gridx = 2;
  
          c.gridy = 0;
          final JButton exitButton = new JButton("Exit");
          exitButton.setMnemonic('x');
          exitButton.addActionListener(ExitAction.INSTANCE);
          gridbag.setConstraints(exitButton, c);
          add(exitButton);
  
          c.gridy++;
          final JButton clearButton = new JButton("Clear");
          clearButton.setMnemonic('c');
          clearButton.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent aEvent) {
                      aModel.clear();
                  }
              });
          gridbag.setConstraints(clearButton, c);
          add(clearButton);
  
          c.gridy++;
          final JButton toggleButton = new JButton("Pause");
          toggleButton.setMnemonic('p');
          toggleButton.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent aEvent) {
                      aModel.toggle();
                      toggleButton.setText(
                          aModel.isPaused() ? "Resume" : "Pause");
                  }
              });
          gridbag.setConstraints(toggleButton, c);
          add(toggleButton);
      }
  }
  
  
  

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to