All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arcadedb.database.Binary Maven / Gradle / Ivy

There is a newer version: 24.11.1
Show newest version
/*
 * Copyright © 2021-present Arcade Data Ltd ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
 * SPDX-License-Identifier: Apache-2.0
 */
package com.arcadedb.database;

import com.arcadedb.log.LogManager;
import com.arcadedb.serializer.BinaryComparator;
import com.arcadedb.serializer.UnsignedBytesComparator;

import java.io.*;
import java.nio.*;
import java.util.*;
import java.util.logging.*;

/**
 * Binary data type. It is backed by Java Byte Buffers.
 * 
* NOTE: This class is not thread safe and must be not used by multiple threads at the same time. * * @author Luca Garulli */ public class Binary implements BinaryStructure, Comparable { public static final int BYTE_SERIALIZED_SIZE = 1; public static final int SHORT_SERIALIZED_SIZE = 2; public static final int INT_SERIALIZED_SIZE = 4; public static final int LONG_SERIALIZED_SIZE = 8; public static final int FLOAT_SERIALIZED_SIZE = 4; public static final int DOUBLE_SERIALIZED_SIZE = 8; private final static int DEFAULT_ALLOCATION_CHUNK = 512; protected boolean autoResizable = true; protected byte[] content; protected ByteBuffer buffer; protected int size; protected int allocationChunkSize = DEFAULT_ALLOCATION_CHUNK; protected FetchCallback fetchCallback; protected final boolean reusable; public interface FetchCallback { void fetch(Binary newBuffer) throws IOException; } public Binary() { this.content = new byte[allocationChunkSize]; this.buffer = ByteBuffer.wrap(content); this.size = 0; this.reusable = false; } public Binary(final int initialSize, final boolean reusable) { this.content = new byte[initialSize]; this.buffer = ByteBuffer.wrap(content); this.size = 0; this.reusable = reusable; } public Binary(final int initialSize) { this(initialSize, false); } public Binary(final byte[] buffer) { this(buffer, buffer.length); } public Binary(final byte[] buffer, final int contentSize) { this.content = buffer; this.buffer = ByteBuffer.wrap(content); this.size = contentSize; this.autoResizable = false; this.reusable = false; } public Binary(final ByteBuffer buffer) { this.content = buffer.array(); this.buffer = buffer; this.size = buffer.limit(); this.autoResizable = false; this.reusable = false; } public Binary copyOfContent() { final Binary copy = new Binary(Arrays.copyOfRange(content, buffer.arrayOffset(), buffer.arrayOffset() + size), size); copy.setAutoResizable(autoResizable); return copy; } /** * Copy the Binary object without copying the underlying buffer. Use this when the buffer is not modified after the copy. */ public Binary copy() { final Binary copy = new Binary(content, size); copy.autoResizable = autoResizable; copy.buffer.position(buffer.position()); copy.buffer.limit(buffer.limit()); return copy; } public void clear() { size = 0; buffer.clear(); buffer.position(0); } /** * Return a not reusable buffer. if the current object is already not reusable and the underlying buffer fits perfectly the content, then the current object * is returned saving from an unnecessary and expensive copy. */ public Binary getNotReusable() { return reusable || size != content.length || buffer.arrayOffset() > 0 ? copyOfContent() : this; } /** * Tells if the current buffer object is reused. Reusable buffers must be copied to avoid concurrent usage by multiple users. */ public boolean isReusable() { return reusable; } public void rewind() { buffer.position(0); } public Binary setAutoResizable(final boolean autoResizable) { this.autoResizable = autoResizable; return this; } public int getAllocationChunkSize() { return allocationChunkSize; } public void setAllocationChunkSize(final int allocationChunkSize) { this.allocationChunkSize = allocationChunkSize; } @Override public void append(final Binary toCopy) { final int contentSize = toCopy.size(); if (contentSize > 0) { checkForAllocation(buffer.position(), contentSize); buffer.put(toCopy.content, toCopy.getContentBeginOffset(), contentSize); } } @Override public int position() { return buffer.position(); } @Override public void position(final int index) { try { buffer.position(index); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException("Invalid position " + index + " (size=" + buffer.limit() + ")"); } } @Override public void putByte(final int index, final byte value) { checkForAllocation(index, BYTE_SERIALIZED_SIZE); buffer.put(index, value); } @Override public void putByte(final byte value) { checkForAllocation(buffer.position(), BYTE_SERIALIZED_SIZE); buffer.put(value); } @Override public int putNumber(final int index, long value) { value = (value << 1) ^ (value >> 63); return putUnsignedNumber(index, value); } @Override public int putNumber(long value) { value = (value << 1) ^ (value >> 63); return putUnsignedNumber(value); } @Override public int putUnsignedNumber(final int index, final long value) { position(index); return putUnsignedNumber(value); } @Override public int putUnsignedNumber(final long value) { int bytesUsed = 0; long v = value; while ((v & 0xFFFFFFFFFFFFFF80L) != 0L) { checkForAllocation(buffer.position(), BYTE_SERIALIZED_SIZE); buffer.put((byte) (v & 0x7F | 0x80)); bytesUsed++; v >>>= 7; } checkForAllocation(buffer.position(), BYTE_SERIALIZED_SIZE); buffer.put((byte) (v & 0x7F)); bytesUsed++; return bytesUsed; } @Override public void putShort(final int index, final short value) { checkForAllocation(index, SHORT_SERIALIZED_SIZE); buffer.putShort(index, value); } @Override public void putShort(final short value) { checkForAllocation(buffer.position(), SHORT_SERIALIZED_SIZE); buffer.putShort(value); } @Override public void putInt(final int index, final int value) { checkForAllocation(index, INT_SERIALIZED_SIZE); buffer.putInt(index, value); } @Override public void putInt(final int value) { checkForAllocation(buffer.position(), INT_SERIALIZED_SIZE); buffer.putInt(value); } @Override public void putLong(final int index, final long value) { checkForAllocation(index, LONG_SERIALIZED_SIZE); buffer.putLong(index, value); } @Override public void putLong(final long value) { checkForAllocation(buffer.position(), LONG_SERIALIZED_SIZE); buffer.putLong(value); } @Override public int putString(final int index, final String value) { return putBytes(index, value.getBytes(DatabaseFactory.getDefaultCharset())); } @Override public int putString(final String value) { return putBytes(value.getBytes(DatabaseFactory.getDefaultCharset())); } @Override public int putBytes(final int index, final byte[] value) { position(index); return putBytes(value); } @Override public int putBytes(final byte[] value) { final int bytesUsed = putUnsignedNumber(value.length); checkForAllocation(buffer.position(), value.length); buffer.put(value); return bytesUsed + value.length; } @Override public int putBytes(final byte[] value, final int size) { final int bytesUsed = putUnsignedNumber(size); checkForAllocation(buffer.position(), size); buffer.put(value, 0, size); return bytesUsed + size; } @Override public void putByteArray(final int index, final byte[] value) { position(index); putByteArray(value); } @Override public void putByteArray(final int index, final byte[] value, final int offset, final int length) { position(index); putByteArray(value, offset, length); } @Override public void putByteArray(final byte[] value) { checkForAllocation(buffer.position(), value.length); buffer.put(value); } @Override public void putByteArray(final byte[] value, final int length) { putByteArray(value, 0, length); } @Override public void putByteArray(final byte[] value, final int offset, final int length) { checkForAllocation(buffer.position(), length); buffer.put(value, offset, length); } @Override public void putBuffer(final ByteBuffer value) { checkForAllocation(buffer.position(), value.limit()); buffer.put(value); } @Override public byte getByte(final int index) { return buffer.get(index); } @Override public byte getByte() { checkForFetching(1); return buffer.get(); } /** * Reads a signed number. This method is not thread safe * * @return An array of longs with the signed number in the 1st position and the occupied bytes on the 2nd position. */ @Override public long[] getNumberAndSize(final int index) { position(index); final long[] raw = getUnsignedNumberAndSize(); final long temp = (((raw[0] << 63) >> 63) ^ raw[0]) >> 1; // This extra step lets us deal with the largest signed values by // treating negative results from read unsigned methods as like unsigned values // Must re-flip the top bit if the original read value had it set. raw[0] = temp ^ (raw[0] & (1L << 63)); return raw; } @Override public long getNumber() { final long raw = getUnsignedNumber(); final long temp = (((raw << 63) >> 63) ^ raw) >> 1; // This extra step lets us deal with the largest signed values by // treating negative results from read unsigned methods as like unsigned values // Must re-flip the top bit if the original read value had it set. return temp ^ (raw & (1L << 63)); } @Override public long getUnsignedNumber() { long value = 0L; int i = 0; long b; while (((b = getByte()) & 0x80L) != 0) { value |= (b & 0x7F) << i; i += 7; if (i > 63) throw new IllegalArgumentException("Variable length quantity is too long (must be <= 63)"); } return value | (b << i); } /** * Reads an unsigned number. * * @return An array of longs with the unsigned number in the 1st position and the occupied bytes on the 2nd position. */ @Override public long[] getUnsignedNumberAndSize() { long value = 0L; int i = 0; long b; int byteRead = 1; while (((b = getByte()) & 0x80L) != 0) { value |= (b & 0x7F) << i; i += 7; if (i > 63) throw new IllegalArgumentException("Variable length quantity is too long (must be <= 63)"); ++byteRead; } return new long[] { value | (b << i), byteRead }; } @Override public short getShort(final int index) { return buffer.getShort(index); } @Override public short getShort() { checkForFetching(2); return buffer.getShort(); } @Override public short getUnsignedShort() { checkForFetching(2); final int firstByte = (0x000000FF & ((int) buffer.get())); final int secondByte = (0x000000FF & ((int) buffer.get())); return (short) (firstByte << 8 | secondByte); } @Override public int getInt() { checkForFetching(4); return buffer.getInt(); } @Override public int getInt(final int index) { return buffer.getInt(index); } @Override public long getLong() { checkForFetching(8); return buffer.getLong(); } @Override public long getLong(final int index) { return buffer.getLong(index); } @Override public String getString() { return new String(getBytes(), DatabaseFactory.getDefaultCharset()); } @Override public String getString(final int index) { return new String(getBytes(index), DatabaseFactory.getDefaultCharset()); } @Override public void getByteArray(final byte[] buffer) { this.buffer.get(buffer); } @Override public void getByteArray(final int index, final byte[] buffer) { this.buffer.position(index); this.buffer.get(buffer); } @Override public void getByteArray(final int index, final byte[] buffer, final int offset, final int length) { this.buffer.position(index); this.buffer.get(buffer, offset, length); } @Override public byte[] getBytes() { final byte[] result = new byte[(int) getUnsignedNumber()]; if (result.length > 0) { checkForFetching(result.length); buffer.get(result); } return result; } @Override public byte[] getBytes(final int index) { buffer.position(index); return getBytes(); } @Override public byte[] toByteArray() { final byte[] result = new byte[size]; System.arraycopy(content, buffer.arrayOffset(), result, 0, result.length); return result; } @Override public byte[] remainingToByteArray() { final int tot = size - buffer.position(); if (tot < 1) return new byte[0]; final byte[] result = new byte[tot]; System.arraycopy(content, buffer.position(), result, 0, result.length); return result; } @Override public ByteBuffer getByteBuffer() { return buffer; } public void flip() { size = buffer.position(); buffer.flip(); } /** * Creates a copy of this object referring to the same underlying buffer. * * @return the binary copy */ public Binary slice() { buffer.rewind(); return new Binary(buffer.slice()); } /** * Creates a copy of this object referring to the same underlying buffer, starting from a position. * * @param position the starting position * * @return the binary copy */ public Binary slice(final int position) { buffer.position(position); return new Binary(buffer.slice()); } /** * Creates a copy of this object referring to the same underlying buffer, starting from a position and with a custom length. * * @param position the starting position * @param length the length * * @return the binary copy */ public Binary slice(final int position, final int length) { final ByteBuffer result; buffer.position(position); result = buffer.slice(); result.position(length); result.flip(); return new Binary(result); } @Override public int size() { return size; } public void size(final int newSize) { if (newSize > content.length) checkForAllocation(0, newSize); else size = newSize; } public void move(final int startPosition, final int destPosition, final int length) { if (length == 0) return; checkForAllocation(0, destPosition + length); System.arraycopy(content, buffer.arrayOffset() + startPosition, content, buffer.arrayOffset() + destPosition, length); } public byte[] getContent() { return content; } public int getContentBeginOffset() { return buffer.arrayOffset(); } public int readFromStream(final InputStream is) throws IOException { final int read = is.read(content, buffer.position(), buffer.capacity() - buffer.position()); size += read; return read; } public int getContentSize() { return content.length; } public static int getNumberSpace(final long value) { return getUnsignedNumberSpace((value << 1) ^ (value >> 63)); } public static int getUnsignedNumberSpace(final long value) { int bytesUsed = 0; long v = value; while ((v & 0xFFFFFFFFFFFFFF80L) != 0L) { bytesUsed++; v >>>= 7; } bytesUsed++; return bytesUsed; } @Override public String toString() { return "Binary size=" + size + " pos=" + buffer.position(); } /** * Allocates enough space (max 1 page) and update the size according to the bytes to write. * * @param offset the offset * @param bytesToWrite number of bytes to write */ protected void checkForAllocation(final int offset, final int bytesToWrite) { final long newSizeAsLong = (long) offset + (long) bytesToWrite; if (newSizeAsLong > Integer.MAX_VALUE) throw new IllegalArgumentException("Binary objects cannot be larger than 2GB"); if (offset + bytesToWrite > content.length - buffer.arrayOffset()) { if (!autoResizable) throw new IllegalArgumentException("Cannot resize the buffer (autoResizable=false)"); final int newSize; if (offset + bytesToWrite > allocationChunkSize) { newSize = (((offset + bytesToWrite) / allocationChunkSize) + 1) * allocationChunkSize; } else newSize = allocationChunkSize; final byte[] newContent = new byte[newSize]; if (size > 0) System.arraycopy(content, buffer.arrayOffset(), newContent, 0, size); this.content = newContent; final int oldPosition = this.buffer.position(); this.buffer = ByteBuffer.wrap(this.content, 0, this.content.length); this.buffer.position(oldPosition); } if (offset + bytesToWrite > size) size = offset + bytesToWrite; } public int capacity() { return content.length; } public void fill(final byte filler, final int size) { checkForAllocation(buffer.position(), size); for (int i = 0; i < size; ++i) buffer.put(filler); } public void fetch(final FetchCallback callback) { fetchCallback = callback; } @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof Binary)) return false; final Binary binary = (Binary) o; return BinaryComparator.equalsBinary(this, binary); } @Override public int hashCode() { return Arrays.hashCode(content); } @Override public int compareTo(final Binary o) { return UnsignedBytesComparator.BEST_COMPARATOR.compare(content, o.content); } private void checkForFetching(final int bytes) { if (fetchCallback == null) return; if (size - buffer.position() - 1 < bytes) { try { // ADD REMAINING CONTENT USING A NEW BUFFER OF THE SAME SIZE OF THE CURRENT ONE final Binary newBuffer = new Binary(buffer.capacity()); newBuffer.putByteArray(this.remainingToByteArray()); // FETCH NEW CONTENT fetchCallback.fetch(newBuffer); // REPLACE NEW CONTENT WITH CURRENT ONE newBuffer.rewind(); buffer = newBuffer.buffer; content = newBuffer.content; size = newBuffer.size; } catch (final Exception e) { LogManager.instance().log(this, Level.SEVERE, "Error on fetching", e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy