/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 New Zealand Digital Library Project * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ package org.greenstone.gatherer.cdm; import java.util.*; import javax.swing.*; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.XMLTools; import org.w3c.dom.*; /** This class provides ListModel like access to a list of nodes within a DOM model. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3d */ public class DOMProxyListModel extends AbstractListModel { protected Element root; private DOMProxyListEntry class_type; private HashMap cache = new HashMap (); private NodeList children = null; private String tag_name; /** Constructor. * @param root the Element at the root of the subtree to be searched for appropriate child elements * @param tag_name the name of appropriate elements as a String * @param class_type the type of object to wrap the elements returned in, as a DOMProxyListEntry */ public DOMProxyListModel (Element root, String tag_name, DOMProxyListEntry class_type) { this.class_type = class_type; this.root = root; this.tag_name = tag_name; this.children = this.root.getElementsByTagName (this.tag_name); } /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version always adds the new element at the very head of the DOM. */ public synchronized void add (DOMProxyListEntry entry) { Element element = entry.getElement (); if(root.hasChildNodes ()) { Node sibling = root.getFirstChild (); root.insertBefore (element, sibling); sibling = null; } else { root.appendChild (element); } element = null; // Regardless fire update event cache.clear (); fireIntervalAdded (this, 0, 0); } /** Used to add an element into the underlying dom, and fire the appropriate repaint events. * @param index the index where the element should be inserted (relative of the other elements in this proxy list) * @param entry the DOMProxyListEntry to be inserted */ public synchronized void add (int index, DOMProxyListEntry entry) { ///atherer.println("Add entry at " + index + " where size = " + getSize()); Element element = entry.getElement (); // retrieve the node where we want to insert if(index < children.getLength ()) { Node sibling = children.item (index); // Find the parent node Node parent_node = sibling.getParentNode (); parent_node.insertBefore (element, sibling); sibling = null; } // If the index is too large, we are adding to the end of our list of entries. However you have to remember that this list is only a viewport on the entire DOM so there might be entries following this group that we actually want to insert before (not append at the very end!) else { // Retrieve the currently last entry index = children.getLength () - 1; Node sibling = null; Node parent_node = null; if(index >= 0) { sibling = children.item (index); parent_node = sibling.getParentNode (); sibling = sibling.getNextSibling (); } if(sibling != null && parent_node != null) { parent_node.insertBefore (element, sibling); } // Add to the root node else { index = 0; root.appendChild (element); } } // Regardless fire update event cache.clear (); fireIntervalAdded (this, index, index); } /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version inserts the new entry immediately -after- the given entry in the DOM. * @param entry the DOMProxyListEntry to be inserted * @param preceeding_entry the DOMProxyListEntry immediately before where we want the new entry */ public synchronized void addAfter (DOMProxyListEntry entry, DOMProxyListEntry preceeding_entry) { Element element = entry.getElement (); Element preceeding_sibling = preceeding_entry.getElement (); Node parent_node = preceeding_sibling.getParentNode (); Node following_sibling = preceeding_sibling.getNextSibling (); if(following_sibling != null) { parent_node.insertBefore (element, following_sibling); } else { parent_node.appendChild (element); } // Regardless fire update event cache.clear (); int index = indexOf (entry); fireIntervalAdded (this, index, index); } /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version inserts the new entry immediately -before- the given entry in the DOM. * @param entry the DOMProxyListEntry to be inserted * @param following_entry the DOMProxyListEntry immediately after where we want the new entry */ public synchronized void addBefore (DOMProxyListEntry entry, DOMProxyListEntry following_entry) { Element element = entry.getElement (); Element following_sibling = following_entry.getElement (); Node parent_node = following_sibling.getParentNode (); parent_node.insertBefore (element, following_sibling); // Regardless fire update event cache.clear (); int index = indexOf (entry); fireIntervalAdded (this, index, index); } public synchronized void add (Node parent, DOMProxyListEntry entry, Node sibling) { Element child = entry.getElement (); if(sibling != null) { parent.insertBefore (child, sibling); } else { parent.appendChild (child); } cache.clear (); int index = indexOf (entry); fireIntervalAdded (this, index, index); } /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version always adds the new element at the end of the children. */ public synchronized void append (DOMProxyListEntry entry) { Element element = entry.getElement (); root.appendChild (element); element = null; // Regardless fire update event cache.clear (); fireIntervalAdded (this, 0, 0); } public synchronized ArrayList children () { ArrayList child_list = new ArrayList (); int child_count = children.getLength (); for(int i = 0; i < child_count; i++) { child_list.add (getElementAt (i)); } return child_list; } public synchronized boolean contains (Object entry) { boolean found = false; int size = getSize (); for(int i = 0; !found && i < size; i++) { DOMProxyListEntry sibling = (DOMProxyListEntry) getElementAt (i); if(sibling.equals (entry)) { found = true; } } return found; } public synchronized Object getElementAt (int index) { /** There are times when the length of the 'cache' is not as same as the length of the 'children', etc. not up to date. eg, when a classifier has been deleted. So we rather not have this efficiency for Format4gs3.java.*/ if (class_type instanceof Format4gs3) { Element element = (Element) children.item (index); // Now wrap it in the object of the users choice Object object = class_type.create (element); return object; } Object object = cache.get (new Integer (index)); if (object != null) { return object; } // Retrieve the required element Element element = (Element) children.item (index); DebugStream.println ("Element at index " + index + " not in cache: " + element); // Now wrap it in the object of the users choice object = class_type.create (element); cache.put (new Integer (index), object); return object; } public synchronized int indexOf (DOMProxyListEntry entry) { Element element = entry.getElement (); int children_length = children.getLength (); for(int i = 0; i < children_length; i++) { Node node = children.item (i); if(element == node) { return i; } } return -1; } public synchronized int getSize () { if(children == null) { children = root.getElementsByTagName (tag_name); } return children.getLength (); } public synchronized void refresh () { fireContentsChanged (this, 0, getSize ()); } public synchronized void refresh (DOMProxyListEntry entry) { int index = indexOf (entry); fireContentsChanged (this, index, index); } public synchronized void remove (DOMProxyListEntry entry) { remove (indexOf (entry)); } public synchronized void remove (int index) { // Retrieve the required element Node node = children.item (index); // Find its parent Node parent_node = node.getParentNode (); // Now remove it parent_node.removeChild (node); // Refresh model cache.clear (); fireIntervalRemoved (this, index, index); } /** Changes the 'root' element that this list sources its information from. * @param root the new root Element */ public synchronized void setRoot (Element root) { this.children = null; cache.clear (); this.root = root; this.children = this.root.getElementsByTagName (this.tag_name); fireContentsChanged (this, 0, getSize ()); } public synchronized void setAssigned (boolean assigned) { if (assigned) { root.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR); } else { root.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.FALSE_STR); } } public boolean isAssigned () { return (root.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals ("") || root.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.TRUE_STR)); } }