package tcg.common.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.border.EtchedBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import tcg.common.Localization;
import tcg.common.LoggerManager;
//TODO: handle closing event!
public class TailFrame extends JFrame implements Runnable, CaretListener
{
private static final long serialVersionUID = 1L;
JScrollPane textPaneScrollPane;
JTextPane textPane;
StyledDocument styledDoc;
Style def;
Style errorStyle;
long counter;
boolean edit;
boolean pause;
public static final Color STATUS_BAR_COLOR= new Color(0x41,0x89,0xDD);
public static int openFrameCount = 0;
static final int xOffset = 30, yOffset = 30;
public static int MAX_LINES_TO_DISPLAY = 500;
//..StatusBar
private JPanel statusBarPanel = null;
private JButton pauseButton = null;
private JButton reloadButton = null;
//private JLabel caretPosLabel = null;
private JLabel fileSizeValue = null;
private JLabel fileLastModifiedValue = null;
//private JLabel linesWaitingLabel = null;
private JTextField searchText_ = null;
//private boolean stopFileDetailsThread = false;
private String fileName = "";
//private ClassLoader cl = null;
private Properties keywords_ = new Properties();
private String keyword_ = "";
//long initialLineNo;
int delayPeriod;
private Tail tail_ = null;
private Thread thread_ = null;
//flag to check whether we should keep the processing thread running
private boolean keepRunning_ = false;
private int linesShowing = 0;
@SuppressWarnings("unused")
private static Logger logger_ = LoggerManager.getLogger(TailFrame.class.getName());
//localized string
private String btnPause = "Pause";
private String tipStopScroll = "Stops scrolling. Logging will continue...";
private String btnScroll = "Scroll";
private String tipStartScroll = "Start scrolling...";
private String btnReload = "Reload";
private String tipReload = "Reload the file...";
private String lblSearch = "Search";
private String tipSearch = "Search for a keyword";
private String lblSize = "Size";
private String tipSize = "Current size of the file";
private String lblLastModified = "Last Modified";
//...Constructors
public TailFrame(String title, Logger logger)
{
super();
//super(title,true,true,true,true);
this.setTitle(title);
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) { }
//get the file name -> the first file appender in the logger
FileAppender file_appender = null;
String file_path = "";
Enumeration<?> appender_enum = logger.getAllAppenders();
while (appender_enum.hasMoreElements())
{
//appender = (Appender) appender_enum.nextElement();
try
{
file_appender = FileAppender.class.cast(appender_enum.nextElement());
file_path = file_appender.getFile();
break;
}
catch (Exception ex)
{
//ignore
}
}
//stored the file path locally
this.fileName = file_path;
//..Set the intialLine to 0
//initialLineNo = 0;
// Get current classloader
//cl = this.getClass().getClassLoader();
//localized
btnPause = Localization.getLocalizedString("TAIL_BTN_PAUSE", btnPause);
tipStopScroll = Localization.getLocalizedString("TAIL_TIPS_STOPSCROLL", tipStopScroll);
btnScroll = Localization.getLocalizedString("TAIL_BTN_SCROLL", btnScroll);
tipStartScroll = Localization.getLocalizedString("TAIL_TIPS_STARTSCROLL", tipStartScroll);
btnReload = Localization.getLocalizedString("TAIL_BTN_RELOAD", btnReload);
tipReload = Localization.getLocalizedString("TAIL_TIPS_RELOAD", tipReload);
lblSearch = Localization.getLocalizedString("TAIL_LBL_SEARCH", lblSearch);
tipSearch = Localization.getLocalizedString("TAIL_TIPS_SEARCH", tipSearch);
lblSize = Localization.getLocalizedString("TAIL_LBL_SIZE", lblSize);
tipSize = Localization.getLocalizedString("TAIL_TIPS_SIZE", tipSize);
lblLastModified = Localization.getLocalizedString("TAIL_LBL_LASTMODIFIED", lblLastModified);
initComponents();
//reset the text
textPane.setText("");
linesShowing = 0;
//default size
this.setSize(800, 600);
//listener for window's event
addWindowListener(new java.awt.event.WindowAdapter()
{
public void windowClosing(WindowEvent winEvt)
{
stop();
}
});
//start right away
start();
}
public TailFrame(String title, String fileName)
{
super();
// super(title,true,true,true,true);
this.setTitle(title);
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) { }
this.fileName = fileName;
//..Set the intialLine to 0
//initialLineNo = 0;
// Get current classloader
//cl = this.getClass().getClassLoader();
initComponents();
//reset the text
textPane.setText("");
linesShowing = 0;
//default size
this.setSize(800, 600);
//listener for window's event
addWindowListener(new java.awt.event.WindowAdapter()
{
public void windowClosing(WindowEvent winEvt)
{
stop();
}
});
//start right away
start();
}
private void initComponents()
{
textPane = new JTextPane();
styledDoc = textPane.getStyledDocument();
//..Regulat Text Style
Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
Style regular = styledDoc.addStyle("regular", def);
StyleConstants.setForeground(regular, Color.BLACK);
StyleConstants.setBackground(regular, Color.WHITE);
StyleConstants.setFontFamily(regular, "Courier New");
StyleConstants.setFontSize(regular,12);
StyleConstants.setLineSpacing(regular, 0.25f);
//..Keyword1 Text Style
Style keyword1Style = styledDoc.addStyle("keyword1Style", regular);
StyleConstants.setForeground(keyword1Style, Color.RED);
StyleConstants.setBackground(keyword1Style, Color.YELLOW);
StyleConstants.setFontFamily(keyword1Style, "Courier New");
StyleConstants.setFontSize(keyword1Style,12);
StyleConstants.setBold(keyword1Style,false);
StyleConstants.setLineSpacing(keyword1Style, 0.25f);
//..Keyword2 Text Style
Style keyword2Style = styledDoc.addStyle("keyword2Style", regular);
StyleConstants.setForeground(keyword2Style, Color.GREEN);
StyleConstants.setBackground(keyword2Style, Color.YELLOW);
StyleConstants.setFontFamily(keyword2Style, "Courier New");
StyleConstants.setFontSize(keyword2Style,12);
StyleConstants.setBold(keyword2Style,false);
StyleConstants.setLineSpacing(keyword2Style, 0.25f);
textPane.setMargin(new Insets(0,15,15,0));
textPane.setBackground(Color.WHITE);
textPane.setLogicalStyle(regular);
//..Set the Caret Color to white
textPane.setCaretColor(Color.BLACK);
textPane.addCaretListener(this);
textPaneScrollPane = new JScrollPane(textPane);
counter=0;
edit = false;
//..StatusBar
statusBarPanel = new JPanel();
statusBarPanel.setBorder(new EtchedBorder());
statusBarPanel.setLayout(new BorderLayout());
//statusBarPanel.setLayout(new GridLayout(1,4));
//statusBarPanel.setLayout(new FlowLayout());
statusBarPanel.setBackground(STATUS_BAR_COLOR);
//..Pause Button
pause = false;
pauseButton = new JButton(btnPause);
pauseButton.setToolTipText(tipStopScroll);
pauseButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
if(pause)
{
pause = false;
pauseButton.setText(btnPause);
pauseButton.setToolTipText(tipStopScroll);
//resume the tailing
tail_.resume();
}
else
{
pause = true;
pauseButton.setText(btnScroll);
pauseButton.setToolTipText(tipStartScroll);
//pause the tailing
tail_.pause();
}
}
});
reloadButton = new JButton(btnReload);
reloadButton.setToolTipText(tipReload);
reloadButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
reset();
}
});
JPanel actionPanel = new JPanel();
actionPanel.setBackground(STATUS_BAR_COLOR);
actionPanel.setLayout(new BorderLayout());
actionPanel.add(pauseButton, BorderLayout.WEST);
actionPanel.add(reloadButton, BorderLayout.CENTER);
//Search label and text field
JLabel searchLabel = new JLabel(" " + lblSearch);
searchLabel.setForeground(Color.white);
searchText_ = new JTextField(10);
searchText_.setForeground(Color.BLACK);
searchText_.setToolTipText(tipSearch);
searchText_.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
String newKeyword = searchText_.getText().trim();
//only process it if the new keyword is different than the old one
if (keyword_ == null || keyword_.length() != newKeyword.length()
|| 0 == keyword_.compareToIgnoreCase(newKeyword))
{
//search the keyword
clearKeywordHighlight();
keyword_ = newKeyword;
searchForKeywords();
}
}
});
//..FileSize Label
fileSizeValue = new JLabel();
fileSizeValue.setForeground(Color.white);
fileSizeValue.setToolTipText(tipSize);
JLabel fileSizeLabel = new JLabel(lblSize + ": ");
fileSizeLabel.setForeground(Color.white);
//..FileLastModifiedDate Label
fileLastModifiedValue = new JLabel();
fileLastModifiedValue.setForeground(Color.white);
//stopFileDetailsThread = false;
JLabel fileModifiedLabel = new JLabel(lblLastModified + ": ");
fileModifiedLabel.setForeground(Color.white);
//..Caret Position Label. Not meaningful in our case
//caretPosLabel = new JLabel("");
//caretPosLabel.setForeground(Color.white);
JPanel internalPanel = new JPanel();
internalPanel.setBackground(STATUS_BAR_COLOR);
internalPanel.setLayout(new BorderLayout());
JPanel searchContainerPanel = new JPanel();
searchContainerPanel.setBackground(STATUS_BAR_COLOR);
JPanel sizeContainerPanel = new JPanel();
sizeContainerPanel.setBackground(STATUS_BAR_COLOR);
//sizeContainerPanel.setToolTipText(tipSize);
JPanel modifiedContainerPanel = new JPanel();
modifiedContainerPanel.setBackground(STATUS_BAR_COLOR);
//build the layout of the status panel
searchContainerPanel.add(searchLabel);
searchContainerPanel.add(searchText_);
sizeContainerPanel.add(fileSizeLabel);
sizeContainerPanel.add(fileSizeValue);
modifiedContainerPanel.add(fileModifiedLabel);
modifiedContainerPanel.add(fileLastModifiedValue);
//labelContainerPanel.add(caretPosLabel);
internalPanel.add(searchContainerPanel, BorderLayout.WEST);
internalPanel.add(sizeContainerPanel, BorderLayout.CENTER);
internalPanel.add(modifiedContainerPanel, BorderLayout.EAST);
statusBarPanel.add(actionPanel, BorderLayout.WEST);
// pauseButton.getParent().setBackground(STATUS_BAR_COLOR);
statusBarPanel.add(internalPanel, BorderLayout.CENTER);
this.getContentPane().setLayout(new BorderLayout());
getContentPane().add(textPaneScrollPane,BorderLayout.CENTER);
getContentPane().add(statusBarPanel,BorderLayout.SOUTH);
//getContentPane().add(containerPanel, BorderLayout.SOUTH);
//TODO
//addInternalFrameListener(new InternalFrameClosingHandler());
setLocation(xOffset*openFrameCount, yOffset*openFrameCount);
setSize(getContentPane().getSize());
openFrameCount++;
//..Add InternalFrameEventListener
//TODO
//addInternalFrameListener(new TailInternalFrameListener(this));
//set default close operation
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//TODO: introduce window handler
}
public void reset()
{
//reset the text
textPane.setText("");
linesShowing = 0;
//stop the current tail job
if (tail_ != null)
{
tail_.stop();
tail_ = null;
}
//create a new tail job
tail_ = new Tail(fileName);
tail_.start();
}
public synchronized void start()
{
if (tail_ != null && !tail_.isRunning())
{
//zombie. reset
tail_ = null;
}
//tailing thread
if (tail_ == null)
{
tail_ = new Tail(fileName);
tail_.start();
}
if (thread_ != null && !thread_.isAlive() && !keepRunning_)
{
//zombie. reset
thread_ = null;
}
//frame update thread
if (thread_ == null)
{
thread_ = new Thread(this);
thread_.start();
}
}
public synchronized void stop()
{
//stop the tailing
if (tail_ != null)
{
tail_.stop();
tail_ = null;
}
//stop processing thread
if (thread_ != null)
{
//stop the processing loop. make sure we interrupt the sleep.
keepRunning_ = false;
thread_.interrupt();
try
{
thread_.join(500);
}
catch (Exception ex)
{
//ignore;
//thread_.stop();
}
thread_ = null;
}
}
public boolean isRunning()
{
if (tail_ != null && tail_.isRunning() && keepRunning_)
{
return true;
}
return false;
}
//..Get fileName
public String getFileName()
{
return fileName;
}
//...appendText
public void append(String text, int countOfLines)
{
// this method is called from Tail.run().
// a sleep statement here will slow down the while loop in run hence adds delay
try
{
if(delayPeriod != 0)
{
Thread.sleep(delayPeriod * 200);
}
}
catch(Exception e)
{
//ignore
}
int docLengthBeforeInsert = textPane.getDocument().getLength();
String docText = "";
int searchFrom = 0;
int removeTextLength = 0;
int cnt = 0;
int tempSearchFrom = 0;
//..First of all, insert the string in regular style
try
{
styledDoc.insertString(styledDoc.getLength(), text, styledDoc.getStyle("regular"));
linesShowing += countOfLines;
if(linesShowing > MAX_LINES_TO_DISPLAY)
{
//TODO: alternative solution: we can just truncate the buffer into half
//remove the same amount of line from the beginning of the text
docText = styledDoc.getText(0,styledDoc.getLength());
//search the same number of new line if possible
searchFrom = 0;
for(cnt = 0; cnt < countOfLines ; cnt++)
{
tempSearchFrom = docText.indexOf(Tail.NEW_LINE, (searchFrom+Tail.NEW_LINE.length()));
if(tempSearchFrom == -1)
{
//can not find a new line.
break;
}
else
{
searchFrom = tempSearchFrom;
}
}
if(searchFrom == -1)
{
removeTextLength = docLengthBeforeInsert;
}
else
{
docText = docText.substring(0,searchFrom);
removeTextLength = docText.length();
//..Adjust the line number for Ln and Cl display in status bar
//initialLineNo += countOfLines;
//caretPosLabel.setText(" Ln "+(getLineAtCaret(textPane)+initialLineNo-1)+" Col "+getColumnAtCaret(textPane));
}
//remove the text
styledDoc.remove(0,removeTextLength);
linesShowing -= cnt;
docLengthBeforeInsert = docLengthBeforeInsert - removeTextLength;
}
}
catch (BadLocationException ble)
{
//TODO
}
catch(Exception e)
{
//TODO
}
//..Highlight the text if some keyword is found
if (keyword_ != null && keyword_.length() > 0)
{
ArrayList<KeywordPosition> matches = getKeywordPositions(text);
int arrSize = matches.size();
if (arrSize > 0)
{
int keyLength = keyword_.length();
for(int i = 0; i<arrSize; i++)
{
styledDoc.setCharacterAttributes( matches.get(i).begin + docLengthBeforeInsert, keyLength,
styledDoc.getStyle("keyword1Style"), false);
}
}
}
//move caret to the end
textPane.setCaretPosition(textPane.getDocument().getLength());
}
//..The thread that runs and updates the file size and last modified date
public void run()
{
File file = new File(fileName);
long fileSize = 0;
long fileLastModifiedDate = 0;
Date date = new Date();
String tempString="";
int countOfLines = 0;
keepRunning_ = true;
// try
// {
do
{
//get file size
fileSize = file.length();
if(fileSize<1024)
{
fileSizeValue.setText( fileSize +" Bytes ");
}
else if( fileSize< (1024*1024))
{
tempString = (fileSize/1024.0)+"";
//..Verify for no of digits after '.'
if(tempString.substring(tempString.indexOf(".")).length() > 3)
{
tempString = tempString.substring(0,tempString.indexOf(".")+3);
}
else
{
tempString = tempString.substring(0,tempString.indexOf("."));
}
fileSizeValue.setText( tempString +" KB ");
}
else if( fileSize< (1024*1024*1024))
{
tempString = (fileSize/(1024.0*1024.0))+"";
//..Verify for no of digits after '.'
if(tempString.substring(tempString.indexOf(".")).length() > 5)
{
tempString = tempString.substring(0,tempString.indexOf(".")+5);
}
else
{
tempString = tempString.substring(0,tempString.indexOf("."));
}
fileSizeValue.setText( tempString +" MB ");
}
else
{
tempString = (fileSize/(1024.0*1024.0*1024.0))+"";
//..Verify for no of digits after '.'
if(tempString.substring(tempString.indexOf(".")).length() > 7)
{
tempString = tempString.substring(0,tempString.indexOf(".")+7);
}
else
{
tempString = tempString.substring(0,tempString.indexOf("."));
}
fileSizeValue.setText( tempString +" GB ");
}
//get file modified date
fileLastModifiedDate = file.lastModified();
date = new Date(fileLastModifiedDate);
fileLastModifiedValue.setText(date.toString()+" ");
//get the latest string
tempString = tail_.read();
countOfLines = tail_.getLastCountOfLines();
if (tempString.length() > 0)
{
this.append(tempString, countOfLines);
}
if (!keepRunning_)
break;
try
{
Thread.sleep(500);
}
catch (Exception ex)
{
//ignore
}
}
while(keepRunning_);
// }
// catch(Exception ex)
// {
// logger_.warn("Exception: " + ex.getMessage());
// }
//reset the flag just in case
keepRunning_ = false;
}
// public boolean isPaused()
// {
// return pause;
// }
// //..Return the TextPane
// public JTextPane getTextPane()
// {
// return textPane;
// }
//..Return the Line no at caret
public int getLineAtCaret(JTextComponent component)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
return root.getElementIndex( caretPosition ) + 1;
}
//..Return the Column no at caret
public int getColumnAtCaret(JTextComponent component)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int line = root.getElementIndex( caretPosition );
int lineStart = root.getElement( line ).getStartOffset();
return caretPosition - lineStart + 1;
}
//..CaretUpdate event
public void caretUpdate(CaretEvent ce)
{
//no need to update the caret position. in our context this information is not meaningful
// even misleading!
//caretPosLabel.setText(" Ln "+(getLineAtCaret(textPane)+initialLineNo-1)+" Col "+getColumnAtCaret(textPane));
}
public void clearKeywordHighlight()
{
styledDoc.setCharacterAttributes( 0, textPane.getDocument().getLength(),
styledDoc.getStyle("regular"), false);
}
public void searchForKeywords()
{
//TODO: Java Bug: JTextPane.getText() inserts extra new line char.
String str = "";
try
{
str = styledDoc.getText(0, styledDoc.getLength());
}
catch(Exception ex)
{
//ignore
};
ArrayList<KeywordPosition> matches = getKeywordPositions(str);
int arrSize = matches.size();
int keyLength = keyword_.length();
for(int i = 0; i<arrSize; i++)
{
int begin = matches.get(i).begin;
try
{
styledDoc.setCharacterAttributes( begin, keyLength,
styledDoc.getStyle("keyword1Style"), false);
}
catch(Exception exc)
{
//ignore
}
}
}
public ArrayList<KeywordPosition> getKeywordPositions(String text)
{
ArrayList<KeywordPosition> matches = new ArrayList<KeywordPosition>();
if (keyword_ == null || keyword_.length() == 0)
return matches;
//make uppercase for comparison
text = text.toUpperCase();
try
{
//make uppercase for comparison. not case-sensitive.
String tempString = keyword_.toUpperCase();
int offSet = 0;
while(true)
{
//search for the keyword
offSet = text.indexOf(tempString, offSet);
if (offSet == -1)
{
//not found
break;
}
//found one
KeywordPosition keyPos = new KeywordPosition(offSet, offSet + tempString.length());
matches.add(keyPos);
//advance the position
offSet += tempString.length();
}
}
catch(Exception exp)
{
//TODO: proper logging
}
return matches;
}
class SearchTextListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//search the keyword
keywords_.setProperty("0", searchText_.getText());
}
}
}
//..Class to store beginning and ending points of keywords
class KeywordPosition
{
public int begin;
public int end;
KeywordPosition(int begin, int end)
{
this.begin = begin;
this.end = end;
}
}