// AVQuery.java // // Sample solution to CMP 318 99-1 Assignment #2 Q2 // // import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; import java.util.*; /** * AVQuery is an applet that queries the AltaVista search engine and outputs * the results in a simple list. * *
Operation Notes
* * The applet allows the user to enter a query term in Altavista's standard * (i.e. not the "advanced" setting) query format and shows the resulting * result set consisting of hits to other web pages. The user can control * the number of hits to load from the server via a simple setting.
* * Note that the server requesting is entirely synchronous, that is, * the user enters a new search term and the program does not come back * to be controlled until the search results are received. * *
Architecture Notes
* * The applet is divided according to the MVC architecture. It looks like * this: *
* * The ResultsModel is completely separated from UI, View, or Controller * code. The Views and Controllers all make use of UI components by * object composition rather than inheritance. That way the GUI * appearances and behaviour can be modified without changing the * interfaces of these components.
*
* Finally, note that the AVQuery object is in a priveleged position
* compared to the MVC components: it does the component construction,
* understands the MVC connections, and knows about the overall UI and
* how to place sub-components of the interface on them. For that reason,
* it knows about the TextArea used to display the results.
*
* @author Andrew Walenstein
*/
public class AVQuery extends Applet
{
// Note that I've violated normal naming convention in order to strongly
// encode the major application objects present in the applet. The suffix
// reflects the role of the object.
//
// results_MOD - the model is the results set
// search_CONT - search is parameterized by a search setting
// maxHits_CONT - search is parameterized by a maximum hits setting
// listing_VCONT - result set is viewed by listing; this controls display
// fakeDispI_VIEW - display is an interface-level view independent of model
//
private final ResultsModel results_MOD = new ResultsModel();
private final SearchControl search_CONT = new SearchControl();
private final MaxHitControl maxHits_CONT = new MaxHitControl();
private final ListView listing_VCONT = new ListView();
private final TextArea fakeDispI_VIEW = new TextArea(4,40);
// There are 4 observers, each corresponding to a connection between
// an applet component.
//
// resultsModelQuery - initiate search when new search term event occurs
// resultsModelNewMaxHits - changes number of hits for further searches
// listViewRefresh - updates view completely from model
// fakeDisplayBrowseURL - fakes a browser follow to a given URL.
//
private final Observer resultsModelQuery = new Observer()
{
public void update ( Observable o, Object newQueryText )
{
results_MOD.setQuery ( (String)newQueryText );
}
};
private final Observer resultsModelNewMaxHits = new Observer()
{
public void update ( Observable o, Object oo )
{
results_MOD.setMaxHits ( maxHits_CONT.getMaxHits() );
}
};
private final Observer listViewRefresh = new Observer()
{
public void update ( Observable o, Object hitsObj )
{
if ( hitsObj != null ) listing_VCONT.updateFrom ( (Hit[]) hitsObj );
}
};
private final Observer fakeDisplayBrowseURL = new Observer()
{
public void update ( Observable o, Object selectedNum )
{
final int selIndex = ((Integer)selectedNum).intValue();
final Hit hSelected = results_MOD.getHit ( selIndex );
fakeDispI_VIEW.setText
( "Selected #" + selIndex + " " + hSelected.theURL + "\n\n" +
hSelected.body );
}
};
/**
* Initializes the browser applet.
*/
public void init()
{
// initialize model from controller defaults
//
results_MOD.setMaxHits ( maxHits_CONT.getMaxHits() );
// Connect up main application objects using observer objects
//
results_MOD.addObserver ( listViewRefresh );
search_CONT.addObserver ( resultsModelQuery );
maxHits_CONT.addObserver ( resultsModelNewMaxHits );
listing_VCONT.addObserver ( fakeDisplayBrowseURL );
// UI part: add Components to our Container
//
final Label appTitle = new Label( "AltaVista Query Applet", Label.CENTER );
appTitle.setFont ( new Font ( "Times", Font.BOLD, 16 ) );
appTitle.setForeground ( new Color ( 255, 250, 250 ) );
appTitle.setBackground ( new Color ( 120, 160, 170 ) );
final Panel headPanel = new Panel ( new BorderLayout() );
headPanel.add ( appTitle, BorderLayout.CENTER );
headPanel.add ( maxHits_CONT.getComponent(), BorderLayout.EAST );
final Panel topPanel = new Panel ( new BorderLayout() );
topPanel.add ( headPanel, BorderLayout.NORTH );
topPanel.add ( search_CONT.getComponent(), BorderLayout.SOUTH );
setLayout ( new BorderLayout() );
add ( topPanel, BorderLayout.NORTH );
add ( listing_VCONT.getComponent(), BorderLayout.CENTER );
add ( fakeDispI_VIEW, BorderLayout.SOUTH );
}
}
/**
* The applet is divided in UI (V & C) and non-UI (M) classes.
* All UI classes need to be connected to a java.awt.Container in
* order to receive or send UI messages. Thus they all need to
* be composed of some java.awt.Components, but the client classes
* do not want to know which. Instead of extending a particular
* component, the UI classes conform to this interface, allowing
* the non-UI classes to subclass other classes (such as Observable).
*/
interface AVQueryUIComponent
{
/**
* All UIComponents "own" one part of the display, allowing clients
* to add this Component to the display as they see fit.
*/
public Component getComponent();
}
/**
* SearchControl is a Controller for the search term. Whenever a new
* search term is entered, it tells all observers.
*
* Note that SearchControl encapsulates completely the UI sub-components
* since no client classes have to deal with java.awt.event.* events.
* Note also that it is completely reusable since (a) it does not make any
* assumptions about listeners, and (b) it observes AVQueryUIComponent to
* allow its client to ask for its displayable part.
*
* The actual search controller is composed of a text field
* and search button. Actions are fired either when the button is
* depressed, or when the text field has a