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