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.chainsaw;
018
019import java.awt.BorderLayout;
020import java.text.MessageFormat;
021import java.util.Date;
022import javax.swing.BorderFactory;
023import javax.swing.JEditorPane;
024import javax.swing.JPanel;
025import javax.swing.JScrollPane;
026import javax.swing.JTable;
027import javax.swing.ListSelectionModel;
028import javax.swing.event.ListSelectionEvent;
029import javax.swing.event.ListSelectionListener;
030import org.apache.log4j.Logger;
031
032/**
033 * A panel for showing a stack trace.
034 *
035 * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
036 */
037class DetailPanel
038    extends JPanel
039    implements ListSelectionListener
040{
041    /** used to log events **/
042    private static final Logger LOG =
043        Logger.getLogger(DetailPanel.class);
044
045    /** used to format the logging event **/
046    private static final MessageFormat FORMATTER = new MessageFormat(
047        "<b>Time:</b> <code>{0,time,medium}</code>" +
048        "&nbsp;&nbsp;<b>Priority:</b> <code>{1}</code>" +
049        "&nbsp;&nbsp;<b>Thread:</b> <code>{2}</code>" +
050        "&nbsp;&nbsp;<b>NDC:</b> <code>{3}</code>" +
051        "<br><b>Logger:</b> <code>{4}</code>" +
052        "<br><b>Location:</b> <code>{5}</code>" +
053        "<br><b>Message:</b>" +
054        "<pre>{6}</pre>" +
055        "<b>Throwable:</b>" +
056        "<pre>{7}</pre>");
057
058    /** the model for the data to render **/
059    private final MyTableModel mModel;
060    /** pane for rendering detail **/
061    private final JEditorPane mDetails;
062
063    /**
064     * Creates a new <code>DetailPanel</code> instance.
065     *
066     * @param aTable the table to listen for selections on
067     * @param aModel the model backing the table
068     */
069    DetailPanel(JTable aTable, final MyTableModel aModel) {
070        mModel = aModel;
071        setLayout(new BorderLayout());
072        setBorder(BorderFactory.createTitledBorder("Details: "));
073
074        mDetails = new JEditorPane();
075        mDetails.setEditable(false);
076        mDetails.setContentType("text/html");
077        add(new JScrollPane(mDetails), BorderLayout.CENTER);
078
079        final ListSelectionModel rowSM = aTable.getSelectionModel();
080        rowSM.addListSelectionListener(this);
081    }
082
083    /** @see ListSelectionListener **/
084    public void valueChanged(ListSelectionEvent aEvent) {
085        //Ignore extra messages.
086        if (aEvent.getValueIsAdjusting()) {
087            return;
088        }
089
090        final ListSelectionModel lsm = (ListSelectionModel) aEvent.getSource();
091        if (lsm.isSelectionEmpty()) {
092            mDetails.setText("Nothing selected");
093        } else {
094            final int selectedRow = lsm.getMinSelectionIndex();
095            final EventDetails e = mModel.getEventDetails(selectedRow);
096            final Object[] args =
097            {
098                new Date(e.getTimeStamp()),
099                e.getPriority(),
100                escape(e.getThreadName()),
101                escape(e.getNDC()),
102                escape(e.getCategoryName()),
103                escape(e.getLocationDetails()),
104                escape(e.getMessage()),
105                escape(getThrowableStrRep(e))
106            };
107            mDetails.setText(FORMATTER.format(args));
108            mDetails.setCaretPosition(0);
109        }
110    }
111
112    ////////////////////////////////////////////////////////////////////////////
113    // Private methods
114    ////////////////////////////////////////////////////////////////////////////
115
116    /**
117     * Returns a string representation of a throwable.
118     *
119     * @param aEvent contains the throwable information
120     * @return a <code>String</code> value
121     */
122    private static String getThrowableStrRep(EventDetails aEvent) {
123        final String[] strs = aEvent.getThrowableStrRep();
124        if (strs == null) {
125            return null;
126        }
127
128        final StringBuffer sb = new StringBuffer();
129        for (int i = 0; i < strs.length; i++) {
130            sb.append(strs[i]).append("\n");
131        }
132
133        return sb.toString();
134    }
135
136    /**
137     * Escape &lt;, &gt; &amp; and &quot; as their entities. It is very
138     * dumb about &amp; handling.
139     * @param aStr the String to escape.
140     * @return the escaped String
141     */
142    private String escape(String aStr) {
143        if (aStr == null) {
144            return null;
145        }
146
147        final StringBuffer buf = new StringBuffer();
148        for (int i = 0; i < aStr.length(); i++) {
149            char c = aStr.charAt(i);
150            switch (c) {
151            case '<':
152                buf.append("&lt;");
153                break;
154            case '>':
155                buf.append("&gt;");
156                break;
157            case '\"':
158                buf.append("&quot;");
159                break;
160            case '&':
161                buf.append("&amp;");
162                break;
163            default:
164                buf.append(c);
165                break;
166            }
167        }
168        return buf.toString();
169    }
170}