    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.collections4.list;
    018
    019import java.io.IOException;
    020import java.io.ObjectInputStream;
    021import java.io.ObjectOutputStream;
    022import java.io.Serializable;
    023import java.util.Collection;
    024
    025/**
    026 * A {@code List} implementation that stores a cache of internal Node objects
    027 * in an effort to reduce wasteful object creation.
    028 * <p>
    029 * A linked list creates one Node for each item of data added. This can result in
    030 * a lot of object creation and garbage collection. This implementation seeks to
    031 * avoid that by maintaining a store of cached nodes.
    032 * </p>
    033 * <p>
    034 * This implementation is suitable for long-lived lists where both add and remove
    035 * are used. Short-lived lists, or lists which only grow will have worse performance
    036 * using this class.
    037 * </p>
    038 * <p>
    039 * <strong>Note that this implementation is not synchronized.</strong>
    040 * </p>
    041 *
    042 * @param <E> The type of the elements in the list.
    043 * @since 3.0
    044 * @deprecated parent {@link AbstractLinkedList} is source incompatible with List methods added in Java 21
    045 */
    046@Deprecated
    047public class NodeCachingLinkedList<E> extends AbstractLinkedList<E> implements Serializable {
    048
    049    /** Serialization version */
    050    private static final long serialVersionUID = 6897789178562232073L;
    051
    052    /**
    053     * The default value for {@link #maximumCacheSize}.
    054     */
    055    private static final int DEFAULT_MAXIMUM_CACHE_SIZE = 20;
    056
    057    /**
    058     * The first cached node, or {@code null} if no nodes are cached.
    059     * Cached nodes are stored in a singly-linked list with
    060     * {@code next} pointing to the next element.
    061     */
    062    private transient Node<E> firstCachedNode;
    063
    064    /**
    065     * The size of the cache.
    066     */
    067    private transient int cacheSize;
    068
    069    /**
    070     * The maximum size of the cache.
    071     */
    072    private int maximumCacheSize;
    073
    074    /**
    075     * Constructor that creates.
    076     */
    077    public NodeCachingLinkedList() {
    078        this(DEFAULT_MAXIMUM_CACHE_SIZE);
    079    }
    080
    081    /**
    082     * Constructor that copies the specified collection
    083     *
    084     * @param coll  The collection to copy
    085     */
    086    public NodeCachingLinkedList(final Collection<? extends E> coll) {
    087        super(coll);
    088        this.maximumCacheSize = DEFAULT_MAXIMUM_CACHE_SIZE;
    089    }
    090
    091    /**
    092     * Constructor that species the maximum cache size.
    093     *
    094     * @param maximumCacheSize  The maximum cache size
    095     */
    096    public NodeCachingLinkedList(final int maximumCacheSize) {
    097        this.maximumCacheSize = maximumCacheSize;
    098        init();  // must call init() as use super();
    099    }
    100
    101    /**
    102     * Adds a node to the cache, if the cache isn't full.
    103     * The node's contents are cleared, so they can be garbage collected.
    104     *
    105     * @param node  The node to add to the cache
    106     */
    107    protected void addNodeToCache(final Node<E> node) {
    108        if (isCacheFull()) {
    109            // don't cache the node.
    110            return;
    111        }
    112        // clear the node's contents and add it to the cache.
    113        final Node<E> nextCachedNode = firstCachedNode;
    114        node.previous = null;
    115        node.next = nextCachedNode;
    116        node.setValue(null);
    117        firstCachedNode = node;
    118        cacheSize++;
    119    }
    120
    121    /**
    122     * Creates a new node, either by reusing one from the cache or creating
    123     * a new one.
    124     *
    125     * @param value  value of the new node
    126     * @return The newly created node
    127     */
    128    @Override
    129    protected Node<E> createNode(final E value) {
    130        final Node<E> cachedNode = getNodeFromCache();
    131        if (cachedNode == null) {
    132            return super.createNode(value);
    133        }
    134        cachedNode.setValue(value);
    135        return cachedNode;
    136    }
    137
    138    /**
    139     * Gets the maximum size of the cache.
    140     *
    141     * @return The maximum cache size
    142     */
    143    protected int getMaximumCacheSize() {
    144        return maximumCacheSize;
    145    }
    146
    147    /**
    148     * Gets a node from the cache. If a node is returned, then the value of
    149     * {@link #cacheSize} is decreased accordingly. The node that is returned
    150     * will have {@code null} values for next, previous and element.
    151     *
    152     * @return A node, or {@code null} if there are no nodes in the cache.
    153     */
    154    protected Node<E> getNodeFromCache() {
    155        if (cacheSize == 0) {
    156            return null;
    157        }
    158        final Node<E> cachedNode = firstCachedNode;
    159        firstCachedNode = cachedNode.next;
    160        cachedNode.next = null; // This should be changed anyway, but defensively
    161                                // set it to null.
    162        cacheSize--;
    163        return cachedNode;
    164    }
    165
    166    /**
    167     * Checks whether the cache is full.
    168     *
    169     * @return true if the cache is full
    170     */
    171    protected boolean isCacheFull() {
    172        return cacheSize >= maximumCacheSize;
    173    }
    174
    175    /**
    176     * Deserializes the data held in this object to the stream specified.
    177     *
    178     * @param in  The input stream
    179     * @throws IOException Thrown if an error occurs while reading from the stream
    180     * @throws ClassNotFoundException if an object read from the stream cannot be loaded
    181     */
    182    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    183        in.defaultReadObject();
    184        doReadObject(in);
    185    }
    186
    187    /**
    188     * Removes all the nodes from the list, storing as many as required in the
    189     * cache for reuse.
    190     */
    191    @Override
    192    protected void removeAllNodes() {
    193        // Add the removed nodes to the cache, then remove the rest.
    194        // We can add them to the cache before removing them, since
    195        // {@link AbstractLinkedList.removeAllNodes()} removes the
    196        // nodes by removing references directly from {@link #header}.
    197        final int numberOfNodesToCache = Math.min(size, maximumCacheSize - cacheSize);
    198        Node<E> node = header.next;
    199        for (int currentIndex = 0; currentIndex < numberOfNodesToCache; currentIndex++) {
    200            final Node<E> oldNode = node;
    201            node = node.next;
    202            addNodeToCache(oldNode);
    203        }
    204        super.removeAllNodes();
    205    }
    206
    207    /**
    208     * Removes the node from the list, storing it in the cache for reuse
    209     * if the cache is not yet full.
    210     *
    211     * @param node  The node to remove
    212     */
    213    @Override
    214    protected void removeNode(final Node<E> node) {
    215        super.removeNode(node);
    216        addNodeToCache(node);
    217    }
    218
    219    /**
    220     * Sets the maximum size of the cache.
    221     *
    222     * @param maximumCacheSize  The new maximum cache size
    223     */
    224    protected void setMaximumCacheSize(final int maximumCacheSize) {
    225        this.maximumCacheSize = maximumCacheSize;
    226        shrinkCacheToMaximumSize();
    227    }
    228
    229    /**
    230     * Reduce the size of the cache to the maximum, if necessary.
    231     */
    232    protected void shrinkCacheToMaximumSize() {
    233        // Rich Dougherty: This could be more efficient.
    234        while (cacheSize > maximumCacheSize) {
    235            getNodeFromCache();
    236        }
    237    }
    238
    239    /**
    240     * Serializes this object to an ObjectOutputStream.
    241     *
    242     * @param out The target ObjectOutputStream.
    243     * @throws IOException thrown when an I/O errors occur writing to the target stream.
    244     */
    245    private void writeObject(final ObjectOutputStream out) throws IOException {
    246        out.defaultWriteObject();
    247        doWriteObject(out);
    248    }
    249
    250}