/* * Zettelkasten - nach Luhmann * Copyright (C) 2001-2015 by Daniel Lüdecke (http://www.danielluedecke.de) * * Homepage: http://zettelkasten.danielluedecke.de * * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, see <http://www.gnu.org/licenses/>. * * * Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU * General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben * und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (wenn Sie möchten) * jeder späteren Version. * * Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein * wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder * der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der * GNU General Public License. * * Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm * erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>. */ package de.danielluedecke.zettelkasten.tasks; import de.danielluedecke.zettelkasten.database.Daten; import de.danielluedecke.zettelkasten.database.SearchRequests; import de.danielluedecke.zettelkasten.database.Synonyms; import de.danielluedecke.zettelkasten.util.Constants; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.commons.lang3.StringUtils; import org.jdom2.Element; /** * * @author danielludecke */ public class StartSearchTask extends org.jdesktop.application.Task<Object, Void> { /** * Daten object, which contains the XML data of the Zettelkasten */ private final Daten dataObj; /** * A reference to the SearchRequests-class which stores the searchterms and other * search settings like case-sensitive search, where to search in and so on... */ private final SearchRequests searchrequest; /** * */ private final Synonyms synonymsObj; /** * get the strings for file descriptions from the resource map */ private final org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class). getContext().getResourceMap(StartSearchTask.class); /** * The search terms, passed as an array. */ private String[] searchTerms = null; /** * The entries which should be searched. */ private int[] searchEntries = null; /** * This indicates where we want to look or where the search should be apllied to. We can search * for the searchterms within keywords, entry-conten or title, authors etc. * * See {@link Constants} for more details. */ private final int where; /** * Indicates the logical-combination of search-terms. See constants below for more details. */ private final int logical; /** * Indicates whether a search should look for whole words only, or if a match is also given * when the search terms is only part of a found word. */ private final boolean wholeword; /** * Indicates whether the search is case sensitive (true) or not (false). */ private final boolean matchcase; /** * the class CStartSearch is not only used for search requests, but also for finding * entries that should be added to the desktop or as luhmann-numbers. If we don't * need the searchresults to be added to the {@link SearchRequests} class, we have * to set this parameter to "true", false if we want to have a "real" search. */ private final boolean desktoponly; /** * whether the search should include synonyms or not. */ private boolean synonyms = false; /** * Reg ex search or not. */ private boolean regex = false; /** * Search for note created/edited within a specific time period. */ private boolean timesearch = false; /** * Whether tags should be removed from entry content before searching the * entry. Increases speed, however, some words may not be found (which * have tags inside a word to emphasize a word part, like <i>Zettel</i>kasten. */ private boolean removeTags = true; private String datefrom = ""; private String dateto = ""; private int timestampindex = -1; private int foundCounter = 0; private String[] dummysearchterms = null; /** * The entry-numbers of the search-result, i.e. the found entries of a search-request * as integer-array. */ private int[] results; /** * A short description of the search, which will be set in the CSearchResult-frame's combobox, so the * user can identify what kind of search is related to the search results */ private String searchLabel; /** * A long description of the search, which will be set in the CSearchResult-frame's combobox, so the * user can identify what kind of search is related to the search results */ private String longdesc; /** * Initially, we assume a usual search */ private int typeOfSearch = Constants.SEARCH_USUAL; private final javax.swing.JDialog parentDialog; private final javax.swing.JLabel msgLabel; /** * * @param parent the parent-window * @param d a reference to the Daten class * @param sr a reference to the CSearchRequesrs class * @param tos Type of Search (in entries, entries w/o keywords etc.) * @param s a string-array containing search terms * @param se an integer array containing the entry-numbers of those entries where * the search should be applied to * @param w where the search should be applied to, i.e. search within content, keywords, authors etc. * @param l the logical-combination of the search * @param ww pass true, if the search should find whole words only * @param mc whether the search is case sensitive (true) or not (false) * @param syn whether the search should include synonyms or not * @param rex whether the search terms contain regular expressions or not * @param ts whether the user requested a time-search, i.e. a search for entries that were created * or changed within a certain period * @param fr the start of the period, when a timesearch is requested. format: "yymmdd". * @param to the end of the period, when a timesearch is requested. format: "yymmdd". * @param tsi the timestampindex, which indicate whether the user wants to search only for entries * within a period of <i>creation</i> date (0), of <i>edited</i> date (1) or both (2). * @param donly whether the search should open the CSearchResults-frame (false), or whether the search-results * are used for other purposes, like e.g. putting the results to the desktop (true) * @param rt Whether tags should be removed from entry content before searching the * entry. Increases speed, however, some words may not be found (which * have tags inside a word to emphasize a word part, like <i>Zettel</i>kasten. */ StartSearchTask(org.jdesktop.application.Application app, javax.swing.JDialog parent, javax.swing.JLabel label, Daten d, SearchRequests sr, Synonyms sy, int tos, String[] s, int[] se, int w, int l, boolean ww, boolean mc, boolean syn, boolean rex, boolean ts, String fr, String to, int tsi, boolean donly, boolean rt) { // Runs on the EDT. Copy GUI state that // doInBackground() depends on from parameters // to ImportFileTask fields, here. super(app); dataObj = d; searchrequest = sr; searchTerms = s; searchEntries = se; where = w; logical = l; wholeword = ww; matchcase = mc; desktoponly = donly; synonyms = syn; timesearch = ts; timestampindex = tsi; datefrom = fr; dateto = to; synonymsObj = sy; regex = rex; removeTags = rt; typeOfSearch = tos; parentDialog = parent; msgLabel = label; // show status text msgLabel.setText(resourceMap.getString("msg1")); } @Override protected Object doInBackground() { // Your Task's code here. This method runs // on a background thread, so don't reference // the Swing GUI from here. // prevent task from processing when the file path is incorrect // // when the searchEntries-array is not null, we usually have a filtered search request boolean filteredSearch = (searchEntries != null); // when we pass "null" as parameter for the entry-numbers of those entries // where the search should be applied to, we assume that we want to search // through the whole data. thus, we fill the arrays with all entrynumbers now... if (null == searchEntries) { searchEntries = new int[dataObj.getCount(Daten.ZKNCOUNT)]; // go through all entries for (int cnt = 1; cnt <= dataObj.getCount(Daten.ZKNCOUNT); cnt++) { searchEntries[cnt - 1] = cnt; } } // when we have a search for entries within a certain period, // filter these entries if (timesearch) { // create new list for integer values List<Integer> intlist = new ArrayList<>(); // go through all entries for (int cnt = 0; cnt < searchEntries.length; cnt++) { // get entrynumber int entrynumber = searchEntries[cnt]; // get entries timestamp String[] timestamp = dataObj.getTimestamp(entrynumber); // if we have a timestamp, go on. if (timestamp != null) { // create string that hold the created and edited date String created = ""; String edited = ""; // try to get the substrings. we use a try/catch block here, because // if one of the dates does not exist, the programm won't throw an error // but just leave the strings empty... try { created = timestamp[0].substring(0, 6); edited = timestamp[1].substring(0, 6); } catch (IndexOutOfBoundsException e) { // nothing here } // check which part of the timestamp is relevant, i.e. whether the entry's // period is onyl relevant for the creation date, the last changed date or both switch (timestampindex) { case Constants.TIMESTAMP_CREATED: // if we have no creation timestamp or the period-start is bigger // than or the period-end is below our creaion date, then leave if (created.isEmpty() || datefrom.compareTo(created) > 0 || dateto.compareTo(created) < 0) { break; } // else add entry to searchlist intlist.add(entrynumber); break; case Constants.TIMESTAMP_EDITED: // if we have no edited timestamp or the period-start is bigger // than or the period-end is below our edited date, then leave if (edited.isEmpty() || datefrom.compareTo(edited) > 0 || dateto.compareTo(edited) < 0) { break; } // else add entry to searchlist intlist.add(entrynumber); break; case Constants.TIMESTAMP_BOTH: // if we have no edited or creation timestamp or the period-start is bigger // than or the period-end is below our edited or creation date, then leave if (created.isEmpty() && edited.isEmpty()) { break; } if ((datefrom.compareTo(created) > 0 || dateto.compareTo(created) < 0) && (datefrom.compareTo(edited) > 0 || dateto.compareTo(edited) < 0)) { break; } // else add entry to searchlist intlist.add(entrynumber); break; } } } // if we have no entries within this time period, // clear result-array and return null (leave task) if (intlist.size() < 1) { results = null; return null; } // create new array with entries that should be searched searchEntries = new int[intlist.size()]; // copy all elements of the list to our searchEntries-array for (int cnt = 0; cnt < intlist.size(); cnt++) { searchEntries[cnt] = intlist.get(cnt); } } // save search time long nt = System.nanoTime(); switch (typeOfSearch) { // // here starts a usual search request // case Constants.SEARCH_USUAL: // here starts the typical search for search terms // the search for regular expressions can be found below... if (!regex) { // copy the original search-terms-array to a dummy-array. we do this // because we want to remove found items from the array by setting that value // to an empty string, so we avoid multiple found-results. if a searchterm "hello" // has already been found in the keywords, and we have another searchterm "friend" to find // (both together, i.e. logical-and), the search should not count another occurence of "hello" // as second match. dummysearchterms = new String[searchTerms.length]; // copy the values to the array... for (int z = 0; z < searchTerms.length; z++) { // check for case-sensitive search if (matchcase) { dummysearchterms[z] = searchTerms[z]; } // if not case-sensitive, transform to lower case else { dummysearchterms[z] = searchTerms[z].toLowerCase(); } } } else { // if we have a regular expression, we do not split the search term after each comma, // but keep the whole expression as one search term... Thus, we only need the // first index of that array, because usually the array should only contain one field dummysearchterms = new String[searchTerms.length]; dummysearchterms[0] = searchTerms[0]; } // get the amount of entries int len = searchEntries.length; // init a linked list that temporarily stores the found entry-numbers List<Integer> finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // this variable counts the amount of matching search-results // if we have logical-or-search, foundCounter finally has to be // euqal to or greater than 1. Having logical-and-serach means, that foundCounter // has to be equal to the amount of search terms. foundCounter = 0; // here starts the typical search for search terms // the search for regular expressions can be found below... if (!regex) { // // here we want to search in keywords... // if ((where & Constants.SEARCH_KEYWORDS) != 0) { findTerm(searchnr, Constants.SEARCH_KEYWORDS); } // // here we want to search in authors... // if ((where & Constants.SEARCH_AUTHOR) != 0) { findTerm(searchnr, Constants.SEARCH_AUTHOR); } // // here we want to search in the content... // if ((where & Constants.SEARCH_CONTENT) != 0) { findTerm(searchnr, Constants.SEARCH_CONTENT); } // // here we want to search in the remarks... // if ((where & Constants.SEARCH_REMARKS) != 0) { findTerm(searchnr, Constants.SEARCH_REMARKS); } // // here we want to search in the titles... // if ((where & Constants.SEARCH_TITLE) != 0) { findTerm(searchnr, Constants.SEARCH_TITLE); } // // here we want to search the attachments' names // if ((where & Constants.SEARCH_LINKS) != 0) { findTerm(searchnr, Constants.SEARCH_LINKS); } // // now we have searched through all parts and can see whether this entry // is a search-result or not... // // if we have logica-and-search, go on here... if (Constants.LOG_AND == logical) { // we then need to have at least the same amount of matches as // the length of the searchterm-array // if this is true, append entry-number to stringbuffer if (foundCounter >= searchTerms.length) { finalresults.add(searchnr); } } // in case we have logical-or-search... else if (Constants.LOG_OR == logical) { // ...we just need at least one found search term // append entry-number to stringbuffer if (foundCounter > 0) { finalresults.add(searchnr); } } // in case we have logical-not-search... else { // if the user wants to exclude the search terms, we have a valid search // result when no search term was found in that entry... // append entry-number to stringbuffer if (0 == foundCounter) { finalresults.add(searchnr); } } } // here we have the procedure when we search for regular expressions... else { // // here we want to search in keywords... // if ((where & Constants.SEARCH_KEYWORDS) != 0) { findRegExTerm(searchnr, Constants.SEARCH_KEYWORDS); } // // here we want to search in authors... // if ((where & Constants.SEARCH_AUTHOR) != 0) { findRegExTerm(searchnr, Constants.SEARCH_AUTHOR); } // // here we want to search in the content... // if ((where & Constants.SEARCH_CONTENT) != 0) { findRegExTerm(searchnr, Constants.SEARCH_CONTENT); } // // here we want to search in the remarks... // if ((where & Constants.SEARCH_REMARKS) != 0) { findRegExTerm(searchnr, Constants.SEARCH_REMARKS); } // // here we want to search in the titles... // if ((where & Constants.SEARCH_TITLE) != 0) { findRegExTerm(searchnr, Constants.SEARCH_TITLE); } // // here we want to search the attachments' names // if ((where & Constants.SEARCH_LINKS) != 0) { findRegExTerm(searchnr, Constants.SEARCH_LINKS); } // ...we just need at least one found search term // append entry-number to stringbuffer if (foundCounter > 0) { finalresults.add(searchnr); } } // update progressbar setProgress(counter, 0, len); } // stop time double searchseconds = (double)(System.nanoTime() - nt) / 1000000000.0; // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // init the final results-array with the index-numbers of the found entries results = new int[finalresults.size()]; // copy all string values to the final array for (int cnt = 0; cnt < results.length; cnt++) { results[cnt] = finalresults.get(cnt); } // // Here we start preparing the description of the search-request. it is a // combination of searchterms, where the user looked for and the logical combination // of the search. finally, a datestamp is added so we always have a unique searchescription // even if the user starts twice the same searchrequest. // // prepare search description StringBuilder desc = new StringBuilder(""); StringBuilder ldesc = new StringBuilder(""); // when we have a filtered search, add description for this here // furthermore, add the previous used search terms (from the original search // that has been filtered) to the filter-search terms, so we have also the // possibilty to highlight the terms from the previous search - and the new // filter-search-terms if (filteredSearch) { // get the short-description of the current search String d = searchrequest.getShortDescription(searchrequest.getCurrentSearch()); // find timestamp in that search int pos = d.lastIndexOf("("); // if we found it, set this as first text to the description if (pos != -1) { desc.append(resourceMap.getString("filteredSearchText")).append(" ").append(d.substring(pos)).append(", "); ldesc.append(resourceMap.getString("filteredSearchText")).append(" ").append(d.substring(pos)).append(System.lineSeparator()).append(System.lineSeparator()); } // create array list. here we will store the original search terms from // the search that was filtered, and the new search term from the filter-request List<String> newsearchterms = new ArrayList<>(); // get original search terms String[] orisearchterms = searchrequest.getSearchTerms(searchrequest.getCurrentSearch()); // for (String s : orisearchterms) newsearchterms.add(s); newsearchterms.addAll(Arrays.asList(orisearchterms)); // now add the new filter-search terms to that list as well // for (String s : searchTerms) newsearchterms.add(s); newsearchterms.addAll(Arrays.asList(searchTerms)); // and copy the "final" list back to the searchTerms-array searchTerms = newsearchterms.toArray(new String[newsearchterms.size()]); } // when we have more than 3 search terms, truncate to three... if (searchTerms.length > 3) { // only copy first three searchterms for (int cnt = 0; cnt < 3; cnt++) { // retrieve each searchterm, so we can check for the length if it String shortendterm = searchTerms[cnt]; // if search term is longer than 50 chars, truncate it... if (shortendterm.length() > 50) { shortendterm = shortendterm.substring(0, 50) + "..."; } // and append search term to description desc.append("\"").append(shortendterm).append("\", "); } // cut off last space and comma desc.setLength(desc.length() - 2); // add text, that there are even more searchterms, that have been let out here desc.append(" ").append(resourceMap.getString("andMoreSearchterms")).append(", "); } // else append all searchterms to the description else { // iterate searchterm-array for (String st : searchTerms) { // if search term is longer than 50 chars, truncate it... if (st.length() > 50) { st = st.substring(0, 50) + "..."; } // and append it to description desc.append("\"").append(st).append("\", "); } } // here we prepare the long description ldesc.append(resourceMap.getString("longdescLabelSearchTerms")); for (String st : searchTerms) { // if search term is longer than 90 chars, truncate it... if (st.length() > 90) { st = st.substring(0, 90) + "..."; } ldesc.append(System.lineSeparator()).append("- ").append(st); } // for search log StringBuilder anonymsearchtext = new StringBuilder(String.valueOf(searchTerms.length) + " search terms; "); // search-options will be displayed in new line ldesc.append(System.lineSeparator()).append("("); // add the logical combination to the search description switch (logical) { // add short and long desctiption for the logical-and-search case Constants.LOG_AND: desc.append(resourceMap.getString("logAndText")); ldesc.append(resourceMap.getString("logAndText")); anonymsearchtext.append(resourceMap.getString("logAndText")); break; // add short and long desctiption for the logical-or-search case Constants.LOG_OR: desc.append(resourceMap.getString("logOrText")); ldesc.append(resourceMap.getString("logOrText")); anonymsearchtext.append(resourceMap.getString("logOrText")); break; // add short and long desctiption for the logical-not-search case Constants.LOG_NOT: desc.append(resourceMap.getString("logNotText")); ldesc.append(resourceMap.getString("logNotText")); anonymsearchtext.append(resourceMap.getString("logNotText")); break; } desc.append(", "); anonymsearchtext.append("; "); // add new line for further search options ldesc.append(")").append(System.lineSeparator()).append(System.lineSeparator()); // append where the user searched for the searchterms desc.append(resourceMap.getString("searchWhere")).append(" "); anonymsearchtext.append("searched in "); ldesc.append(resourceMap.getString("searchWhere")).append(":").append(System.lineSeparator()); int wherecnt = 0; // when we searched keywords, add to description that keywords where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_KEYWORDS) != 0) { desc.append(resourceMap.getString("searchInKeywords")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInKeywords")).append(", "); ldesc.append("- ").append(resourceMap.getString("searchInKeywords")).append(System.lineSeparator()); wherecnt++; } // when we searched authors, add to description that authors where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_AUTHOR) != 0) { desc.append(resourceMap.getString("searchInAuthors")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInAuthors")).append(", "); ldesc.append("- ").append(resourceMap.getString("searchInAuthors")).append(System.lineSeparator()); wherecnt++; } // when we searched content, add to description that content where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_CONTENT) != 0) { desc.append(resourceMap.getString("searchInContent")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInContent")).append(", "); ldesc.append("- ").append(resourceMap.getString("searchInContent")).append(System.lineSeparator()); wherecnt++; } // when we searched titles, add to description that titles where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_TITLE) != 0) { // long description contains all search-areas ldesc.append("- ").append(resourceMap.getString("searchInTitle")).append(System.lineSeparator()); // short description only 3 parts if (wherecnt < 3) { desc.append(resourceMap.getString("searchInTitle")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInTitle")).append(", "); } wherecnt++; } // when we searched attachments, add to description that attachments where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_LINKS) != 0) { // long description contains all search-areas ldesc.append("- ").append(resourceMap.getString("searchInLinks")).append(System.lineSeparator()); // short description only 3 parts if (wherecnt < 3) { desc.append(resourceMap.getString("searchInLinks")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInLinks")).append(", "); } wherecnt++; } // when we searched attachments' contents, add to description that attachments' contents where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_LINKCONTENT) != 0) { // long description contains all search-areas ldesc.append("- ").append(resourceMap.getString("searchInLinksContent")).append(System.lineSeparator()); // short description only 3 parts if (wherecnt < 3) { desc.append(resourceMap.getString("searchInLinksContent")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInLinksContent")).append(", "); } wherecnt++; } // when we searched remarks, add to description that remarks where one of the // entries fields that have been searched for the find term... if ((where & Constants.SEARCH_REMARKS) != 0) { // long description contains all search-areas ldesc.append("- ").append(resourceMap.getString("searchInRemarks")).append(System.lineSeparator()); if (wherecnt < 3) { desc.append(resourceMap.getString("searchInRemarks")).append(", "); anonymsearchtext.append(resourceMap.getString("searchInRemarks")).append(", "); } wherecnt++; } // cut off last space and comma if (desc.length() > 2) { desc.setLength(desc.length() - 2); } if (anonymsearchtext.length() > 2) { anonymsearchtext.setLength(anonymsearchtext.length() - 2); } // when we have more than 3 search areas, add this to description if (wherecnt > 3) { // add text, that there are even more search-areas, that have been let out here desc.append(" "); desc.append(resourceMap.getString("andMoreSearchwhere")); anonymsearchtext.append(" "); anonymsearchtext.append(resourceMap.getString("andMoreSearchwhere")); } // append a time-string, so we always have a unique search-description, // even if the user searches twice for the same searchterms DateFormat df = new SimpleDateFormat("kkmmss"); desc.append(" (").append(df.format(new Date())).append(")"); // append matchcase/wholeword if (wholeword || matchcase || synonyms) { ldesc.append(System.lineSeparator()).append(resourceMap.getString("longDescSearchOptions")); if (wholeword) { ldesc.append(System.lineSeparator()).append("- ").append(resourceMap.getString("longDescWholeWord")); anonymsearchtext.append("; whole word"); } if (matchcase) { ldesc.append(System.lineSeparator()).append("- ").append(resourceMap.getString("longDescMatchCase")); anonymsearchtext.append("; match case"); } if (synonyms) { ldesc.append(System.lineSeparator()).append("- ").append(resourceMap.getString("longDescSynonyms")); anonymsearchtext.append("; synonym search"); } } // copy description to string searchLabel = desc.toString(); longdesc = ldesc.toString(); // log search time Constants.zknlogger.log(Level.INFO, "Search Request: {0}; Duration: {1} seconds (avg. {2} seconds per note).", new Object[]{anonymsearchtext.toString(), String.format("%.03f", searchseconds), String.format("%.03f", searchseconds / searchEntries.length) }); // add all search request data and search results to our search-request-class // but only, if we don't want to add the data to the desktop instead of having // a searchrequest if (!desktoponly) { searchrequest.addSearch(searchTerms, where, logical, wholeword, matchcase, synonyms, regex, results, searchLabel, longdesc); } } else { results = null; } break; // // here starts a search for entries without authors // case Constants.SEARCH_NO_AUTHORS: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // when no author found, we have a match if (null == dataObj.getAuthors(searchnr)) { finalresults.add(searchnr); } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchNoAuthors"), Constants.SEARCH_AUTHOR); } else { results = null; } break; // // here starts a search for entries without keywords // case Constants.SEARCH_NO_KEYWORDS: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // when no author found, we have a match if (null == dataObj.getKeywords(searchnr)) { finalresults.add(searchnr); } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchNoKeywords"), Constants.SEARCH_KEYWORDS); } else { results = null; } break; // // here starts a search for first-level follower (luhmann) entries // case Constants.SEARCH_TOP_LEVEL_LUHMANN: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // check whether entry is a follower and has a first-level parent if (dataObj.isTopLevelLuhmann(searchnr)) { // check if entry is not already in searchresults // if not, add search result if (!finalresults.contains(searchnr)) { finalresults.add(searchnr); } } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchInLuhmannParents"), Constants.SEARCH_LUHMANN); } else { results = null; } break; // // here starts a search for follower (luhmann) entries, i.e. // entries that do have a parent-trail entry // case Constants.SEARCH_IS_LUHMANN_PARENT: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // find first parent int lp = dataObj.findParentlLuhmann(searchnr, true); // check whether entry is a follower and has a first-level parent if (lp != -1) { // check if entry is not already in searchresults // if not, add search result if (!finalresults.contains(searchnr)) { finalresults.add(searchnr); } } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchInLuhmann"), Constants.SEARCH_LUHMANN); } else { results = null; } break; // // here starts a search for follower (luhmann) entries, i.e. // entries that do have a parent-trail entry // case Constants.SEARCH_IS_ANY_LUHMANN: // retrieve all note sequence IDs finalresults = dataObj.getAllLuhmannNumbers(); // finally, check whether we have any searchresults at all... if (finalresults != null && finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchInLuhmann"), Constants.SEARCH_LUHMANN); } else { results = null; } break; // // here starts a search for follower (luhmann) entries, i.e. // entries that do have a parent-trail entry // case Constants.SEARCH_WITHOUT_MANUAL_LINKS: // init result array finalresults = new ArrayList<>(); // get all manual link IDs List<Integer> alllinksresult = dataObj.getAllManualLinks(); // check if we have any values if (alllinksresult != null && alllinksresult.size() > 0) { // if yes, we need to remove these from all search entries for (int i : searchEntries) { // is current note ID a manual link? if (!alllinksresult.contains(i)) { // if not, add it finalresults.add(i); } } } else { // if we don't have any manual links, all notes are // part of the search result for (int i : searchEntries) { finalresults.add(i); } } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchNoManLinks"), Constants.SEARCH_LUHMANN); } else { results = null; } break; // // here starts a search for entries without remarks // case Constants.SEARCH_NO_REMARKS: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // when no author found, we have a match if (dataObj.getRemarks(searchnr).isEmpty()) { finalresults.add(searchnr); } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchNoRemarks"), Constants.SEARCH_REMARKS); } else { results = null; } break; // // here starts a search for entries *with* remarks // case Constants.SEARCH_WITH_REMARKS: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // when no author found, we have a match if (!dataObj.getRemarks(searchnr).isEmpty()) { finalresults.add(searchnr); } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchWithRemarks"), Constants.SEARCH_REMARKS); } else { results = null; } break; // // here starts a search for entries with attachments // case Constants.SEARCH_WITH_ATTACHMENTS: // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // when no author found, we have a match if (!dataObj.getAttachments(searchnr).isEmpty()) { finalresults.add(searchnr); } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, resourceMap.getString("searchWithAttachments"), Constants.SEARCH_LINKS); } else { results = null; } break; // // here starts a search for entries with/out ratings // case Constants.SEARCH_WITH_RATINGS: case Constants.SEARCH_WITHOUT_RATINGS: // get description String descr = ""; // set description for a search for entries that have been rated if (Constants.SEARCH_WITH_RATINGS == typeOfSearch) { descr = resourceMap.getString("searchRatings"); } // set description for a search for entries that have *not* been rated if (Constants.SEARCH_WITHOUT_RATINGS == typeOfSearch) { descr = resourceMap.getString("searchNoRatings"); } // get the amount of entries len = searchEntries.length; // init a stringbuffer that temporarily stores the found entry-numbers finalresults = new ArrayList<>(); // iterate all entries where the search should be applied to... // this may differ. a search request from the main-window usually searches through // all entries, while a filter of search results only is applied to certain entries. // therefor, we store all relevant entry-numbers for the search in an integer-array for (int counter = 0; counter < len; counter++) { // get the number of the entry which we want to search through... int searchnr = searchEntries[counter]; // check whether we want to look for entries that have been rated if (Constants.SEARCH_WITH_RATINGS == typeOfSearch) { // if we have a rating-count higher than 0, entry has been rated, so found if (dataObj.getZettelRatingCount(searchnr) > 0) { finalresults.add(searchnr); } } // check whether we want to look for entries that have *not* been rated else if (Constants.SEARCH_WITHOUT_RATINGS == typeOfSearch) { // if we have a rating-count that equals 0, entry has been *not* rated, so found if (dataObj.getZettelRatingCount(searchnr) == 0) { finalresults.add(searchnr); } } // update progressbar setProgress(counter, 0, len); } // finally, check whether we have any searchresults at all... if (finalresults.size() > 0) { // create search results array, create search description and // add search results to the search-data-class. prepareExtraSearchResults(finalresults, descr, Constants.SEARCH_RATINGS); } else { results = null; } break; // // here starts a search for entries with attachments // case Constants.SEARCH_WITH_CREATED_TIME: case Constants.SEARCH_WITH_EDITED_TIME: // since the filtering for timestamp was already made in the beginning of the method, // we already have our final results in the search entries results = searchEntries; // finally, check whether we have any searchresults at all... if (results != null) { // prepare search description StringBuilder desc = new StringBuilder(""); int ts; // create search description desc.append(resourceMap.getString("searchWithTimeMsg")); desc.append(" ").append(searchTerms[0]).append(" "); desc.append(resourceMap.getString("searchWithTimeTo")); desc.append(" ").append(searchTerms[1]).append(" "); if (Constants.SEARCH_WITH_CREATED_TIME == typeOfSearch) { // append text for no authors desc.append(resourceMap.getString("searchWithTimeCreated")); ts = Constants.SEARCH_TIMESTAMP_CREATED; } else { // append text for no authors desc.append(resourceMap.getString("searchWithTimeEdited")); ts = Constants.SEARCH_TIMESTAMP_EDITED; } // append a time-string, so we always have a unique search-description, // even if the user searches twice for the same searchterms desc.append(" ("); DateFormat df = new SimpleDateFormat("kkmmss"); desc.append(df.format(new Date())); desc.append(")"); // copy description to string longdesc = searchLabel = desc.toString(); // add all search request data and search results to our search-request-class // but only, if we don't want to add the data to the desktop instead of having // a searchrequest if (!desktoponly) { searchrequest.addSearch(searchTerms, ts, Constants.LOG_OR, false, false, false, false, results, searchLabel, longdesc); } } break; } return null; // return your result } @Override protected void succeeded(Object result) { // Runs on the EDT. Update the GUI based on // the result computed by doInBackground(). } @Override protected void finished() { super.finished(); // save latest search results. searchrequest.setCurrentSearchResults(results); // and close window parentDialog.dispose(); parentDialog.setVisible(false); } /** * This method searches through all entries and tries to find the regular * expression that is stored in the first field of the search term array * {@link #dummysearchterms dummysearchterms}. * <br><br> * If a regular expression (i.e. the find term) was found, the * {@link #foundCounter foundCounter} is increased, indicating that the * entry with the index-number {@code searchnr} belongs to the search * results. {@code searchnr} is added to the search results in the * {@link #startSearch() startSearch()} task. * * @param searchnr the entry-number of that entry that is currently searched * @param type the type in which fields of an entry the findterm is searched * for. An ORed value of following constants:<br> * - CConstant.SEARCH_KEYWORDS<br> * - CConstant.SEARCH_AUTHOR<br> * - CConstant.SEARCH_TITLE<br> * - CConstant.SEARCH_REMARKS<br> * - CConstant.SEARCH_CONTENT */ private void findRegExTerm(int searchnr, int type) { // first check, whether the searchterm is not empty // if we have a regular expression, we do not split the search term after each comma, // but keep the whole expression as one search term... Thus, we only need the // first index of that array, because usually the array should only contain one field if (!dummysearchterms[0].isEmpty()) { // prepare searchterms. String[] sterms = null; // here we retrieve the searchterms when we look for authors if (Constants.SEARCH_AUTHOR == type) { sterms = dataObj.getAuthors(searchnr); } // here we retrieve the searchterms when we look for keywords if (Constants.SEARCH_KEYWORDS == type) { sterms = dataObj.getKeywords(searchnr); } if (Constants.SEARCH_TITLE == type) { sterms = new String[]{dataObj.getZettelTitle(searchnr)}; } if (Constants.SEARCH_CONTENT == type) { sterms = new String[]{dataObj.getCleanZettelContent(searchnr)}; } if (Constants.SEARCH_REMARKS == type) { sterms = new String[]{dataObj.getRemarks(searchnr)}; } if (Constants.SEARCH_LINKS == type) { // get the content of an entry List<Element> attachments = dataObj.getAttachments(searchnr); List<String> content = new ArrayList<>(); // check whether we have any attachments at all if (attachments != null) { // create iterator Iterator<Element> i = attachments.iterator(); // iterate all attachments while (i.hasNext()) { content.add(i.next().getText()); } } sterms = content.toArray(new String[content.size()]); } // check whether we have any search terms if (sterms != null) { try { // create a pattern from the first search term. if it fails, go on // to the catch-block, else contiue here. Pattern p = Pattern.compile(dummysearchterms[0]); // iterate the array of all found search terms... for (String loop : sterms) { // now we know we have a valid regular expression. we now want to // retrieve all matching groups Matcher m = p.matcher(loop); // check whether we have found anything at all... if (m.find()) { // increase found counter foundCounter++; return; } } } catch (PatternSyntaxException e) { System.out.println(e.getLocalizedMessage()); } } } } /** * This method searches for all searchterms in the entry {@code searchnr} in a given part {@code type}. * <br><br> * If a find term was found, the {@link #foundCounter foundCounter} * is increased, indicating that the entry with the index-number {@code searchnr} belongs to the * search results. {@code searchnr} is added to the search results in the {@link #startSearch() startSearch()} * task. * * @param searchnr the entrynumber of the entry that should be searched * @param type the type, i.e. which part of the entry is relevant for the search. An ORed value * of following constants:<br> * - CConstant.SEARCH_KEYWORDS<br> * - CConstant.SEARCH_AUTHOR<br> * - CConstant.SEARCH_TITLE<br> * - CConstant.SEARCH_REMARKS<br> * - CConstant.SEARCH_CONTENT */ private void findTerm(int searchnr, int type) { // go through all search terms for (String dummysearchterm : dummysearchterms) { // first check, whether the searchterm is not empty if (!dummysearchterm.isEmpty()) { // init variable String[] synline = null; // when we have synonyms included in the search, prepare the searchterm // therefor, retrieve the synonyms for the current search term if (synonyms) { // retrieve the synonymline, where the current searchterm might be an // index-word or any related synonym synline = synonymsObj.getSynonymLineFromAny(dummysearchterm, matchcase); } // if there are synonyms for the current searchterm... if (null == synline) { synline = new String[]{dummysearchterm}; } // go through all searchterms, including synonyms for the search term for (String synline1 : synline) { // if we have whole-word-search, identify search results by // comparing the keyword-index-numbers if (wholeword) { // prepare found-indicator boolean somethingfound = false; // when we have a whole-word-search, we can use an already existing // method to check whether this keyword exists in an entry or not if (Constants.SEARCH_KEYWORDS == type) { somethingfound = dataObj.existsInKeywords(synline1, searchnr, matchcase); } else if (Constants.SEARCH_AUTHOR == type) { somethingfound = dataObj.existsInAuthors(synline1, searchnr); } else { // create emptry string String content = ""; // now, depending on the part of entry we want to look, retrieve the related content if (Constants.SEARCH_TITLE == type) { content = dataObj.getZettelTitle(searchnr); } if (Constants.SEARCH_CONTENT == type) { content = cleanZettelContent(searchnr); } if (Constants.SEARCH_REMARKS == type) { content = dataObj.getRemarks(searchnr); } if (Constants.SEARCH_LINKS == type) { // get the content of an entry List<Element> attachments = dataObj.getAttachments(searchnr); // check whether we have any attachments at all if (attachments != null) { // create iterator Iterator<Element> i = attachments.iterator(); // iterate all attachments while (i.hasNext()) { content = content + " " + i.next().getText(); } } } // split the content at each new word-boundary - i.e. we have a whole-word-search here String[] sterms = content.split("\\b"); // when we are looking for title, content or remarks, the variable "sterms" is not null // if we were looking for keywords or authors, sterms is null, so this part // is ignored... if (sterms != null) { // iterate the array of keyword- or author-strings of that entry for (String loop : sterms) { // // if the search is not case-sensitive, convert to lowercase // if (!matchcase) { // // set found-indicator to true // loop = loop.toLowerCase(); // } // if the search is not case-sensitive, convert to lowercase if (!matchcase && StringUtils.equalsIgnoreCase(loop, synline1)) { // set found-indicator to true somethingfound = true; // and leave loop break; } // when we found something, tell that our found indicator if (StringUtils.equals(loop, synline1)) { // set found-indicator to true somethingfound = true; // and leave loop break; } } } } // when we found something, increase foundcounter, so we know how many matches we have // furthermore, delete the searchterm, so we don't have multiple match-counts for just // a single searchterm if (somethingfound) { // increase found counter foundCounter++; // do we have logical OR or NOT? if yes, we // can leave now if (Constants.LOG_OR == logical || Constants.LOG_NOT == logical) { return; } // leave synonym-loop, we don't need to look for further synomyns break; } } else { // prepare searchterms. String content = null; // here we retrieve the searchterms when we look for authors if (Constants.SEARCH_AUTHOR == type) { content = Arrays.toString(dataObj.getAuthors(searchnr)); } // here we retrieve the searchterms when we look for keywords if (Constants.SEARCH_KEYWORDS == type) { content = Arrays.toString(dataObj.getKeywords(searchnr)); } // now, depending on the part of entry we want to look, retrieve the related content if (Constants.SEARCH_TITLE == type) { content = dataObj.getZettelTitle(searchnr); } if (Constants.SEARCH_CONTENT == type) { content = cleanZettelContent(searchnr); } if (Constants.SEARCH_REMARKS == type) { content = dataObj.getRemarks(searchnr); } if (Constants.SEARCH_LINKS == type) { // get the content of an entry List<Element> attachments = dataObj.getAttachments(searchnr); // check whether we have any attachments at all if (attachments != null) { // create iterator Iterator<Element> i = attachments.iterator(); // iterate all attachments while (i.hasNext()) { content = content + " " + i.next().getText(); } } } // when we are looking for keywords or authors, the variable "sterms" is not null // if we were looking for content, title or remarks, sterms is null, so this part // is ignored... if (content != null) { // if the search is not case-sensitive, convert to lowercase // if (!matchcase) { // content = content.toLowerCase(); // } if (!matchcase && StringUtils.containsIgnoreCase(content, synline1)) { // increase found counter foundCounter++; // do we have logical OR or NOT? if yes, we // can leave now if (Constants.LOG_OR == logical || Constants.LOG_NOT == logical) { return; } // leave loop, we don't need to look for further synomyns break; } // if the content contains the searchterm, set foundindicator to true if (StringUtils.contains(content, synline1)) { // increase found counter foundCounter++; // do we have logical OR or NOT? if yes, we // can leave now if (Constants.LOG_OR == logical || Constants.LOG_NOT == logical) { return; } // leave loop, we don't need to look for further synomyns break; } } } } } } } /** * * @param finalresults * @param descwhat * @param searchwhat */ private void prepareExtraSearchResults(List<Integer> finalresults, String descwhat, int searchwhat) { // init the final results-array with the index-numbers of the found entries results = new int[finalresults.size()]; // copy all string values to the final array for (int cnt = 0; cnt < results.length; cnt++) { results[cnt] = finalresults.get(cnt); } // prepare search description StringBuilder desc = new StringBuilder(""); // append text for no authors desc.append(descwhat); // append a time-string, so we always have a unique search-description, // even if the user searches twice for the same searchterms desc.append(" ("); DateFormat df = new SimpleDateFormat("kkmmss"); desc.append(df.format(new Date())); desc.append(")"); // copy description to string longdesc = searchLabel = desc.toString(); // add all search request data and search results to our search-request-class // but only, if we don't want to add the data to the desktop instead of having // a searchrequest if (!desktoponly) { searchrequest.addSearch(new String[]{""}, searchwhat, Constants.LOG_NOT, true, true, false, false, results, searchLabel, longdesc); } } public String cleanZettelContent(int nr) { String content = dataObj.getZettelContent(nr); if (removeTags) { String dummy = ""; if (content != null && !content.isEmpty()) { dummy = content.replaceAll("\\[k\\]", "") .replaceAll("\\[f\\]", "") .replaceAll("\\[u\\]", "") .replaceAll("\\[q\\]", "") .replaceAll("\\[d\\]", "") .replaceAll("\\[c\\]", "") .replaceAll("\\[code\\]", "") .replaceAll("\\[sup\\]", "") .replaceAll("\\[sub\\]", "") .replaceAll("\\[/k\\]", "") .replaceAll("\\[/f\\]", "") .replaceAll("\\[/u\\]", "") .replaceAll("\\[/q\\]", "") .replaceAll("\\[/d\\]", "") .replaceAll("\\[/c\\]", "") .replaceAll("\\[/code\\]", "") .replaceAll("\\[/sup\\]", "") .replaceAll("\\[/sub\\]", "") .replaceAll("\\[color ([^\\[]*)\\](.*?)\\[/color\\]", "$2") .replaceAll("\\[font ([^\\[]*)\\](.*?)\\[/font\\]", "$2") .replaceAll("\\[h ([^\\[]*)\\](.*?)\\[/h\\]", "$2") .replaceAll("\\[m ([^\\[]*)\\](.*?)\\[/m\\]", "$2") .replaceAll("\\[n\\](.*?)\\[/n\\]", "$1") .replaceAll("\\[l\\](.*?)\\[/l\\]", "$1") .replaceAll("\\[\\*\\](.*?)\\[/\\*\\]", "- $1\n") .replaceAll("\\[tc\\](.*?)\\[/tc\\]", "$1") .replaceAll("\\*\\*\\*(.*?)\\*\\*\\*", "$1") .replaceAll("___(.*?)___", "$1") .replaceAll("__(.*?)__", "$1") .replaceAll("\\*\\*(.*?)\\*\\*", "$1") .replaceAll("_(.*?)_", "$1") .replaceAll("\\*(.*?)\\*", "$1") .replaceAll("---(.*?)---", "$1"); } return dummy; } return content; } }