View Javadoc
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 <a href="mailto:tom.anderson@univ.oxon.org">Tom Anderson</a>
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 }