de.danielluedecke.zettelkasten.database.Synonyms.java Source code

Java tutorial

Introduction

Here is the source code for de.danielluedecke.zettelkasten.database.Synonyms.java

Source

/*
 * 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.util.Constants;
import java.util.ArrayList;
import java.util.Arrays;
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;
import org.jdom2.IllegalNameException;

/**
 *
 * @author danielludecke
 */
public class Synonyms {
    /**
     * XML Document that Stores the main data
     */
    private Document synonymsFile;
    private boolean modified;
    /**
     * XML document that is used as backup
     */
    private Document backupdoc = null;

    public Synonyms() {
        clear();
    }

    /**
     * Clears the XML document and creates a dummy-backup of the document, in case the original
     * XML-document contains data.
     */
    public final void clear() {
        // check whether backup document exists, whether autokorrektur-document exists and whether
        // the autokorrektur-document has any data. only in this case we create a backup
        if (synonymsFile != null && synonymsFile.getRootElement().getContentSize() > 1) {
            // create new backup doc
            backupdoc = new Document(new Element("backup_synonyms"));
            // copy content
            backupdoc.getRootElement().addContent(synonymsFile.getRootElement().cloneContent());
        }
        synonymsFile = new Document(new Element("synonyms"));
        // reset modified state
        modified = false;
    }

    /**
     * This method checks whether the XML document is ok or corrupted. in case there have been
     * jdom-errors when adding new elements, the XML document {@link #synonymsFile} might
     * be empty, while the backup-document {@link #backupdoc} has data. In this case,
     * {@code false} is returned. If the XML-document is ok, {@code true} is returned.
     * @return {@code true} if the main XML-document is ok, {@code false}if it might be corrupted.
     * <br><br>
     * You can use {@link #restoreDocument() restoreDocument()} to restore a corrupted document.
     */
    public boolean isDocumentOK() {
        // check whether we have any XML-document at all. proceed only, if we have no document
        // of if the XML-document does not contain data
        if ((null == synonymsFile) || (synonymsFile.getRootElement().getContentSize() < 1)) {
            // now check whether we have a backup of the XML document, which has content
            if ((backupdoc != null) && (backupdoc.getRootElement().getContentSize() > 0)) {
                // if so, the backup contains data that the main document does not has
                // so, we assume the document is *not* ok
                return false;
            }
        }
        // else everything is fine
        return true;
    }

    /**
     * In case we have a corrupted XML document with a backup document that has data
     * (see {@link #isDocumentOK() isDocumentOK()}), we can restore the backupped data
     * with this method.<br><br>
     * So, this method copies back the content of the {@link #backupdoc} to the
     * original XML document {@link #synonymsFile}.
     */
    public void restoreDocument() {
        // check whether we have a backup document that also contains data
        if ((backupdoc != null) && (backupdoc.getRootElement().getContentSize() > 1)) {
            // if we have it, create new main XML document
            synonymsFile = new Document(new Element("synonyms"));
            // and copy the content of the backup document to it
            synonymsFile.getRootElement().addContent(backupdoc.getRootElement().cloneContent());
        }
    }

    /**
     * Sets the document, e.g. after loading the settings
     * @param d the document with the synonyms-data
     */
    public void setDocument(Document d) {
        synonymsFile = d;
        // check whether backup document exists, whether autokorrektur-document exists and whether
        // the autokorrektur-document has any data. only in this case we create a backup
        if (synonymsFile != null && synonymsFile.getRootElement().getContentSize() > 1) {
            // create new backup doc
            backupdoc = new Document(new Element("backup_synonyms"));
            // copy content
            backupdoc.getRootElement().addContent(synonymsFile.getRootElement().cloneContent());
        }
    }

    /**
     * Gets the xml-document that contains the synonyms-data
     * @return the xml-document with the synonyms-data
     */
    public Document getDocument() {
        return synonymsFile;
    }

    /**
     * This method returns the size of the xml data files
     * @return the size of the data file
     */
    public int getCount() {
        return synonymsFile.getRootElement().getContentSize();
    }

