org.netbeans.modules.quicksearch.recent.RecentSearches Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.quicksearch.recent;
import java.awt.EventQueue;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.quicksearch.CategoryResult;
import org.netbeans.modules.quicksearch.CommandEvaluator;
import org.netbeans.modules.quicksearch.ProviderModel.Category;
import org.netbeans.modules.quicksearch.ResultsModel;
import org.netbeans.modules.quicksearch.ResultsModel.ItemResult;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
/**
* Recent Searches items storage and its persistance
*
* @author Jan Becicka
* @authoe Max Sauer
*/
public class RecentSearches {
private static final int MAX_ITEMS = 5;
private static final long FIVE_DAYS = 86400000 * 5;
private LinkedList recent;
private static RecentSearches instance;
private static final char dateSep = ':';
private static final Pattern RECENT_PREFS_PATTERN =
Pattern.compile("^(.*):(\\d{8,})(?::(.*))?$"); //NOI18N
private static RequestProcessor RP =
new RequestProcessor(RecentSearches.class.getName(), 2);
private RecentSearches() {
recent = new LinkedList();
readRecentFromPrefs(); //read recent searhces from preferences
}
public static RecentSearches getDefault() {
if (instance==null) {
instance = new RecentSearches();
}
return instance;
}
public synchronized void add(ItemResult result) {
Date now = new GregorianCalendar().getTime();
// don't create duplicates, however poor-man's test only
for (ItemResult ir : recent) {
if (stripHTMLnames(ir.getDisplayName()).equals(
stripHTMLnames(result.getDisplayName()))) {
ir.setDate(now);
return;
}
}
// ugly hack to not include special Maven setup search item
if ("SearchSetup".equals(result.getAction().getClass().getSimpleName())) {
return;
}
if (recent.size()>=MAX_ITEMS) {
recent.removeLast();
}
result.setDate(now);
recent.addFirst(result);
storeRecentToPrefs();
}
public synchronized List getSearches() {
LinkedList fiveDayList = new LinkedList();
for (ItemResult ir : recent) {
if ((new GregorianCalendar().getTime().getTime() - ir.getDate().getTime()) < FIVE_DAYS)
fiveDayList.add(ir);
}
//provide only recent searches newer than five days
return fiveDayList;
}
/**
* Clears the list of recent searches.
*/
public synchronized void clear() {
recent.clear();
RP.post(new Runnable() {
@Override
public void run() {
storeRecentToPrefs();
}
});
}
//preferences
private static final String RECENT_SEARCHES = "recentSearches"; // NOI18N
private Preferences prefs() {
return NbPreferences.forModule(RecentSearches.class);
}
private void storeRecentToPrefs() {
Iterator it = recent.iterator();
for (int i = 0; i < MAX_ITEMS; i++) {
if (it.hasNext()) {
ItemResult td = it.next();
CategoryResult cr = td.getCategory();
Category category = cr == null ? null : cr.getCategory();
String categoryName = category == null ? null
: stripHTMLnames(category.getDisplayName());
prefs().put(RECENT_SEARCHES + i, stripHTMLnames(td.getDisplayName())
+ dateSep + td.getDate().getTime()
+ (categoryName == null ? "" : dateSep + categoryName));
} else {
prefs().put(RECENT_SEARCHES + i, "");
}
}
}
private void readRecentFromPrefs() {
for (int i = 0; i < MAX_ITEMS; i++) {
String item = prefs().get(RECENT_SEARCHES + i, "");
Matcher m = RECENT_PREFS_PATTERN.matcher(item);
if (m.find()) {
try {
final String name = m.group(1);
final long time = Long.parseLong(m.group(2));
final String categ = m.group(3);
ItemResult incomplete = new ItemResult(null,
new FakeAction(name, categ), name, new Date(time));
recent.add(incomplete);
} catch (NumberFormatException nfe) {
Logger l = Logger.getLogger(RecentSearches.class.getName());
l.log(Level.INFO, "Failed to read recent searches", item);
}
}
}
}
/**
* Lazy initied action used for recent searches
* In order to not init all recent searched item
*/
public final class FakeAction implements Runnable {
private String name; //display name to search for
private String category;
private Runnable action; //remembered action
private FakeAction(String name, String category) {
this.name = name;
this.category = category;
}
@Override
public void run() {
if (action == null || action instanceof FakeAction) {
RP.post(new Runnable() {
@Override
public void run() {
findAndRunAction();
}
});
} else {
action.run();
}
}
/**
* Find action by display name and run it.
*/
@NbBundle.Messages({
"LBL_SearchingRecentResult=Searching for a Quick Search Item",
"MSG_RecentResultNotFound=Recent Quick Search Item was not found."})
private void findAndRunAction() {
final AtomicBoolean cancelled = new AtomicBoolean(false);
ProgressHandle handle = ProgressHandleFactory.createHandle(
Bundle.LBL_SearchingRecentResult(), new Cancellable() {
@Override
public boolean cancel() {
cancelled.set(true);
return true;
}
});
handle.start();
ResultsModel model = ResultsModel.getInstance();
Task evaluate = CommandEvaluator.evaluate(
stripHTMLandPackageNames(name), model);
RP.post(evaluate);
int tries = 0;
boolean found = false;
while (tries++ < 30 && !cancelled.get()) {
if (checkActionWasFound(model, true)) {
found = true;
break;
} else if (evaluate.isFinished()) {
found = checkActionWasFound(model, false);
break;
}
}
handle.finish();
if (!found && !cancelled.get()) {
NotifyDescriptor nd = new NotifyDescriptor.Message(
Bundle.MSG_RecentResultNotFound(),
NotifyDescriptor.INFORMATION_MESSAGE);
DialogDisplayer.getDefault().notifyLater(nd);
}
}
/**
* Check if the correct action was found, and invoke it if so.
*
* @param model Model containing current search results.
* @param wait Wait one second before testing the results. It is useful
* if the search is still in progress.
*/
private boolean checkActionWasFound(ResultsModel model, boolean wait) {
if (wait) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
}
int rSize = model.getSize();
for (int j = 0; j < rSize; j++) {
ItemResult res = (ItemResult) model.getElementAt(j);
if (nameMatches(res) && categoryMatches(res)) {
action = res.getAction();
if (!(action instanceof FakeAction)) {
EventQueue.invokeLater(action);
return true;
}
}
}
return false;
}
private boolean nameMatches(ItemResult res) {
return stripHTMLnames(res.getDisplayName()).equals(
stripHTMLnames(name));
}
private boolean categoryMatches(ItemResult res) {
if (category == null) {
return true;
} else if (res.getCategory() == null
|| res.getCategory().getCategory() == null) {
return false;
} else {
return stripHTMLnames(
res.getCategory().getCategory().getDisplayName()).equals(
stripHTMLnames(category));
}
}
private String stripHTMLandPackageNames(String s) {
s = stripHTMLnames(s);
return s.replaceAll("\\(.*\\)", "").trim();
}
}
private String stripHTMLnames(String s) {
return translateHTMLEntities(s.replaceAll("<.*?>", "")).trim(); //NOI18N
}
/**
* Convert HTML entities to plain characters. For efficiency and dependency
* reasons support only entities that are known to appear in the search
* results.
*/
private String translateHTMLEntities(String s) {
return s.replaceAll("\\&", "&"); //NOI18N
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy