View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.handler.codec;
17  
18  import io.netty5.util.concurrent.FastThreadLocal;
19  import io.netty5.util.internal.MathUtil;
20  
21  import java.util.AbstractList;
22  import java.util.RandomAccess;
23  
24  import static java.util.Objects.requireNonNull;
25  
26  /**
27   * Special {@link AbstractList} implementation which is used within our codec base classes.
28   */
29  final class CodecOutputList extends AbstractList<Object> implements RandomAccess {
30  
31      private static final CodecOutputListRecycler NOOP_RECYCLER = object -> {
32          // drop on the floor and let the GC handle it.
33      };
34  
35      private static final FastThreadLocal<CodecOutputLists> CODEC_OUTPUT_LISTS_POOL =
36              new FastThreadLocal<>() {
37                  @Override
38                  protected CodecOutputLists initialValue() throws Exception {
39                      // 16 CodecOutputList per Thread are cached.
40                      return new CodecOutputLists(16);
41                  }
42              };
43  
44      private interface CodecOutputListRecycler {
45          void recycle(CodecOutputList codecOutputList);
46      }
47  
48      private static final class CodecOutputLists implements CodecOutputListRecycler {
49          private final CodecOutputList[] elements;
50          private final int mask;
51  
52          private int currentIdx;
53          private int count;
54  
55          CodecOutputLists(int numElements) {
56              elements = new CodecOutputList[MathUtil.safeFindNextPositivePowerOfTwo(numElements)];
57              for (int i = 0; i < elements.length; ++i) {
58                  // Size of 16 should be good enough for the majority of all users as an initial capacity.
59                  elements[i] = new CodecOutputList(this, 16);
60              }
61              count = elements.length;
62              currentIdx = elements.length;
63              mask = elements.length - 1;
64          }
65  
66          public CodecOutputList getOrCreate() {
67              if (count == 0) {
68                  // Return a new CodecOutputList which will not be cached. We use a size of 4 to keep the overhead
69                  // low.
70                  return new CodecOutputList(NOOP_RECYCLER, 4);
71              }
72              --count;
73  
74              int idx = (currentIdx - 1) & mask;
75              CodecOutputList list = elements[idx];
76              currentIdx = idx;
77              return list;
78          }
79  
80          @Override
81          public void recycle(CodecOutputList codecOutputList) {
82              int idx = currentIdx;
83              elements[idx] = codecOutputList;
84              currentIdx = (idx + 1) & mask;
85              ++count;
86              assert count <= elements.length;
87          }
88      }
89  
90      static CodecOutputList newInstance() {
91          return CODEC_OUTPUT_LISTS_POOL.get().getOrCreate();
92      }
93  
94      private final CodecOutputListRecycler recycler;
95      private int size;
96      private Object[] array;
97      private boolean insertSinceRecycled;
98  
99      private CodecOutputList(CodecOutputListRecycler recycler, int size) {
100         this.recycler = recycler;
101         array = new Object[size];
102     }
103 
104     @Override
105     public Object get(int index) {
106         checkIndex(index);
107         return array[index];
108     }
109 
110     @Override
111     public int size() {
112         return size;
113     }
114 
115     @Override
116     public boolean add(Object element) {
117         requireNonNull(element, "element");
118         try {
119             insert(size, element);
120         } catch (IndexOutOfBoundsException ignore) {
121             // This should happen very infrequently so we just catch the exception and try again.
122             expandArray();
123             insert(size, element);
124         }
125         ++ size;
126         return true;
127     }
128 
129     @Override
130     public Object set(int index, Object element) {
131         requireNonNull(element, "element");
132         checkIndex(index);
133 
134         Object old = array[index];
135         insert(index, element);
136         return old;
137     }
138 
139     @Override
140     public void add(int index, Object element) {
141         requireNonNull(element, "element");
142         checkIndex(index);
143 
144         if (size == array.length) {
145             expandArray();
146         }
147 
148         if (index != size) {
149             System.arraycopy(array, index, array, index + 1, size - index);
150         }
151 
152         insert(index, element);
153         ++ size;
154     }
155 
156     @Override
157     public Object remove(int index) {
158         checkIndex(index);
159         Object old = array[index];
160 
161         int len = size - index - 1;
162         if (len > 0) {
163             System.arraycopy(array, index + 1, array, index, len);
164         }
165         array[-- size] = null;
166 
167         return old;
168     }
169 
170     @Override
171     public void clear() {
172         // We only set the size to 0 and not null out the array. Null out the array will explicit requested by
173         // calling recycle()
174         size = 0;
175     }
176 
177     /**
178      * Returns {@code true} if any elements where added or set. This will be reset once {@link #recycle()} was called.
179      */
180     boolean insertSinceRecycled() {
181         return insertSinceRecycled;
182     }
183 
184     /**
185      * Recycle the array which will clear it and null out all entries in the internal storage.
186      */
187     void recycle() {
188         for (int i = 0 ; i < size; i ++) {
189             array[i] = null;
190         }
191         size = 0;
192         insertSinceRecycled = false;
193 
194         recycler.recycle(this);
195     }
196 
197     /**
198      * Returns the element on the given index. This operation will not do any range-checks and so is considered unsafe.
199      */
200     Object getUnsafe(int index) {
201         return array[index];
202     }
203 
204     private void checkIndex(int index) {
205         if (index >= size) {
206             throw new IndexOutOfBoundsException("expected: index < ("
207                     + size + "),but actual is (" + size + ")");
208         }
209     }
210 
211     private void insert(int index, Object element) {
212         array[index] = element;
213         insertSinceRecycled = true;
214     }
215 
216     private void expandArray() {
217         // double capacity
218         int newCapacity = array.length << 1;
219 
220         if (newCapacity < 0) {
221             throw new OutOfMemoryError();
222         }
223 
224         Object[] newArray = new Object[newCapacity];
225         System.arraycopy(array, 0, newArray, 0, array.length);
226 
227         array = newArray;
228     }
229 }