    /**
     * Retrieves all synonyms - both index words and related synonyms - that 
     * currently are in the synonyms data base.
     * 
     * @return all current synonyms as string array.
     */
    public String[] getAllSynonyms() {
        LinkedList<String> synlist = new LinkedList<String>();
        // iterate all synonyms elements
        for (int i = 0; i < getCount(); i++) {
            // retrieve synonyms
            String[] syns = getSynonymLine(i, true);
            // add to list
            for (String sy : syns) {
                // check for doubles
                if (!synlist.contains(sy))
                    synlist.add(sy);
            }
        }
        return synlist.toArray(new String[synlist.size()]);
    }

    public void appendSynonyms(Document syndoc) {
        int count = syndoc.getRootElement().getContentSize();
        for (int i = 0; i < count; i++) {
            String[] synline = getSynonymLine(syndoc, i, false);
            addSynonym(synline);
        }
    }

    public void addSynonym(String[] synline) {
        // we need at least two elements in the array: the original word and at least one synonym
        if (null == synline || synline.length < 2) {
            return;
        }
        // if the synonyms-index-word already exists, don't add it...
        if (getSynonymPosition(synline[0]) != -1) {
            return;
        }
        // create new synonyms element
        Element synonym = new Element(Daten.ELEMENT_ENTRY);
        try {
            // trim spaces
            synline[0] = synline[0].trim();
            // set the original word as value-attribute to the "entry"-element
            synonym.setAttribute("indexword", synline[0]);
            // now go through the rest of the string-array
            for (int cnt = 1; cnt < synline.length; cnt++) {
                // create a sub-child "syn" for each further synonym
                Element syn = new Element("syn");
                // set text from string array
                syn.setText(synline[cnt].trim());
                // add child to synonym-element
                synonym.addContent(syn);
            }
            // finally, add new element to the document
            synonymsFile.getRootElement().addContent(synonym);
            setModified(true);
        } 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());
        }
    }

    public String[] getSynonymLine(Document doc, int nr, boolean matchcase) {
        // get element
        Element syn = retrieveElement(doc, nr);
        // init return value
        String[] retval = null;
        // if we have a valid element, go on
        if (syn != null) {
            // get list of child-element with synonyms
            List l = syn.getChildren();
            // create array
            retval = new String[l.size() + 1];
            // first element of the array is the index word
            if (matchcase) {
                // retrieve indexword-attribute
                String attr = syn.getAttributeValue("indexword");
                // check whether value ok
                if (attr != null) {
                    // then use it for array
                    retval[0] = attr;
                } else {
                    // else log error message, telling the number of the corrupted synonym-data
                    Constants.zknlogger.log(Level.WARNING, "No index word for synonym {0} found.",
                            String.valueOf(nr));
                    // and return null
                    return null;
                }
            } else {
                // retrieve indexword-attribute
                String attr = syn.getAttributeValue("indexword");
                // check whether value ok
                if (attr != null) {
                    // then use it for array
                    retval[0] = attr.toLowerCase();
                } else {
                    // else log error message, telling the number of the corrupted synonym-data
                    Constants.zknlogger.log(Level.WARNING, "No index word for synonym {0} found.",
                            String.valueOf(nr));
                    // and return null
                    return null;
                }
            }
            // following elements are the synonyms. therefore, copy the children's text to the array
            for (int cnt = 0; cnt < l.size(); cnt++) {
                // get the element
                Element e = (Element) l.get(cnt);
                // get the element's text
                if (matchcase) {
                    retval[cnt + 1] = e.getText();
                } else {
                    retval[cnt + 1] = e.getText().toLowerCase();
                }
            }
        }
        return retval;
    }

    /**
     * This method returns a synonym (as index-word) with its related synonyms. The return-value
     * is a string-array with the first element being the index-word, and the following elements
     * being the related synonyms.
     *
     * @param nr the number of the requested synonym, with a range from 0 to {@link #getCount()}-1
     * @param matchcase use {@code true} if the synonyms (strings in the return array)
     * should be returned in original case-letters.
     * use {@code false} if they should be returned in lower-case-letters
     * @return a string-array with the first element being the index-word, and the following elements
     * being the related synonyms; or {@code null}, if no synonym was found
     */
    public String[] getSynonymLine(int nr, boolean matchcase) {
        return getSynonymLine(synonymsFile, nr, matchcase);
    }

    /**
     * This method sets a new synonm-line, i.e. a synonym (as index-word) with its related synonyms.
     * The new synonyms have to passed as string-parameter {@code synline}.
     *
     * @param nr the number of the requested synonym, with a range from 0 to (getCount()-1)
     * @param synline a string-array with the first element being the index-word, and the following elements
     * being the related synonyms
     */
    public void setSynonymLine(int nr, String[] synline) {
        // get element
        Element synonym = retrieveElement(nr);
        // remove all child-content (i.e. all synonyms)
        synonym.removeContent();
        try {
            // set the original word as value-attribute to the "entry"-element
            synonym.setAttribute("indexword", synline[0]);
            // now go through the rest of the string-array
            for (int cnt = 1; cnt < synline.length; cnt++) {
                // create a sub-child "syn" for each further synonym
                Element syn = new Element("syn");
                // set text from string array
                syn.setText(synline[cnt]);
                // add child to synonym-element
                synonym.addContent(syn);
                setModified(true);
            }
        } catch (IllegalDataException ex) {
            Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
        } catch (IllegalNameException ex) {
            Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
        }
    }

    public boolean mergeSynonymLines(String originalLineIndexWord, String mergedLineIndexWord) {
        return mergeSynonymLines(getSynonymPosition(originalLineIndexWord),
                getSynonymPosition(mergedLineIndexWord));
    }

    /**
     * This method merges two synonym-lines, where the line given by the index-number {@code originalLine}
     * remains, while the line with the index-number {@code mergedLine} will be appended to the synonyms
     * of the {@code originalLine}.
     *
     * @param originalLine the index-number of the original line that should remain in the database
     * @param mergedLine the index-number of the synonym-line that should be appended to the original line.
     * @return 
     */
    public boolean mergeSynonymLines(int originalLine, int mergedLine) {
        // check for valid parameters
        if (originalLine < 0 || originalLine >= getCount() || mergedLine < 0 || mergedLine >= getCount()) {
            return false;
        }
        // retrieve original synonyms line
        String[] oriline = getSynonymLine(originalLine, true);
        // retrieve apped-synonyms line
        String[] appendline = getSynonymLine(mergedLine, true);
        // check for valid values
        if (null == oriline || null == appendline) {
            return false;
        }
        // create array-list that will contain new synonym line
        List<String> newline = new ArrayList<String>();
        // add all "old", original synonyms
        newline.addAll(Arrays.asList(oriline));
        // now append all "merged" synonyms
        newline.addAll(Arrays.asList(appendline));
        // set new line
        setSynonymLine(originalLine, newline.toArray(new String[newline.size()]));
        // remove old synonym-line
        synonymsFile.getRootElement().removeContent(mergedLine);
        setModified(true);
        return true;
    }

    /**
     * This method sets a new index-word to the synonyms-line with the number {@code nr}.
     *
     * @param nr the number of the synonyms-line, where the index-word should be changed
     * @param indexword the new index-word, as string
     */
    public void setIndexWord(int nr, String indexword) {
        // get element
        Element synonym = retrieveElement(nr);
        // set the original word as value-attribute to the "entry"-element
        synonym.setAttribute("indexword", indexword);
        setModified(true);
    }

    /**
     * This method adds a new synonym {@code appendsyn} to an existing synonym-line {@code nr}.
     * @param nr the number of the existing synonym-line
     * @param appendsyn the new synonym that should be appended to that line.
     */
    public void appendSingleSynonym(int nr, String appendsyn) {
        // get element
        Element synonym = retrieveElement(nr);
        // chekc for valid value
        if (synonym != null) {
            try {
                // create a sub-child "syn" for each further synonym
                Element syn = new Element("syn");
                // set text from string array
                syn.setText(appendsyn);
                // add child to synonym-element
                synonym.addContent(syn);
                setModified(true);
            } catch (IllegalAddException ex) {
                Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
            } catch (IllegalDataException ex) {
                Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
            }
        }
    }

    /**
     * This method returns a synonym (as index-word) with its related synonyms. The return-value
     * is a string-array with the first element being the index-word, and the following elements
     * being the related synonyms.
     * <br><br>
     * If a complete synonyme-line from <b>any word</b> is requested,
     * use {@link #getSynonymLineFromAny(java.lang.String) #getSynonymLineFromAny()}
     *
     * @param indexword string-value of the synonym (original- or index-word) which is searched for in the list
     * @param matchcase
     * @return a string-array with the first element being the index-word, and the following elements
     * being the related synonyms; or {@code null}, if no synonym was found
     */
    public String[] getSynonymLine(String indexword, boolean matchcase) {
        // retrieve position of the index-word
        int pos = getSynonymPosition(indexword);
        // if we found an index-word, return the synonyms, else return null
        if (pos != -1) {
            return getSynonymLine(pos, matchcase);
        } else {
            return null;
        }
    }

    private Element retrieveElement(Document doc, int pos) {
        // create a list of all elements from the given xml file
        try {
            List<?> elementList = doc.getRootElement().getContent();
            // and return the requestet Element
            try {
                return (Element) elementList.get(pos);
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        } catch (IllegalStateException e) {
            Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage());
            return null;
        }
    }

    /**
     * This function retrieves an element of a xml document at a given
     * position. used for other methods like getAuthor or
     * getKeyword. The position is a value from 0 to (size-1).
     *
     * @param pos the position of the element, ranged from 0 to (document-size - 1)
     * @return the element if a match was found, otherwise null
     */
    private Element retrieveElement(int pos) {
        return retrieveElement(synonymsFile, pos);
    }

    /**
     * This method returns the position of a synonym (original or index word) in the XML file
     * if the synonym doesn't exist, the return value is -1.
     * <br><br>
     * 
     * @param indexword string-value of the synonym (original- or index-word) which is searched for in the list
     * @return the position of the synonym or -1 if no match was found
     */
    public int getSynonymPosition(String indexword) {
        // trim spaces
        indexword = indexword.trim();
        // create a list of all author elements from the author xml file
        try {
            List<?> synList = synonymsFile.getRootElement().getContent();
            // and an iterator for the loop below
            Iterator<?> iterator = synList.iterator();
            // counter for the return value if a found synonym matches the parameter
            int cnt = 0;

            while (iterator.hasNext()) {
                Element syn = (Element) iterator.next();
                // if synonym-index-word matches the parameter string, return the position
                if (indexword.equalsIgnoreCase(syn.getAttributeValue("indexword"))) {
                    return cnt;
                }
                // else increase counter
                cnt++;
            }
            // if no author was found, return -1
            return -1;
        } catch (IllegalStateException e) {
            Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage());
            return -1;
        }
    }

    /**
     * This method returns the position of a synonym in the XML file, independent from wether the requested
     * word is an index-word or a related synonym. if the synonym doesn't exist, the return value is -1.
     * <br><br>
     * If the position of a certain index-word only is requestes, use {@link #getSynonymPosition(java.lang.String) getSynonymPosition(java.lang.String)}
     * instead.
     * <br><br>
     * If a complete synonyme-line is requested, use {@link #getSynonymLine(java.lang.String) #getSynonymLine(java.lang.String)}
     * or {@link #getSynonymLineFromAny(java.lang.String) getSynonymLineFromAny(java.lang.String)}.
     *
     * @param synonym string-value of the synonym (wither original- or index-word, or any related synonym)
     * which is searched for in the list.
     * @param matchcase
     * @return the position of the synonym or -1 if no match was found
     */
    public int findSynonym(String synonym, boolean matchcase) {
        // go through all entries
        for (int cnt = 0; cnt < getCount(); cnt++) {
            // get each synonym-line
            String[] synline = getSynonymLine(cnt, matchcase);
            // check for valid value
            if (synline != null) {
                // if the array contains the requested synonym, return line-position
                for (String s : synline) {
                    if (s.equalsIgnoreCase(synonym)) {
                        return cnt;
                    }
                }
            }
        }
        return -1;
    }

    /**
     * This method searches all index-words of the synonyms-file and compares them to the
     * parameter {@code synonym}. If a match was found, i.e. if the parameter {@code synonym} is
     * an index-word, {@code true} is returned, {@code false} otherwise.
     *
     * @param synonym string-value which should be checked whether it is an index-word or not
     * @param matchcase
     * @return {@code true} if the parameter {@code synonym} is an index-word, {@code false} otherwise
     */
    public boolean isIndexWord(String synonym, boolean matchcase) {
        // go through all entries
        for (int cnt = 0; cnt < getCount(); cnt++) {
            // get each synonym-line
            String[] synline = getSynonymLine(cnt, matchcase);
            // if the index-word of the array contains the requested synonym, return true
            if (synline != null && synline[0].equalsIgnoreCase(synonym)) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method searches through all synonyms in the data-file and returns the first index-word
     * when a related synonym of that index-word matches {@code synvalue}.<br><br><b>Note:</b> This method
     * excludes index-words from the search! Only non-index-words of each synonym-line are checked for
     * matching the parameter {@code synvalue}.
     *
     * @param synvalue the synonym where we want to retrieve the related index-word
     * @param matchcase whether the check for the synonyms should be case sensitive {@code true} or not.
     * @return the index-word of the related synonym {@code synvalue}, or {@code null} if no index-word
     * was found.
     */
    public String getIndexWord(String synvalue, boolean matchcase) {
        // go through all entries
        for (int cnt = 0; cnt < getCount(); cnt++) {
            // get each synonym-line
            String[] synline = getSynonymLine(cnt, matchcase);
            // go through array to find the value "synvalue"...
            if (synline != null && synline.length > 1) {
                for (int c = 1; c < synline.length; c++) {
                    // if we found the synvalue, return the related indexword
                    if (synline[c].equalsIgnoreCase(synvalue)) {
                        return synline[0];
                    }
                }
            }
        }
        // else return null...
        return null;
    }

    /**
     * This method returns the related synonyms of a given synonym in the XML file, independent from wether the requested
     * word is an index-word or a related synonym. if the synonym doesn't exist, the return value is null.
     * <br><br>
     * If a complete synonyme-line from the <b>index-word</b> only is requested,
     * use {@link #getSynonymLine(java.lang.String) #getSynonymLine(java.lang.String)}
     *
     * @param synonym string-value of the synonym (wither original- or index-word, or any related synonym)
     * which is searched for in the list.
     * @param matchcase
     * @return the other related synonyms as string array, with the parameter {@code synonym} <b>included</b>,
     * or null if {@code synonym} wasn't found.
     */
    public String[] getSynonymLineFromAny(String synonym, boolean matchcase) {
        // go through all entries
        for (int cnt = 0; cnt < getCount(); cnt++) {
            // get each synonym-line
            String[] synline = getSynonymLine(cnt, matchcase);
            // check for valid value
            if (synline != null) {
                // if the array contains the requested synonym, return the synonym-line (string-array)
                for (String s : synline) {
                    if (s.equalsIgnoreCase(synonym)) {
                        return synline;
                    }
                }
            }
        }
        return null;
    }

    /**
     * sets the modified state of the bookmark-data
     * @param m true when the bookmark-data was modified, or false if modifications were saved.
     */
    public void setModified(boolean m) {
        modified = m;
    }

    /**
     * returns the modified state of the bookmark-data
     * @return {@code true} if the bookmark-data was modified, false if it's unchanged
     */
    public boolean isModified() {
        return modified;
    }
}