001/*
002 * Copyright 2013 Alex Kasko (alexkasko.com)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.alexkasko.unsafe.offheapstruct;
018
019import com.alexkasko.unsafe.bytearray.ByteArrayTool;
020import com.alexkasko.unsafe.offheap.OffHeapDisposable;
021import com.alexkasko.unsafe.offheap.OffHeapDisposableIterator;
022import com.alexkasko.unsafe.offheap.OffHeapMemory;
023
024/**
025 * <p>Implementation of off-heap array list of structs (memory areas of equal sizes).
026 *
027 * <p>Default implementation uses {@code sun.misc.Unsafe}, with all operations guarded with {@code assert} keyword.
028 * With assertions enabled in runtime ({@code -ea} java switch) {@link AssertionError}
029 * will be thrown on illegal index access. Without assertions illegal index will crash JVM.
030 *
031 * <p>Array won't be zeroed after creation (will contain garbage by default).
032 * Allocated memory may be freed manually using {@link #free()} (thread-safe
033 * and may be called multiple times) or it will be freed after {@link com.alexkasko.unsafe.offheapstruct.OffHeapStructArray}
034 * instance will be garbage collected.
035 *
036 *
037 * @author alexkasko
038 * Date: 7/3/13
039 */
040public class OffHeapStructArrayList implements OffHeapStructCollection, OffHeapDisposable {
041    private static final int MIN_CAPACITY_INCREMENT = 12;
042
043    private final ByteArrayTool bt; // not-null only for on-heap arrays
044    private final int structLength;
045    private OffHeapMemory ohm;
046    private long size;
047    private long capacity;
048
049    /**
050     * Constructor with default capacity = {@code 12}
051     *
052     * @param structLength length of the single struct in bytes, must be >= {@code 8}
053     */
054    public OffHeapStructArrayList(int structLength) {
055        this(MIN_CAPACITY_INCREMENT, structLength);
056    }
057
058    /**
059     * Constructor
060     *
061     * @param capacity initial capacity for this list
062     * @param structLength length of the single struct in bytes, must be >= {@code 8}
063     */
064    public OffHeapStructArrayList(long capacity, int structLength) {
065        this.bt = null;
066        this.structLength = structLength;
067        this.capacity = capacity;
068        this.ohm = OffHeapMemory.allocateMemory(capacity * structLength);
069    }
070
071    /**
072     * Constructor, uses {@link com.alexkasko.unsafe.offheap.OnHeapMemory} underneath
073     * effectively making this instance an <b>OnHeap</b> collection
074     *
075     * @param bt byte array tool to manage on-heap memory of this collection
076     * @param capacity initial capacity for this list
077     * @param structLength length of struct in bytes, must be >= {@code 8}
078     */
079    public OffHeapStructArrayList(ByteArrayTool bt, int capacity, int structLength) {
080        this.bt = bt;
081        this.structLength = structLength;
082        this.capacity = capacity;
083        this.ohm = OffHeapMemory.allocateMemoryOnHeap(bt, capacity * structLength);
084    }
085
086    /**
087     * Returns length of the single struct in bytes
088     *
089     * @return length of the single struct in bytes
090     */
091    @Override
092    public int structLength() {
093        return structLength;
094    }
095
096    /**
097     * Returns number of elements in this list
098     *
099     * @return number of elements in this list
100     */
101    @Override
102    public long size() {
103        return size;
104    }
105
106    /**
107     * Returns max number of possible elements in this list without memory reallocation
108     *
109     * @return max number of possible elements in this list without memory reallocation
110     */
111    public long capacity() {
112        return capacity;
113    }
114
115    /**
116     * Frees allocated memory, may be called multiple times from any thread
117     */
118    @Override
119    public void free() {
120        ohm.free();
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public OffHeapDisposableIterator<byte[]> iterator() {
128        return new OffHeapStructIterator(this);
129    }
130
131    /**
132     * Whether unsafe implementation of {@link OffHeapMemory} is used
133     *
134     * @return whether unsafe implementation of {@link OffHeapMemory} is used
135     */
136    public boolean isUnsafe() {
137        return ohm.isUnsafe();
138    }
139
140    /**
141     * Copies struct on specified index into specified buffer
142     *
143     * @param index array index
144     * @param buffer buffer to copy struct into
145     */
146    @Override
147    public void get(long index, byte[] buffer) {
148        ohm.get(index * structLength, buffer);
149    }
150
151    /**
152     * Copies struct on specified index into specified buffer
153     *
154     * @param index array index
155     * @param buffer buffer to copy struct into
156     * @param bufferPos start position in specified buffer
157     */
158    public void get(long index, byte[] buffer, int bufferPos) {
159        ohm.get(index * structLength, buffer, bufferPos, structLength);
160    }
161
162    /**
163     * Copies part of struct on specified index into specified buffer
164     *
165     * @param index array index
166     * @param pos position in struct
167     * @param buffer buffer to copy struct into
168     * @param bufferPos start position in specified buffer
169     * @param length number of bytes to copy
170     */
171    @Override
172    public void get(long index, int pos, byte[] buffer, int bufferPos, int length) {
173        ohm.get(index * structLength + pos, buffer, bufferPos, length);
174    }
175
176    /**
177     * Copies specified struct contents onto specified index
178     *
179     * @param index array index
180     * @param struct struct to copy into array
181     */
182    @Override
183    public void set(long index, byte[] struct) {
184        ohm.put(index * structLength, struct);
185    }
186
187    /**
188     * Copies specified struct contents onto specified index
189     *
190     * @param index array index
191     * @param struct struct to copy into array
192     * @param structPos start position in specified struct
193     */
194    public void set(long index, byte[] struct, int structPos) {
195        ohm.put(index * structLength, struct, structPos, structLength);
196    }
197
198    /**
199     * Gets byte from struct on specified index with specified offset
200     *
201     * @param index  array index
202     * @param offset struct offset
203     * @return byte
204     */
205    @Override
206    public byte getByte(long index, int offset) {
207        assert offset <= structLength - 1 : offset;
208        return ohm.getByte(index * structLength + offset);
209    }
210
211    /**
212     * Puts byte into struct onto specified index with specified offset
213     *
214     * @param index  array index
215     * @param offset struct offset
216     * @param value  value
217     */
218    @Override
219    public void putByte(long index, int offset, byte value) {
220        assert offset <= structLength - 1 : offset;
221        ohm.putByte(index * structLength + offset, value);
222    }
223
224    /**
225     * Gets one byte (stored as one signed byte) from struct on specified index
226     * with specified offset, converts it to unsigned and returns it as short
227     *
228     * @param index  array index
229     * @param offset struct offset
230     * @return unsigned byte as short
231     */
232    @Override
233    public short getUnsignedByte(long index, int offset) {
234        assert offset <= structLength - 1 : offset;
235        return ohm.getUnsignedByte(index * structLength + offset);
236    }
237
238    /**
239     * Puts short with value from 0 to 255 inclusive into struct onto specified
240     * index with specified offset as one signed byte
241     *
242     * @param index  array index
243     * @param offset struct offset
244     * @param value  unsigned byte
245     */
246    @Override
247    public void putUnsignedByte(long index, int offset, short value) {
248        assert offset <= structLength - 1 : offset;
249        ohm.putUnsignedByte(index * structLength + offset, value);
250    }
251
252    /**
253     * Gets two bytes as short from struct on specified index with specified offset
254     *
255     * @param index  array index
256     * @param offset struct offset
257     * @return short value
258     */
259    @Override
260    public short getShort(long index, int offset) {
261        assert offset <= structLength - 2 : offset;
262        return ohm.getShort(index * structLength + offset);
263    }
264
265    /**
266     * Puts short into struct onto specified index with specified offset as two bytes
267     *
268     * @param index  array index
269     * @param offset struct offset
270     * @param value  short value
271     */
272    @Override
273    public void putShort(long index, int offset, short value) {
274        assert offset <= structLength - 2 : offset;
275        ohm.putShort(index * structLength + offset, value);
276    }
277
278    /**
279     * Gets unsigned short (stored as two bytes) from struct on specified index
280     * with specified offset and returns it as int
281     *
282     * @param index  array index
283     * @param offset struct offset
284     * @return unsigned short as int
285     */
286    @Override
287    public int getUnsignedShort(long index, int offset) {
288        assert offset <= structLength - 2 : offset;
289        return ohm.getUnsignedShort(index * structLength + offset);
290    }
291
292    /**
293     * Puts int with value from 0 to 65535 inclusive into struct onto specified
294     * index with specified offset as two bytes
295     *
296     * @param index  array index
297     * @param offset struct offset
298     * @param value  unsigned short as int
299     */
300    @Override
301    public void putUnsignedShort(long index, int offset, int value) {
302        assert offset <= structLength - 2 : offset;
303        ohm.putUnsignedShort(index * structLength + offset, value);
304    }
305
306    /**
307     * Gets four bytes as int from struct on specified index with specified offset
308     *
309     * @param index  array index
310     * @param offset struct offset
311     * @return int value
312     */
313    @Override
314    public int getInt(long index, int offset) {
315        assert offset <= structLength - 4 : offset;
316        return ohm.getInt(index * structLength + offset);
317    }
318
319    /**
320     * Puts int into struct onto specified index with specified offset as four bytes
321     *
322     * @param index  array index
323     * @param offset struct offset
324     * @param value  int value
325     */
326    @Override
327    public void putInt(long index, int offset, int value) {
328        assert offset <= structLength - 4 : offset;
329        ohm.putInt(index * structLength + offset, value);
330    }
331
332    /**
333     * Gets unsigned int (stored as 4 bytes) and returns it as long
334     * from struct on specified index with specified offset
335     *
336     * @param index  array index
337     * @param offset struct offset
338     * @return unsigned int as long
339     */
340    @Override
341    public long getUnsignedInt(long index, int offset) {
342        assert offset <= structLength - 4 : offset;
343        return ohm.getUnsignedInt(index * structLength + offset);
344    }
345
346    /**
347     * Puts long value from 0 to 4294967295 inclusive into struct onto specified index
348     * with specified offset as four bytes
349     *
350     * @param index  array index
351     * @param offset struct offset
352     * @param value  unsigned int as long
353     */
354    @Override
355    public void putUnsignedInt(long index, int offset, long value) {
356        assert offset <= structLength - 4 : offset;
357        ohm.putUnsignedInt(index * structLength + offset, value);
358    }
359
360    /**
361     * Gets long from struct on specified index with specified offset
362     *
363     * @param index  array index
364     * @param offset struct offset
365     * @return long value
366     */
367    @Override
368    public long getLong(long index, int offset) {
369        assert offset <= structLength - 8 : offset;
370        return ohm.getLong(index * structLength + offset);
371    }
372
373    /**
374     * Puts long into struct onto specified index with specified offset as eight bytes
375     *
376     * @param index  array index
377     * @param offset struct offset
378     * @param value  long value
379     */
380    @Override
381    public void putLong(long index, int offset, long value) {
382        assert offset <= structLength - 8 : offset;
383        ohm.putLong(index * structLength + offset, value);
384    }
385
386    /**
387     * Adds element to the end of this list. Memory area will be allocated another time and copied
388     * on capacity exceed.
389     *
390     * @param struct to add
391     */
392    public void add(byte[] struct) {
393        add(struct, 0);
394    }
395
396    /**
397     * Adds element to the end of this list. Memory area will be allocated another time and copied
398     * on capacity exceed.
399     *
400     * @param struct struct to add
401     * @param structPos struct offset
402     */
403    public void add(byte[] struct, int structPos) {
404        OffHeapMemory oh = ohm;
405        long s = size;
406        if (s == capacity) {
407            long len = s + (s < (MIN_CAPACITY_INCREMENT / 2) ? MIN_CAPACITY_INCREMENT : s >> 1);
408            OffHeapMemory newOhm = null == bt ? OffHeapMemory.allocateMemory(len * structLength)
409                    : OffHeapMemory.allocateMemoryOnHeap(bt, len * structLength);
410            // maybe it's better to use Unsafe#reallocateMemory here
411            oh.copy(0, newOhm, 0, oh.length());
412            oh.free();
413            ohm = newOhm;
414            capacity = len;
415        }
416        size = s + 1;
417        set(s, struct, structPos);
418    }
419
420    /**
421     * Resets the collection setting size to 0.
422     * Actual memory contents stays untouched.
423     */
424    public void reset() {
425        this.size = 0;
426    }
427
428    /**
429     * Shrinks array list capacity to current size
430     */
431    public void shrinkToFit() {
432        long byteslen = size * structLength;
433        OffHeapMemory oh = ohm;
434        OffHeapMemory newOhm = null == bt ? OffHeapMemory.allocateMemory(byteslen)
435                : OffHeapMemory.allocateMemoryOnHeap(bt, byteslen);
436        oh.copy(0, newOhm, 0, byteslen);
437        oh.free();
438        ohm = newOhm;
439        capacity = size;
440    }
441
442    /**
443     * {@inheritDoc}
444     */
445    @Override
446    public String toString() {
447        final StringBuilder sb = new StringBuilder();
448        sb.append("OffHeapStructArrayList");
449        sb.append("{structLength=").append(structLength);
450        sb.append(", ohm=").append(ohm);
451        sb.append(", size=").append(size);
452        sb.append(", capacity=").append(capacity);
453        sb.append('}');
454        return sb.toString();
455    }
456}