HowToUseSearchAPI
Warning: This page is work in progress, the API has not been added to NetBeans platform, and maybe it will never be.
Contents |
Overview
You will need to use this API:
- if you implement a custom project type, and want to define which files and directories should be searched and which should be skipped.
- if you want to provide customized search types that require special settings and algorithms (e.g. searching in a specific file type, searching in database), and want them to be accessible from standard search panel (Ctrl-Shift-F) or replace panel (Ctrl-Shift-H).
Customizing search in project
Here we will learn how to set which files will be searched and which skipped.
The more complex way - SearchInfoDefinition
TODO
Simpler way - SubTreeSearchOptions
TODO
Provide custom search
This part of Search API is useful to add specialized search to platform applications, or to enhance the IDE with optimized searching (for various operating systems, search options, etc.).
What happens when you register your custom search provider? The Find in Projects dialog will contain not single form with search options, but tabbed panel where each tab will contain a form for its provider.
TODO: Add Screenshot - search dialog with tabs
You will need to implement several things:
- SearchProvider - to register your search, tell what it can do and whether it is enabled.
- SearchProvider.Presenter - to create a visual component for displaying settings and to let it interact with search dialog.
- SearchComposition - to start and control search algorithm.
- SearchResultsDisplayer - an component that can display matching objects and some controls in the Search Results window (you can used simple, predefined implementation, if you are comfortable with it).
Example: Simple provider, searching for recently modified files.
/**
* Provider for searching recently modified files.
*/
@ServiceProvider(service = SearchProvider.class)
public class CustomProvider extends SearchProvider {
@Override
public Presenter createPresenter(boolean replaceMode) {
return new CustomPresenter(this);
}
@Override
public boolean isReplaceSupported() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getTitle() {
return "Recent Files Search";
}
/**
* Presenter is an object that holds a UI component for setting search
* criteria.
*/
private class CustomPresenter extends Presenter {
private JPanel panel = null;
ScopeOptionsController scopeSettingsPanel;
FileNameController fileNameComboBox;
ScopeController scopeComboBox;
ChangeListener changeListener;
JSlider slider;
public CustomPresenter(SearchProvider searchProvider) {
super(searchProvider, false);
}
/**
* Get UI component that can be added to the search dialog.
*/
@Override
public synchronized JComponent getForm() {
if (panel == null) {
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.LEADING));
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.LEADING));
JPanel row3 = new JPanel(new FlowLayout(FlowLayout.LEADING));
row1.add(new JLabel("Age in hours: "));
slider = new JSlider(1, 72);
row1.add(slider);
final JLabel hoursLabel = new JLabel(String.valueOf(slider.getValue()));
row1.add(hoursLabel);
row2.add(new JLabel("File name: "));
fileNameComboBox = ComponentUtils.adjustComboForFileName(new JComboBox());
row2.add(fileNameComboBox.getComponent());
scopeSettingsPanel = ComponentUtils.adjustPanelForOptions(new JPanel(),
false, fileNameComboBox);
row3.add(new JLabel("Scope: "));
scopeComboBox = ComponentUtils.adjustComboForScope(new JComboBox(), null);
row3.add(scopeComboBox.getComponent());
panel.add(row1);
panel.add(row3);
panel.add(row2);
panel.add(scopeSettingsPanel.getComponent());
initChangeListener();
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
hoursLabel.setText(String.valueOf(slider.getValue()));
}
});
}
return panel;
}
private void initChangeListener() {
this.changeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
fireChange();
}
};
fileNameComboBox.addChangeListener(changeListener);
scopeSettingsPanel.addChangeListener(changeListener);
slider.addChangeListener(changeListener);
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx("org.netbeans.modules.search.ui.prototype.about"); //NOI18N
}
/**
* Create search composition for criteria specified in the form.
*/
@Override
public SearchComposition<?> composeSearch() {
SearchScopeOptions sso = scopeSettingsPanel.getSearchScopeOptions();
return new CustomComposition(sso, scopeComboBox.getSearchInfo(),
slider.getValue(), this);
}
/**
* Here we return always true, but could return false e.g. if file name
* pattern is empty.
*/
@Override
public boolean isUsable(NotificationLineSupport notifySupport) {
return true;
}
}
/**
* Custom algorithm that check date of last modification of searched files.
*/
private class CustomComposition extends SearchComposition<DataObject> {
SearchScopeOptions searchScopeOptions;
SearchInfo searchInfo;
int oldInHours;
SearchResultsDisplayer<DataObject> resultsDisplayer;
private final Presenter presenter;
AtomicBoolean terminated = new AtomicBoolean(false);
public CustomComposition(SearchScopeOptions searchScopeOptions,
SearchInfo searchInfo, int oldInHours, Presenter presenter) {
this.searchScopeOptions = searchScopeOptions;
this.searchInfo = searchInfo;
this.oldInHours = oldInHours;
this.presenter = presenter;
}
@Override
public void start(SearchListener listener) {
for (FileObject fo : searchInfo.getFilesToSearch(
searchScopeOptions, listener, terminated)) {
if (ageInHours(fo) < oldInHours) {
try {
DataObject dob = DataObject.find(fo);
getSearchResultsDisplayer().addMatchingObject(dob);
} catch (DataObjectNotFoundException ex) {
listener.fileContentMatchingError(fo.getPath(), ex);
}
}
}
}
@Override
public void terminate() {
terminated.set(true);
}
@Override
public boolean isTerminated() {
return terminated.get();
}
/**
* Use default displayer to show search results.
*/
@Override
public synchronized SearchResultsDisplayer<DataObject> getSearchResultsDisplayer() {
if (resultsDisplayer == null) {
resultsDisplayer = createResultsDisplayer();
}
return resultsDisplayer;
}
private SearchResultsDisplayer<DataObject> createResultsDisplayer() {
/**
* Object to transform matching objects to nodes.
*/
SearchResultsDisplayer.NodeDisplayer<DataObject> nd =
new SearchResultsDisplayer.NodeDisplayer<DataObject>() {
@Override
public org.openide.nodes.Node matchToNode(
final DataObject match) {
return new FilterNode(match.getNodeDelegate()) {
@Override
public String getDisplayName() {
return super.getDisplayName()
+ " (" + ageInMinutes(match.getPrimaryFile()) + " minutes old)";
}
};
}
};
return SearchResultsDisplayer.createDefault(nd, this,
presenter, "less than " + oldInHours + " hours old");
}
}
private static long ageInMinutes(FileObject fo) {
long fileDate = fo.lastModified().getTime();
long now = System.currentTimeMillis();
return (now - fileDate) / 60000;
}
private static long ageInHours(FileObject fo) {
return ageInMinutes(fo) / 60;
}
}
TODO: Add examples - usage of SearchControl
