Java tutorial
/* * Zettelkasten - nach Luhmann ** Copyright (C) 2001-2014 by Daniel Ldecke (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 knnen es unter den Bedingungen der GNU * General Public License, wie von der Free Software Foundation verffentlicht, weitergeben * und/oder modifizieren, entweder gem Version 3 der Lizenz oder (wenn Sie mchten) * jeder spteren Version. * * Die Verffentlichung 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 FR 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.database; import de.danielluedecke.zettelkasten.ZettelkastenView; import de.danielluedecke.zettelkasten.util.classes.Comparer; import de.danielluedecke.zettelkasten.util.Constants; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.IllegalAddException; import org.jdom2.IllegalDataException; /** * * @author danielludecke */ public class SearchRequests { /** * <searches> * <searchrequest> * <searchterms> * <term>luhmann</term> * <term>computer</term> * </searchterms> * <where>16</where> * <logical>1</logical> * <wholeword>false</wholeword> * <matchcase>false</matchcase> * <synonyms>true</synonyms> * <regex>true</regex> * <description> * "computer", "luhmann", und-verknpft, Suche in Schlagwrtern, Literaturangaben, * Zetteln (und weitere...) (221037) * </description> * <results>1106,1110,1380,1553,1556,1558,1560,1561,1566,1594,1595</results> * </searchrequest> * </searches> */ private Document searches; /** * */ private boolean modified; /** * */ private final LinkedList<String> searchTermsHistory = new LinkedList<String>(); /** * */ private static final int searchTermsHistoryMax = 20; /** * Reference to the main frame. */ private final ZettelkastenView zknframe; /** * */ private int[] searchresults; public SearchRequests(ZettelkastenView zkn) { zknframe = zkn; clear(); } /** * Clears all search data. */ public final void clear() { // create new XML document searches = new Document(new Element("searches")); // reset last-used search result attribute searches.getRootElement().setAttribute("lastused", "-1"); // set modified state modified = false; } /** * Set search data, e.g. when loading data, set loaded XML document * with this method. * * @param d the XML document with the search results that should be set as new * search results data */ public void setSearchData(Document d) { searches = d; } /** * Retrieve the search results data document (XML-data) * * @return the search results data document (XML-data) */ public Document getSearchData() { return searches; } /** * This method saves the results (i.e. the entry-numbers as integer-array) of the latest search request. * Use this method to indicate whether a search request (see {@link de.danielluedecke.zettelkasten.tasks.StartSearchTask StartSearchTask} * had results or not. Use {@code null} if the search had no results. * * @param sr an integer array with entry-numbers of found entries that matched the search parameters, * or {@code null} if nothing was found. */ public void setCurrentSearchResults(int[] sr) { searchresults = sr; } /** * This method returns the results (i.e. the entry-numbers as integer-array) of the latest search request. * Use this method to check whether a search request (see {@link de.danielluedecke.zettelkasten.tasks.StartSearchTask StartSearchTask} * had results or not. Returns {@code null} if the search had no results. * * @return the found entries, with their numbers ad integer array, or {@code null} if nothing was found. */ public int[] getCurrentSearchResults() { return searchresults; } /** * This method adds a new search request to the search-object. The relevant parameters * are stored in an xml-file, so we can save/load the data easily. * * @param s the search terms, in a string-array * @param w where we want to look (e.g. in keywords, content, authors... * @param l which kind of logical-combination we have * @param ww if true, find wholewords only * @param mc if true, search is case-sensitive * @param syn true, if the search included synonyms, false otherwise * @param rex true, if the search term is a regular expression, false otherwise * @param r the search results, i.e. the entry-numbers as integer-array * @param n a shorted description of the search request, so we know what the user was looking for * @param ld a long description of the search request, so we know what the user was looking for * @return {@code true} if search result was successfully added, {@code false} if an error occured */ public boolean addSearch(String[] s, int w, int l, boolean ww, boolean mc, boolean syn, boolean rex, int[] r, String n, String ld) { // check for valid parameters. do we have search terms? if (null == s || s.length < 1) { return false; } // do we have search results? if (null == r || r.length < 1) { return false; } // create a new element for search requests Element sr = new Element("searchrequest"); try { // and add it to the document searches.getRootElement().addContent(sr); // create the child-elements Element st = new Element("searchterms"); // iterate search terms and add each term as sub-element for (String sterm : s) { Element term = new Element("term"); term.setText(sterm); st.addContent(term); } // now add the search terms to the parent sr.addContent(st); // create element for where to look Element where = new Element("where"); where.setText(String.valueOf(w)); // now add the element to the parent sr.addContent(where); // create element for the logical combination Element log = new Element("logical"); log.setText(String.valueOf(l)); // now add the element to the parent sr.addContent(log); // create element for whole-word Element whole = new Element("wholeword"); whole.setText((ww) ? "true" : "false"); // now add the element to the parent sr.addContent(whole); // create element for match case Element match = new Element("matchcase"); match.setText((mc) ? "true" : "false"); // now add the element to the parent sr.addContent(match); // create element for synonymsearch Element synonyms = new Element("synonyms"); synonyms.setText((syn) ? "true" : "false"); // now add the element to the parent sr.addContent(synonyms); // create element for synonymsearch Element regex = new Element("regex"); regex.setText((rex) ? "true" : "false"); // now add the element to the parent sr.addContent(regex); // create element for the description Element label = new Element(Daten.ELEMEMT_DESCRIPTION); label.setText(n); // now add the element to the parent sr.addContent(label); // create element for the long description Element longlabel = new Element("longdesc"); longlabel.setText(ld); // now add the element to the parent sr.addContent(longlabel); // create element for search results Element results = new Element("results"); // create stringbuilder, since we want to copy the ineger array // to an stringarray StringBuilder sb = new StringBuilder(""); // iterate array and convert all integer to string for (int nr : r) { sb.append(String.valueOf(nr)); sb.append(","); } // delete last comma if (sb.length() > 1) { sb.setLength(sb.length() - 1); } // add string to element results.setText(sb.toString()); // now add the element to the parent sr.addContent(results); // change modified state setModified(true); // return success return true; } catch (IllegalAddException ex) { // log error Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { // log error Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } return false; } /** * This method retrieves the searchterms for a given search-request. * * @param nr The number of the search request * @return the searchterms as string array, or null if an error occured */ public String[] getSearchTerms(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, go on... if (el != null) { // retrieve a list with all search terms List<?> terms = el.getChild("searchterms").getChildren(); // if we have no elements, return null if ((terms.isEmpty()) || (terms.size() < 1)) { return null; } // create linked list for return results LinkedList<String> st = new LinkedList<String>(); // create iterator Iterator<?> i = terms.iterator(); // go through all search terms while (i.hasNext()) { // get each search-term-element Element e = (Element) i.next(); // if it has text, add it to linked list if (!e.getText().isEmpty()) { st.add(e.getText()); } } // when we searched for entries whithout authors or keywords e.g., the // searchterms are emptry. in this case, return null if (st.isEmpty()) { return null; } // copy all children to an array String[] retval = st.toArray(new String[st.size()]); // return results return retval; } return null; } /** * This method retrieves the searchterms for a given search-request. * * @param nr The number of the search request * @return {@code true} if the search was a synonym-search, false otherweise */ public boolean isSynonymSearch(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, return "true" if (el != null) { // retrieve synonyms-child-element el = el.getChild("synonyms"); // if we have any element, return value if (el != null) { return (el.getText().equalsIgnoreCase("true")); } } return false; } /** * This method retrieves the reg-ex-state, i.e. whether a search contained regular expressions * as search terms or "normal" search terms * * @param nr The number of the search request * @return {@code true} if the search was a regular-expression-search, false otherweise */ public boolean isRegExSearch(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, return "true" if (el != null) { // retrieve synonyms-child-element el = el.getChild("regex"); // if we have any element, return value if (el != null) { return (el.getText().equalsIgnoreCase("true")); } } return false; } /** * Adds a new searchterm from a search-request to the history of search terms. This histroy, * a linked list of type String, stores the 20 last used search terms. If this list is already * "full", the first (oldest) element will be removed and the new search term will be added to * the end of this list. * * @param st the new search term that should be added to the history */ public void addToHistory(String st) { // get current size of history int count = searchTermsHistory.size(); // if it reached the limit, remove first element if (count >= searchTermsHistoryMax) { searchTermsHistory.pollFirst(); } // add new element to the end of the list searchTermsHistory.add(st); } /** * Retrieves an array of all previously used search terms. * * @return a string-array with the last 20 used search terms, or null if no entries are * currently saved in the history-list */ public String[] getHistory() { // if we have no elements in our history, return null if (null == searchTermsHistory || 0 == searchTermsHistory.size()) { return null; } // else copy list to string arary String[] hist = searchTermsHistory.toArray(new String[searchTermsHistory.size()]); // and sort that string array if (hist != null && hist.length > 0) { Arrays.sort(hist, new Comparer()); } // return array return hist; } /** * This method returns the searcresults (i.e. the entry-numbers of the found entries) * of a certain search-request. * * @param nr the number of the search request which results we want to have * @return the search-results (i.e. the entry-numbers of the found entries) as int-array, or {@code null} * if an error occured */ public int[] getSearchResults(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, go on... if (el != null) { // get all results and split them into an array String[] r = el.getChild("results").getText().split(","); // create integer array int[] retval = new int[r.length]; try { // go through string-array and convert each string to integer for (int cnt = 0; cnt < r.length; cnt++) { retval[cnt] = Integer.parseInt(r[cnt]); } return retval; } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Saved search result contained illegal entry numbers!"); // return null return null; } } return null; } /** * This method returns the searcresults (i.e. the entry-numbers of the found entries) * of a certain search-request. * * @param nr the number of the searchrequest where we want to set the new results * @param results the new results for the searchrequest {@code nr} * @return */ public boolean setSearchResults(int nr, int[] results) { // get the element Element el = retrieveElement(nr); // if we found an element, go on... if ((el != null) && (results != null)) { // create string builder StringBuilder sb = new StringBuilder(""); // go through all results for (int r : results) { // append them to stringbuilder sb.append(String.valueOf(r)); sb.append(","); } // delete last comma if (sb.length() > 1) { sb.setLength(sb.length() - 1); } // get all results and split them into an array el.getChild("results").setText(sb.toString()); // change modified state setModified(true); // everything is ok return true; } // error occurred return false; } /** * Sets the currently selected search results/request. Used in the CStartSearch-dialog * when filtering search results. * * @return the number of the currently activated/selected search request, or -1 if an error occured. * Remember that this value has a range from 0 to ({@link #getCount() getCount()}-1) */ public int getCurrentSearch() { try { // get the value for the last-used-searchrequest int ls = Integer.parseInt(searches.getRootElement().getAttributeValue("lastused")); // check whether it is out of bounds... if (ls >= getCount()) { ls = -1; } // return result return ls; } catch (NumberFormatException e) { return -1; } } /** * Sets the currently selected search results/request. Is used when a combobox-item in the * CSearchResults-frame is selected. * * @param nr the number of the currently used search request. usually, this number corresponds * to the combobox-selection of the CSearchResults-frame. * Remember that this value has a range from 0 to ({@link #getCount() getCount()}-1) * @return {@code true} is current-searchindex could be successfully set, false if an error occured */ public boolean setCurrentSearch(int nr) { // check whether "nr" is within valid bounds if (nr >= 0 && nr < getCount()) { // if so, set last-used-attribute searches.getRootElement().setAttribute("lastused", String.valueOf(nr)); // and return true return true; } // otherwise return false return false; } /** * This method deletes the entry {@code nr} from the search-results-list of the * searchrequest {@code searchrequest}. * * @param searchrequest the number of the searchrequest * @param nr the entrynumber that should be removed from that searchrequest's rsultlist */ public void deleteResultEntry(int searchrequest, int nr) { // get searchresults int[] results = getSearchResults(searchrequest); // check whether we have any if (results != null) { // if we only have one result, and this one entry equals that entry that should be deleted, // we can completely delete the search-request if (1 == results.length && nr == results[0]) { deleteSearchRequest(searchrequest); } else { // create new array which is one field shorter - because one entry // has to be deleted int[] newresults = new int[results.length - 1]; int newcnt = 0; // go through array of old results. // when result-value does not equal the entry that should be removed, // add that entry to the new results. for (int cnt = 0; cnt < results.length; cnt++) { if (results[cnt] != nr) { newresults[newcnt++] = results[cnt]; } } // set new search results setSearchResults(searchrequest, newresults); } // change modified state setModified(true); } } /** * This method retrieves the position of the entry {@code zettelnumber} within the searchrequest * {@code searchrequest}. if the requested entry was not found in that searchrequest, -1 is returned. * * @param searchrequest the searchrequest-index, where we want to look for an entry's position * @param zettelnumber the number of the entry we are looking for * @return the position within the search result, if the requestes entry {@code zettelnumber} was found, * or -1 if no entry was found */ public int getZettelPositionInResult(int searchrequest, int zettelnumber) { // get searchresults from the related search request int[] results = getSearchResults(searchrequest); // check whether we have any search results if (results != null) { // init counter int cnt = 0; // go through all search result entries... for (int r : results) { // if search result entry equals zettelnumber, return position if (r == zettelnumber) { return cnt; } // else increase position counter cnt++; } } // if no entry was found in the search result, return -1 return -1; } /** * This method deleted a complete search request. * @param nr the number of the search request to be deleted */ public void deleteSearchRequest(int nr) { // get amount of search requests. int count = getCount(); // if we don't have any, return... if (0 == count) { return; } // if we have exactly one search result, clear data if (1 == count) { clear(); } // remove the required search request else { searches.getRootElement().removeContent(nr); // decrease search-result-counter count--; // if the last current-search-index is now out of bounds, // set the current-search-index to the last search-request if (getCurrentSearch() >= count) { setCurrentSearch(count - 1); } } // change modified state setModified(true); } /** * This method duplicated the current search result and appends it to the end of the * search results data. Furthermore, the duplicated search result is set as current search result * (i.e. which is in use). */ public void duplicateSearchRequest() { // retrieve the current search XML element and add it to the root element, thus duplicating // the current search results data searches.getRootElement() .addContent((Element) searches.getRootElement().getContent(getCurrentSearch()).clone()); // set duplicated search results as currently used search results setCurrentSearch(searches.getRootElement().getContentSize()); // 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"); String desc = " (" + df.format(new Date()) + ")"; // update search description setShortDescription(getCurrentSearch(), getShortDescription(getCurrentSearch()) + desc); // and set modified state setModified(true); } /** * This method deletes all search requests and clears the search-request-xml-file. * The modified state is set to true, so the cleared xml-file will be saved, leading * to an empty or resettet search-request-file. */ public void deleteAllSearchRequests() { clear(); setModified(true); } /** * Gets the short description of a certain search-request. * The position is a value from 0 to (size of xml file - 1) * * @param nr the number of the search-requests which label/description we want to retrieve. * @return the description of the search-request as string */ public String getShortDescription(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, go on... if (el != null) { String retval = el.getChild(Daten.ELEMEMT_DESCRIPTION).getText(); return retval; } return ""; } /** * Sets the short description of a certain search-request. * The position {@code nr} is a value from 0 to (size of xml file - 1) * * @param nr the number of the search-requests which label/description we want to change. * @param desc the description of the search-request as string */ public void setShortDescription(int nr, String desc) { // get the element Element el = retrieveElement(nr); // if we found an element, change description if (el != null) { el.getChild(Daten.ELEMEMT_DESCRIPTION).setText(desc); } } /** * Gets the short description of a certain search-request. * * @param nr the number of the search-requests which label/description we want to retrieve. * @return the description of the search-request as string */ public String getLongDescription(int nr) { // get the element Element el = retrieveElement(nr); // if we found an element, go on... if (el != null) { String retval = el.getChild("longdesc").getText(); return retval; } return ""; } /** * This method returns the amount of saved search requests. * * @return the amount of saved search requests */ public int getCount() { return searches.getRootElement().getContentSize(); } /** * Whenenver we have changes to current searches, added new or deleted existing * searches, we should change the modified state with this method. * * @param m whether the modified-state is true or not */ public void setModified(boolean m) { modified = m; // update indicator for autobackup zknframe.setBackupNecessary(); } /** * Gets the modified state * @return {@code true} if we have any changes to the search requests, false otherwise */ public boolean isModified() { return modified; } /** * This function retrieves an element of a xml document at a given * position. The position is a value from 0 to (size of xml file - 1) * * @param pos (the position of the element) * @return the element if a match was found, otherwise null) */ private Element retrieveElement(int pos) { // create a list of all elements from the given xml file try { List<?> elementList = searches.getRootElement().getContent(); // and return the requestet Element try { return (Element) elementList.get(pos); } catch (IndexOutOfBoundsException e) { return null; } } catch (IllegalStateException e) { return null; } } }