Searching on Each Keystroke
Now that we have the ability to search, we need to hook it up to the
keystrokes on the search field. Dealing with the keystroke events on the
text field can get problematic, since we want to capture the backspace but not the
arrow keys. Really, we just want to know when the text itself has changed.
Instead of listening to keystroke events on the component, we will listen for
document events on the underlying text field document.
query.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent evt) {
try {
localSearch(query.getText());
} catch (Exception ex) { u.p(ex); }
}
public void insertUpdate(DocumentEvent evt) {
try {
localSearch(query.getText());
} catch (Exception ex) { u.p(ex); }
}
public void removeUpdate(DocumentEvent evt) {
try {
localSearch(query.getText());
} catch (Exception ex) { u.p(ex); }
}
});
Syncing the BrainFeeds
Now our application does real-time searching through our local database, but
how do we get the data into our database to begin with? The first time we
connect to a feed, we will want to download the whole thing, but thereafter we
only want the updates. This is called syncing, and to do it we need a way of
storing not only the downloaded entries, but also the timestamp of the last
download.
First, we need a list of previous access times. Since this is external to the
dataset, we can just store it in an XML file. Below is the feeds.xml
file that the program uses to store a list of URIs and when they were last
accessed.
<?xml version="1.0" encoding="UTF-8"?>
<uris>
<uri last-read="19/01/2004-09:47:27-EST">
file:/C:/brain/testdata/acidtest.xml
</uri>
<uri last-read="19/01/2004-09:47:27-EST">
http://mybrain.com/feed.xml
</uri>
</uris>
Once we have the date of the last sync (or a really old date, if we've never
synced before), we need to actually make the query. The BrainSearch
utility class implements the actual search (the HTTP GET request and parsing
into BrainEntry objects) that we will use here. First, we set the
URL to search, and then the time of the last sync. Next, we execute the search and
read the entries back. After dumping each entry into the repository, we finally
set the last modified date to the current time.
private void syncFeed(Feed feed) throws Exception {
try {
// init search
BrainSearch search = new BrainSearch();
search.setURL(feed.url);
search.setLastModifiedTimestampAfter(feed.getLastRead());
// execute the search
search.search();
// loop through the results
BrainEntry[] entries = search.getEntryArray();
for(int i=0; i<entries.length; i++) {
brain.add(entries[i]);
}
brain.setLastModified(feed.url,new Date());
} catch (Exception ex) {
System.out.println(ex.toString());
}
}
Creating an HTML View
Now that we can sync and search through our database, it would be nice to
actually see each entry once it is selected. The content of BrainFeed entries is
in strict XHTML, so we will need an HTML renderer to view them. Fortunately, we have
one: Swing's text package (javax.swing.text) can render styled text, and it includes an HTML viewer/editor. All we have to do is initialize it properly and then load in our HTML.
Swing's text package includes a series of EditorKits along
with an actual Swing component, the JEditorPane (and its subclass
the JTextPane). To create a specific type of viewer, we have to
initialize a JEditorPane with the right EditorKit. The
code below creates an editor kit with some placeholder content. Since HTML is
one of the built-in kits, the easiest way to create one is by just telling the
editor kit we want to support the "text/html" mime type. No further
configuration is required. The JEditorPane is scrolling-aware, so we can just
drop it into a scroll pane. Notice the scrolling constants. Since this is sort of
a web browser with small pages, we want the text to only scroll vertically.
JEditorPane view =
new JEditorPane("text/html","<p>empty</p>");
JScrollPane view_scroll = new JScrollPane(view,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
To load new content into the view, we take the content from the entry and
wrap it in html and body tags. Since the title is
separate from the content but would be useful to see, it's added, as well.
Finally, the text is added to the view.
BrainEntry be = (BrainEntry)results.getSelectedValue();
if(be!=null) {
StringBuffer sb = new StringBuffer();
view.setContentType("text/html");
HTMLDocument d = (HTMLDocument)view.getDocument();
d.setBase(new File(".").toURL());
sb.append("<html>");
sb.append("<body>");
sb.append("<h1>"+be.getTitle()+"</h1>");
sb.append(be.getContentString());
sb.append("</body>");
sb.append("</html>");
view.setText(sb.toString());
}
Now, with an HTML pane in our program, we can turn this:
<entry id="3">
<keyword>java</keyword>
<keyword>awt</keyword>
<keyword>swing</keyword>
<title>How can I make a screen capture?</title>
<content>
<p>Java has a method in the <i>java.awt</i> package that will
capture the screen into a buffered image</p>
<blockquote><pre>
Robot robot = new java.awt.Robot();
BufferedImage img =
robot.createScreenCapture(
new Rectangle(0,0,100,100)
);
</pre></blockquote>
</content>
</entry>
into this:

Figure 2. HTML rendering
Adding Style to the Layout
Well, it works, but it's not the prettiest screen we've ever seen. In fact, it
looks like Netscape circa 1995. When it was originally released, the HTML kit was
very slow and buggy, but recent versions of the JDK have improved it
considerably. It's still not up to a modern browser level, but it can handle a fair
amount of CSS Level 1. It's a bit finicky, though, and we'll have to work around
the bugs.
Below is some simple CSS that will set a nice background color and a border
around blockquoted code samples, and will colorize the header.
body {
background-color: #f0fff0;
font-family: Helvetica, Arial, sans-serif;
font-size: 10pt;
}
blockquote {
border: 1px solid #008800;
padding: 5px;
background-color: #b0ffb0;
}
h1 {
border: 1px solid #008800;
background-color: #88ff88;
padding: 3px 3px 5px 3px;
font-size: 120%;
font-weight: bold;
color: #005500;
}
Now we just need to apply the CSS to the HTML. The HTMLEditorKit can load
CSS via LINKs, so we can just add a reference to it at the top of the HTML when we stuff it into the editor. The code above is now modified with to add a head element with a stylesheet reference.
sb.append("<html>");
sb.append("<head>" +
"<link rel=stylesheet href='src/css/style.css'>" +
</head>");
sb.append("<body>");
sb.append("<h1>"+be.getTitle()+"</h1>");
Now our HTML renders like this:

Figure 3. HTML with CSS
It looks better, but our borders are missing. Maybe the
HTMLEditorKit doesn't support borders? Research on the Web doesn't
turn up much, but browsing through the Swing source code, we discover that it
does support borders, just not the border shorthand. It also
doesn't support borders with different widths for each side, or any width other
than one pixel! But still, with a quick CSS change we can get something
that looks pretty good.
We change the border line for the blockquote and h1
to this:
border-width: 1px;
border-style: solid;
border-color: #008800;
and now we have an attractive display for each Brain Entry, as seen in Figure 4.

Figure 4. HTML with correct CSS
The nice thing about using CSS is that the style is determined by the viewer
instead of the content author. This means you can fit the display to your own
personal preferences or repurpose it to use in another web site or application.
The Future
The application we built in this article (brainfeed.zip) could be
embedded into an IDE, searching through Javadocs and developer forums in real
time for whatever code is selected. Or it could be integrated into an email
program like Outlook's daily summary. Or we could write a chatter bot that
answers questions by doing brain searches. I am sure that others will come
up with even stranger uses for this technology.
With the BrainFeed system, we can subscribe to and search through multiple
feeds across a network. Its simple protocol allows us to create a wide variety
of clients to make targeted searches and distribute lightweight information. I
hope this will be a launching pad for others to create their own clients and
servers, and more importantly, create their own BrainFeeds to share with others.
Joshua Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.