/*
* NotesTreeNode.java - 'node' for the notes tree in the Notes Plugin for GMGen
* Copyright (C) 2003 Devon Jones
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on May 24, 2003
*/
package plugin.notes.gui;
import gmgen.GMGenSystem;
import gmgen.gui.ExtendedHTMLDocument;
import gmgen.gui.ExtendedHTMLEditorKit;
import gmgen.util.MiscUtilities;
import pcgen.cdom.base.Constants;
import pcgen.system.LanguageBundle;
import pcgen.util.Logging;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.html.HTMLWriter;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DropTargetDropEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.*;
/**
* This defines the preferences tree
*
* @author devon
*/
public class NotesTreeNode implements MutableTreeNode, DocumentListener
{
/**
* Property used to know data root?
*/
private static final String DOCROOT = "docroot"; //$NON-NLS-1$
/**
* ?
*/
private static final String DATA_HTML = "data.html"; //$NON-NLS-1$
/**
* To ignore CVS file in the node
*/
private static final String CVS_DIR = "CVS"; //$NON-NLS-1$
/**
* An enumeration that is always empty. This is used when an enumeration of a
* leaf node's children is requested.
*/
public static final Enumeration<MutableTreeNode> EMPTY_ENUMERATION = new Enumeration<MutableTreeNode>()
{
@Override
public boolean hasMoreElements()
{
return false;
}
@Override
public MutableTreeNode nextElement()
{
throw new NoSuchElementException("No more elements"); //$NON-NLS-1$
}
};
/** Document to be displayed when this node is selected. */
protected ExtendedHTMLDocument notesDoc;
/** directory this NotesTreeNode represents. */
protected File dir;
/** Cache of the pane that is displayed when this node is clicked. */
protected JTextPane pane;
/**
* Parent tree that this node is on used for updating the tree when certain
* changes happen.
*/
protected JTree tree;
/** this node's parent, or null if this node has no parent. */
protected MutableTreeNode parent;
/** optional user object */
protected transient Object userObject;
/**
* array of children, may be null if this node has no children.
*
* Note that this cannot be converted into an ArrayList or something other
* than Vector due to the TreeNode interface which uses Enumeration as
* the return type of children();
*/
protected Vector<MutableTreeNode> children;
/** true if the node is able to have children. */
protected boolean allowsChildren = true;
/** is this node dirty (has notesDoc been edited, but is unsaved). */
protected boolean dirty = false;
/** Flag to determine if this node has had it's children populated. */
protected boolean hasBeenPopulated = false;
/**
* setDocument causes an event to fire, which makes the document dirty This
* semaphore prevents that. This is only used if we are not caching the
* JTextPane
*/
protected boolean ignoreUpdateSemaphore = false;
/** Counter used to determine if this node needs to be flushed of it's data. */
protected int cacheCounter = 0;
/**
* Constructor for the NotesTreeNode object.
*
* @param name
* Name of the node
* @param dir
* Directory this node represents
* @param tree
* tree the node will live in
*/
public NotesTreeNode(String name, File dir, JTree tree)
{
userObject = name;
this.tree = tree;
this.dir = dir;
}
/**
* Gets the allowsChildren attribute of the NotesTreeNode object
*
* @return The allowsChildren value
*/
@Override
public boolean getAllowsChildren()
{
return allowsChildren;
}
/**
* Gets the child At a certain index of the NotesTreeNode object
*
* @param index
* index to get the child from
* @return The child At value
*/
@Override
public TreeNode getChildAt(int index)
{
if (!hasBeenPopulated)
{
populate();
}
if (children == null)
{
throw new ArrayIndexOutOfBoundsException("node has no children"); //$NON-NLS-1$
}
return children.elementAt(index);
}
//MutableTreeNode Methods
/**
* Gets the childCount attribute of the NotesTreeNode object
*
* @return The childCount value
*/
@Override
public int getChildCount()
{
int counter = 0;
if (children != null)
{
counter = children.size();
}
if (!hasBeenPopulated)
{
File[] kids = dir.listFiles();
if (kids != null)
{
for (int i = 0; i < kids.length; i++)
{
if (include(kids[i]))
{
counter++;
}
}
}
}
return counter;
}
/**
* Gets the directory that this object represents
*
* @return The dir
*/
public File getDir()
{
return dir;
}
/**
* Determines if the 'data.html' for this dir has been modified, but is not
* saved.
*
* @return boolean of the dirty state
*/
public boolean isDirty()
{
return dirty;
}
public boolean isEmpty()
{
File[] aChildren = dir.listFiles();
if ((aChildren.length > 0) || dirty)
{
return false;
}
return true;
}
/**
* Gets the files that are in the directory this object represents (but not
* directories
*
* @return A List of File objects
*/
public List<File> getFiles()
{
ArrayList<File> list = new ArrayList<>();
for (File child : dir.listFiles())
{
if (!child.isDirectory())
{
if (!child.getName().equals(DATA_HTML))
{
list.add(child);
}
}
}
return list;
}
/**
* Gets the index of a particular TreeNode
*
* @param node
* Node to get the index of
* @return The index value
*/
@Override
public int getIndex(TreeNode node)
{
if (node == null)
{
throw new IllegalArgumentException("argument is null"); //$NON-NLS-1$
}
if (!hasBeenPopulated)
{
populate();
}
if (!isNodeChild(node))
{
return -1;
}
return children.indexOf(node);
// linear search
}
/**
* determines if this node is a leaf or a branch
*
* @return The leaf value
*/
@Override
public boolean isLeaf()
{
return (getChildCount() == 0);
}
/**
* Gets the nodeAncestor attribute of the NotesTreeNode object
*
* @param node
* Description of the Parameter
* @return The nodeAncestor value
*/
public boolean isNodeAncestor(TreeNode node)
{
if (node == null)
{
return false;
}
TreeNode ancestor = this;
do
{
if (ancestor == node)
{
return true;
}
}
while ((ancestor = ancestor.getParent()) != null);
return false;
}
/**
* Gets the nodeChild attribute of the NotesTreeNode object
*
* @param node
* Description of the Parameter
* @return The nodeChild value
*/
public boolean isNodeChild(TreeNode node)
{
boolean retval;
if (node == null)
{
retval = false;
}
else
{
if (getChildCount() == 0)
{
retval = false;
}
else
{
retval = (node.getParent() == this);
}
}
return retval;
}
/**
* Sets the node of this object.
*
* @param newParent
* The new parent value
*/
@Override
public void setParent(MutableTreeNode newParent)
{
parent = newParent;
}
/**
* Gets the parent TreeNode of the NotesTreeNode object
*
* @return The parent value
*/
@Override
public TreeNode getParent()
{
return parent;
}
/**
* Gets a JTextPane that contains the content of the "data.html" in this
* directory (or the modified document if it has been modified), or is empty
* if that file does not exist. This function takes in an external JTextPan
* that it populates in excruciatingly slow speed.
*
* @param editor
* Editor pane you want to populate
* @return The populated Pane
*/
public JTextPane getTextPane(JTextPane editor)
{
boolean repopulate = false;
cacheCounter = 10;
if (notesDoc != null)
{
//setDocument causes an event to fire, which makes the document dirty -
// these semaphores prevent that
ignoreUpdateSemaphore = true;
}
if (pane == null)
{
pane = editor;
repopulate = true;
ExtendedHTMLEditorKit htmlKit = new ExtendedHTMLEditorKit();
pane.setEditorKit(htmlKit);
notesDoc = (ExtendedHTMLDocument) (htmlKit.createDefaultDocument());
notesDoc.putProperty(DOCROOT, dir.getAbsolutePath()
+ File.separator + DATA_HTML);
}
pane.setDocument(notesDoc);
if (repopulate)
{
File notes =
new File(dir.getAbsolutePath() + File.separator
+ DATA_HTML);
if (notes.exists())
{
try
{
BufferedReader br =
new BufferedReader(new FileReader(notes));
StringBuilder sb = new StringBuilder();
String newLine;
do
{
newLine = br.readLine();
if (newLine != null)
{
sb.append(newLine).append(Constants.LINE_SEPARATOR);
}
}
while (newLine != null);
br.close();
pane.setText(sb.toString());
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
notesDoc.addDocumentListener(this);
}
return pane;
}
/**
* Gets a JTextPane that contains the content of the "data.html" in this
* directory (or the modified document if it has been modified), or is empty
* if that file does not exist. This function caches the JTextPan so that the
* speed doesn't suck.
*
* @return The populated JTextPane
*/
public JTextPane getTextPane()
{
boolean repopulate = false;
cacheCounter = 10;
if (pane == null)
{
pane = new JTextPane();
repopulate = true;
ExtendedHTMLEditorKit htmlKit = new ExtendedHTMLEditorKit();
pane.setEditorKit(htmlKit);
notesDoc = (ExtendedHTMLDocument) (htmlKit.createDefaultDocument());
notesDoc.putProperty(DOCROOT, dir.getAbsolutePath()
+ File.separator + DATA_HTML);
}
pane.setDocument(notesDoc);
if (repopulate)
{
File notes =
new File(dir.getAbsolutePath() + File.separator
+ DATA_HTML);
if (notes.exists())
{
try
{
BufferedReader br =
new BufferedReader(new FileReader(notes));
StringBuilder sb = new StringBuilder();
String newLine;
do
{
newLine = br.readLine();
if (newLine != null)
{
sb.append(newLine).append(Constants.LINE_SEPARATOR);
}
}
while (newLine != null);
br.close();
pane.setText(sb.toString());
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
pane.setCaretPosition(0);
notesDoc.addDocumentListener(this);
}
return pane;
}
/**
* Determines if any of this node's children are dirty
*
* @return true if even a single node is dirty, false otherwise.
*/
public boolean isTreeDirty()
{
if (dirty)
{
return true;
}
if (hasBeenPopulated)
{
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
if (node.isTreeDirty())
{
return true;
}
}
}
return false;
}
/**
* Sets the userObject attribute of the NotesTreeNode object
*
* @param object
* The new userObject value
*/
@Override
public void setUserObject(Object object)
{
this.userObject = object;
}
//DefaultMutableTreeNode Methods we like ;)
/**
* Gets the userObject attribute of the NotesTreeNode object
*
* @return The userObject value
*/
public Object getUserObject()
{
return userObject;
}
/**
* adds a MutableTreeNode
*
* @param node
* Node to add
*/
public void add(MutableTreeNode node)
{
if ((node != null) && (node.getParent() == this))
{
insert(node, getChildCount() - 1);
}
else
{
insert(node, getChildCount());
}
}
public void appendText(String text)
{
try
{
if (notesDoc == null)
{
getTextPane();
}
notesDoc.insertAfterEnd(notesDoc.getCharacterElement(notesDoc
.getLength()), text);
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
/**
* This listener method is intended to listen to the notesDoc. A change event
* has occurred, so mark the document as dirty.
*
* @param e
* a Document Event
*/
@Override
public void changedUpdate(DocumentEvent e)
{
if (ignoreUpdateSemaphore)
{
ignoreUpdateSemaphore = false;
}
else
{
dirty = true;
tree.updateUI();
}
}
/**
* Check to see if this object's cache should be cleared. If it should,
* revert. Then check all children
*/
public void checkCache()
{
if (!dirty)
{
if (pane != null)
{
if (cacheCounter > 0)
{
cacheCounter--;
}
else
{
revert();
}
}
}
if (hasBeenPopulated)
{
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
node.checkCache();
}
}
}
public static String checkName(String name)
{
String returnName = name.replaceAll("\\:", "-");
returnName = returnName.replaceAll("\\;", "-");
returnName = returnName.replaceAll("\\+", "-");
returnName = returnName.replaceAll("\\=", "-");
returnName = returnName.replaceAll("\\|", "-");
returnName = returnName.replaceAll("\\?", "-");
returnName = returnName.replaceAll("\\*", "-");
return returnName;
}
/**
* Check to see if this node or any of it's children are dirty. If they are,
* ask if the user wants to save them, and then do so.
*/
public void checkSave()
{
if (userObject.equals("Logs"))
{
if (isTreeDirty())
{
int choice =
JOptionPane.showConfirmDialog(GMGenSystem.inst,
"You have unsaved Logs. Save?", "Save",
JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION)
{
save();
saveChildren();
}
}
trimEmpty();
}
else
{
if (dirty)
{
int choice =
JOptionPane.showConfirmDialog(GMGenSystem.inst,
"Note '" + getUserObject() + "' changed. Save?",
"Save", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION)
{
save();
}
}
if (hasBeenPopulated)
{
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
node.checkSave();
}
}
}
}
/**
* Returns an enumeration of this node's children
*
* @return Enumeration containing MutableTreeNodes
*/
@Override
public Enumeration<MutableTreeNode> children()
{
if (!hasBeenPopulated)
{
populate();
}
if (children == null)
{
return EMPTY_ENUMERATION;
}
return children.elements();
}
/**
* Create a new child named newName (n), and create it's directory.
*
* @param newName
* name to attempt to create - if it exists, (n) will be appended
* where n = the number of existing directories with the same name
* @return NotesTreeNode
*/
public NotesTreeNode createChild(String newName)
{
boolean notDone = true;
newName = checkName(newName);
int num = 1;
if (!hasBeenPopulated)
{
populate();
}
while (notDone)
{
String baseName = newName;
if (num > 1)
{
baseName += num;
}
File newDir =
new File(dir.getAbsolutePath() + File.separator + baseName);
if (!newDir.exists())
{
try
{
newDir.mkdir();
notDone = false;
NotesTreeNode newNode =
new NotesTreeNode(baseName, newDir, tree);
add(newNode);
return newNode;
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
return null;
}
}
num++;
}
return null;
}
/** Create a new child named "New Node (n)", and create it's directory.
* @return NotesTreeNode */
public NotesTreeNode createChild()
{
return createChild(LanguageBundle.getString("in_plugin_notes_newNote"));
}
/**
* Try to delete this node. If it has children, or content, throw a dialog to
* let the user block this deletion. If any files or directories cannot be
* deleted, let the use know.
*/
public void delete()
{
if (!isEmpty())
{
int choice =
JOptionPane.showConfirmDialog(GMGenSystem.inst, "Node "
+ dir.getName() + " Contains Content. Delete?",
"Node Populated", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (choice != JOptionPane.YES_OPTION)
{
return;
}
}
try
{
for (File child : dir.listFiles())
{
boolean test = child.delete();
if (!test)
{
JOptionPane.showMessageDialog(null, "Cannot delete file "
+ child.getName());
break;
}
}
boolean test = dir.delete();
if (test)
{
removeFromParent();
}
else
{
JOptionPane.showMessageDialog(null, "Cannot delete directory "
+ dir.getName());
}
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
//Listener functions
/**
* handles a drop of a java file list
*
* @param dtde
* drop target drop even - a java dile list has been dropped on
* something that represents this node.
* @return returns true if the drop takes place, false if not
*/
public boolean handleDropJavaFileList(DropTargetDropEvent dtde)
{
dtde.acceptDrop(dtde.getDropAction());
Transferable t = dtde.getTransferable();
try
{
List fileList =
((List) t.getTransferData(DataFlavor.javaFileListFlavor));
for (int i = 0; i < fileList.size(); i++)
{
File newFile = (File) fileList.get(i);
if (newFile.exists())
{
MiscUtilities.copy(newFile, new File(dir.getAbsolutePath()
+ File.separator + newFile.getName()));
}
}
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
return false;
}
return true;
}
/**
* Inserts a new MutableTreeNode into this node as a child.
*
* @param child
* Child to insert
* @param index
* Location to insert it.
*/
@Override
public void insert(MutableTreeNode child, int index)
{
if (!allowsChildren)
{
throw new IllegalStateException("node does not allow children");
}
else if (child == null)
{
throw new IllegalArgumentException("new child is null");
}
else if (isNodeAncestor(child))
{
throw new IllegalArgumentException("new child is an ancestor");
}
if (!hasBeenPopulated)
{
populate();
}
MutableTreeNode oldParent = (MutableTreeNode) child.getParent();
if (oldParent != null)
{
oldParent.remove(child);
}
child.setParent(this);
if (children == null)
{
children = new Vector<MutableTreeNode>();
}
children.insertElementAt(child, index);
}
/**
* This listener method is intended to listen to the notesDoc. An incert event
* has occurred, so mark the document as dirty.
*
* @param e
* a Document Event
*/
@Override
public void insertUpdate(DocumentEvent e)
{
dirty = true;
tree.updateUI();
}
/** Refereshs the tree to take into account any added/removed directories */
public void refresh()
{
// TODO: This function seems to not always generate the proper results.
// Sometimes duplicating nodes.
if (hasBeenPopulated)
{
Enumeration<MutableTreeNode> childNodes = children();
List<File> childDirs = Arrays.asList(dir.listFiles());
List<File> removeDirs = new ArrayList<File>();
for (; childNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) childNodes.nextElement();
File nodeDir = node.getDir();
if (nodeDir.exists())
{
for (int i = 0; i < childDirs.size(); i++)
{
File childDir = childDirs.get(i);
if (nodeDir.getName().equals(childDir.getName()))
{
removeDirs.add(childDir);
continue;
}
}
}
else
{
remove(node);
}
}
for (File childDir : childDirs)
{
if (!removeDirs.contains(childDir))
{
if (include(childDir))
{
add(new NotesTreeNode(childDir.getName(), childDir,
tree));
}
}
}
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
node.refresh();
}
}
}
/**
*
* @param childDir
* @return true if the file is to be included
*/
private boolean include(File f)
{
return f.isDirectory()
&& !f.getName().equals(CVS_DIR)
&& !f.isHidden();
}
/**
* As we rename a directory, we need to re-home all of it's children to the
* new directory. This function replaces the Directory of this object, then
* re-homs all of it's children.
*
* @param path
* New path to move the children to.
*/
public void rehome(String path)
{
// TODO: Children cease being editable after a rehome, fix this.
dir = new File(path + File.separator + dir.getName());
rehomeChildren(dir.getAbsolutePath());
notesDoc.putProperty(DOCROOT, dir.getAbsolutePath() + File.separator
+ DATA_HTML);
}
/**
* Rehomes the children
*
* @param path
* New path for the child
*/
public void rehomeChildren(String path)
{
if (hasBeenPopulated)
{
Enumeration<MutableTreeNode> childNodes = children();
for (; childNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) childNodes.nextElement();
node.rehome(path);
}
}
}
/**
* removes the child node at index
*
* @param index
* index of child to remove
*/
@Override
public void remove(int index)
{
if (!hasBeenPopulated)
{
populate();
}
MutableTreeNode child = (MutableTreeNode) getChildAt(index);
children.removeElementAt(index);
child.setParent(null);
}
/**
* removes the passed in MutableTreeNode
*
* @param node
* node to remove
*/
@Override
public void remove(MutableTreeNode node)
{
if (node == null)
{
throw new IllegalArgumentException("argument is null");
}
if (!isNodeChild(node))
{
throw new IllegalArgumentException("argument is not a child");
}
if (!hasBeenPopulated)
{
populate();
}
remove(getIndex(node));
// linear search
}
/** Removes all child nodes */
public void removeAllChildren()
{
for (int i = children.size() - 1; i >= 0; i--)
{
remove(i);
}
}
/** Removes this node from it's parent */
@Override
public void removeFromParent()
{
MutableTreeNode aParent = (MutableTreeNode) getParent();
if (aParent != null)
{
aParent.remove(this);
}
}
/**
* This listener method is intended to listen to the notesDoc. A remove event
* has occurred, so mark the document as dirty.
*
* @param e
* a Document Event
*/
@Override
public void removeUpdate(DocumentEvent e)
{
dirty = true;
tree.updateUI();
}
/**
* This Renames the object, and it's directory, and then re-homes all of the
* children.
*
* @param newName
* New name for the node
*/
public void rename(String newName)
{
String path = dir.getParent();
if (dir.exists())
{
String oldPath = dir.getAbsolutePath();
String oldName = dir.getName();
boolean tryrename =
dir.renameTo(new File(path + File.separator + newName));
if (!tryrename)
{
dir = new File(oldPath);
setUserObject(oldName);
}
else
{
dir = new File(path + File.separator + newName);
rehomeChildren(dir.getAbsolutePath());
}
}
else
{
dir = new File(path + File.separator + newName);
dir.mkdirs();
}
}
/** reverts back to the saved file (and as a consequence, clears the cache) */
public void revert()
{
//If it is modified, confirm
if (dirty)
{
int choice =
JOptionPane
.showConfirmDialog(
GMGenSystem.inst,
"Note '"
+ getUserObject()
+ "' has been altered, are you sure you wish to revert to the saved copy?",
"Revert?", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.NO_OPTION)
{
return;
}
}
dirty = false;
pane = null;
notesDoc.removeDocumentListener(this);
notesDoc = null;
}
/** Saves this node's data. */
public void save()
{
if (dirty)
{
try
{
File notes =
new File(dir.getAbsolutePath() + File.separator
+ DATA_HTML);
if (!notes.exists())
{
notes.createNewFile();
}
if (pane != null)
{
FileWriter fw = new FileWriter(notes);
HTMLWriter hw = new HTMLWriter(fw, notesDoc);
hw.write();
fw.flush();
fw.close();
dirty = false;
}
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
}
public void saveChildren()
{
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
node.save();
node.saveChildren();
}
}
//Other functions
/**
* this method is called to print the name of the node in the tree
*
* @return the name of the node (with a * if it is dirty)
*/
@Override
public String toString()
{
if (dirty)
{
return "*" + getUserObject().toString();
}
return getUserObject().toString();
}
private void populate()
{
hasBeenPopulated = true;
for (File child : dir.listFiles())
{
if (include(child))
{
add(new NotesTreeNode(child.getName(), child, tree));
}
}
}
private void trimEmpty()
{
Enumeration<MutableTreeNode> newNodes = children();
for (; newNodes.hasMoreElements();)
{
NotesTreeNode node = (NotesTreeNode) newNodes.nextElement();
node.trimEmpty();
if (node.isEmpty())
{
node.delete();
}
}
}
}