/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution 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 verion 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 License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ /* * Created on Dec 3, 2005 */ package org.lobobrowser.html.dombl; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.lobobrowser.html.HtmlAttributeProperties; import org.lobobrowser.html.domfilter.NodeFilter; import org.lobobrowser.html.domimpl.DOMElementImpl; import org.lobobrowser.html.domimpl.DOMNodeImpl; import org.lobobrowser.html.domimpl.HTMLDocumentImpl; import org.lobobrowser.js.AbstractScriptableDelegate; import org.lobobrowser.util.Nodes; import org.lobobrowser.w3c.html.HTMLCollection; import org.w3c.dom.Node; /** * The Class DescendentHTMLCollection. */ public class DescendentHTMLCollection extends AbstractScriptableDelegate implements HTMLCollection { /** The root node. */ private final DOMNodeImpl rootNode; /** The node filter. */ private final NodeFilter nodeFilter; /** The tree lock. */ private final Object treeLock; /** The nest into matching nodes. */ private final boolean nestIntoMatchingNodes; /** * Instantiates a new descendent html collection. * * @param node * the node * @param filter * the filter * @param treeLock * the tree lock */ public DescendentHTMLCollection(DOMNodeImpl node, NodeFilter filter, Object treeLock) { this(node, filter, treeLock, true); } /** * Instantiates a new descendent html collection. * * @param node * the node * @param filter * the filter * @param treeLock * the tree lock * @param nestMatchingNodes * the nest matching nodes */ public DescendentHTMLCollection(DOMNodeImpl node, NodeFilter filter, Object treeLock, boolean nestMatchingNodes) { rootNode = node; nodeFilter = filter; this.treeLock = treeLock; this.nestIntoMatchingNodes = nestMatchingNodes; HTMLDocumentImpl document = (HTMLDocumentImpl) node.getOwnerDocument(); document.addDocumentNotificationListener(new LocalNotificationListener(document, this)); } /** The items by name. */ private Map<String, DOMElementImpl> itemsByName = null; /** The items by index. */ private List<DOMNodeImpl> itemsByIndex = null; /** * Ensure populated impl. */ private void ensurePopulatedImpl() { if (this.itemsByName == null) { ArrayList<DOMNodeImpl> descendents = this.rootNode.getDescendents(this.nodeFilter, this.nestIntoMatchingNodes); this.itemsByIndex = descendents == null ? Collections.emptyList() : descendents; int size = descendents == null ? 0 : descendents.size(); Map<String, DOMElementImpl> itemsByName = new HashMap<String, DOMElementImpl>((size * 3) / 2); this.itemsByName = itemsByName; for (int i = 0; i < size; i++) { Object descNode = descendents.get(i); if (descNode instanceof DOMElementImpl) { DOMElementImpl element = (DOMElementImpl) descNode; String id = element.getId(); if ((id != null) && (id.length() != 0)) { itemsByName.put(id, element); } String name = element.getAttribute(HtmlAttributeProperties.NAME); if ((name != null) && (name.length() != 0) && !name.equals(id)) { itemsByName.put(name, element); } } } } } /** * Invalidate. */ private void invalidate() { synchronized (this.treeLock) { this.itemsByName = null; this.itemsByIndex = null; } } /** * Checks if is valid. * * @return true, if is valid */ private boolean isValid() { synchronized (this.treeLock) { return (this.itemsByName != null) && (this.itemsByIndex != null); } } /* * (non-Javadoc) * * @see org.lobobrowser.w3c.html.HTMLCollection#getLength() */ @Override public int getLength() { synchronized (this.treeLock) { this.ensurePopulatedImpl(); return this.itemsByIndex.size(); } } /* * (non-Javadoc) * * @see org.lobobrowser.w3c.html.HTMLCollection#item(int) */ @Override public Node item(int index) { synchronized (this.treeLock) { this.ensurePopulatedImpl(); try { return this.itemsByIndex.get(index); } catch (IndexOutOfBoundsException iob) { return null; } } } /* * (non-Javadoc) * * @see org.lobobrowser.w3c.html.HTMLCollection#namedItem(java.lang.String) */ @Override public Node namedItem(String name) { synchronized (this.treeLock) { this.ensurePopulatedImpl(); return this.itemsByName.get(name); } } /** * Index of. * * @param node * the node * @return the int */ public int indexOf(Node node) { synchronized (this.treeLock) { this.ensurePopulatedImpl(); return this.itemsByIndex.indexOf(node); } } /** * The listener interface for receiving localNotification events. The class * that is interested in processing a localNotification event implements * this interface, and the object created with that class is registered with * a component using the component's * <code>addLocalNotificationListener</code> method. When the * localNotification event occurs, that object's appropriate method is * invoked. * * @see LocalNotificationEvent */ private static class LocalNotificationListener extends DocumentNotificationAdapter { // Needs to be a static class with a weak reference to // the collection object. /** The document. */ private final HTMLDocumentImpl document; /** The collection ref. */ private final WeakReference<DescendentHTMLCollection> collectionRef; /** * Instantiates a new local notification listener. * * @param document * the document * @param collection * the collection */ public LocalNotificationListener(final HTMLDocumentImpl document, final DescendentHTMLCollection collection) { super(); this.document = document; this.collectionRef = new WeakReference<DescendentHTMLCollection>(collection); } /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DocumentNotificationAdapter# * structureInvalidated (org.lobobrowser.html.domimpl.DOMNodeImpl) */ @Override public void structureInvalidated(DOMNodeImpl node) { DescendentHTMLCollection collection = this.collectionRef.get(); if (collection == null) { // Gone! this.document.removeDocumentNotificationListener(this); return; } if (collection.isValid()) { if (Nodes.isSameOrAncestorOf(collection.rootNode, node)) { collection.invalidate(); } } } /* * (non-Javadoc) * * @see * org.lobobrowser.html.dombl.DocumentNotificationAdapter#nodeLoaded(org * .lobobrowser .html.domimpl.DOMNodeImpl) */ @Override public void nodeLoaded(DOMNodeImpl node) { this.structureInvalidated(node); } } }