/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.support.log;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.concurrent.Future;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import org.apache.commons.collections.list.TreeList;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.support.UISupport;
/**
* Component for displaying log entries
*
* @author Ole.Matzura
*/
public class JLogList extends JPanel
{
private long maxRows = 1000;
private JList logList;
private SimpleAttributeSet requestAttributes;
private SimpleAttributeSet responseAttributes;
private LogListModel model;
private List<Logger> loggers = new ArrayList<Logger>();
private InternalLogAppender internalLogAppender = new InternalLogAppender();
private boolean tailing = true;
private Stack<Object> linesToAdd = new Stack<Object>();
private EnableAction enableAction;
private JCheckBoxMenuItem enableMenuItem;
private final String title;
private boolean released;
private Future<?> future;
public JLogList( String title )
{
super( new BorderLayout() );
this.title = title;
model = new LogListModel();
logList = new JList( model );
logList.setToolTipText( title );
logList.setCellRenderer( new LogAreaCellRenderer() );
logList.setPrototypeCellValue( "Testing 123" );
logList.setFixedCellWidth( -1 );
JPopupMenu listPopup = new JPopupMenu();
listPopup.add( new ClearAction() );
enableAction = new EnableAction();
enableMenuItem = new JCheckBoxMenuItem( enableAction );
enableMenuItem.setSelected( true );
listPopup.add( enableMenuItem );
listPopup.addSeparator();
listPopup.add( new CopyAction() );
listPopup.add( new SetMaxRowsAction() );
listPopup.addSeparator();
listPopup.add( new ExportToFileAction() );
logList.setComponentPopupMenu( listPopup );
setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ) );
JScrollPane scrollPane = new JScrollPane( logList );
UISupport.addPreviewCorner( scrollPane, true );
add( scrollPane, BorderLayout.CENTER );
requestAttributes = new SimpleAttributeSet();
StyleConstants.setForeground( requestAttributes, Color.BLUE );
responseAttributes = new SimpleAttributeSet();
StyleConstants.setForeground( responseAttributes, Color.GREEN );
try
{
maxRows = Long.parseLong( SoapUI.getSettings().getString( "JLogList#" + title, "1000" ) );
}
catch( NumberFormatException e )
{
}
}
public void clear()
{
model.clear();
}
public JList getLogList()
{
return logList;
}
public long getMaxRows()
{
return maxRows;
}
public void setMaxRows( long maxRows )
{
this.maxRows = maxRows;
}
public synchronized void addLine( Object line )
{
if( !isEnabled() )
return;
synchronized( model.lines )
{
if( line instanceof LoggingEvent )
{
LoggingEvent ev = ( LoggingEvent )line;
linesToAdd.push( new LoggingEventWrapper( ev ) );
if( ev.getThrowableInformation() != null )
{
Throwable t = ev.getThrowableInformation().getThrowable();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter( sw );
t.printStackTrace( pw );
StringTokenizer st = new StringTokenizer( sw.toString(), "\r\n" );
while( st.hasMoreElements() )
linesToAdd.push( " " + st.nextElement() );
}
}
else
{
linesToAdd.push( line );
}
}
if( future == null )
{
released = false;
future = SoapUI.getThreadPool().submit( model );
}
}
public void setEnabled( boolean enabled )
{
super.setEnabled( enabled );
logList.setEnabled( enabled );
enableMenuItem.setSelected( enabled );
}
private static class LogAreaCellRenderer extends DefaultListCellRenderer
{
private Map<Level, Color> levelColors = new HashMap<Level, Color>();
private LogAreaCellRenderer()
{
levelColors.put( Level.ERROR, new Color( 192, 0, 0 ) );
levelColors.put( Level.INFO, new Color( 0, 92, 0 ) );
levelColors.put( Level.WARN, Color.ORANGE.darker().darker() );
levelColors.put( Level.DEBUG, new Color( 0, 0, 128 ) );
}
public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus )
{
JLabel component = ( JLabel )super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
if( value instanceof LoggingEventWrapper )
{
LoggingEventWrapper eventWrapper = ( LoggingEventWrapper )value;
if( levelColors.containsKey( eventWrapper.getLevel() ) )
component.setForeground( levelColors.get( eventWrapper.getLevel() ) );
}
// Limit the length of the tool tip, to prevent long delays.
String toolTip = component.getText();
if( toolTip != null && toolTip.length() > 1000 )
toolTip = toolTip.substring( 0, 1000 );
component.setToolTipText( toolTip );
return component;
}
}
private final static class LoggingEventWrapper
{
private final LoggingEvent loggingEvent;
private String str;
public LoggingEventWrapper( LoggingEvent loggingEvent )
{
this.loggingEvent = loggingEvent;
}
public Level getLevel()
{
return loggingEvent.getLevel();
}
public String toString()
{
if( str == null )
{
StringBuilder builder = new StringBuilder();
builder.append( new Date( loggingEvent.timeStamp ) );
builder.append( ':' ).append( loggingEvent.getLevel() ).append( ':' ).append( loggingEvent.getMessage() );
str = builder.toString();
}
return str;
}
}
public void addLogger( String loggerName, boolean addAppender )
{
Logger logger = Logger.getLogger( loggerName );
if( addAppender )
logger.addAppender( internalLogAppender );
loggers.add( logger );
}
public Logger[] getLoggers()
{
return loggers.toArray( new Logger[loggers.size()] );
}
public void setLevel( Level level )
{
for( Logger logger : loggers )
{
logger.setLevel( level );
}
}
public Logger getLogger( String loggerName )
{
for( Logger logger : loggers )
{
if( logger.getName().equals( loggerName ) )
return logger;
}
return null;
}
private class InternalLogAppender extends AppenderSkeleton
{
protected void append( LoggingEvent event )
{
addLine( event );
}
public void close()
{
}
public boolean requiresLayout()
{
return false;
}
}
public boolean monitors( String loggerName )
{
for( Logger logger : loggers )
{
if( loggerName.startsWith( logger.getName() ) )
return true;
}
return false;
}
public void removeLogger( String loggerName )
{
for( Logger logger : loggers )
{
if( loggerName.equals( logger.getName() ) )
{
logger.removeAppender( internalLogAppender );
}
}
}
public boolean isTailing()
{
return tailing;
}
public void setTailing( boolean tail )
{
this.tailing = tail;
}
private class ClearAction extends AbstractAction
{
public ClearAction()
{
super( "Clear" );
}
public void actionPerformed( ActionEvent e )
{
model.clear();
}
}
private class SetMaxRowsAction extends AbstractAction
{
public SetMaxRowsAction()
{
super( "Set Max Rows" );
}
public void actionPerformed( ActionEvent e )
{
String val = UISupport.prompt( "Set maximum number of log rows to keep", "Set Max Rows",
String.valueOf( maxRows ) );
if( val != null )
{
try
{
maxRows = Long.parseLong( val );
SoapUI.getSettings().setString( "JLogList#" + title, val );
}
catch( NumberFormatException e1 )
{
UISupport.beep();
}
}
}
}
private class ExportToFileAction extends AbstractAction
{
public ExportToFileAction()
{
super( "Export to File" );
}
public void actionPerformed( ActionEvent e )
{
if( model.getSize() == 0 )
{
UISupport.showErrorMessage( "Log is empty; nothing to export" );
return;
}
File file = UISupport.getFileDialogs().saveAs( JLogList.this, "Save Log [] to File", "*.log", "*.log", null );
if( file != null )
saveToFile( file );
}
}
private class CopyAction extends AbstractAction
{
public CopyAction()
{
super( "Copy to clipboard" );
}
public void actionPerformed( ActionEvent e )
{
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringBuffer buf = new StringBuffer();
int[] selectedIndices = logList.getSelectedIndices();
if( selectedIndices.length == 0 )
{
for( int c = 0; c < logList.getModel().getSize(); c++ )
{
buf.append( logList.getModel().getElementAt( c ).toString() );
buf.append( "\r\n" );
}
}
else
{
for( int c = 0; c < selectedIndices.length; c++ )
{
buf.append( logList.getModel().getElementAt( selectedIndices[c] ).toString() );
buf.append( "\r\n" );
}
}
StringSelection selection = new StringSelection( buf.toString() );
clipboard.setContents( selection, selection );
}
}
private class EnableAction extends AbstractAction
{
public EnableAction()
{
super( "Enable" );
}
public void actionPerformed( ActionEvent e )
{
JLogList.this.setEnabled( enableMenuItem.isSelected() );
}
}
/**
* Internal list model that for optimized storage and notifications
*
* @author Ole.Matzura
*/
@SuppressWarnings( "unchecked" )
private final class LogListModel extends AbstractListModel implements Runnable
{
private List<Object> lines = new TreeList();
public int getSize()
{
synchronized( lines )
{
return lines.size();
}
}
public Object getElementAt( int index )
{
synchronized( lines )
{
return lines.get( index );
}
}
public void clear()
{
int sz = lines.size();
if( sz == 0 )
return;
synchronized( lines )
{
lines.clear();
fireIntervalRemoved( this, 0, sz - 1 );
}
}
public void run()
{
Thread.currentThread().setName( "LogList Updater for " + title );
try
{
while( !released && !linesToAdd.isEmpty() )
{
try
{
if( !linesToAdd.isEmpty() )
{
SwingUtilities.invokeAndWait( new Runnable()
{
public void run()
{
try
{
synchronized( lines )
{
while( !linesToAdd.isEmpty() )
{
int sz = lines.size();
lines.addAll( linesToAdd );
linesToAdd.clear();
fireIntervalAdded( LogListModel.this, sz, lines.size() - sz );
}
int cnt = 0;
while( lines.size() > maxRows )
{
lines.remove( 0 );
cnt++ ;
}
if( cnt > 0 )
fireIntervalRemoved( LogListModel.this, 0, cnt - 1 );
if( tailing )
{
logList.ensureIndexIsVisible( lines.size() - 1 );
}
}
}
catch( Throwable e )
{
SoapUI.logError( e );
}
}
} );
}
Thread.sleep( 500 );
}
catch( Throwable e )
{
SoapUI.logError( e );
}
}
}
finally
{
future = null;
}
}
}
public void release()
{
released = true;
}
public void saveToFile( File file )
{
try
{
PrintWriter writer = new PrintWriter( file );
for( int c = 0; c < model.getSize(); c++ )
{
writer.println( model.getElementAt( c ) );
}
writer.close();
}
catch( Exception e )
{
UISupport.showErrorMessage( e );
}
}
}