Download RegExLab.java source file
To run, either double-click jar file or type
"java -jar RegExLab.jar".
Note that the jar file includes sample text, the source, the jar file manifest,
and a DOS build script.
// Regular expression lab. Allows user to enter REs and match them against
// text from any selected file. This GUI application has a swing
// user interface showing buttons, (split) scroll panes, file choosers,
// checkboxes, Mnemonic (shortcut) keys, and sophisticated layout.
//
// Todo: Add (Regular Expression) help, add checkboxes for additional options.
//
// Written 6/2004 by Wayne Pollock, Tampa Florida USA.
package com.wpollock.regexp;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
public class RegExLab extends JFrame
{
// State Information:
private String text; // Contents of selected file.
private boolean regExpChanged = true; // Forces RE to re-compile if true.
private boolean textChanged = true; // Resets matcher position to 0 if true.
private Pattern refExpPattern = null; // The compiled RE.
private Matcher regExp = null; // The current state (position) of the RE.
private Container container;
private java.util.List checkboxOptions; // Collection of option checkboxes.
// Visual Components:
private JFileChooser chooser;
private JTextField regExTF; // This is where the user types the RE
private JButton nextMatchBtn; // highlight next match. (Default btn).
private JButton resetBtn; // Reset the matcher to the start of the text.
private JButton selectFileBtn; // Read in new text from file.
private JButton helpBtn; // Reg.Exp. brief help.
private JLabel textPaneTitle; // Holds the file name of the selected text.
private JTextArea textPane; // Holds the text of the selected file.
private JScrollPane textScrollPane;
private JTextArea captureGroupPane; // Displays RE "capture groups", if any.
private JScrollPane captureGroupScrollPane;
private JSplitPane splitPane; // Holds the text and capture group panes.
private JCheckBox caseInsensitiveOption, multilineOption, dotallOption,
unicodeCaseOption, canonEqOption, unixLineOption, commentsOption;
public static void main ( String [] args )
{
JFrame frame = new RegExLab();
frame.pack(); // Size the frame to just hold everything.
// Center the Frame on the screen:
Toolkit tk = Toolkit.getDefaultToolkit();
int screenWidth = tk.getScreenSize().width;
int screenHeight = tk.getScreenSize().height;
int frameWidth = frame.getWidth();
int frameHeight = frame.getHeight();
frame.setLocation( ( screenWidth - frameWidth ) / 2,
( screenHeight - frameHeight ) / 2 );
frame.setVisible( true );
}
public RegExLab ( )
{
super( "Regular Expression Lab" );
// Create components and set their properties:
text = initializeText();
nextMatchBtn = new JButton( "Show Next Match" );
nextMatchBtn.setMnemonic( KeyEvent.VK_N );
resetBtn = new JButton( "Reset" );
resetBtn.setMnemonic( KeyEvent.VK_R );
selectFileBtn = new JButton( "Select File..." );
selectFileBtn.setMnemonic( KeyEvent.VK_S );
helpBtn = new JButton( "Help..." );
helpBtn.setMnemonic( KeyEvent.VK_H );
helpBtn.setToolTipText( "Show Regular Expression Summary" );
helpBtn.setEnabled( false ); // Need permission before adding this!
checkboxOptions = new ArrayList();
caseInsensitiveOption = new JCheckBox( "Case Insensitive" );
caseInsensitiveOption.setActionCommand(
Integer.toString( Pattern.CASE_INSENSITIVE ) );
caseInsensitiveOption.setMnemonic( KeyEvent.VK_I );
caseInsensitiveOption.setToolTipText(
"Enables case-insensitive matching" );
checkboxOptions.add( caseInsensitiveOption );
multilineOption = new JCheckBox( "Multi-line" );
multilineOption.setActionCommand( Integer.toString(Pattern.MULTILINE) );
multilineOption.setMnemonic( KeyEvent.VK_M );
multilineOption.setToolTipText( "Enables multi-line mode" );
checkboxOptions.add( multilineOption );
dotallOption = new JCheckBox( "Dot All" );
dotallOption.setActionCommand( Integer.toString(Pattern.DOTALL) );
dotallOption.setMnemonic( KeyEvent.VK_D );
dotallOption.setToolTipText( "Enables Dot All mode" );
checkboxOptions.add( dotallOption );
unicodeCaseOption = new JCheckBox( "Unicode Case" );
unicodeCaseOption.setActionCommand(
Integer.toString( Pattern.UNICODE_CASE ) );
unicodeCaseOption.setMnemonic( KeyEvent.VK_U );
unicodeCaseOption.setToolTipText( "Enables Unicode-aware case folding");
checkboxOptions.add( unicodeCaseOption );
canonEqOption = new JCheckBox( "Canon Eq" );
canonEqOption.setActionCommand( Integer.toString(Pattern.CANON_EQ) );
canonEqOption.setMnemonic( KeyEvent.VK_E );
canonEqOption.setToolTipText( "Enables canonical equivalence" );
checkboxOptions.add( canonEqOption );
unixLineOption = new JCheckBox( "Unix Line" );
unixLineOption.setActionCommand( Integer.toString(Pattern.UNIX_LINES) );
unixLineOption.setMnemonic( KeyEvent.VK_L );
unixLineOption.setToolTipText( "Enable Unix Line Mode" );
checkboxOptions.add( unixLineOption );
commentsOption = new JCheckBox( "Comments" );
commentsOption.setActionCommand( Integer.toString(Pattern.COMMENTS) );
commentsOption.setMnemonic( KeyEvent.VK_C );
commentsOption.setToolTipText( "Allow comments and white-space in RE" );
checkboxOptions.add( commentsOption );
regExTF = new JTextField( 35 ); // width will adjust if frame is resized.
regExTF.setFont( new Font( "Monospaced", Font.BOLD, 16 ) );
textPaneTitle = new JLabel( "Select a file to change the search text:",
JLabel.CENTER );
textPaneTitle.setFont( new Font( "SansSeriff", Font.BOLD, 18 ) );
textPaneTitle.setOpaque( true ); // So background color shows.
textPaneTitle.setForeground( Color.BLUE );
textPaneTitle.setBackground( Color.CYAN );
textPane = new JTextArea( 10, 50 );
textPane.setFont( new Font( "Monospaced", Font.BOLD, 16 ) );
textPane.setBackground( Color.CYAN );
textPane.setSelectionColor( Color.YELLOW );
textPane.setMargin( new Insets(5,5,5,5) ); // Adds some padding.
textPane.setTabSize( 4 );
textPane.setEditable( false );
textPane.setFocusable( false ); // So tab key focus cycle skips this.
textPane.setText( text );
textPane.getCaret().setBlinkRate( 0 ); // Set cursor to non-blinking.
textPane.getCaret().setVisible( true );
textScrollPane = new JScrollPane( textPane );
textScrollPane.setBackground( Color.CYAN );
textScrollPane.setColumnHeaderView( textPaneTitle );
captureGroupPane = new JTextArea( 6, 50 );
captureGroupPane.setFont( new Font( "Monospaced", Font.BOLD, 16 ) );
captureGroupPane.setBackground( Color.CYAN );
captureGroupPane.setMargin( new Insets(5,5,5,5) );
captureGroupPane.setEditable( false );
captureGroupPane.setFocusable( false );
captureGroupPane.getCaret().setVisible( false ); // Always hide cursor.
captureGroupScrollPane = new JScrollPane( captureGroupPane );
JLabel capturePaneTitle = new JLabel( "Capture Groups from last match:",
JLabel.CENTER );
capturePaneTitle.setFont( new Font( "SansSeriff", Font.BOLD, 18 ) );
capturePaneTitle.setOpaque( true ); // So background color shows.
capturePaneTitle.setForeground( Color.BLUE );
capturePaneTitle.setBackground( Color.CYAN );
captureGroupScrollPane.setBackground( Color.CYAN );
captureGroupScrollPane.setColumnHeaderView( capturePaneTitle );
chooser = new JFileChooser();
chooser.setDialogTitle( "Select text file:" );
TextFilter tf = new TextFilter(); // This is a nested class, below.
chooser.addChoosableFileFilter( tf );
chooser.setFileFilter( tf ); // Make text filter the default.
// Layout the components:
container = getContentPane();
container.setLayout( new BorderLayout() );
JPanel top = new JPanel( new BorderLayout() );
JPanel row1 = new JPanel( new BorderLayout() );
row1.add( new JLabel(" Enter Regular Expression: " ),
BorderLayout.WEST );
row1.add( regExTF, BorderLayout.CENTER );
JPanel btnPanel = new JPanel();
btnPanel.add( nextMatchBtn );
btnPanel.add( resetBtn );
btnPanel.add( selectFileBtn );
row1.add( btnPanel, BorderLayout.EAST );
top.add( row1, BorderLayout.NORTH );
JPanel row2 = new JPanel( new BorderLayout() );
row2.setBorder( new EmptyBorder( 0, 5, 2, 5 ) ); // tweak spacing
Box optionsPanel = Box.createHorizontalBox();
optionsPanel.add( new JLabel( "Options: " ) );
optionsPanel.add( Box.createGlue() );
for ( Iterator it = checkboxOptions.iterator(); it.hasNext(); )
{ optionsPanel.add( (JCheckBox) it.next() );
optionsPanel.add( Box.createGlue() );
}
optionsPanel.add( Box.createGlue() );
row2.add( optionsPanel, BorderLayout.CENTER );
row2.add( helpBtn, BorderLayout.EAST );
top.add( row2, BorderLayout.SOUTH );
container.add( top, BorderLayout.NORTH );
splitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true,
textScrollPane, captureGroupScrollPane );
splitPane.setOneTouchExpandable( true );
container.add( splitPane, BorderLayout.CENTER );
// Hook up event handling:
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// Hitting enter activates the default button unless otherwise handled:
getRootPane().setDefaultButton( nextMatchBtn );
// What to do when user changes the regular expression in regExTF:
// (Note: by default, hitting enter causes the default button to fire.)
regExTF.getDocument().addDocumentListener( new DocumentListener()
{ public void insertUpdate ( DocumentEvent ignored )
{ regExpChanged = true; }
public void removeUpdate ( DocumentEvent ignored )
{ regExpChanged = true; }
public void changedUpdate ( DocumentEvent ignored ) { }
}
);
// When the "select file" btn is clicked, let user chose a new text file,
// and update the displayed text (and reset the RE) if sucessful:
selectFileBtn.addActionListener( new ActionListener()
{ public void actionPerformed ( ActionEvent ae )
{ readFile();
regExTF.requestFocus();
}
}
);
// When clicked, find and highlight the next match in the text.
// Show dialog if no more matches:
nextMatchBtn.addActionListener( new ActionListener()
{ public void actionPerformed ( ActionEvent e )
{ showNextMatch();
regExTF.requestFocus();
}
}
);
// When clicked set the match position to zero and show cursor:
resetBtn.addActionListener( new ActionListener()
{ public void actionPerformed ( ActionEvent e )
{ if ( regExp == null )
return; // Nothing to reset yet.
regExp.reset();
textPane.setCaretPosition( 0 );
textPane.getCaret().setVisible( true );
textPane.getCaret().setSelectionVisible( true );
regExTF.requestFocus();
}
}
);
// When clicked show the cursor (and hide previous selection):
textPane.addMouseListener ( new MouseAdapter ()
{ public void mousePressed ( MouseEvent me )
{ textPane.getCaret().setVisible( true );
textPane.getCaret().setSelectionVisible( true );
}
}
);
// When any option checkbox changes state, the regular expression
// must be re-compiled with the new flags:
ItemListener optionChangeListener = new ItemListener()
{ public void itemStateChanged( ItemEvent ie )
{ regExpChanged = true;
regExTF.requestFocus();
}
};
for ( Iterator it = checkboxOptions.iterator(); it.hasNext(); )
( (JCheckBox) it.next() ).addItemListener( optionChangeListener );
// Since the scrollpane never has the focus, the mouse wheel does
// nothing. So we intercept the mousewheel events and foward them
// the the textScrollPane. (The captureGroupScrollPane will thus never
// respond to mousewheel events. Alas!)
/* regExTF.addMouseWheelListener ( new MouseWheelListener()
{ public void mouseWheelMoved ( MouseWheelEvent mwe )
{ textScrollPane.dispatchEvent( mwe );
System.err.println( "Mouse Wheel Event!" );
}
}
);
*/
}
// Read in initial search text from "data.txt" file in jar:
private String initializeText ( )
{
StringBuffer sb = new StringBuffer( 1024 );
try
{ BufferedReader in = new BufferedReader( new InputStreamReader(
getClass().getResourceAsStream( "Data.txt" ) ) );
for ( String line = in.readLine(); line != null; line = in.readLine() )
{ sb.append( line );
sb.append( "\n" );
}
in.close();
text = sb.toString();
}
catch ( Exception e )
{ sb.setLength( 0 );
sb.append( "Select a text file to change this search text.\n" );
}
return sb.toString();
}
private void readFile ()
{
// Show a file dialog box and have the user select a data file:
int returnVal = chooser.showOpenDialog( null );
if ( returnVal != JFileChooser.APPROVE_OPTION )
return; // User cancelled operation
// Read in the selected file's text:
File file = chooser.getSelectedFile();
try { textPane.read( new FileReader( file ), null ); }
catch ( Exception ignored )
{ JOptionPane.showMessageDialog( null, "Could not read from file \""
+ file.getName() + "\"", "Error!", JOptionPane.ERROR_MESSAGE );
return;
}
text = textPane.getText();
textChanged = true;
textPane.setCaretPosition( 0 );
textPane.getCaret().setVisible( true );
textPane.getCaret().setSelectionVisible( true );
textPaneTitle.setText( file.getName() );
}
// Highlight the next match (if any). Note if the RE only changed
// the Matcher must be regenerated, but the next match should be after
// the current position. If the text changed the Matcher must be
// reset to position 0 of the new text.
// After a reset, show the caret position only (not the last match).
private void showNextMatch ()
{
// Start the next search after the start of the last, to allow
// overlapping matches:
int pos = textPane.getSelectionStart() + 1;
if ( regExpChanged )
{
String re = regExTF.getText();
if ( re == null || re.length() == 0 )
{ JOptionPane.showMessageDialog( null, "No Regular Expression!",
"Illegal Regular Expression", JOptionPane.ERROR_MESSAGE );
return;
}
// This code adds the value of the action command of each selected
// option checkbox, which was earlier set to the corresponding
// integer constant from the Pattern class:
int options = 0;
for ( Iterator it = checkboxOptions.iterator(); it.hasNext(); )
{ JCheckBox cb = (JCheckBox) it.next();
if ( cb.isSelected() )
options += Integer.parseInt( cb.getActionCommand() );
}
try
{ refExpPattern = Pattern.compile( re, options );
}
catch ( PatternSyntaxException pse )
{ JOptionPane.showMessageDialog( null, pse.getMessage(),
"Illegal Regular Expression", JOptionPane.ERROR_MESSAGE );
return;
}
}
if ( regExpChanged || textChanged )
regExp = refExpPattern.matcher( text );
if ( textChanged )
{ pos = 0;
textPane.getCaret().setVisible( true );
textPane.getCaret().setSelectionVisible( false );
}
regExpChanged = textChanged = false;
if ( regExp.find( pos ) )
{ // Highlight the match:
textPane.setCaretPosition( regExp.start() );
textPane.moveCaretPosition( regExp.end() );
textScrollPane.validate();
textPane.getCaret().setVisible( false );
textPane.getCaret().setSelectionVisible( true );
//Add any capture groups to capture group pane:
captureGroupPane.setText( "" );
int numGroups = regExp.groupCount();
for ( int i = 1; i <= numGroups; ++i )
captureGroupPane.append( i + ": " + regExp.group( i ) + "\n" );
}
else
{ JOptionPane.showMessageDialog( null, "No matches remaining!" );
}
}
private static class TextFilter extends javax.swing.filechooser.FileFilter
{
public final static String textExtension = "txt";
public static String getExtension ( File f )
{
String ext = null;
String s = f.getName();
int i = s.lastIndexOf( '.' );
if ( i > 0 && i < s.length() - 1 ) // Make sure dot isn't last char.
ext = s.substring( i+1 ) . toLowerCase();
return ext;
}
// Accept all directories and text files:
public boolean accept ( File file )
{
if ( file.isDirectory() )
return true;
String extension = getExtension( file );
if ( extension != null && extension.equals( textExtension ) )
return true;
return false;
}
// Return the description of this filter:
public String getDescription ( )
{
return "Text Documents";
}
}
}