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.DesktopFrame; import de.danielluedecke.zettelkasten.ZettelkastenView; import de.danielluedecke.zettelkasten.util.Constants; import de.danielluedecke.zettelkasten.util.Tools; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import org.jdom2.Content; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.IllegalAddException; import org.jdom2.IllegalDataException; import org.jdom2.IllegalNameException; import org.jdom2.Parent; /** * The root-element is names "desktops". For each desktop, a new child named "desktop" is created. This * child holds the content for one desktop. * * The first Element is always "bullet", a bullet-point. Bullet-points do always have the attribute "name" * which holds the name of the bullet-point that is displayed in the jTree. Furthermore, each bullet-element * automatically creates a child-element "comment", even if a bullet-point has no comments. * * "comment" is therefor always the first child-element of "bullet". * * Futher child-elements are the entries, named "entry". Each entry-element has an id with the related * entry-number from the main-datafile (see CDaten-class) and a timestamp which indicates when this * entry was added to the desktop. * * After the entries, each bullet has possible new bullet-elements as child-elements. These bullet-element * indicate the next level of the outline, repeating the structure described here and above. * * <desktops><br> * <desktop name="MyFirstDesktop"><br> * <bullet name="Name of Bullet point"><br> * <omment>The comment of this bullet</comment><br> * <entry id="4" timestamp="0901155107">A possible comment to the entry</entry><br> * <entry id="11" timestamp="0901153112" /><br> * <entry id="17" timestamp="0901155313">A possible comment to the entry</entry><br> * <bullet name="Name of Bullet Point"><br> * <comment /><br> * <entry id="14" timestamp="0902114301" /><br> * <entry id="1" timestamp="0901121313">A possible comment to the entry</entry><br> * <entry id="23" timestamp="0901453208" /><br> * <entry id="35" timestamp="0902251714" /><br> * <bullet name="Name of Bullet Point"><br> * <comment /><br> * <entry id="40" timestamp="0902114301" /><br> * <entry id="44" timestamp="0902114301" /><br> * </bullet><br> * <bullet name="Name of Bullet Point"><br> * <comment /><br> * <entry id="50" timestamp="0902114301" /><br> * <entry id="54" timestamp="0902114301" /><br> * </bullet><br> * </bullet><br> * </bullet><br> * </desktop><br> * </desktops> * * @author danielludecke */ public class DesktopData { private Document desktop; private Document modifiedEntries; private Document desktopNotes; private int currentDesktop; private boolean modified; private int timestampid = 0; private Element foundDesktopElement = null; private static final String TREE_FOLDING_EXPANDED = "expand"; private static final String TREE_FOLDING_COLLAPSED = "collaps"; public static String ATTR_TIMESTAMP = "timestamp"; public static String ATTR_COMMENT = "comment"; public static String ELEMENT_ENTRY = "entry"; public static String ELEMENT_BULLET = "bullet"; /** * Constants for the method {@link #importArchivedDesktop(org.jdom.Document) importArchivedDesktop()}. * Indicates that the import of an desktop-archive was successful. */ public static final int IMPORT_ARCHIVE_OK = 1; /** * Constants for the method {@link #importArchivedDesktop(org.jdom.Document) importArchivedDesktop()}. * Indicates that the import of an desktop-archive failed, because the desktop-name of the imported * archive already exists. */ public static final int IMPORT_ARCHIVE_ERR_DESKTOPNAME_EXISTS = 2; /** * Constants for the method {@link #importArchivedDesktop(org.jdom.Document) importArchivedDesktop()}. * Indicates that the import of an desktop-archive failed, because a technical error occured. */ public static final int IMPORT_ARCHIVE_ERR_OTHER = 3; /** * This element stores a bullet-element that is currently in the clipboard - either due to * a cut or copy operation from the CDesktop-Dialog */ private Element clipbullet = null; /** * Reference to the main frame. */ private final ZettelkastenView zknframe; /** * */ private ArrayList<Integer> retrieveList; /** * */ private ArrayList<String> timestampList; /** * 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(DesktopFrame.class); /** * @param zkn */ public DesktopData(ZettelkastenView zkn) { zknframe = zkn; clear(); } /** * */ public final void clear() { // create empty documents desktop = new Document(new Element("desktops")); modifiedEntries = new Document(new Element("modifiedEntries")); desktopNotes = new Document(new Element("desktopNotes")); currentDesktop = -1; modified = false; } /** * Sets the complete desktop-data-file, usually needed when loading new data. * @param doc an xml-file containing the desktop-data */ public void setDesktopData(Document doc) { desktop = doc; } /** * Gets the complete desktop-data-file, usually needed when savin the data. * @return an xml-file containing the desktop-data */ public Document getDesktopData() { return desktop; } /** * Sets the complete desktop-data-file, usually needed when loading new data. * @param doc an xml-file containing the desktop-data */ public void setDesktopModifiedEntriesData(Document doc) { modifiedEntries = doc; } /** * Gets the complete desktop-data-file, usually needed when savin the data. * @return an xml-file containing the desktop-data */ public Document getDesktopModifiedEntriesData() { return modifiedEntries; } /** * Sets the complete desktop-data-file, usually needed when loading new data. * @param doc an xml-file containing the desktop-data */ public void setDesktopNotesData(Document doc) { desktopNotes = doc; } /** * Gets the complete desktop-data-file, usually needed when savin the data. * @return an xml-file containing the desktop-data */ public Document getDesktopNotesData() { return desktopNotes; } /** * sets the modified state. should be called whenever changes have been made to the desktopfile * (set it to true) or when the data has been saved (set it to false) * @param m true when changes are unsaved, false otherwise */ public void setModified(boolean m) { modified = m; // update indicator for autobackup zknframe.setBackupNecessary(); } /** * Checks whether the datafile is modified * @return {@code true} if it is modified, false otherwise */ public boolean isModified() { return modified; } /** * This method returns the index-number for the currently used desktop-data. This is * necessary since a desktop-document may contain several desktop-data-units. * * @return the index-number of the currently used desktop, starting with {@code 0} for * the first desktop-data */ public int getCurrentDesktopNr() { return currentDesktop; } /** * This method sets the index-number for the currently used desktop-data. This is * necessary since a desktop-document may contain several desktop-data-units. * * @param nr the number of the current desktop, using {@code 0} as the first index number. */ public void setCurrentDesktopNr(int nr) { currentDesktop = nr; } /** * Returns the amount of saved desktops * @return the amount of desktop-data-units. returns {@code 0} if no desktop data exists. */ public int getCount() { return desktop.getRootElement().getContentSize(); } /** * This method retrieves the name of currently active desktop. * * @return a String containing the name of the currently active desktop or a string {@code noDesktopNameFound} * if the desktop was not found... */ public String getCurrentDesktopName() { // return name of current desktop return getDesktopName(currentDesktop); } /** * This method retrieves the name of the desktop with the index {@code nr}. * * @param nr the desktop-index of which we want to have the name, ranged from 0 to (count-1) * @return a String containing the name of the desktop, or a string {@code noDesktopNameFound} * if the desktop was not found... */ public String getDesktopName(int nr) { // get the requested dektop-element Element d = getDesktopElement(nr); // if no dektop was found, return empty string if (null == d) return resourceMap.getString("noDesktopNameFound"); // get the desktop's name-attribute String retval = d.getAttributeValue("name"); // check for content if ((null == retval) || (retval.isEmpty())) retval = resourceMap.getString("noDesktopNameFound"); // and return it return retval; } /** * This method sets the name of the desktop with the index {@code nr}. * * @param nr the desktop-index of which we want to have the name * @param name the new name of the desktop * @return {@code true} if name was successfully set, {@code false} */ public boolean setDesktopName(int nr, String name) { // get the requested dektop-element Element d = getDesktopElement(nr); // if no dektop was found, return empty string if (null == d) return false; try { // change attribute d.setAttribute("name", name); // change modified state setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } return true; } /** * This method recursivly searches through all entris of the desktop {@code desktopnr} and * looks for occurences of the entry with the numer {@code entrynr}. If an entry was found, * true is returned, otherwise false is returned.<br><br> * This method can be used to look for multiple entry occurences not only in the current sub-tree * or sub-bullet, but within all desktop-files - so the user knows that he has already added the * entry {@code entrynr} to this or another existing desktop in the past. * * @param desktopnr the desktop where we should look for occurences of the entry {@code entrynr} * @param entrynr the number of the entry that should be looked for. usually, this number corresponds * to entry-numbers of entries that are currently being added * @return {@code true} if the entry with the number {@code entrynr} exists in the desktop {@code desktopnr}, * false otherwise */ public boolean checkForDoubleEntry(int desktopnr, int entrynr) { // retrieve desktop-element Element desktopelement = getDesktopElement(desktopnr); // if desktop-element does not exist, return false if (null == desktopelement) return false; // find elements LinkedList<Element> retval = findElements(desktopelement, String.valueOf(entrynr), new LinkedList<Element>()); // if we found something, return linked list, else return null return (retval.size() > 0); } /** * This method removes all entries which entry-number are passed in the int-array {@code entries} * from all available desktop-elements. * * @param entries an integer-array containing the entry-numbers of those entries that should be * removed from the desktop * @return */ public boolean deleteEntries(int[] entries) { // indicator for deletes entries boolean haveDeletedEntries = false; // check for valid parameter if (entries != null && entries.length > 0 && getCount() > 0) { // create linked list which will contain all to be deleted entries... LinkedList<Element> finallist = new LinkedList<Element>(); // go through all desktops (outer loop)... for (int cnt = 0; cnt < getCount(); cnt++) { // ...and search for all entries in each desktop (inner loop) for (int e : entries) { // retrieve all found entries within the desktop LinkedList<Element> lle = searchForEntry(cnt, e); // check whether we have any returned entries... if (lle != null && lle.size() > 0) { // create a new iterator for the found results Iterator<Element> prepare = lle.iterator(); // iterate them while (prepare.hasNext()) { // get each single entry as element Element entry = prepare.next(); // and add it to the final list if (entry != null) finallist.add(entry); } } } } // if we found any elements that should be deleted, do // this now... if (finallist.size() > 0) { // create iterator Iterator<Element> i = finallist.iterator(); // go trhough linked list while (i.hasNext()) { // get each element that should be deleted Element entry = i.next(); // if we have a valid element, go on... if (entry != null) { // retrieve timestamp String timestamp = entry.getAttributeValue(ATTR_TIMESTAMP); // check whether we have any modified entry. if so, delete it, since // we no longer need it... deleteModifiedEntry(timestamp); // get the entry's parent Parent p = entry.getParent(); // remove entry from parent p.removeContent(entry); // set deleted-indicator to true haveDeletedEntries = true; } } } } // change modified state if (haveDeletedEntries) setModified(true); // return result return haveDeletedEntries; } /** * This method recursivles searches through all entris of the desktop {@code desktopnr} and * looks for occurences of the entry with the numer {@code entrynr}. If an entry was found, * this entry is returned as {@code Element}, otherwise {@code null} is returned.<br><br> * This method can be used to look for entries that should be removed from the desktop. * * @param desktopnr the desktop where we should look for occurences of the entry {@code entrynr} * @param entrynr the number of the entry that should be looked for. usually, this number corresponds * to entry-numbers of entries that are currently being added * @return a linked list of type {@code Element}, which contains the found entry-Elements, in the desktop * {@code desktopnr}. If no Elements (entries) have been found, the method return {@code null}. */ public LinkedList<Element> searchForEntry(int desktopnr, int entrynr) { // retrieve desktop-element Element desktopelement = getDesktopElement(desktopnr); // if desktop-element does not exist, return false if (null == desktopelement) return null; // find elements LinkedList<Element> retval = findElements(desktopelement, String.valueOf(entrynr), new LinkedList<Element>()); // if we found something, return linked list, else return null return (retval.size() > 0) ? retval : null; } /** * This method is called from the {@link #checkForDoubleEntry(int, int) checkForDoubleEntry(int, int)} method * and used to recursivly scan all elements of a desktop. If an entry-element which id-attribute matches * the parameter {@code entrynumber} was found, this element is returned, else {@code null} is returned. * * @param e the element where we start scanning. for the first call, use {@link #getDesktopElement(int) getDesktopElement(int)} * to retrieve the root-element for starting the recursive scan. * @param entrynumber the number of the entry we are looking for. if any element's id-attribut matches this * parameter, the element is return, else null * @return an element which id-attribute matches the parameter {@code entrynumber}, or null if no element * was found */ private LinkedList<Element> findElements(Element e, String entrynumber, LinkedList<Element> foundelements) { // if we don't have any element, return null if (e == null) return foundelements; // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // else check whether we have child-elements - if so, re-call method if (hasChildren(e)) foundelements = findElements(e, entrynumber, foundelements); // check whether we have an entry-element that matched the requested id-number if (e != null && e.getName().equals(ELEMENT_ENTRY)) { // check whether attribute exists String att = e.getAttributeValue("id"); // if so, and it machtes the requested id-number, add element to list if (att != null && att.equals(entrynumber)) foundelements.add(e); } } return foundelements; } public boolean desktopHasComments(Element desktopelement) { return getCommentCount(desktopelement, 0) > 0; } public int getCommentCount(Element e, int commentcount) { // if we don't have any element, return null if (e == null) return commentcount; // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // else check whether we have child-elements - if so, re-call method if (hasChildren(e)) commentcount = getCommentCount(e, commentcount); // check whether we have an entry-element that matched the requested id-number if (e != null) { // check whether we have a bullet-point if (e.getName().equals(ELEMENT_BULLET)) { // if we have a bullet, return the text of it's comment-child. Element comel = e.getChild(ATTR_COMMENT); if (comel != null && !comel.getText().isEmpty()) commentcount++; } else { // else return the element's text if (!e.getText().isEmpty()) commentcount++; } } } return commentcount; } /** * This method recursivly scans all elements of a desktop. If an entry-element which id-attribute matches * the parameter {@code entrynumber} was found, this method returns {@code true}. * * @param e the element where we start scanning. for the first call, use {@link #getDesktopElement(int) getDesktopElement(int)} * to retrieve the root-element for starting the recursive scan. * @param entrynumber the number of the entry we are looking for. if any element's id-attribut matches this * parameter, the element is return, else null * @param found the initial value, should be {@code false} when initially called * @return {@code true} when the entry with the number {@code entrynumber} was found, {@code false} otherwise. */ public boolean desktopHasElement(Element e, String entrynumber, boolean found) { // if we don't have any element, return null if (e == null) return false; // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // else check whether we have child-elements - if so, re-call method if (hasChildren(e)) found = desktopHasElement(e, entrynumber, found); // check whether an entry was found in children if (found) return true; // check whether we have an entry-element that matched the requested id-number if (e != null && e.getName().equals(ELEMENT_ENTRY)) { // check whether attribute exists String att = e.getAttributeValue("id"); // if so, and it machtes the requested id-number, add element to list if (att != null && att.equals(entrynumber)) { // save element foundDesktopElement = e; return true; } } } return found; } /** * This method checks whether an entry with the number {@code entrynumber} is stored in any * of the available desktops. If any of the desktops contains an entry with the number {@code entrynumber}, * {@code true} is returned. * * @param entrynumber the number of that entry that should be found in the desktops. * @return {@code true} if this entry was found in any desktop, {@code false} otherwise or if no desktops exist. */ public boolean isEntryInAnyDesktop(int entrynumber) { // init return value boolean entryfound = false; // reset found elements foundDesktopElement = null; // iterate all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // check whether the desktop has this entry as element entryfound = desktopHasElement(getDesktopElement(cnt), String.valueOf(entrynumber), entryfound); // if yes, break out of loop if (entryfound) return true; } // return result return entryfound; } /** * This method checks whether an entry with the number {@code entrynumber} is stored in the current desktop. * If this desktop has an entry with the number {@code entrynumber}, * {@code true} is returned. * * @param entrynumber the number of that entry that should be found in the current desktop. * @return {@code true} if this entry was found in this desktop, {@code false} otherwise or if no such desktop exist. */ public boolean isEntryInCurrentDesktop(int entrynumber) { return isEntryInDesktop(entrynumber, getCurrentDesktopNr()); } /** * This method checks whether an entry with the number {@code entrynumber} is stored in the desktops with * the desktop-nr {@code desktopnr}. If this desktop has an entry with the number {@code entrynumber}, * {@code true} is returned. * * @param entrynumber the number of that entry that should be found in the desktop with the index-number {@code desktopnr}. * @param desktopnr the number of the desktop where the entry should be searched for. * @return {@code true} if this entry was found in this desktop, {@code false} otherwise or if no such desktop exist. */ public boolean isEntryInDesktop(int entrynumber, int desktopnr) { // init return value boolean entryfound = false; // reset found elements foundDesktopElement = null; // if parameter is out of range, return false if (desktopnr < 0 || desktopnr > getCount()) return false; // check whether the desktop has this entry as element return desktopHasElement(getDesktopElement(desktopnr), String.valueOf(entrynumber), entryfound); } /** * If we have an element found with {@link #desktopHasElement(org.jdom2.Element, java.lang.String, boolean)} * we can get this {@code Element} with this function for further use. * @return the Element of an entry that was found on a desktop (via {@link #desktopHasElement()}, or * {@code null} if no element / entry was found. */ public Element getFoundDesktopElement() { return foundDesktopElement; } /** * This methods searches all desktops for the entry {@code entrynr} and * returns the name of the desktop where the entry is located. If no desktop * with this entry was found, an empty string will be returned. * @param entrynr the number of the entry that should be searched for * in all desktops * @return the name of the desktop containing the entry {@code entrynr} as * string or an empty string if now desktop with this entry was found. */ public String getDesktopNameOfEntry(int entrynr) { // itereate all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // check whether entry is in this desktop if (isEntryInDesktop(entrynr, cnt)) { // quit method return getDesktopName(cnt); } } return ""; } /** * This method returns a desktop-element-unit at the given position "nr". Remind * to use a range from 0 to (amount of desktops-1)! * * @param nr the desktop element we want to retrieve, ranged from 0 to (count-1) * @return the element of the requested desktop-data (dektop including child-elements like * bullets and nodes), or null if an error occured */ public Element getDesktopElement(int nr) { // create a list of all elements from the given xml file try { List<?> elementList = desktop.getRootElement().getContent(); // and return the requestet Element try { return (Element) elementList.get(nr); } catch (IndexOutOfBoundsException e) { return null; } } catch (IllegalStateException e) { Constants.zknlogger.log(Level.SEVERE, e.getLocalizedMessage()); return null; } } public boolean desktopNameExists(String desktopname) { // iterate all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // check whether desktopname equals the requested parameter if (getDesktopName(cnt).equalsIgnoreCase(desktopname)) { return true; } } return false; } /** * This method returns a desktop-element-unit of the desktop with the name {@code desktopname}. * If no such desktop exists that matches the parameter {@code desktopname}, {@code null} * is returned. * * @param desktopname the name of the desktop of which element we want to retrieve * @return the element of the requested desktop-data (dektop including child-elements like * bullets and nodes), or |@code null} if an error occured */ public Element getDesktopElement(String desktopname) { // desktop-number of element that is relevant for us int desktopnr = -1; // iterate all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // check whether desktopname equals the requested parameter if (getDesktopName(cnt).equals(desktopname)) { // if so, set desktopnumber desktopnr = cnt; // and leave loop break; } } // if we have found a desktop, get its element if (desktopnr != -1) { // create a list of all elements from the given xml file try { List<?> elementList = desktop.getRootElement().getContent(); // and return the requestet Element try { return (Element) elementList.get(desktopnr); } catch (IndexOutOfBoundsException e) { return null; } } catch (IllegalStateException e) { Constants.zknlogger.log(Level.SEVERE, e.getLocalizedMessage()); return null; } } return null; } /** * This method returns the currently used desktop as jdom-element. * * @return the currently used desktop as jdom-element, or null if an error occured */ public Element getCurrentDesktopElement() { return getDesktopElement(currentDesktop); } /** * This method adds a new desktop-element to the document. Furthermore, currentDesktop-number * is set to the latest added desktop-index-number. * * @param name (the name of the desktop, which appears in the desktopDialog's combobox) * @return {@code true} if the desktop was successfully added, false otherwise (e.g. because the * desktop-name already existed) */ public boolean addNewDesktop(String name) { return addNewDesktop(name, null); } /** * This method adds a new desktop-element to the document. Furthermore, currentDesktop-number * is set to the latest added desktop-index-number. * * @param name the name of the desktop, which appears in the desktopDialog's combobox * @param notes the initial desktop-notes that should be associated with this desktop. use this * e.g. when importing an archived desktop (see {@link #importArchivedDesktop(org.jdom.Document) importArchivedDesktop()}. * use {@code null} when no notes-content should be added (i.e. the elements are being created, but * they have no text). * @return {@code true} if the desktop was successfully added, {@code false} otherwise (e.g. because the * desktop-name already existed) */ public boolean addNewDesktop(String name, String[] notes) { // first of all, go through all desktops and check whether the name // already exist, to avoid double naming... // when such a desktopname as "name" already exists, return false for (int cnt = 0; cnt < getCount(); cnt++) if (name.equalsIgnoreCase(getDesktopName(cnt))) return false; // create new element Element d = new Element("desktop"); try { // set the desktop's name as attribute d.setAttribute("name", name); // add the element to the desktop desktop.getRootElement().addContent(d); // set currentDesktop index to the new desktop-element currentDesktop = desktop.getRootElement().getContentSize() - 1; // also add new desktop-notes-element Element desk = new Element("desktop"); // set name attribute desk.setAttribute("name", name); // create notes elements Element n1 = new Element("notes1"); Element n2 = new Element("notes2"); Element n3 = new Element("notes3"); // set initial notes text, if we have any... if (notes != null && notes.length > 0) { // check for array-length before setting text if (notes.length > 0) n1.setText(notes[0]); // check for array-length before setting text if (notes.length > 1) n2.setText(notes[1]); // check for array-length before setting text if (notes.length > 2) n3.setText(notes[2]); } // add notes-sub-elements desk.addContent(n1); desk.addContent(n2); desk.addContent(n3); // add element to desktop-notes desktopNotes.getRootElement().addContent(desk); // change modified state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } return true; } /** * Adds a new bullet-point to the xml-document. * * @param timestamp the timestamp of the currently selected bullet-point after which the new bullet should * be inserted. * @param name the name of the bullet-point * @return the timestamp of the added bullet-element as {@code String} or {@code null} if an error * occured. */ public String addBullet(String timestamp, String name) { // find the bulletgroup that is described in the treepath Element parentb = (timestamp != null) ? findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp) : getCurrentDesktopElement(); String newts; // if we have a valid bullet-group, add the new bullet to the xml-file if (parentb != null) { // create a new element from the given name Element b = new Element(ELEMENT_BULLET); try { b.setAttribute("name", name); // set attribute for collapsed/expanded state of bullet b.setAttribute("treefold", TREE_FOLDING_EXPANDED); // create timestamp newts = Tools.getTimeStampWithMilliseconds(); // check whether timestamp already exists. this is particulary the // case when a user adds several entries at once. while (timeStampExists(newts)) newts = Tools.getTimeStampWithMilliseconds(); // set timestamp as attribute b.setAttribute(ATTR_TIMESTAMP, newts); // add a "comment" to that bullet. remember, that each bullet // automatically gets a child-element called "comment" b.addContent(new Element(ATTR_COMMENT)); // add new bullet to the bulletgroup parentb.addContent(b); // change modified state setModified(true); // return timestamp of addes bullet return newts; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } } return null; } private boolean timeStampExists(String timestamp) { // init return value boolean retval = false; // go through all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // try to find any element in each desktop that matches the timestamp Element found = findEntryElementFromTimestamp(getDesktopElement(cnt), timestamp); // if we found anything, modify return-value if (found != null) retval = true; } // return result return retval; } /** * This method renames the bullet which is given through the path {@code tp}. * * @param timestamp the timestamp of the bullet that should be renamed * @param newName the new name of the bullet * @return {@code true} is renaming was successful, {@code false} if an error occured. */ public boolean renameBullet(String timestamp, String newName) { // retrieve selected bullet element Element b = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); if (b != null) { try { // change name b.setAttribute("name", newName); // change modified state setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } } return true; } /** * This method retrieves a bullet element from the given path {@code tp} and copies it into * a global Element-variable, so this element is stored for later use. * * @param timestamp the timestamp of the bullet that should be copied to the "clipboard" */ public void copyBulletToClip(String timestamp) { // retrieve bullet for copy-operation clipbullet = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // but use new timestamp! if (clipbullet != null) { // create timestamp String newts = Tools.getTimeStampWithMilliseconds(); // check whether timestamp already exists. this is particulary the // case when a user adds several entries at once. while (timeStampExists(newts)) newts = Tools.getTimeStampWithMilliseconds(); // set timestamp as attribute clipbullet.setAttribute(ATTR_TIMESTAMP, newts); } } /** * This method retrieves a bullet element from the given path {@code tp} and copies it into * a global Element-variable, so this element is stored for later use. Furthermore, the element * retrieve from the treepath {@code tp} is removed from the XML-document * * @param timestamp the timestamp of the bullet that should be copied to the "clipboard" and deleted afterwards * (cut-operation) */ public void cutBulletToClip(String timestamp) { // retrieve the entry that should be deleted Element bullet = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); if (bullet != null) { // get the entry's parent Parent p = bullet.getParent(); // get the index from "bullet" int index = p.indexOf(bullet); // if we have a valid index, go on if (index != -1) { // remove the content and save it to the clipboard-element clipbullet = (Element) p.removeContent(index); // change modified state setModified(true); } } } /** * Pastes the bullet-point {@code clipbullet} into the xml-document. The Element {@code clipbullet} * must be retrieved by {@link #copyBulletToClip(java.util.LinkedList) copyBulletToClip(java.util.LinkedList)}. * * @param timestamp the timestamp of the bullet that should be inserted * @param isRoot {@code true} when the drop-source is the root-element of the jTree, {@code false} when * the drop-source is any other parent-element */ public void pasteBulletFromClip(String timestamp, boolean isRoot) { // find the bulletgroup that is described in the treepath Element parentb = (isRoot) ? getCurrentDesktopElement() : findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // if we have a valid bullet-group, add the new bullet to the xml-file if (parentb != null && clipbullet != null) { // create a new element Element b = new Element(ELEMENT_BULLET); try { // set the name of the copied bullet-value b.setAttribute("name", clipbullet.getAttributeValue("name")); // and use copied timestamp b.setAttribute(ATTR_TIMESTAMP, clipbullet.getAttributeValue(ATTR_TIMESTAMP)); // clone the content from the clipboard-bullet to the new element. we // have to clone the content, since the element "clipbullet" still might // be attached to the document, when the bullet is just copied, not cut. b.addContent(clipbullet.cloneContent()); // add new bullet to the bulletgroup parentb.addContent(b); // change modified state setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } } /** * This method moves an element (entry-node or bullet-point) one position up- or downwards, depending * on the parameter {@code movement}. * @param movement indicates whether the element should be moved up or down. use following constants:<br> * - {@code CConstants.MOVE_UP}<br> * - {@code CConstants.MOVE_DOWN}<br> * @param timestamp */ public void moveElement(int movement, String timestamp) { // get the selected element, independent from whether it's a node or a bullet Element e = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); if (e != null) { // get the element's parent Element p = e.getParentElement(); // get the index of the element that should be moved int index = p.indexOf(e); // remove the element that should be moved Element dummy = (Element) p.removeContent(index); try { // and insert element one index-position below the previous index-position p.addContent((Constants.MOVE_UP == movement) ? index - 1 : index + 1, dummy); // change modifed state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } } /** * This method adds a new entry (child-node) to the xml-document. * * @param timestamp the timestamp of the element, where the entry "nr" should be inserted as new child * @param nr the entry-number of the entry that should be added * @param insertpos the position where the new entry should be inserted. necessary when we have already * more children and the entry should be inserted in between, at the beginning or end of the children-list. * @return the timestamp of the added entry-element as {@code String} or {@code null} if an error * occured. */ public String addEntry(String timestamp, String nr, int insertpos) { // find the bullet that is described in the treepath Element b = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // if we have a valid bullet, add the new enry to the xml-file if (b != null) { // check whether element is a bullet, if not, retrieve its parent element if (!b.getName().equals(ELEMENT_BULLET)) b = b.getParentElement(); // create a new element Element e = new Element(ELEMENT_ENTRY); try { e.setAttribute("id", nr); // create timestamp String ts = Tools.getTimeStampWithMilliseconds(); // check whether timestamp already exists. this is particulary the // case when a user adds several entries at once. while (timeStampExists(ts)) ts = Tools.getTimeStampWithMilliseconds(); // add timestamp to entry element e.setAttribute(ATTR_TIMESTAMP, ts); // add new enry to the bullet at insert-position+1 (because at first // position in the bullet is always the comment) b.addContent(insertpos, e); // change modified state setModified(true); // return timestamp return ts; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } return null; } /** * This method changes the treefold-state of a bullet-point. The <i>treefold</i>-attribute of the * bullet-point is either set to {@link #TREE_FOLDING_EXPANDED TREE_FOLDING_EXPANDED} or * {@link #TREE_FOLDING_COLLAPSED TREE_FOLDING_COLLAPSED}.<br><br> * With this attribute, we can check whether a bullet-point should be expanded or not, * when creating the TreeView in the CDesktop-Window. * * @param timestamp the timestamp of the bullet point which treefold-state was changed * @param expanded {@code true} if the bullet point was expanded, {@code false} if it was * collapsed. */ public void setBulletTreefold(String timestamp, boolean expanded) { // retrieve bullet of which fold-state should be switched Element b = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // check for valid value if (b != null) { try { // set new treefold-attribute b.setAttribute("treefold", (expanded) ? TREE_FOLDING_EXPANDED : TREE_FOLDING_COLLAPSED); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } } /** * Checks whether a bullet-point's treefold-state is expanded or not. * * @param timestamp the timestamp of the bzllet point, which treefold-state we want to check * @return {@code true} if bullet is expanded, {@code false} if it is collapsed */ public boolean isBulletTreefoldExpanded(String timestamp) { // retrieve bullet of which fold-state should be switched Element b = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // check for valid value if (b != null) { // check whether attribute exists String tf = b.getAttributeValue("treefold"); // set new treefold-attribute return (tf != null && !tf.isEmpty()) ? tf.equals(TREE_FOLDING_EXPANDED) : true; } return true; } /** * This method deletes the selected entry from the desktop. * * @param timestamp the timestamp of the to be deleted entry */ public void deleteEntry(String timestamp) { // retrieve the entry that should be deleted Element entry = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); if (entry != null) { // check whether we have any modified entry. if so, delete it, since // we no longer need it... deleteModifiedEntry(timestamp); // get the entry's parent Parent p = entry.getParent(); // remove entry from parent p.removeContent(entry); // change modified state setModified(true); } } /** * This method removes the element with the timestamp-attribute {@code timestamp} * from the list of modified entries. * * @param timestamp the timestamp of that entry that should be removed from the modification-list */ public void deleteModifiedEntry(String timestamp) { Element delentry = retrieveModifiedEntryElementFromTimestamp(timestamp); if (delentry != null) { modifiedEntries.getRootElement().removeContent(delentry); setModified(true); } } /** * This method deletes the selected entry from the desktop. * * @param timestamp the timestamp of the to the selected entry. */ public void deleteBullet(String timestamp) { // retrieve the entry that should be deleted Element bullet = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); if (bullet != null) { // retrieve all timestamps of those entries which are children of the to be // deleted bullet String[] timestamps = retrieveBulletTimestamps(bullet); // get the entry's parent Parent p = bullet.getParent(); // remove entry from parent p.removeContent(bullet); // now remove all possible modified entries of that list if (timestamps != null && timestamps.length > 0) { // iterate all timestamps for (String singlets : timestamps) { // delete modified entries that already have been deleted due // to the removal of the bullet-point deleteModifiedEntry(singlets); } } // change modified state setModified(true); } } /** * This method deletes the current activated desktop. */ public void deleteDesktop() { // check whether we have any desktops at all if (getCount() > 0) { // get current desktop Element d = getCurrentDesktopElement(); // retrieve all timestamps of those entries which are children of the to be // deleted desktop String[] timestamps = retrieveBulletTimestamps(d); // now remove all possible modified entries of that list if (timestamps != null && timestamps.length > 0) { // iterate all timestamps for (String singlets : timestamps) { // delete modified entries that already have been deleted due // to the removal of the bullet-point deleteModifiedEntry(singlets); } } // delete notes that are associated to this desktop deleteDesktopNotes(d.getAttributeValue("name")); // and remove it from the root desktop.getRootElement().removeContent(d); // set currentDesktop index to the new desktop-element currentDesktop = desktop.getRootElement().getContentSize() - 1; // change modified state setModified(true); } } private void deleteDesktopNotes(String desktopname) { // get all children from deskopNotes, since we need to find the right // desktop-element first... List<Element> elementList = desktopNotes.getRootElement().getChildren(); // create an iterartor Iterator<Element> it = elementList.iterator(); // go through all desktop-elements of the desktopNores-file while (it.hasNext()) { // retrieve element Element desk = it.next(); // get desktop-name-attribute String att = desk.getAttributeValue("name"); // check for desktop-name if (att != null && att.equals(desktopname)) { // if we found it, remove desktop-notes desktopNotes.getRootElement().removeContent(desk); // change modified flag setModified(true); // and leave methode return; } } } /** * This methods checks whether a given element {@code e} has child-elements * or not. * @param e the element which should be checked for children * @return {@code true} if {@code e} has children, {@code false} otherwise */ public boolean hasChildren(Element e) { if (null == e) return false; return (!e.getChildren().isEmpty()); } /** * This methods checks whether a given desktop "nr" has any child-elements (i.e. bullets) * or not. * * @param nr the desktop which should be checked for children/bullets * @return {@code true} if the desktop has any bullets (children, elements), false otherwise */ public boolean desktopHasBullets(int nr) { // get the requested desktop Element e = getDesktopElement(nr); if (e != null) { // retrieve the list of all bullet-children List<Element> l = e.getChildren(ELEMENT_BULLET); // return true if we have any bullets... return (!l.isEmpty()); } return false; } /** * Gets the comment of the selected entry in the jTree in the CDesktop-frame. This method * traverses the xml-datafile, looking for each element and checks whether the elements * <i>timestamp</i>-attribute matches the parameter {@code timestamp}. * <br><br> * If it was found, the comment as string will be returned. * * @param timestamp * @param linesep * @return a string containing the comment, or an empty string if no comment was found */ public String getComment(String timestamp, String linesep) { // check for valid value if (null == timestamp || timestamp.isEmpty()) { return ""; } // retrieve comment String comment = findEntryComment(getCurrentDesktopElement(), timestamp, ""); // check for valid value if (comment != null && !comment.isEmpty()) { // now replace the br-tags with the requested line-sepatator comment = comment.replace("[br]", linesep); } return comment; } /** * Sets the comment of the selected entry in the jTree in the CDesktop-frame. This method * traverses the xml-datafile, looking in each "depth-level" for the element that is * given in the linked list parameter <i>tp</i>. This parameter contains the treepath to * the selected element. * * @param timestamp * @param comment a string containing the comment * @return {@code true} if comment was successfully set, {@code false} if an error occured. */ public boolean setComment(String timestamp, String comment) { // retrieve element that matches the timestamp Element e = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // check whether an entry was found or not if (e != null) { try { // check whether we have a bullet-point if (e.getName().equals(ELEMENT_BULLET)) { // if we have a bullet, return the text of it's comment-child. e.getChild(ATTR_COMMENT).setText(comment); } else { // set comment for entry e.setText(comment); } // change modified state setModified(true); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); return false; } } return true; } /** * This method retrieves all entry-numbers of the current acticated desktop in ascending sorted * order and returns them as an integer-array. multiple entry-numbers are removed, so this array * contains each entry-number only once. * * @return an integer-array with all entry-numbers of the current activated desktop in ascending sorted * order. multiple entry-numbers are removed, so this array * contains each entry-number only once. */ public int[] retrieveDesktopEntries() { return retrieveDesktopEntries(getCurrentDesktopNr()); } /** * This method retrieves all entry-numbers of the desktop {@code deskopnr} in ascending sorted * order and returns them as an integer-array. multiple entry-numbers are removed, so this array * contains each entry-number only once. * * @param desktopnr the desktop-number of the desktop which entries should be retrieved. * @return an integer-array with all entry-numbers of the desktop {@code deskopnr} in ascending sorted * order. multiple entry-numbers are removed, so this array * contains each entry-number only once. */ public int[] retrieveDesktopEntries(int desktopnr) { // retrieve desktop xml-element Element desktopelement = getDesktopElement(desktopnr); // if we have such element, go on... if (desktopelement != null) { // create new list that will contain the return values retrieveList = new ArrayList<Integer>(); // fill list with all entry-numbers. since we have sub-bullets/levels, we // recursevly go through the desktop-data retrieveDesktopEntries(desktopelement); // if we have any results, go on... if (retrieveList.size() > 0) { // create return value int[] retval = new int[retrieveList.size()]; // and copy all integer-values from the list to that array for (int cnt = 0; cnt < retval.length; cnt++) { retval[cnt] = retrieveList.get(cnt); } return retval; } } return null; } /** * This method retrieves all entry-numbers of element {@code bullet} in ascending sorted * order and returns them as an integer-array. multiple entry-numbers are removed, so this array * contains each entry-number only once. * * @param bullet the (bullet-)element of the desktop which entries should be retrieved. * @return an integer-array with all entry-numbers of {@code bullet} in ascending sorted * order. multiple entry-numbers are removed, so this array * contains each entry-number only once. */ public int[] retrieveBulletEntries(Element bullet) { // if we have such element, go on... if (bullet != null) { // create new list that will contain the return values retrieveList = new ArrayList<Integer>(); // fill list with all entry-numbers. since we have sub-bullets/levels, we // recursevly go through the desktop-data retrieveDesktopEntries(bullet); // if we have any results, go on... if (retrieveList.size() > 0) { // create return value int[] retval = new int[retrieveList.size()]; // and copy all integer-values from the list to that array for (int cnt = 0; cnt < retval.length; cnt++) { retval[cnt] = retrieveList.get(cnt); } return retval; } } return null; } /** * This method retrieves all entry-timestamps of the desktop {@code desktopnr} and returns them as an string-array. * * @param desktopnr the number of of the desktop which entry-timestamps should be retrieved. * @return a string-array with all entry-timestamps of those entry with the parent * {@code bullet}. */ public String[] retrieveEntryTimestampsFromDesktop(int desktopnr) { return retrieveBulletTimestamps(getDesktopElement(desktopnr)); } /** * This method retrieves all entry-timestamps of the currently activated desktop * and returns them as an string-array. * * @return a string-array with all entry-timestamps of those entry with the parent * {@code bullet}. */ public String[] retrieveEntryTimestampsFromDesktop() { return retrieveBulletTimestamps(getCurrentDesktopElement()); } /** * This method retrieves all entry-timestamps of those entry which are children of the * element {@code bullet} and returns them as an string-array. * * @param bullet the (bullet-)element of the desktop which entries should be retrieved. * @return a string-array with all entry-timestamps of those entry with the parent * {@code bullet}. */ public String[] retrieveBulletTimestamps(Element bullet) { // if we have such element, go on... if (bullet != null) { // create new list that will contain the return values timestampList = new ArrayList<String>(); // fill list with all entry-numbers. since we have sub-bullets/levels, we // recursevly go through the desktop-data retrieveDesktopEntriesTimestamps(bullet); // if we have any results, go on... if (timestampList.size() > 0) { // return list return timestampList.toArray(new String[timestampList.size()]); } } return null; } /** * * @param e */ private void retrieveDesktopEntries(Element e) { // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // we have to ignore the comment-tags here. comments are no tags that will // be displayed in the jtree, but that store comments which will be displayed // in the jeditorpane (see "updateDisplay" method for further details) if (!e.getName().equals(ATTR_COMMENT)) { // check whether we have no bullet, just an entry if (!e.getName().equals(ELEMENT_BULLET)) { // retrieve id-attribute String att = e.getAttributeValue("id"); // check for valid value if (att != null) { // get entry-number int enr = Integer.parseInt(att); // sort list so we can search whether entry-number already exists Collections.sort(retrieveList); // search for double entries if (Collections.binarySearch(retrieveList, enr) < 0) { // now we know we have an entry. so get the entry number... retrieveList.add(enr); } } } // when the new element also has children, call this method again, // so we go through the strucuture recursively... if (hasChildren(e)) { retrieveDesktopEntries(e); } } } } /** * This method retrieves all entries' timestamps of the element {@code e} and all its * child-element. Thus, {@code e} could either be a root-(desktop-)element or a bullet-element. * * @param e the starting-element from where we want to have all entries' timestamps, including * all children of {@code e}. */ private void retrieveDesktopEntriesTimestamps(Element e) { // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // we have to ignore the comment-tags here. comments are no tags that will // be displayed in the jtree, but that store comments which will be displayed // in the jeditorpane (see "updateDisplay" method for further details) if (!e.getName().equals(ATTR_COMMENT)) { // check whether we have no bullet, just an entry if (!e.getName().equals(ELEMENT_BULLET)) { // get timestamp-attribute String att = e.getAttributeValue(ATTR_TIMESTAMP); // and add its timestamp to the list if (att != null) timestampList.add(att); } // when the new element also has children, call this method again, // so we go through the strucuture recursively... if (hasChildren(e)) { retrieveDesktopEntriesTimestamps(e); } } } } /** * This method retrieves the content of a modified entry. the modified entries' content is stored * in a separated XML-Document (see {@link #modifiedEntries modifiedEntries}. each element of this * document has a timestamp-attribute that equals the timestamp-attribute of an entry in the * {@link #desktop desktop}-Document. * <br><br> * So, by passing a {@code timestamp} value, this method searches whether we have any modified entry * that has the same timestamp-attribut, and if so, it returns the content of that element, which was * modified (and thus differs from an entry's content as it is stored in the original database). * * @param timestamp the timestamp which should match the requested entry's timestamp-attribute * @return the content of the modified entry, or {@code null} if no entry was found. */ public String retrieveModifiedEntryContentFromTimestamp(String timestamp) { // retrieve element Element retval = retrieveModifiedEntryElementFromTimestamp(timestamp); // check for content if (retval != null) return retval.getText(); // else return null return null; } /** * This method retrieves the content of an entry (original content, not the modifications!). * <br><br> * By passing a {@code timestamp} value, this method searches whether we have any entry * that has the same timestamp-attribut, and if so, it returns the original content of that element. * * @param timestamp the timestamp which should match the requested entry's timestamp-attribute * @return the content of the entry, or {@code null} if no entry was found. */ public String retrieveOriginalEntryContentFromTimestamp(String timestamp) { // retrieve element Element retval = findEntryElementFromTimestamp(getCurrentDesktopElement(), timestamp); // check for content if (retval != null) return retval.getText(); // else return null return null; } /** * This method retrieves the element of a modified entry. the modified entries' content is stored * in a separated XML-Document (see {@link #modifiedEntries modifiedEntries}. each element of this * document has a timestamp-attribute that equals the timestamp-attribute of an entry in the * {@link #desktop desktop}-Document. * <br><br> * So, by passing a {@code timestamp} value, this method searches whether we have any modified entry * that has the same timestamp-attribut, and if so, it returns that element which was * modified (and thus differs from an entry's content as it is stored in the original database). * * @param timestamp the timestamp which should match the requested entry's timestamp-attribute * @return the modified entry as element, or {@code null} if no entry was found. */ private Element retrieveModifiedEntryElementFromTimestamp(String timestamp) { // retrieve all elements List<Content> elementList = modifiedEntries.getRootElement().getContent(); // when we have any content, go on... if (elementList.size() > 0) { for (Content elementList1 : elementList) { // retrieve each single element Element e = (Element) elementList1; // retrieve timestamp-attribute String att = e.getAttributeValue(ATTR_TIMESTAMP); // compare timestamp-attribute-value to timestamp-parameter if (att != null && att.equals(timestamp)) { // if they match, return that element return e; } } } // else return null return null; } /** * Returns one of the three notes that can be saved with the desktop. * @param nr the number of the notes, either 1, 2 or 3 * @return the content of the notes-textfield, or an empty if an error occured */ public String getDesktopNotes(int nr) { // check for valid parameter if (nr >= 1 && nr <= 3) { // get all children from deskopNotes, since we need to find the right // desktop-element first... List<Element> elementList = desktopNotes.getRootElement().getChildren(); // create an iterartor Iterator<Element> it = elementList.iterator(); // go through all desktop-elements of the desktopNores-file while (it.hasNext()) { // retrieve element Element desk = it.next(); // get name sttribute String att = desk.getAttributeValue("name"); // check for desktop-name if (att != null && att.equals(getCurrentDesktopName())) { // retrieve notes-element Element note = desk.getChild("notes" + String.valueOf(nr)); // return note return (note != null) ? note.getText() : ""; } } } return ""; } /** * Returns one of the three notes that can be saved with the desktop. * @param nr the number of the notes, either 1, 2 or 3 * @param note the content of the notes-textfield */ public void setDesktopNotes(int nr, String note) { // check for valid parameter if (nr >= 1 && nr <= 3 && note != null) { // first check, whether the note has been modified or not. therefor, retrieve // current note-text and compare it to the parameter String currentnote = getDesktopNotes(nr); // if notes don't equal, go on... if (!currentnote.equals(note)) { // get all children from deskopNotes, since we need to find the right // desktop-element first... List<Element> elementList = desktopNotes.getRootElement().getChildren(); // create an iterartor Iterator<Element> it = elementList.iterator(); // go through all desktop-elements of the desktopNores-file while (it.hasNext()) { // retrieve element Element desk = it.next(); // get name sttribute String att = desk.getAttributeValue("name"); // check for desktop-name if (att != null && att.equals(getCurrentDesktopName())) { // retrieve notes-element Element el = desk.getChild("notes" + String.valueOf(nr)); // set note text el.setText(note); // change modify-tag setModified(true); } } } } } /** * This method checks whether an entry with the timestamp {@code timestamp} has been modified * or not. that means, the methods looks for any elements in the {@link #modifiedEntries modifiedEntries} * document that match the timestamp-attribute. * * @param timestamp the timestamp of the possible modified entry. * @return {@code true} if the entry with the timestamp-attribute {@code timestamp} was modified before, * {@code false} otherwise. */ public boolean isModifiedEntry(String timestamp) { return (retrieveModifiedEntryElementFromTimestamp(timestamp) != null); } /** * This method adds a modified entry to the {@link #modifiedEntries modifiedEntries}-Document, * in case the user modifies an entry on the desktop, while the original content of that entry * in the main database should be left unchanged. * * @param timestamp a unique timestamp which is associated to the entry that is modified, so we * can later easily find the related modified content. * @param content the modified content itself. * @return {@code true} if entry was successfully saved, {@code false} if an error occured. */ public boolean addModifiedEntry(String timestamp, String content) { // check for valid parameters if (null == timestamp || timestamp.isEmpty()) return false; if (null == content) content = ""; // first check, whether modified entry already exists. this may // occur, when importing archived desktop-files if (null == retrieveModifiedEntryElementFromTimestamp(timestamp)) { // create a new element Element entry = new Element(ELEMENT_ENTRY); try { // set timestamp-attribute entry.setAttribute(ATTR_TIMESTAMP, timestamp); // add content-string entry.setText(content); // add element to XML-document modifiedEntries.getRootElement().addContent(entry); // change modified-flag setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); return false; } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); return false; } } return true; } /** * This method changes the content of a modified entry in the {@link #modifiedEntries modifiedEntries}-Document. * Modified entries are entries that the user modifies on the desktop, while the original content of that entry * in the main database should be left unchanged. * * @param timestamp a unique timestamp which is associated to the entry that is modified, so we * can later easily find the related modified content. * @param content the modified content itself. * @return {@code true} if the entry was successfully changed, {@code false} if an error occured. */ public boolean changeEntry(String timestamp, String content) { // retrieve entry-element that should be changed Element entry = retrieveModifiedEntryElementFromTimestamp(timestamp); // check for valid element if (null == entry) { // if we have no such element, add the modified entry return addModifiedEntry(timestamp, content); } else { try { // else change the content of that element entry.setText(content); // and change modified flag setModified(true); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } } return true; } /** * This method updates the timestamp-attributes when the datafile is being updated due to * a newer file-version.<br><br> * This method is called when updating a file with version number 3.0 or 3.1 to 3.2 and higher. */ public void db_updateTimestamps() { // use increasing id, for a unique timestamp-value timestampid = 1; // go through all desktops and update all timestamp-attributes for (int cnt = 0; cnt < getCount(); cnt++) { updateTimestamps(getDesktopElement(cnt)); } } /** * This method initialises the desktopNotes-xml-file during an update-process.<br><br> * This method is called when updating a file with version number 3.0 or 3.1 to 3.2 and higher. */ public void initDesktopNotesUpdate() { // iterate all desktops for (int cnt = 0; cnt < getCount(); cnt++) { // retrieve desktopname String dname = getDesktopName(cnt); // create new desktop-element Element desk = new Element("desktop"); try { // set name attribute desk.setAttribute("name", dname); // add notes-sub-elements desk.addContent(new Element("notes1")); desk.addContent(new Element("notes2")); desk.addContent(new Element("notes3")); // add element to desktop-notes desktopNotes.getRootElement().addContent(desk); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } /** * This method updates the timestamp-attributes when the datafile is being updated due to * a newer file-version.<br><br> * This method is called when updating a file with version number 3.0 or 3.1 to 3.2 and higher. * * @param e the initial element, where the updating should start. usually, use something * like {@link #getDesktopElement(int) getDesktopElement(int)}. */ private void updateTimestamps(Element e) { // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); DecimalFormat df = new DecimalFormat("00000"); // go through all children while (it.hasNext()) { // get the child e = it.next(); try { if (e.getName().equals(ELEMENT_ENTRY)) { String timestamp = e.getAttributeValue(ATTR_TIMESTAMP); if (timestamp != null) e.setAttribute(ATTR_TIMESTAMP, timestamp.concat(df.format(timestampid++))); } if (e.getName().equals(ELEMENT_BULLET)) { e.setAttribute(ATTR_TIMESTAMP, Tools.getTimeStamp() + df.format(timestampid++)); e.setAttribute("treefold", TREE_FOLDING_EXPANDED); } } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } // when the new element also has children, call this method again, // so we go through the strucuture recursively... if (hasChildren(e)) { updateTimestamps(e); } } } /** * This method retrieves the comment of an entry-element which <i>timestamp</i>-attribute * matches the parameter {@code t}. * * @param e the initial element where the search starts. usually, use * {@link #getCurrentDesktopElement() getCurrentDesktopElement()} fo this. * @param t the timestamp which should match the timestamp-attribute of the entry-element * @param c the comment as string. when initially calling this method, pass an empty string * as parameter * @return the comment as string, or an emtpy string if no comment was found */ private String findEntryComment(Element e, String t, String c) { // check for valid string if (null == c) return ""; // check for valid comment. if we already found a comment, // return it if (!c.isEmpty()) return c; // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // check whether element has a timestamp value at all, and if it matches the parameter "t". if (e.getAttributeValue(ATTR_TIMESTAMP) != null && e.getAttributeValue(ATTR_TIMESTAMP).equals(t)) { // check whether we have a bullet-point if (e.getName().equals(ELEMENT_BULLET)) { // if we have a bullet, return the text of it's comment-child. Element comel = e.getChild(ATTR_COMMENT); return (comel != null) ? comel.getText() : ""; } else { // else return the element's text return e.getText(); } } // when the new element also has children, call this method again, // so we go through the strucuture recursively... if (hasChildren(e)) { c = findEntryComment(e, t, c); } } return c; } public int findEntryNrFromTimestamp(String t) { return findEntryNrFromTimestamp(getCurrentDesktopNr(), t); } public int findEntryNrFromTimestamp(int desktopnr, String t) { Element e = findEntryElementFromTimestamp(getDesktopElement(desktopnr), t); // check for valid entry if (null == e) return -1; String att = e.getAttributeValue("id"); if (att != null) { try { // get entry-number return Integer.parseInt(att); } catch (NumberFormatException ex) { return -1; } } return -1; } /** * This method retrieves the entry-element which <i>timestamp</i>-attribute * matches the parameter {@code t}. * * @param e the initial element where the search starts. usually, use * {@link #getCurrentDesktopElement() getCurrentDesktopElement()} fo this. * @param t the timestamp which should match the timestamp-attribute of the entry-element * @return the element which timestamp-attribute matches the parameter {@code t}, or {@code null} * if no element was found */ public Element findEntryElementFromTimestamp(Element e, String t) { // check for valid entry if (null == e) return null; // check whether the element "e" passed as parameter already has a timestamp-attribute that // matches the parameter "t". if so, return that element. if (e.getAttributeValue(ATTR_TIMESTAMP) != null && e.getAttributeValue(ATTR_TIMESTAMP).equals(t)) return e; // get a list with all children of the element List<Element> children = e.getChildren(); // create an iterator Iterator<Element> it = children.iterator(); // go through all children while (it.hasNext()) { // get the child e = it.next(); // check whether element has a timestamp value at all, and if it matches the parameter "t". if (e.getAttributeValue(ATTR_TIMESTAMP) != null && e.getAttributeValue(ATTR_TIMESTAMP).equals(t)) return e; // when the new element also has children, call this method again, // so we go through the strucuture recursively... if (hasChildren(e)) { // go into method again to traverse child-elements e = findEntryElementFromTimestamp(e, t); // when one of the child matches the requested entry, leave function without any further iteration if (e != null && e.getAttributeValue(ATTR_TIMESTAMP) != null && e.getAttributeValue(ATTR_TIMESTAMP).equals(t)) return e; } } if (e != null) return (e.getAttributeValue(ATTR_TIMESTAMP) != null && e.getAttributeValue(ATTR_TIMESTAMP).equals(t)) ? e : null; else return null; } /** * This method retrieves the level (depth) of an bullet-element within the tree-hierarchy. This * method can be used for example to determine the level of a heading, when exporting desktop-data. * * @param t the timestamp which should match the timestamp-attribute of the entry-element * @return the level (depth) of the bullet within the tree-hierarchy as integer-value, * or -1 if an error occured; */ public int getBulletLevel(String t) { // retrieve requestes bullet-element Element el = findEntryElementFromTimestamp(getCurrentDesktopElement(), t); // init bullet-level int bulletlevel = -1; // check for valid returnvalue if (el != null) { // iterate parents while (el.getParentElement() != null) { // increase bullet-level bulletlevel++; // get parent-element el = el.getParentElement(); } } return bulletlevel; } /** * This method archives the desktop-data of the desktop with the name {@code name} to a * zipped xml-file. The file contains the desktop-data, the modifed-entries-data for those entries * that appear on the desktop and the saved desktop-notes. * * @param name the name of the desktop that should be archived. * @return the archived document as XML-focument, or {@code null} if an error occured. */ public Document archiveDesktop(String name) { // create new document Document archive = new Document(new Element("archivedDesktop")); // add desktop-element of desktop that should be archived Element deskel = getDesktopElement(name); // if we found a valid value, go on if (deskel != null) { try { // set name attribute archive.getRootElement().setAttribute("name", name); // create desktop-element Element content_desktop = new Element("desktop"); // clone content from current desktop content_desktop.addContent(deskel.cloneContent()); // add element to archive-file archive.getRootElement().addContent(content_desktop); // now retrieve desktop-notes Element noteel = getDesktopNotes(name); // if we found a valid value, go on if (noteel != null) { // create notes-element Element content_notes = new Element("desktopNotes"); // clone content from current desktop-notes content_notes.addContent(noteel.cloneContent()); // add content archive.getRootElement().addContent(content_notes); } // now retrieve all timestamps from the archived desktop // and look for modified entries... // create new list that will contain the timestamps timestampList = new ArrayList<String>(); // fill list with all entry-numbers. since we have sub-bullets/levels, we // recursevly go through the desktop-data retrieveDesktopEntriesTimestamps(deskel); // if we have any results, go on... if (timestampList.size() > 0) { // create base element Element modifiedel = new Element("modifiedEntries"); // add all modified entries that appear on the archived desktop String[] timestamps = timestampList.toArray(new String[timestampList.size()]); for (String ts : timestamps) { // retrieve modifed entry Element me_dummy = retrieveModifiedEntryElementFromTimestamp(ts); // check for valid value if (me_dummy != null) { // crate new modified-entry-element Element me = new Element(ELEMENT_ENTRY); // set timestamp-attribute me.setAttribute(ATTR_TIMESTAMP, ts); // and add modified text me.setText(me_dummy.getText()); // and add content modifiedel.addContent(me); } } archive.getRootElement().addContent(modifiedel); } } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return null; } } return archive; } /** * This method archives the desktop-data of the desktop with the name {@code name} to a * zipped xml-file. The file contains the desktop-data, the modifed-entries-data for those entries * that appear on the desktop and the saved desktop-notes. * * @param desktopnr the number of the desktop that should be archived. * @return the archived document as XML-document, or {@code null} if an error occured. */ public Document archiveDesktop(int desktopnr) { return archiveDesktop(getDesktopName(desktopnr)); } /** * This method imports an archived desktop-file and appends it to the current desktop-data. * desktop-content, desktop-notes and modifed entries are being added. * @param archive the archive-file as xml-Document * @return one of the following return values:<br> * <ul> * <li>{@link #IMPORT_ARCHIVE_OK IMPORT_ARCHIVE_OK} in case the import was successful</li> * <li>{@link #IMPORT_ARCHIVE_ERR_DESKTOPNAME_EXISTS IMPORT_ARCHIVE_ERR_DESKTOPNAME_EXISTS} in case * the desktop-name already exists, so the user is asked to enter another name</li>> * <li>{@link #IMPORT_ARCHIVE_ERR_OTHER IMPORT_ARCHIVE_ERR_OTHER} in case a general error occured</li> * </ul> */ public int importArchivedDesktop(Document archive) { // get imported desktopname String name = archive.getRootElement().getAttributeValue("name"); // check whether we have any name at all. if not, return false if (null == name || name.isEmpty()) return IMPORT_ARCHIVE_ERR_DESKTOPNAME_EXISTS; // first of all, go through all desktops and check whether the name // already exist, to avoid double naming... // when such a desktopname as "name" already exists, return false for (int cnt = 0; cnt < getCount(); cnt++) if (name.equalsIgnoreCase(getDesktopName(cnt))) return IMPORT_ARCHIVE_ERR_DESKTOPNAME_EXISTS; // create new element Element d = new Element("desktop"); try { // set the desktop's name as attribute d.setAttribute("name", name); // get desktop-content from archive d.addContent(archive.getRootElement().getChild("desktop").cloneContent()); // add the element to the desktop desktop.getRootElement().addContent(d); // set currentDesktop index to the new desktop-element currentDesktop = desktop.getRootElement().getContentSize() - 1; // also add new desktop-notes-element Element desk = new Element("desktop"); // set name attribute desk.setAttribute("name", name); // create notes elements Element n1 = new Element("notes1"); Element n2 = new Element("notes2"); Element n3 = new Element("notes3"); // get notes-child Element noteschild = archive.getRootElement().getChild("desktopNotes"); // check whether we have any content if (noteschild != null) { // get and add notes... Element nc1 = noteschild.getChild("notes1"); if (nc1 != null) n1.setText(nc1.getText()); // get and add notes... Element nc2 = noteschild.getChild("notes2"); if (nc2 != null) n2.setText(nc2.getText()); // get and add notes... Element nc3 = noteschild.getChild("notes3"); if (nc3 != null) n3.setText(nc3.getText()); } // add notes-sub-elements desk.addContent(n1); desk.addContent(n2); desk.addContent(n3); // add element to desktop-notes desktopNotes.getRootElement().addContent(desk); // finally, add modified entries... List<Element> modent = archive.getRootElement().getChild("modifiedEntries").getChildren(); // create iterator Iterator<Element> modit = modent.iterator(); // and add all mofied entries while (modit.hasNext()) { // get element Element mod = modit.next(); // and add modified entry addModifiedEntry(mod.getAttributeValue(ATTR_TIMESTAMP), mod.getText()); } setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return IMPORT_ARCHIVE_ERR_OTHER; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return IMPORT_ARCHIVE_ERR_OTHER; } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return IMPORT_ARCHIVE_ERR_OTHER; } return IMPORT_ARCHIVE_OK; } /** * This method returns all desktop-notes of the desktop with the name {@code desktopname} * as {@code Element} with the notes as children. * If no such desktop exists that matches the parameter {@code desktopname}, {@code null} * is returned. * * @param desktopname the name of the desktop of which we want to retrieve the notes-elements * @return the note-elements of the requested desktop-data, or {@code null} if an error occured */ public Element getDesktopNotes(String desktopname) { // get all children from deskopNotes, since we need to find the right // desktop-element first... List<Element> elementList = desktopNotes.getRootElement().getChildren(); // create an iterartor Iterator<Element> it = elementList.iterator(); // go through all desktop-elements of the desktopNores-file while (it.hasNext()) { // retrieve element Element desk = it.next(); // check for desktop-name if (desk.getAttributeValue("name").equals(desktopname)) { // return note return desk; } } return null; } }