1 package com.fasterxml.jackson.databind.util;
2 
3 import java.lang.reflect.Array;
4 import java.util.List;
5 
6 /**
7  * Helper class to use for constructing Object arrays by appending entries
8  * to create arrays of various lengths (length that is not known a priori).
9  */
10 public final class ObjectBuffer
11 {
12     // // // Config constants
13 
14     /**
15      * Also: let's expand by doubling up until 64k chunks (which is 16k entries for
16      * 32-bit machines)
17      */
18     private final static int SMALL_CHUNK = (1 << 14);
19 
20     /**
21      * Let's limit maximum size of chunks we use; helps avoid excessive allocation
22      * overhead for huge data sets.
23      * For now, let's limit to quarter million entries, 1 meg chunks for 32-bit
24      * machines.
25      */
26     private final static int MAX_CHUNK = (1 << 18);
27 
28     // // // Data storage
29 
30     private LinkedNode<Object[]> _head;
31 
32     private LinkedNode<Object[]> _tail;
33 
34     /**
35      * Number of total buffered entries in this buffer, counting all instances
36      * within linked list formed by following {@link #_head}.
37      */
38     private int _size;
39 
40     // // // Simple reuse
41 
42     /**
43      * Reusable Object array, stored here after buffer has been released having
44      * been used previously.
45      */
46     private Object[] _freeBuffer;
47 
48     /*
49     /**********************************************************
50     /* Construction
51     /**********************************************************
52      */
53 
ObjectBuffer()54     public ObjectBuffer() { }
55 
56     /*
57     /**********************************************************
58     /* Public API
59     /**********************************************************
60      */
61 
62     /**
63      * Method called to start buffering process. Will ensure that the buffer
64      * is empty, and then return an object array to start chunking content on
65      */
resetAndStart()66     public Object[] resetAndStart()
67     {
68         _reset();
69         if (_freeBuffer == null) {
70             return (_freeBuffer = new Object[12]);
71         }
72         return _freeBuffer;
73     }
74 
75     /**
76      * @since 2.9
77      */
resetAndStart(Object[] base, int count)78     public Object[] resetAndStart(Object[] base, int count)
79     {
80         _reset();
81         if ((_freeBuffer == null) || (_freeBuffer.length < count)) {
82             _freeBuffer = new Object[Math.max(12, count)];
83         }
84         System.arraycopy(base, 0, _freeBuffer, 0, count);
85         return _freeBuffer;
86     }
87 
88     /**
89      * Method called to add a full Object array as a chunk buffered within
90      * this buffer, and to obtain a new array to fill. Caller is not to use
91      * the array it gives; but to use the returned array for continued
92      * buffering.
93      *
94      * @param fullChunk Completed chunk that the caller is requesting
95      *   to append to this buffer. It is generally chunk that was
96      *   returned by an earlier call to {@link #resetAndStart} or
97      *   {@link #appendCompletedChunk} (although this is not required or
98      *   enforced)
99      *
100      * @return New chunk buffer for caller to fill
101      */
appendCompletedChunk(Object[] fullChunk)102     public Object[] appendCompletedChunk(Object[] fullChunk)
103     {
104         LinkedNode<Object[]> next = new LinkedNode<Object[]>(fullChunk, null);
105         if (_head == null) { // first chunk
106             _head = _tail = next;
107         } else { // have something already
108             _tail.linkNext(next);
109             _tail = next;
110         }
111         int len = fullChunk.length;
112         _size += len;
113         // double the size for small chunks
114         if (len < SMALL_CHUNK) {
115             len += len;
116         } else if (len < MAX_CHUNK) { // but by +25% for larger (to limit overhead)
117             len += (len >> 2);
118         }
119         return new Object[len];
120     }
121 
122     /**
123      * Method called to indicate that the buffering process is now
124      * complete; and to construct a combined exactly-sized result
125      * array. Additionally the buffer itself will be reset to
126      * reduce memory retention.
127      *<p>
128      * Resulting array will be of generic <code>Object[]</code> type:
129      * if a typed array is needed, use the method with additional
130      * type argument.
131      */
completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries)132     public Object[] completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries)
133     {
134         int totalSize = lastChunkEntries + _size;
135         Object[] result = new Object[totalSize];
136         _copyTo(result, totalSize, lastChunk, lastChunkEntries);
137         _reset();
138         return result;
139     }
140 
141     /**
142      * Type-safe alternative to
143      * {@link #completeAndClearBuffer(Object[], int)}, to allow
144      * for constructing explicitly typed result array.
145      *
146      * @param componentType Type of elements included in the buffer. Will be
147      *   used for constructing the result array.
148      */
completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries, Class<T> componentType)149     public <T> T[] completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries, Class<T> componentType)
150     {
151        int totalSize = lastChunkEntries + _size;
152  	   @SuppressWarnings("unchecked")
153         T[] result = (T[]) Array.newInstance(componentType, totalSize);
154         _copyTo(result, totalSize, lastChunk, lastChunkEntries);
155         _reset();
156         return result;
157     }
158 
completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries, List<Object> resultList)159     public void completeAndClearBuffer(Object[] lastChunk, int lastChunkEntries, List<Object> resultList)
160     {
161         for (LinkedNode<Object[]> n = _head; n != null; n = n.next()) {
162             Object[] curr = n.value();
163             for (int i = 0, len = curr.length; i < len; ++i) {
164                 resultList.add(curr[i]);
165             }
166         }
167         // and then the last one
168         for (int i = 0; i < lastChunkEntries; ++i) {
169             resultList.add(lastChunk[i]);
170         }
171         _reset();
172     }
173 
174     /**
175      * Helper method that can be used to check how much free capacity
176      * will this instance start with. Can be used to choose the best
177      * instance to reuse, based on size of reusable object chunk
178      * buffer holds reference to.
179      */
initialCapacity()180     public int initialCapacity() {
181         return (_freeBuffer == null) ? 0 : _freeBuffer.length;
182     }
183 
184     /**
185      * Method that can be used to check how many Objects have been buffered
186      * within this buffer.
187      */
bufferedSize()188     public int bufferedSize() { return _size; }
189 
190     /*
191     /**********************************************************
192     /* Internal methods
193     /**********************************************************
194      */
195 
_reset()196     protected void _reset()
197     {
198         // can we reuse the last (and thereby biggest) array for next time?
199         if (_tail != null) {
200             _freeBuffer = _tail.value();
201         }
202         // either way, must discard current contents
203         _head = _tail = null;
204         _size = 0;
205     }
206 
_copyTo(Object resultArray, int totalSize, Object[] lastChunk, int lastChunkEntries)207     protected final void _copyTo(Object resultArray, int totalSize,
208             Object[] lastChunk, int lastChunkEntries)
209     {
210         int ptr = 0;
211 
212         for (LinkedNode<Object[]> n = _head; n != null; n = n.next()) {
213             Object[] curr = n.value();
214             int len = curr.length;
215             System.arraycopy(curr, 0, resultArray, ptr, len);
216             ptr += len;
217         }
218         System.arraycopy(lastChunk, 0, resultArray, ptr, lastChunkEntries);
219         ptr += lastChunkEntries;
220 
221         // sanity check (could have failed earlier due to out-of-bounds, too)
222         if (ptr != totalSize) {
223             throw new IllegalStateException("Should have gotten "+totalSize+" entries, got "+ptr);
224         }
225     }
226 }
227