/home/wpollock1/public_html/Java/RegExLab.java
// 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, and possibly dropdown list
// of previously used REs.
//
// Written 6/2004 by Wayne Pollock, Tampa Florida USA.
// Updated 9/2015 by WP: Had bug (won't match first character after Reset);
// Updated for modern Java (Generics).
package com.wpollock.regexp;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
public class RegExLab extends JFrame
{
private static final long serialVersionUID = 1L;
private static final String DEFAULT_TEXT = "Data.txt";
// 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<JCheckBox> checkboxOptions; // List of option checkboxes.
// Visual Components:
private JFileChooser chooser;
private JTextField regExTF; // This is where the user types the RE.
private JButton clearBtn; // Clears regExTF.
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, literalOption,
unicodeCCOption, 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:
frame.setLocationRelativeTo(null);
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 ASCII 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 "
+ "('^' and '$' match after/before line terminators, "
+ "rather than the beginning and end of the whole text)" );
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 "
+ "(so '.' matches line terminators too)" );
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-insensitive matching");
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 "
+ "(when different Unicode sequences of characters represent the "
+ "same text)" );
checkboxOptions.add( canonEqOption );
unixLineOption = new JCheckBox( "Unix Line" );
unixLineOption.setActionCommand( Integer.toString(Pattern.UNIX_LINES) );
unixLineOption.setMnemonic( KeyEvent.VK_X );
unixLineOption.setToolTipText( "Enable Unix Line "
+ "(\\n only as end of line) Mode" );
checkboxOptions.add( unixLineOption );
literalOption = new JCheckBox( "Literal" );
literalOption.setActionCommand( Integer.toString(Pattern.LITERAL) );
literalOption.setMnemonic( KeyEvent.VK_L );
literalOption.setToolTipText( "Enable Literal Mode (Treat all "
+ "characters in the Reg Exp literally, including '.', '*', etc.)" );
checkboxOptions.add( literalOption );
unicodeCCOption = new JCheckBox( "Unicode Character Class" );
unicodeCCOption.setActionCommand( Integer.toString(Pattern.UNICODE_CHARACTER_CLASS) );
unicodeCCOption.setMnemonic( KeyEvent.VK_O );
unicodeCCOption.setToolTipText( "Enable conforming Unicode Character classes Mode and Unicode Case mode" );
checkboxOptions.add( unicodeCCOption );
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 ) );
clearBtn = new JButton();
clearBtn.setMnemonic( KeyEvent.VK_R );
Image clearIcon = null;
try {
InputStream is = getClass().getResourceAsStream("clear-rtl-icon.png");
clearIcon = ImageIO.read( is );
} catch ( IOException ioe ) {
clearIcon = null;
}
if ( clearIcon != null ) {
clearIcon =
clearIcon.getScaledInstance(16, 16, java.awt.Image.SCALE_SMOOTH);
clearBtn.setIcon( new ImageIcon(clearIcon) );
clearBtn.setBorderPainted( false );
clearBtn.setContentAreaFilled( false );
} else {
clearBtn.setText( "Clear" );
}
Box tfWithBtn = Box.createHorizontalBox();
tfWithBtn.add( regExTF );
tfWithBtn.add( Box.createGlue() );
tfWithBtn.add( clearBtn );
tfWithBtn.setBackground( regExTF.getBackground() );
tfWithBtn.setBorder( regExTF.getBorder() );
regExTF.setBorder(null);
tfWithBtn.setBorder( new CompoundBorder(
new EmptyBorder(5, 5, 5, 5), new EtchedBorder() ) );
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( 12, 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( tfWithBtn, 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<JCheckBox> it
= checkboxOptions.iterator(); it.hasNext(); )
{ optionsPanel.add( 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 ) { }
}
);
// The Clear icon button clears the entered regular expression:
clearBtn.addActionListener( new ActionListener()
{ public void actionPerformed ( ActionEvent ae )
{ regExTF.setText( null );
regExTF.requestFocus();
}
}
);
// When the "select file" btn is clicked, let user chose a new text file,
// and update the displayed text (and reset the RE) if successful:
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<JCheckBox> it = checkboxOptions.iterator(); it.hasNext(); )
it.next().addItemListener( optionChangeListener );
}
// Read in initial search text from "data.txt" file in jar:
// TODO: Update to use NIO.
private String initializeText ( )
{
StringBuffer sb = new StringBuffer( 1024 );
try
{ BufferedReader in = new BufferedReader( new InputStreamReader(
getClass().getResourceAsStream( DEFAULT_TEXT ) ) );
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 canceled 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. If pos == 0 and nothing is selected, then
// there was no previous match, so don't change pos from zero:
int pos = textPane.getSelectionStart();
if ( pos != 0 || textPane.getSelectionEnd() != 0 ) {
++pos;
}
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<JCheckBox> it
= checkboxOptions.iterator(); it.hasNext(); )
{ JCheckBox cb = 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";
}
}
}