/home/wpollock1/public_html/restricted/Java2/SearchEngine/com/wpollock/searchengine/Main.java
/* Search Engine - Java files and collections project (model solution).
* In this solution, I used a custom (UTF-8 text, with long lines) file format,
* and the Prefs API to store the window position and name of the data file.
* (The GUI is simple, and will go away someday when this application is
* converted to a web application.) The file format is documented in the
* file "FileFormat.txt", included in the source. It too may change, maybe
* to JSON.
*
* The use cases for this application are simple: AND, OR, and PHRASE search
* from the main window, and "Add file" and "remove file" from the
* "maintenance" window. (Also a "reset prefs" button, and a "rebuild index"
* button may be added, time permitting.)
*
* Note many methods are static. Since there is one index file, only one
* instance of this application should be running at any one time.
*
* Written 2/2012 by Wayne Pollock, Tampa Florida USA
*/
package com.wpollock.searchengine;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.prefs.*;
import javax.swing.*;
import javax.swing.event.*;
import static com.wpollock.searchengine.IndexUtils.*;
/**
* @author Wayne Pollock <pollock@acm.org>
* @version 1.1
*
*/
enum SearchType { AND, OR, PHRASE }
public class Main extends JFrame implements ComponentListener {
private static final long serialVersionUID = 1L;
static final String DEFAULT_FILE_NAME = "SrchNgn.txt"; // In user's home.
static final String VERSION = "1.1"; // App's version number
static String indexFileName = null; // Pathname to data file.
static Preferences prefs = null;
static long nextID; // Next fileID to use when indexing new files
static ImageIcon appIcon = null;
final static JLabel numItemsIndexed = new JLabel( "0" );
private final JButton searchBtn, aboutBtn;
private SearchType selectedSearch = SearchType.AND;
private JTextField searchTerms;
private JTextPane results;
static JFrame maintenanceWindow = null;
// Preference keys for this package
static final String INDEX_FILE_NAME = "index_file";
static final String MAIN_X = "Xpos";
static final String MAIN_Y = "Ypos";
static final String MAIN_HEIGHT = "height";
static final String MAIN_WIDTH = "width";
static final String MAINT_X = "maintXpos";
static final String MAINT_Y = "maintYpos";
static final String MAINT_HEIGHT = "maintHeight";
static final String MAINT_WIDTH = "maintWidth";
static final String FILE_HEADER = "SearchData 1.0";
static final String FILE_ENCODING = "UTF-8";
static final String LAST_DIRECTORY = "LastvisitedDir";
// static final String JTABLE_DIVIDER_POS = ""; // Use JTable default
public static void main(String[] args) {
// Create main window:
Main mainWindow = new Main();
indexFileName = prefs.get( INDEX_FILE_NAME, DEFAULT_FILE_NAME );
String userHomeDir = System.getProperty( "user.home" );
String sepChar = System.getProperty( "file.separator" );
indexFileName = userHomeDir + sepChar + indexFileName;
// Locate and read index data file; if missing, create a new one.
// If it is found but is unreadable, try to rename it and then
// create a new one:
mainWindow.setVisible( true );
IndexUtils.initializeIndexFromFile(); // read file or create if missing
maintenanceWindow = new MaintenanceWindow( mainWindow, prefs );
Main.numItemsIndexed.setText( "" +
FileListModel.getModel().getRowCount() );
// If config file empty, start with "Maintenance" window open:
if ( nextID == 0 )
maintenanceWindow.setVisible( true );
}
// Build the main window GUI:
public Main () {
setTitle( "Search Engine" );
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// Icon found with Creative Commons License:
appIcon = new ImageIcon(getClass().getResource("MagnifyingGlass.jpg"));
setIconImage( appIcon.getImage() );
// Set window bounds from saved prefs:
prefs = Preferences.userNodeForPackage(Main.class);
positionWindow();
// Add components and set their properties:
Box mainBox = Box.createVerticalBox();
add ( mainBox );
// Add application title:
JLabel title = new JLabel("Search Engine");
title.setFont( new Font("SansSerif", Font.BOLD, 24) );
Box titleBox = Box.createHorizontalBox();
titleBox.add( title );
titleBox.add( Box.createHorizontalStrut(50) );
mainBox.add( titleBox );
mainBox.add( Box.createGlue() );
// In the middle put a two rows of controls:
Box searchBox = Box.createHorizontalBox();
mainBox.add( searchBox );
mainBox.add( Box.createGlue() );
searchBox.add( Box.createGlue() );
searchBox.add( new JLabel("Search Terms:", SwingConstants.RIGHT) );
searchTerms = new JTextField(40);
JPanel searchTermsPanel = new JPanel();
searchTermsPanel.add( searchTerms );
searchBox.add( searchTermsPanel );
searchBtn = new JButton( "Search" );
searchBtn.setMnemonic(KeyEvent.VK_S);
searchBtn.setToolTipText( "Search indexed files for search terms" );
searchBtn.setEnabled( false ); // Enable when searchTerms not empty
getRootPane().setDefaultButton( searchBtn );
searchBox.add( searchBtn );
searchBox.add( Box.createGlue() );
// Create the controls, 3 radiobuttons, a textbox, and a Search button:
Box btnsBox = Box.createHorizontalBox();
ButtonGroup group = new ButtonGroup(); // Groups radio buttons
JRadioButton andBtn = new JRadioButton( "All of the Search Terms" );
andBtn.setMnemonic(KeyEvent.VK_A);
andBtn.setActionCommand( "AND" );
andBtn.setSelected( true );
group.add( andBtn );
btnsBox.add( andBtn );
JRadioButton orBtn = new JRadioButton( "Any of the Search Terms" );
orBtn.setMnemonic(KeyEvent.VK_O);
orBtn.setActionCommand( "OR" );
group.add( orBtn );
btnsBox.add( orBtn );
JRadioButton phraseBtn = new JRadioButton( "Exact Phrase" );
phraseBtn.setMnemonic(KeyEvent.VK_P);
phraseBtn.setActionCommand( "PHRASE" );
group.add( phraseBtn );
btnsBox.add( phraseBtn );
mainBox.add( btnsBox );
mainBox.add( Box.createGlue() );
results = new JTextPane();
//results.setVisible( false ); // Show after hitting Search button
results.setEditable( false );
mainBox.add( results );
mainBox.add( Box.createGlue() );
// At bottom, show Maint btn, num of files indexed, and app version:
Box footerBox = Box.createHorizontalBox();
footerBox.add( Box.createHorizontalStrut(10) );
JButton maintBtn = new JButton( "Maintenance..." );
maintBtn.setMnemonic(KeyEvent.VK_M);
maintBtn.setToolTipText( "Show index maintenance window" );
footerBox.add( maintBtn );
footerBox.add( Box.createGlue() );
footerBox.add( new JLabel("Number of files indexed: ") );
footerBox.add( numItemsIndexed );
footerBox.add( Box.createGlue() );
aboutBtn = new JButton( "About..." );
aboutBtn.setActionCommand( "ABOUT" );
aboutBtn.setMnemonic(KeyEvent.VK_B);
aboutBtn.setToolTipText( "About Search Engine" );
footerBox.add( aboutBtn );
footerBox.add( Box.createHorizontalStrut(10) );
mainBox.add( footerBox );
// Hook up event handlers:
addComponentListener( this ); // Handles window resize and move
searchTerms.requestFocusInWindow();
searchTerms.requestFocus();
// If non-empty, enable the Search button:
searchTerms.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) { updateSearchBtn(); }
@Override
public void removeUpdate(DocumentEvent e) { updateSearchBtn(); }
@Override
public void changedUpdate(DocumentEvent e) { updateSearchBtn(); }
});
maintBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
searchTerms.requestFocus();
maintenanceWindow.setVisible( true );
maintenanceWindow.toFront();
}
});
andBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
selectedSearch = SearchType.AND;
searchTerms.requestFocus();
}
});
orBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
selectedSearch = SearchType.OR;
searchTerms.requestFocus();
}
});
phraseBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
selectedSearch = SearchType.PHRASE;
searchTerms.requestFocus();
}
});
searchBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
switch ( selectedSearch ) {
case AND: doAndSearch(searchTerms.getText(), results);
break;
case OR: doOrSearch(searchTerms.getText(), results);
break;
case PHRASE: doPhraseSearch(searchTerms.getText(), results);
break;
}
searchTerms.requestFocus();
}
});
// TODO: Add a button, to clear old results (and the search textfield)
aboutBtn.addActionListener( new ActionListener() {
@Override
public void actionPerformed ( ActionEvent evnt ) {
File indexFile = new File( indexFileName );
long size = indexFile.length();
JOptionPane.showMessageDialog(null, "Search Engine "
+ VERSION + "\n"
+ "Model Solution to COP-2805 Search Engine Project\n"
+ "Written by Wayne Pollock, Tampa Florida USA\n"
+ "(Index data file: " + indexFileName + ",\n"
+ "size: " + size + " bytes)",
"Search Engine", JOptionPane.INFORMATION_MESSAGE,
appIcon );
searchTerms.requestFocus();
}
});
}
private void updateSearchBtn () {
String terms = searchTerms.getText();
searchBtn.setEnabled(terms.length() != 0); // Enable button if usable
}
// ComponentListener methods:
public void componentMoved (ComponentEvent e) { updatePrefs(); }
public void componentResized (ComponentEvent e) { updatePrefs(); }
public void componentShown (ComponentEvent e) {}
public void componentHidden (ComponentEvent e) {}
/** Store current window bounds into the prefs when the window
* size or position is changed:
*/
void updatePrefs () {
prefs.put(LAST_DIRECTORY, System.getProperty("user.home") );
prefs.putInt( MAIN_WIDTH, (int) getWidth() );
prefs.putInt( MAIN_HEIGHT, (int) getHeight() );
prefs.putInt( MAIN_X, (int) getX() );
prefs.putInt( MAIN_Y, (int) getY() );
if ( maintenanceWindow != null && maintenanceWindow.isVisible() ) {
prefs.putInt( MAINT_WIDTH, (int) maintenanceWindow.getWidth() );
prefs.putInt( MAINT_HEIGHT, (int) maintenanceWindow.getHeight() );
prefs.putInt( MAINT_X, (int) maintenanceWindow.getX() );
prefs.putInt( MAINT_Y, (int) maintenanceWindow.getY() );
}
}
/** Position main window to defaults:
*/
void positionWindow () {
// POsition main window:
int xPos = prefs.getInt( MAIN_X, -1 ); // "-1" means never set
int yPos = prefs.getInt( MAIN_Y, -1 );
int height = prefs.getInt( MAIN_HEIGHT, 600 );
int width = prefs.getInt( MAIN_WIDTH, 800 );
setSize( width, height );
// if xPos is -1, the window position was never set before,
// so center it:
if ( xPos == -1 ) {
setLocationRelativeTo( null ); // Center on Screen
prefs.putInt( MAIN_X, getX() );
prefs.putInt( MAIN_Y, getY() );
} else
setLocation( xPos, yPos );
}
}