001: package org.columba.mail.search;
002:
003: import java.net.URI;
004: import java.text.DateFormat;
005: import java.text.MessageFormat;
006: import java.text.SimpleDateFormat;
007: import java.util.Collections;
008: import java.util.Comparator;
009: import java.util.Date;
010: import java.util.Hashtable;
011: import java.util.Iterator;
012: import java.util.List;
013: import java.util.Locale;
014: import java.util.Map;
015: import java.util.ResourceBundle;
016: import java.util.TimeZone;
017: import java.util.Vector;
018: import java.util.logging.Logger;
019:
020: import javax.swing.ImageIcon;
021:
022: import org.columba.api.gui.frame.IFrameMediator;
023: import org.columba.api.plugin.PluginLoadingFailedException;
024: import org.columba.core.command.CommandProcessor;
025: import org.columba.core.filter.FilterCriteria;
026: import org.columba.core.filter.FilterRule;
027: import org.columba.core.gui.frame.FrameManager;
028: import org.columba.core.gui.search.StringCriteriaRenderer;
029: import org.columba.core.gui.search.api.ICriteriaRenderer;
030: import org.columba.core.gui.search.api.IResultPanel;
031: import org.columba.core.resourceloader.ImageLoader;
032: import org.columba.core.search.SearchCriteria;
033: import org.columba.core.search.api.ISearchCriteria;
034: import org.columba.core.search.api.ISearchProvider;
035: import org.columba.core.search.api.ISearchRequest;
036: import org.columba.core.search.api.ISearchResult;
037: import org.columba.mail.command.MailFolderCommandReference;
038: import org.columba.mail.filter.MailFilterFactory;
039: import org.columba.mail.folder.IMailFolder;
040: import org.columba.mail.folder.virtual.VirtualFolder;
041: import org.columba.mail.folder.virtual.VirtualHeader;
042: import org.columba.mail.gui.frame.TreeViewOwner;
043: import org.columba.mail.gui.search.ComplexResultPanel;
044: import org.columba.mail.gui.search.CriteriaResultPanel;
045: import org.columba.mail.gui.table.command.ViewHeaderListCommand;
046: import org.columba.mail.gui.tree.FolderTreeModel;
047: import org.columba.mail.message.IHeaderList;
048: import org.columba.mail.resourceloader.IconKeys;
049: import org.columba.mail.resourceloader.MailImageLoader;
050: import org.columba.ristretto.message.Address;
051: import org.columba.ristretto.message.Flags;
052:
053: /**
054: * Search provider uses virtual folder to search over all existing mail folders.
055: *
056: * @author frd
057: */
058: public class MailSearchProvider implements ISearchProvider {
059: private static final String CRITERIA_BODY_CONTAINS = "body_contains";
060:
061: public static final String CRITERIA_FROM_CONTAINS = "from_contains";
062:
063: private static final String CRITERIA_SIZE_GREATER_THAN = "size_greater_than";
064:
065: private static final String CRITERIA_SIZE_SMALLER_THAN = "size_smaller_than";
066:
067: private static final String CRITERIA_SUBJECT_CONTAINS = "subject_contains";
068:
069: private static final Logger LOG = Logger
070: .getLogger("org.columba.mail.search.MailSearchProvider");
071:
072: private Vector<SearchIndex> indizes = new Vector<SearchIndex>();
073:
074: private Map<String, VirtualFolder> searchFolders = new Hashtable<String, VirtualFolder>();
075: private VirtualFolder lastSearchFolder;
076:
077: private int totalResultCount = 0;
078:
079: private ResourceBundle bundle;
080:
081: /**
082: * check if a single criteria search was done. False, if a multiple criteria search was done.
083: */
084: private boolean singleCriteriaSearch = false;
085:
086: public MailSearchProvider() {
087: bundle = ResourceBundle
088: .getBundle("org.columba.mail.i18n.search");
089:
090: }
091:
092: public String getTechnicalName() {
093: return "MailSearchProvider";
094: }
095:
096: public String getName() {
097: return bundle.getString("provider_title");
098: }
099:
100: public String getDescription() {
101: return bundle.getString("provider_description");
102: }
103:
104: public ImageIcon getIcon() {
105: return MailImageLoader.getSmallIcon(IconKeys.MESSAGE_READ);
106: }
107:
108: public List<ISearchCriteria> getAllCriteria(String searchTerm) {
109: List<ISearchCriteria> list = new Vector<ISearchCriteria>();
110:
111: // check if string is a number
112: boolean numberFormat = false;
113: try {
114: int searchTermInt = Integer.parseInt(searchTerm);
115: numberFormat = true;
116: } catch (NumberFormatException e) {
117: }
118:
119: if (searchTerm.equals(""))
120: numberFormat = true;
121:
122: list.add(getCriteria(
123: MailSearchProvider.CRITERIA_SUBJECT_CONTAINS,
124: searchTerm));
125: list.add(getCriteria(MailSearchProvider.CRITERIA_FROM_CONTAINS,
126: searchTerm));
127: list.add(getCriteria(MailSearchProvider.CRITERIA_BODY_CONTAINS,
128: searchTerm));
129:
130: if (numberFormat)
131: list.add(getCriteria(
132: MailSearchProvider.CRITERIA_SIZE_GREATER_THAN,
133: searchTerm));
134: if (numberFormat)
135: list.add(getCriteria(
136: MailSearchProvider.CRITERIA_SIZE_SMALLER_THAN,
137: searchTerm));
138:
139: return list;
140: }
141:
142: public IResultPanel getResultPanel(
143: String searchCriteriaTechnicalName) {
144: return new CriteriaResultPanel(getTechnicalName(),
145: searchCriteriaTechnicalName);
146: }
147:
148: public IResultPanel getComplexResultPanel() {
149: return new ComplexResultPanel(getTechnicalName());
150: }
151:
152: public ICriteriaRenderer getCriteriaRenderer(
153: String searchCriteriaTechnicalName) {
154: if (searchCriteriaTechnicalName
155: .equals(MailSearchProvider.CRITERIA_BODY_CONTAINS))
156: return new StringCriteriaRenderer(getCriteria(
157: MailSearchProvider.CRITERIA_BODY_CONTAINS, ""),
158: this );
159: else if (searchCriteriaTechnicalName
160: .equals(MailSearchProvider.CRITERIA_SUBJECT_CONTAINS))
161: return new StringCriteriaRenderer(getCriteria(
162: MailSearchProvider.CRITERIA_SUBJECT_CONTAINS, ""),
163: this );
164: else if (searchCriteriaTechnicalName
165: .equals(MailSearchProvider.CRITERIA_FROM_CONTAINS))
166: return new StringCriteriaRenderer(getCriteria(
167: MailSearchProvider.CRITERIA_FROM_CONTAINS, ""),
168: this );
169: else if (searchCriteriaTechnicalName
170: .equals(MailSearchProvider.CRITERIA_SIZE_GREATER_THAN))
171: return new StringCriteriaRenderer(getCriteria(
172: MailSearchProvider.CRITERIA_SIZE_GREATER_THAN, ""),
173: this );
174: else if (searchCriteriaTechnicalName
175: .equals(MailSearchProvider.CRITERIA_SIZE_SMALLER_THAN))
176: return new StringCriteriaRenderer(getCriteria(
177: MailSearchProvider.CRITERIA_SIZE_SMALLER_THAN, ""),
178: this );
179:
180: else
181: throw new IllegalArgumentException(
182: "no renderer available for <"
183: + searchCriteriaTechnicalName + ">");
184: }
185:
186: public ISearchCriteria getCriteria(String technicalName,
187: String searchTerm) {
188:
189: String title = MessageFormat.format(bundle
190: .getString(technicalName + "_title"),
191: new Object[] { searchTerm });
192: String name = bundle.getString(technicalName + "_name");
193: String description = MessageFormat.format(bundle
194: .getString(technicalName + "_description"),
195: new Object[] { searchTerm });
196:
197: return new SearchCriteria(technicalName, name, title,
198: description);
199: }
200:
201: public List<ISearchResult> query(String searchTerm,
202: String searchCriteriaTechnicalName, boolean searchInside,
203: int startIndex, int resultCount) {
204:
205: LOG.info("searchTerm=" + searchTerm);
206: LOG.info("criteriaName=" + searchCriteriaTechnicalName);
207: LOG.info("searchInside=" + searchInside);
208:
209: List<ISearchResult> result = new Vector<ISearchResult>();
210:
211: indizes = new Vector<SearchIndex>();
212:
213: // create search criteria
214:
215: FilterCriteria criteria = createFilterCriteria(searchTerm,
216: searchCriteriaTechnicalName);
217:
218: // remember request id for "search in results"
219: String searchRequestId = searchCriteriaTechnicalName;
220:
221: // remove memorized search folders
222: if (!searchInside) {
223: lastSearchFolder = null;
224: //searchFolders.remove(searchRequestId);
225: }
226:
227: // return empty result, in case the criteria doesn't match the search
228: // term
229: if (criteria == null)
230: return result;
231: try {
232:
233: // Iterator<String> it3 = searchFolders.keySet().iterator();
234: // while (it3.hasNext()) {
235: // String key = it3.next();
236: // VirtualFolder f = searchFolders.get(key);
237: // LOG.info("current cache id=" + key + ":" + f.getId());
238: // }
239:
240: VirtualFolder folder = null;
241:
242: // create virtual folder for criteria
243: if (searchInside) {
244: if (lastSearchFolder != null) {
245: LOG.info("reuse existing virtual folder");
246:
247: // get first one
248: VirtualFolder vFolder = lastSearchFolder;
249: // create new search folder, but re-use old search folder
250: folder = SearchFolderFactory.prepareSearchFolder(
251: criteria, vFolder);
252: } else {
253: totalResultCount = 0;
254: return result;
255: }
256: } else {
257: LOG.info("create new virtual folder");
258: IMailFolder rootFolder = (IMailFolder) FolderTreeModel
259: .getInstance().getRoot();
260: folder = SearchFolderFactory.createSearchFolder(
261: criteria, rootFolder);
262: }
263:
264: // do the search
265: IHeaderList headerList = folder.getHeaderList();
266:
267: Object[] uids = headerList.getUids();
268: LOG.info("result count=" + uids.length);
269:
270: for (int i = 0; i < uids.length; i++) {
271: SearchIndex idx = new SearchIndex(folder, uids[i]);
272:
273: // System.out.println("--> idx.folder="+idx.folder.getId());
274: // System.out.println("--> idx.message="+idx.messageId);
275:
276: indizes.add(idx);
277: }
278:
279: // retrieve the actual search result data
280: List<ISearchResult> l = retrieveResultData(indizes,
281: startIndex, resultCount);
282: result.addAll(l);
283:
284: // sort all the results
285: Collections.sort(result, new MyComparator());
286:
287: // remember search folder for "show total results" action
288: searchFolders.put(searchRequestId, folder);
289: lastSearchFolder = folder;
290:
291: LOG.info("cache search folder=" + searchRequestId);
292:
293: } catch (Exception e) {
294: throw new RuntimeException(e);
295: }
296:
297: // memorize total result count
298: totalResultCount = indizes.size();
299:
300: singleCriteriaSearch = true;
301:
302: return result;
303: }
304:
305: private FilterCriteria createFilterCriteria(String searchTerm,
306: String searchCriteriaTechnicalName) {
307: FilterCriteria criteria = null;
308: if (searchCriteriaTechnicalName
309: .equals(MailSearchProvider.CRITERIA_BODY_CONTAINS)) {
310: criteria = MailFilterFactory.createBodyContains(searchTerm);
311: } else if (searchCriteriaTechnicalName
312: .equals(MailSearchProvider.CRITERIA_SUBJECT_CONTAINS)) {
313: criteria = MailFilterFactory
314: .createSubjectContains(searchTerm);
315: } else if (searchCriteriaTechnicalName
316: .equals(MailSearchProvider.CRITERIA_FROM_CONTAINS)) {
317: criteria = MailFilterFactory.createFromContains(searchTerm);
318: } else if (searchCriteriaTechnicalName
319: .equals(MailSearchProvider.CRITERIA_SIZE_GREATER_THAN)) {
320: criteria = MailFilterFactory.createSizeIsBigger(Integer
321: .parseInt(searchTerm));
322: } else if (searchCriteriaTechnicalName
323: .equals(MailSearchProvider.CRITERIA_SIZE_SMALLER_THAN)) {
324: criteria = MailFilterFactory.createSizeIsSmaller(Integer
325: .parseInt(searchTerm));
326: } else
327: throw new IllegalArgumentException("no criteria <"
328: + searchCriteriaTechnicalName + "> found");
329: return criteria;
330: }
331:
332: public List<ISearchResult> query(List<ISearchRequest> list,
333: boolean matchAll, boolean searchInside, int startIndex,
334: int resultCount) {
335: List<ISearchResult> result = new Vector<ISearchResult>();
336:
337: indizes = new Vector<SearchIndex>();
338:
339: // remove all memorized search folders
340: if (!searchInside) {
341: lastSearchFolder = null;
342: }
343:
344: // create search criteria
345: FilterRule rule = new FilterRule();
346: if (matchAll)
347: rule.setCondition(FilterRule.MATCH_ALL);
348: else
349: rule.setCondition(FilterRule.MATCH_ANY);
350:
351: Iterator<ISearchRequest> it = list.iterator();
352: StringBuffer buf = new StringBuffer();
353: while (it.hasNext()) {
354: ISearchRequest r = it.next();
355: String searchCriteriaTechnicalName = r.getCriteria();
356: buf.append(searchCriteriaTechnicalName);
357:
358: String searchTerm = r.getSearchTerm();
359:
360: FilterCriteria criteria = createFilterCriteria(searchTerm,
361: searchCriteriaTechnicalName);
362: rule.add(criteria);
363: }
364:
365: // remember request id for "search in results"
366: String searchRequestId = buf.toString();
367:
368: try {
369:
370: VirtualFolder folder = null;
371: // create virtual folder for each criteria
372: IMailFolder rootFolder = (IMailFolder) FolderTreeModel
373: .getInstance().getRoot();
374:
375: if (searchInside && (lastSearchFolder != null)) {
376: folder = lastSearchFolder;
377:
378: SearchFolderFactory.prepareSearchFolder(rule, folder);
379: } else {
380: folder = SearchFolderFactory.createSearchFolder(rule,
381: rootFolder);
382: }
383:
384: // do the search
385: IHeaderList headerList = folder.getHeaderList();
386:
387: Object[] uids = headerList.getUids();
388:
389: for (int i = 0; i < uids.length; i++) {
390: SearchIndex idx = new SearchIndex(folder, uids[i]);
391: // System.out.println("--> idx.folder="+idx.folder.getId());
392: // System.out.println("--> idx.message="+idx.messageId);
393:
394: indizes.add(idx);
395: }
396:
397: // retrieve the actual search result data
398: List<ISearchResult> l = retrieveResultData(indizes,
399: startIndex, resultCount);
400: result.addAll(l);
401:
402: // sort all the results
403: Collections.sort(result, new MyComparator());
404:
405: // remember search folder for "show total results" action
406: searchFolders.put(searchRequestId, folder);
407: lastSearchFolder = folder;
408:
409: } catch (Exception e) {
410: throw new RuntimeException(e);
411: }
412:
413: // memorize total result count
414: totalResultCount = indizes.size();
415:
416: singleCriteriaSearch = false;
417:
418: return result;
419: }
420:
421: private List<ISearchResult> retrieveResultData(
422: Vector<SearchIndex> indizes, int startIndex, int resultCount)
423: throws Exception {
424: // ensure we are in existing result range
425: int count = (startIndex + resultCount <= indizes.size()) ? resultCount
426: : indizes.size();
427: List<ISearchResult> result = new Vector<ISearchResult>();
428: // gather result results
429: for (int i = startIndex; i < count; i++) {
430: SearchIndex idx = indizes.get(i);
431: VirtualFolder folder = idx.folder;
432: Object messageId = idx.messageId;
433:
434: // TODO @author fdietz: ensure that we don't fetch individual
435: // headers
436: // to reduce client/server roundtrips
437:
438: String title = (String) folder.getAttribute(messageId,
439: "columba.subject");
440: Address from = (Address) folder.getAttribute(messageId,
441: "columba.from");
442: Date date = (Date) folder.getAttribute(messageId,
443: "columba.date");
444: String description = from.toString() + " " + date;
445:
446: VirtualHeader h = (VirtualHeader) folder.getHeaderList()
447: .get(messageId);
448: URI uri = SearchResultBuilder.createURI(h.getSrcFolder()
449: .getId(), h.getSrcUid());
450: System.out.println("uri=" + uri.toString());
451: ImageIcon statusIcon = null;
452: Flags flags = folder.getFlags(messageId);
453: if (flags.getDeleted()) {
454: statusIcon = ImageLoader.getSmallIcon("user-trash.png");
455:
456: } else if (flags.getAnswered()) {
457: statusIcon = MailImageLoader
458: .getSmallIcon("message-mail-replied.png");
459: } else if (flags.getDraft()) {
460: statusIcon = MailImageLoader.getSmallIcon("edit.png");
461: } else if (!flags.getSeen()) {
462: statusIcon = MailImageLoader
463: .getSmallIcon("message-mail-unread.png");
464: } else if (flags.getSeen()) {
465: statusIcon = MailImageLoader
466: .getSmallIcon("message-mail-read.png");
467: }
468:
469: String dateString = new DateHelper().format(date);
470:
471: result.add(new MailSearchResult(title, description, uri,
472: dateString, date, from, statusIcon, flags
473: .getFlagged()));
474: }
475: return result;
476: }
477:
478: class DateHelper {
479: SimpleDateFormat dfWeek = new SimpleDateFormat("EEE HH:mm",
480: Locale.getDefault());
481:
482: // use local date settings
483: DateFormat dfCommon = DateFormat.getDateInstance();
484:
485: static final long OneDay = 24 * 60 * 60 * 1000;
486:
487: TimeZone localTimeZone = TimeZone.getDefault();
488:
489: private int getLocalDaysDiff(long t) {
490: return (int) (((System.currentTimeMillis() + localTimeZone
491: .getRawOffset()) - (((t + localTimeZone
492: .getRawOffset()) / OneDay) * OneDay)) / OneDay);
493: }
494:
495: public String format(Date date) {
496: String dateString = null;
497:
498: int diff = getLocalDaysDiff(date.getTime());
499:
500: // if ( today
501: if ((diff >= 0) && (diff < 7)) {
502: dateString = dfWeek.format(date);
503: } else {
504: dateString = dfCommon.format(date);
505: }
506: return dateString;
507: }
508:
509: }
510:
511: class SearchIndex {
512: VirtualFolder folder;
513:
514: Object messageId;
515:
516: SearchIndex(VirtualFolder folder, Object messageId) {
517: this .folder = folder;
518: this .messageId = messageId;
519: }
520: }
521:
522: public int getTotalResultCount() {
523: return totalResultCount;
524: }
525:
526: public void showAllResults(IFrameMediator mediator,
527: String searchTerm, String searchCriteriaTechnicalName) {
528:
529: VirtualFolder vFolder = lastSearchFolder;
530: // if complex use the last search folder
531: if (searchCriteriaTechnicalName == null) {
532: vFolder = searchFolders.values().iterator().next();
533: } else
534: vFolder = searchFolders.get(searchCriteriaTechnicalName);
535:
536: if (vFolder == null)
537: throw new IllegalArgumentException(
538: "vFolder for search critera <"
539: + searchCriteriaTechnicalName
540: + "> not found");
541:
542: // ensure that we are currently in the mail component
543: IFrameMediator newMediator = null;
544: try {
545: newMediator = FrameManager.getInstance().switchView(
546: mediator.getContainer(), "ThreePaneMail");
547: } catch (PluginLoadingFailedException e) {
548: e.printStackTrace();
549: }
550:
551: // select invisible virtual folder
552: ((TreeViewOwner) newMediator).getTreeController().setSelected(
553: vFolder);
554:
555: // update message list
556: CommandProcessor.getInstance().addOp(
557: new ViewHeaderListCommand(newMediator,
558: new MailFolderCommandReference(vFolder)));
559: }
560:
561: /**
562: * Comparator for message result. Sortes results by Date. More recent
563: * results are shown first.
564: *
565: * @author frd
566: */
567: class MyComparator implements Comparator {
568: MyComparator() {
569: }
570:
571: public int compare(Object o1, Object o2) {
572: MailSearchResult result = (MailSearchResult) o1;
573: MailSearchResult result2 = (MailSearchResult) o2;
574:
575: Date date = result.getDate();
576: Date date2 = result2.getDate();
577:
578: if (date == null && date2 == null)
579: return 0;
580: if (date != null && date2 == null)
581: return -1;
582: if (date == null && date2 != null)
583: return 1;
584:
585: return -date.compareTo(date2);
586: }
587:
588: }
589:
590: public ISearchCriteria getDefaultCriteria(String searchTerm) {
591: return getCriteria(
592: MailSearchProvider.CRITERIA_SUBJECT_CONTAINS,
593: searchTerm);
594: }
595:
596: public boolean hasSingleCriteriaSearchResult() {
597: return singleCriteriaSearch && lastSearchFolder != null;
598: }
599:
600: }
|