org.h2.mvstore.DataUtils Maven / Gradle / Ivy
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants;
import org.h2.util.New;
/**
* Utility methods
*/
public class DataUtils {
/**
* An error occurred while reading from the file.
*/
public static final int ERROR_READING_FAILED = 1;
/**
* An error occurred when trying to write to the file.
*/
public static final int ERROR_WRITING_FAILED = 2;
/**
* An internal error occurred. This could be a bug, or a memory corruption
* (for example caused by out of memory).
*/
public static final int ERROR_INTERNAL = 3;
/**
* The object is already closed.
*/
public static final int ERROR_CLOSED = 4;
/**
* The file format is not supported.
*/
public static final int ERROR_UNSUPPORTED_FORMAT = 5;
/**
* The file is corrupt or (for encrypted files) the encryption key is wrong.
*/
public static final int ERROR_FILE_CORRUPT = 6;
/**
* The file is locked.
*/
public static final int ERROR_FILE_LOCKED = 7;
/**
* An error occurred when serializing or de-serializing.
*/
public static final int ERROR_SERIALIZATION = 8;
/**
* The application was trying to read data from a chunk that is no longer
* available.
*/
public static final int ERROR_CHUNK_NOT_FOUND = 9;
/**
* The block in the stream store was not found.
*/
public static final int ERROR_BLOCK_NOT_FOUND = 50;
/**
* The transaction store is corrupt.
*/
public static final int ERROR_TRANSACTION_CORRUPT = 100;
/**
* An entry is still locked by another transaction.
*/
public static final int ERROR_TRANSACTION_LOCKED = 101;
/**
* There are too many open transactions.
*/
public static final int ERROR_TOO_MANY_OPEN_TRANSACTIONS = 102;
/**
* The transaction store is in an illegal state (for example, not yet
* initialized).
*/
public static final int ERROR_TRANSACTION_ILLEGAL_STATE = 103;
/**
* The type for leaf page.
*/
public static final int PAGE_TYPE_LEAF = 0;
/**
* The type for node page.
*/
public static final int PAGE_TYPE_NODE = 1;
/**
* The bit mask for compressed pages (compression level fast).
*/
public static final int PAGE_COMPRESSED = 2;
/**
* The bit mask for compressed pages (compression level high).
*/
public static final int PAGE_COMPRESSED_HIGH = 2 + 4;
/**
* The maximum length of a variable size int.
*/
public static final int MAX_VAR_INT_LEN = 5;
/**
* The maximum length of a variable size long.
*/
public static final int MAX_VAR_LONG_LEN = 10;
/**
* The maximum integer that needs less space when using variable size
* encoding (only 3 bytes instead of 4).
*/
public static final int COMPRESSED_VAR_INT_MAX = 0x1fffff;
/**
* The maximum long that needs less space when using variable size
* encoding (only 7 bytes instead of 8).
*/
public static final long COMPRESSED_VAR_LONG_MAX = 0x1ffffffffffffL;
/**
* The estimated number of bytes used per page object.
*/
public static final int PAGE_MEMORY = 128;
/**
* The estimated number of bytes used per child entry.
*/
public static final int PAGE_MEMORY_CHILD = 16;
/**
* The marker size of a very large page.
*/
public static final int PAGE_LARGE = 2 * 1024 * 1024;
/**
* The UTF-8 character encoding format.
*/
public static final Charset UTF8 = Charset.forName("UTF-8");
/**
* The ISO Latin character encoding format.
*/
public static final Charset LATIN = Charset.forName("ISO-8859-1");
/**
* An 0-size byte array.
*/
private static final byte[] EMPTY_BYTES = {};
/**
* Get the length of the variable size int.
*
* @param x the value
* @return the length in bytes
*/
public static int getVarIntLen(int x) {
if ((x & (-1 << 7)) == 0) {
return 1;
} else if ((x & (-1 << 14)) == 0) {
return 2;
} else if ((x & (-1 << 21)) == 0) {
return 3;
} else if ((x & (-1 << 28)) == 0) {
return 4;
}
return 5;
}
/**
* Get the length of the variable size long.
*
* @param x the value
* @return the length in bytes
*/
public static int getVarLongLen(long x) {
int i = 1;
while (true) {
x >>>= 7;
if (x == 0) {
return i;
}
i++;
}
}
/**
* Read a variable size int.
*
* @param buff the source buffer
* @return the value
*/
public static int readVarInt(ByteBuffer buff) {
int b = buff.get();
if (b >= 0) {
return b;
}
// a separate function so that this one can be inlined
return readVarIntRest(buff, b);
}
private static int readVarIntRest(ByteBuffer buff, int b) {
int x = b & 0x7f;
b = buff.get();
if (b >= 0) {
return x | (b << 7);
}
x |= (b & 0x7f) << 7;
b = buff.get();
if (b >= 0) {
return x | (b << 14);
}
x |= (b & 0x7f) << 14;
b = buff.get();
if (b >= 0) {
return x | b << 21;
}
x |= ((b & 0x7f) << 21) | (buff.get() << 28);
return x;
}
/**
* Read a variable size long.
*
* @param buff the source buffer
* @return the value
*/
public static long readVarLong(ByteBuffer buff) {
long x = buff.get();
if (x >= 0) {
return x;
}
x &= 0x7f;
for (int s = 7; s < 64; s += 7) {
long b = buff.get();
x |= (b & 0x7f) << s;
if (b >= 0) {
break;
}
}
return x;
}
/**
* Write a variable size int.
*
* @param out the output stream
* @param x the value
*/
public static void writeVarInt(OutputStream out, int x) throws IOException {
while ((x & ~0x7f) != 0) {
out.write((byte) (0x80 | (x & 0x7f)));
x >>>= 7;
}
out.write((byte) x);
}
/**
* Write a variable size int.
*
* @param buff the source buffer
* @param x the value
*/
public static void writeVarInt(ByteBuffer buff, int x) {
while ((x & ~0x7f) != 0) {
buff.put((byte) (0x80 | (x & 0x7f)));
x >>>= 7;
}
buff.put((byte) x);
}
/**
* Write characters from a string (without the length).
*
* @param buff the target buffer (must be large enough)
* @param s the string
* @param len the number of characters
*/
public static void writeStringData(ByteBuffer buff,
String s, int len) {
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (c < 0x80) {
buff.put((byte) c);
} else if (c >= 0x800) {
buff.put((byte) (0xe0 | (c >> 12)));
buff.put((byte) (((c >> 6) & 0x3f)));
buff.put((byte) (c & 0x3f));
} else {
buff.put((byte) (0xc0 | (c >> 6)));
buff.put((byte) (c & 0x3f));
}
}
}
/**
* Read a string.
*
* @param buff the source buffer
* @param len the number of characters
* @return the value
*/
public static String readString(ByteBuffer buff, int len) {
char[] chars = new char[len];
for (int i = 0; i < len; i++) {
int x = buff.get() & 0xff;
if (x < 0x80) {
chars[i] = (char) x;
} else if (x >= 0xe0) {
chars[i] = (char) (((x & 0xf) << 12)
+ ((buff.get() & 0x3f) << 6) + (buff.get() & 0x3f));
} else {
chars[i] = (char) (((x & 0x1f) << 6) + (buff.get() & 0x3f));
}
}
return new String(chars);
}
/**
* Write a variable size long.
*
* @param buff the target buffer
* @param x the value
*/
public static void writeVarLong(ByteBuffer buff, long x) {
while ((x & ~0x7f) != 0) {
buff.put((byte) (0x80 | (x & 0x7f)));
x >>>= 7;
}
buff.put((byte) x);
}
/**
* Write a variable size long.
*
* @param out the output stream
* @param x the value
*/
public static void writeVarLong(OutputStream out, long x)
throws IOException {
while ((x & ~0x7f) != 0) {
out.write((byte) (0x80 | (x & 0x7f)));
x >>>= 7;
}
out.write((byte) x);
}
/**
* Copy the elements of an array, with a gap.
*
* @param src the source array
* @param dst the target array
* @param oldSize the size of the old array
* @param gapIndex the index of the gap
*/
public static void copyWithGap(Object src, Object dst, int oldSize,
int gapIndex) {
if (gapIndex > 0) {
System.arraycopy(src, 0, dst, 0, gapIndex);
}
if (gapIndex < oldSize) {
System.arraycopy(src, gapIndex, dst, gapIndex + 1, oldSize
- gapIndex);
}
}
/**
* Copy the elements of an array, and remove one element.
*
* @param src the source array
* @param dst the target array
* @param oldSize the size of the old array
* @param removeIndex the index of the entry to remove
*/
public static void copyExcept(Object src, Object dst, int oldSize,
int removeIndex) {
if (removeIndex > 0 && oldSize > 0) {
System.arraycopy(src, 0, dst, 0, removeIndex);
}
if (removeIndex < oldSize) {
System.arraycopy(src, removeIndex + 1, dst, removeIndex, oldSize
- removeIndex - 1);
}
}
/**
* Read from a file channel until the buffer is full.
* The buffer is rewind after reading.
*
* @param file the file channel
* @param pos the absolute position within the file
* @param dst the byte buffer
* @throws IllegalStateException if some data could not be read
*/
public static void readFully(FileChannel file, long pos, ByteBuffer dst) {
try {
do {
int len = file.read(dst, pos);
if (len < 0) {
throw new EOFException();
}
pos += len;
} while (dst.remaining() > 0);
dst.rewind();
} catch (IOException e) {
long size;
try {
size = file.size();
} catch (IOException e2) {
size = -1;
}
throw newIllegalStateException(
ERROR_READING_FAILED,
"Reading from {0} failed; file length {1} " +
"read length {2} at {3}",
file, size, dst.remaining(), pos, e);
}
}
/**
* Write to a file channel.
*
* @param file the file channel
* @param pos the absolute position within the file
* @param src the source buffer
*/
public static void writeFully(FileChannel file, long pos, ByteBuffer src) {
try {
int off = 0;
do {
int len = file.write(src, pos + off);
off += len;
} while (src.remaining() > 0);
} catch (IOException e) {
throw newIllegalStateException(
ERROR_WRITING_FAILED,
"Writing to {0} failed; length {1} at {2}",
file, src.remaining(), pos, e);
}
}
/**
* Convert the length to a length code 0..31. 31 means more than 1 MB.
*
* @param len the length
* @return the length code
*/
public static int encodeLength(int len) {
if (len <= 32) {
return 0;
}
int code = Integer.numberOfLeadingZeros(len);
int remaining = len << (code + 1);
code += code;
if ((remaining & (1 << 31)) != 0) {
code--;
}
if ((remaining << 1) != 0) {
code--;
}
code = Math.min(31, 52 - code);
// alternative code (slower):
// int x = len;
// int shift = 0;
// while (x > 3) {
// shift++;
// x = (x >>> 1) + (x & 1);
// }
// shift = Math.max(0, shift - 4);
// int code = (shift << 1) + (x & 1);
// code = Math.min(31, code);
return code;
}
/**
* Get the chunk id from the position.
*
* @param pos the position
* @return the chunk id
*/
public static int getPageChunkId(long pos) {
return (int) (pos >>> 38);
}
/**
* Get the maximum length for the given code.
* For the code 31, PAGE_LARGE is returned.
*
* @param pos the position
* @return the maximum length
*/
public static int getPageMaxLength(long pos) {
int code = (int) ((pos >> 1) & 31);
if (code == 31) {
return PAGE_LARGE;
}
return (2 + (code & 1)) << ((code >> 1) + 4);
}
/**
* Get the offset from the position.
*
* @param pos the position
* @return the offset
*/
public static int getPageOffset(long pos) {
return (int) (pos >> 6);
}
/**
* Get the page type from the position.
*
* @param pos the position
* @return the page type (PAGE_TYPE_NODE or PAGE_TYPE_LEAF)
*/
public static int getPageType(long pos) {
return ((int) pos) & 1;
}
/**
* Get the position of this page. The following information is encoded in
* the position: the chunk id, the offset, the maximum length, and the type
* (node or leaf).
*
* @param chunkId the chunk id
* @param offset the offset
* @param length the length
* @param type the page type (1 for node, 0 for leaf)
* @return the position
*/
public static long getPagePos(int chunkId, int offset,
int length, int type) {
long pos = (long) chunkId << 38;
pos |= (long) offset << 6;
pos |= encodeLength(length) << 1;
pos |= type;
return pos;
}
/**
* Calculate a check value for the given integer. A check value is mean to
* verify the data is consistent with a high probability, but not meant to
* protect against media failure or deliberate changes.
*
* @param x the value
* @return the check value
*/
public static short getCheckValue(int x) {
return (short) ((x >> 16) ^ x);
}
/**
* Append a map to the string builder, sorted by key.
*
* @param buff the target buffer
* @param map the map
* @return the string builder
*/
public static StringBuilder appendMap(StringBuilder buff,
HashMap map) {
ArrayList list = New.arrayList(map.keySet());
Collections.sort(list);
for (String k : list) {
appendMap(buff, k, map.get(k));
}
return buff;
}
/**
* Append a key-value pair to the string builder. Keys may not contain a
* colon. Values that contain a comma or a double quote are enclosed in
* double quotes, with special characters escaped using a backslash.
*
* @param buff the target buffer
* @param key the key
* @param value the value
*/
public static void appendMap(StringBuilder buff, String key, Object value) {
if (buff.length() > 0) {
buff.append(',');
}
buff.append(key).append(':');
String v;
if (value instanceof Long) {
v = Long.toHexString((Long) value);
} else if (value instanceof Integer) {
v = Integer.toHexString((Integer) value);
} else {
v = value.toString();
}
if (v.indexOf(',') < 0 && v.indexOf('\"') < 0) {
buff.append(v);
} else {
buff.append('\"');
for (int i = 0, size = v.length(); i < size; i++) {
char c = v.charAt(i);
if (c == '\"') {
buff.append('\\');
}
buff.append(c);
}
buff.append('\"');
}
}
/**
* Parse a key-value pair list.
*
* @param s the list
* @return the map
* @throws IllegalStateException if parsing failed
*/
public static HashMap parseMap(String s) {
HashMap map = New.hashMap();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
if (i < 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
String key = s.substring(startKey, i++);
StringBuilder buff = new StringBuilder();
while (i < size) {
char c = s.charAt(i++);
if (c == ',') {
break;
} else if (c == '\"') {
while (i < size) {
c = s.charAt(i++);
if (c == '\\') {
if (i == size) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"Not a map: {0}", s);
}
c = s.charAt(i++);
} else if (c == '\"') {
break;
}
buff.append(c);
}
} else {
buff.append(c);
}
}
map.put(key, buff.toString());
}
return map;
}
/**
* Calculate the Fletcher32 checksum.
*
* @param bytes the bytes
* @param length the message length (if odd, 0 is appended)
* @return the checksum
*/
public static int getFletcher32(byte[] bytes, int length) {
int s1 = 0xffff, s2 = 0xffff;
int i = 0, evenLength = length / 2 * 2;
while (i < evenLength) {
// reduce after 360 words (each word is two bytes)
for (int end = Math.min(i + 720, evenLength); i < end;) {
int x = ((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff);
s2 += s1 += x;
}
s1 = (s1 & 0xffff) + (s1 >>> 16);
s2 = (s2 & 0xffff) + (s2 >>> 16);
}
if (i < length) {
// odd length: append 0
int x = (bytes[i] & 0xff) << 8;
s2 += s1 += x;
}
s1 = (s1 & 0xffff) + (s1 >>> 16);
s2 = (s2 & 0xffff) + (s2 >>> 16);
return (s2 << 16) | s1;
}
/**
* Throw an IllegalArgumentException if the argument is invalid.
*
* @param test true if the argument is valid
* @param message the message
* @param arguments the arguments
* @throws IllegalArgumentException if the argument is invalid
*/
public static void checkArgument(boolean test, String message,
Object... arguments) {
if (!test) {
throw newIllegalArgumentException(message, arguments);
}
}
/**
* Create a new IllegalArgumentException.
*
* @param message the message
* @param arguments the arguments
* @return the exception
*/
public static IllegalArgumentException newIllegalArgumentException(
String message, Object... arguments) {
return initCause(new IllegalArgumentException(
formatMessage(0, message, arguments)),
arguments);
}
/**
* Create a new UnsupportedOperationException.
*
* @param message the message
* @return the exception
*/
public static UnsupportedOperationException
newUnsupportedOperationException(String message) {
return new UnsupportedOperationException(formatMessage(0, message));
}
/**
* Create a new ConcurrentModificationException.
*
* @param message the message
* @return the exception
*/
public static ConcurrentModificationException
newConcurrentModificationException(String message) {
return new ConcurrentModificationException(formatMessage(0, message));
}
/**
* Create a new IllegalStateException.
*
* @param errorCode the error code
* @param message the message
* @param arguments the arguments
* @return the exception
*/
public static IllegalStateException newIllegalStateException(
int errorCode, String message, Object... arguments) {
return initCause(new IllegalStateException(
formatMessage(errorCode, message, arguments)),
arguments);
}
private static T initCause(T e, Object... arguments) {
int size = arguments.length;
if (size > 0) {
Object o = arguments[size - 1];
if (o instanceof Exception) {
e.initCause((Exception) o);
}
}
return e;
}
/**
* Format an error message.
*
* @param errorCode the error code
* @param message the message
* @param arguments the arguments
* @return the formatted message
*/
public static String formatMessage(int errorCode, String message,
Object... arguments) {
// convert arguments to strings, to avoid locale specific formatting
for (int i = 0; i < arguments.length; i++) {
Object a = arguments[i];
if (!(a instanceof Exception)) {
String s = a == null ? "null" : a.toString();
if (s.length() > 1000) {
s = s.substring(0, 1000) + "...";
}
arguments[i] = s;
}
}
return MessageFormat.format(message, arguments) +
" [" + Constants.VERSION_MAJOR + "." +
Constants.VERSION_MINOR + "." + Constants.BUILD_ID +
"/" + errorCode + "]";
}
/**
* Get the error code from an exception message.
*
* @param m the message
* @return the error code, or 0 if none
*/
public static int getErrorCode(String m) {
if (m != null && m.endsWith("]")) {
int dash = m.lastIndexOf('/');
if (dash >= 0) {
String s = m.substring(dash + 1, m.length() - 1);
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
// no error code
}
}
}
return 0;
}
/**
* Create an array of bytes with the given size. If this is not possible
* because not enough memory is available, an OutOfMemoryError with the
* requested size in the message is thrown.
*
* This method should be used if the size of the array is user defined, or
* stored in a file, so wrong size data can be distinguished from regular
* out-of-memory.
*
* @param len the number of bytes requested
* @return the byte array
* @throws OutOfMemoryError if the allocation was too large
*/
public static byte[] newBytes(int len) {
if (len == 0) {
return EMPTY_BYTES;
}
try {
return new byte[len];
} catch (OutOfMemoryError e) {
Error e2 = new OutOfMemoryError("Requested memory: " + len);
e2.initCause(e);
throw e2;
}
}
/**
* Read a hex long value from a map.
*
* @param map the map
* @param key the key
* @param defaultValue if the value is null
* @return the parsed value
* @throws IllegalStateException if parsing fails
*/
public static long readHexLong(Map map,
String key, long defaultValue) {
Object v = map.get(key);
if (v == null) {
return defaultValue;
} else if (v instanceof Long) {
return (Long) v;
}
try {
return parseHexLong((String) v);
} catch (NumberFormatException e) {
throw newIllegalStateException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", v, e);
}
}
/**
* Parse an unsigned, hex long.
*
* @param x the string
* @return the parsed value
* @throws IllegalStateException if parsing fails
*/
public static long parseHexLong(String x) {
try {
if (x.length() == 16) {
// avoid problems with overflow
// in Java 8, this special case is not needed
return (Long.parseLong(x.substring(0, 8), 16) << 32) |
Long.parseLong(x.substring(8, 16), 16);
}
return Long.parseLong(x, 16);
} catch (NumberFormatException e) {
throw newIllegalStateException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", x, e);
}
}
/**
* Parse an unsigned, hex long.
*
* @param x the string
* @return the parsed value
* @throws IllegalStateException if parsing fails
*/
public static int parseHexInt(String x) {
try {
// avoid problems with overflow
// in Java 8, we can use Integer.parseLong(x, 16);
return (int) Long.parseLong(x, 16);
} catch (NumberFormatException e) {
throw newIllegalStateException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", x, e);
}
}
/**
* Read a hex int value from a map.
*
* @param map the map
* @param key the key
* @param defaultValue if the value is null
* @return the parsed value
* @throws IllegalStateException if parsing fails
*/
public static int readHexInt(HashMap map,
String key, int defaultValue) {
Object v = map.get(key);
if (v == null) {
return defaultValue;
} else if (v instanceof Integer) {
return (Integer) v;
}
try {
// support unsigned hex value
return (int) Long.parseLong((String) v, 16);
} catch (NumberFormatException e) {
throw newIllegalStateException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", v, e);
}
}
/**
* An entry of a map.
*
* @param the key type
* @param the value type
*/
public static class MapEntry implements Map.Entry {
private final K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw DataUtils.newUnsupportedOperationException(
"Updating the value is not supported");
}
}
}