1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import java.io.Serializable;
18 import java.lang.ref.WeakReference;
19 import java.util.AbstractList;
20 import java.util.Collections;
21 import java.util.List;
22
23 import org.w3c.dom.Node;
24
25 /**
26 * A generic DomNodeList implementation of {@link org.w3c.dom.NodeList}.
27 *
28 * @param <E> The element type
29 *
30 * @author Daniel Gredler
31 * @author Tom Anderson
32 * @author Ronald Brill
33 */
34 public abstract class AbstractDomNodeList<E extends DomNode> extends AbstractList<E>
35 implements DomNodeList<E>, Serializable {
36
37 /** This node list's root node. */
38 private final DomNode node_;
39
40 /** Element cache, used to avoid XPath expression evaluation as much as possible. */
41 private List<E> cachedElements_;
42
43 /**
44 * Creates a new node list. The elements will be "calculated" using the specified XPath
45 * expression applied on the specified node.
46 * @param node the node to serve as root for the XPath expression
47 */
48 public AbstractDomNodeList(final DomNode node) {
49 super();
50 node_ = node;
51
52 if (node == null) {
53 cachedElements_ = Collections.EMPTY_LIST;
54 return;
55 }
56
57 final DomHtmlAttributeChangeListenerImpl listener = new DomHtmlAttributeChangeListenerImpl(this);
58 node_.addDomChangeListener(listener);
59 }
60
61 /**
62 * Returns the DOM node.
63 * @return the DOM node
64 */
65 protected DomNode getDomNode() {
66 return node_;
67 }
68
69 /**
70 * Returns the elements.
71 * @return the elements
72 */
73 protected abstract List<E> provideElements();
74
75 /**
76 * Returns the nodes in this node list, caching as necessary.
77 * @return the nodes in this node list
78 */
79 private List<E> getNodes() {
80 // a bit of a hack, but I like to avoid synchronization
81 // see https://github.com/HtmlUnit/htmlunit/issues/882
82 //
83 // there is a small chance that the cachedElements_ are
84 // set to null after the assignment and before the return
85 // but this is a race condition at all and depending on the
86 // thread state the same overall result might happen also with sync
87 List<E> shortLivedCache = cachedElements_;
88 if (cachedElements_ == null) {
89 shortLivedCache = provideElements();
90 cachedElements_ = shortLivedCache;
91 }
92 return shortLivedCache;
93 }
94
95 /**
96 * {@inheritDoc}
97 */
98 @Override
99 public int size() {
100 return getLength();
101 }
102
103 /**
104 * {@inheritDoc}
105 */
106 @Override
107 public int getLength() {
108 return getNodes().size();
109 }
110
111 /**
112 * {@inheritDoc}
113 */
114 @Override
115 public Node item(final int index) {
116 return getNodes().get(index);
117 }
118
119 /**
120 * {@inheritDoc}
121 */
122 @Override
123 public E get(final int index) {
124 return getNodes().get(index);
125 }
126
127 /**
128 * DOM change listener which clears the node cache when necessary.
129 */
130 private static final class DomHtmlAttributeChangeListenerImpl implements DomChangeListener {
131
132 private final transient WeakReference<AbstractDomNodeList<?>> nodeList_;
133
134 DomHtmlAttributeChangeListenerImpl(final AbstractDomNodeList<?> nodeList) {
135 super();
136
137 nodeList_ = new WeakReference<>(nodeList);
138 }
139
140 /**
141 * {@inheritDoc}
142 */
143 @Override
144 public void nodeAdded(final DomChangeEvent event) {
145 clearCache();
146 }
147
148 /**
149 * {@inheritDoc}
150 */
151 @Override
152 public void nodeDeleted(final DomChangeEvent event) {
153 clearCache();
154 }
155
156 private void clearCache() {
157 if (nodeList_ != null) {
158 final AbstractDomNodeList<?> nodes = nodeList_.get();
159 if (nodes != null && nodes.node_ != null) {
160 nodes.cachedElements_ = null;
161 }
162 }
163 }
164 }
165
166 }