/*
* Created on 4. August 2008, 21:51
*/
package net.java.nboglpack.quicksearch;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.netbeans.spi.quicksearch.SearchProvider;
import org.netbeans.spi.quicksearch.SearchRequest;
import org.netbeans.spi.quicksearch.SearchResponse;
import org.openide.awt.HtmlBrowser.URLDisplayer;
import org.openide.util.RequestProcessor;
/**
* Abstract Quicksearch provider. Just implement the filter method and register
* in layer.xml and you are done.
* @author Michael Bien
*/
public abstract class WebPageQuickSearchProvider implements SearchProvider {
/**
* Pattern to find hyperlinks and group them into attributes and name part.
*/
private final static Pattern linkPattern = Pattern.compile("<a([^>]+)>([^<]+)</a>", Pattern.CASE_INSENSITIVE);
/**
* Pattern to find the value of the 'href' attribute.
*/
private final static Pattern hrefPattern = Pattern.compile("href\\s*=\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE);
private SearchItem[] items;
private RequestProcessor.Task harvestTask;
public WebPageQuickSearchProvider(final String url) {
// async harvest task
harvestTask = new RequestProcessor("GL Quicksearch Requestprocessor").create(new Runnable() {
@Override
public void run() {
try {
harvest(new URL(url));
} catch (MalformedURLException ex) {
Logger.getLogger(WebPageQuickSearchProvider.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(WebPageQuickSearchProvider.class.getName()).log(Level.INFO, "unable to index page, retry after 5 minutes timeout", ex);
harvestTask.schedule((int)TimeUnit.MINUTES.toMillis(5));
}
}
});
harvestTask.schedule(0);
}
private void harvest(URL url) throws IOException {
BufferedReader reader = null;
ArrayList<SearchItem> itemList = new ArrayList<SearchItem>(128);
try {
// download content
reader = new BufferedReader(new InputStreamReader(url.openStream()));
char[] buffer = new char[512];
StringBuilder sb = new StringBuilder();
while(reader.read(buffer) != -1){
sb.append(buffer);
}
// harvest everything usefull
Matcher linkMatcher = linkPattern.matcher(sb);
// find <a ></a>
while(linkMatcher.find()) {
String attributes = linkMatcher.group(1);
Matcher hrefMatcher = hrefPattern.matcher(attributes);
String name = linkMatcher.group(2);
// find href and link name
if(hrefMatcher.find()) {
String href = hrefMatcher.group(1);
href = filter(href, name);
if(href != null) {
itemList.add(new SearchItem(name, href));
}
}else{
Logger.getLogger(WebPageQuickSearchProvider.class.getName()).log(Level.WARNING,
"no href found in attributes:\n"+attributes+"\nof hyplerlink: "+name);
}
// safepoint for beeing interrupted
if (Thread.interrupted())
break;
}
}finally{
if(reader != null)
reader.close();
}
items = itemList.toArray(new SearchItem[itemList.size()]);
}
/**
* Returns a valid link or null computed from the href attribute and the link name.
*/
abstract String filter(String href, String name);
/**
* Method is called by infrastructure when search operation was requested.
* Implementors should evaluate given request and fill response object with
* apropriate results
*
* @param request Search request object that contains information what to search for
* @param response Search response object that stores search results.
* Note that it's important to react to return value of SearchResponse.addResult(...) method
* and stop computation if false value is returned.
*/
@Override
public void evaluate(SearchRequest request, SearchResponse response) {
// make sure harvester is done
if(harvestTask != null) {
harvestTask.waitFinished();
if(items != null)
harvestTask = null;
}
if(items == null)
return;
// handle multi token search properly (everything case insensitive)
String[] token = request.getText().toLowerCase().split("\\s+");
for (SearchItem item : items) {
boolean matches = true;
for (int i = 0; i < token.length; i++) {
if(!item.nameLC.contains(token[i])) {
matches = false;
break;
}
}
if(matches) {
boolean doContinue = response.addResult(item, item.name);
if(!doContinue)
break;
}
}
}
/**
* Item which may fit to the search string. Openes URL in browser when selected.
*/
private static class SearchItem implements Runnable {
private final URL url;
private final String name;
private final String nameLC;
public SearchItem(String name, String url) throws MalformedURLException {
this.name = name;
this.nameLC = name.toLowerCase();
this.url = new URL(url);
}
@Override
public void run() {
URLDisplayer.getDefault().showURL(url);
}
}
}