org.h2.mvstore.DataUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2-mvstore Show documentation
Show all versions of h2-mvstore Show documentation
Fork of h2database to maintain Java 8 compatibility
The newest version!
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://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.StandardCharsets;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants;
import org.h2.jdbc.JdbcException;
import org.h2.util.StringUtils;
/**
* Utility methods
*/
public final 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 transaction contains too many changes.
*/
public static final int ERROR_TRANSACTION_TOO_BIG = 104;
/**
* Deadlock discovered and one of transactions involved chosen as victim and rolled back.
*/
public static final int ERROR_TRANSACTIONS_DEADLOCK = 105;
/**
* The transaction store can not be initialized because data type
* is not found in type registry.
*/
public static final int ERROR_UNKNOWN_DATA_TYPE = 106;
/**
* 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 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 marker size of a very large page.
*/
public static final int PAGE_LARGE = 2 * 1024 * 1024;
// The following are key prefixes used in layout map
/**
* The prefix for chunks ("chunk."). This, plus the chunk id (hex encoded)
* is the key, and the serialized chunk metadata is the value.
*/
public static final String META_CHUNK = "chunk.";
/**
* The prefix for root positions of maps ("root."). This, plus the map id
* (hex encoded) is the key, and the position (hex encoded) is the value.
*/
public static final String META_ROOT = "root.";
// The following are key prefixes used in meta map
/**
* The prefix for names ("name."). This, plus the name of the map, is the
* key, and the map id (hex encoded) is the value.
*/
public static final String META_NAME = "name.";
/**
* The prefix for maps ("map."). This, plus the map id (hex encoded) is the
* key, and the serialized in the map metadata is the value.
*/
public static final String META_MAP = "map.";
/**
* 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
* @throws IOException if some data could not be written
*/
public static void writeVarInt(OutputStream out, int x) throws IOException {
while ((x & ~0x7f) != 0) {
out.write((byte) (x | 0x80));
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) (x | 0x80));
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
* @return the value
*/
public static String readString(ByteBuffer buff) {
return readString(buff, readVarInt(buff));
}
/**
* 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) (x | 0x80));
x >>>= 7;
}
buff.put((byte) x);
}
/**
* Write a variable size long.
*
* @param out the output stream
* @param x the value
* @throws IOException if some data could not be written
*/
public static void writeVarLong(OutputStream out, long x)
throws IOException {
while ((x & ~0x7f) != 0) {
out.write((byte) (x | 0x80));
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 MVStoreException 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 newMVStoreException(
ERROR_READING_FAILED,
"Reading from file {0} failed at {1} (length {2}), " +
"read {3}, remaining {4}",
file, pos, size, dst.position(), dst.remaining(), 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 newMVStoreException(
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) {
assert len >= 0;
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 map id from the chunk's table of content element.
*
* @param tocElement packed table of content element
* @return the map id
*/
public static int getPageMapId(long tocElement) {
return (int) (tocElement >>> 38);
}
/**
* Get the maximum length for the given page position.
*
* @param pos the position
* @return the maximum length
*/
public static int getPageMaxLength(long pos) {
int code = (int) ((pos >> 1) & 31);
return decodePageLength(code);
}
/**
* Get the maximum length for the given code.
* For the code 31, PAGE_LARGE is returned.
*
* @param code encoded page length
* @return the maximum length
*/
public static int decodePageLength(int code) {
if (code == 31) {
return PAGE_LARGE;
}
return (2 + (code & 1)) << ((code >> 1) + 4);
}
/**
* Get the offset from the position.
*
* @param tocElement packed table of content element
* @return the offset
*/
public static int getPageOffset(long tocElement) {
return (int) (tocElement >> 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;
}
/**
* Determines whether specified file position corresponds to a leaf page
* @param pos the position
* @return true if it is a leaf, false otherwise
*/
public static boolean isLeafPosition(long pos) {
return getPageType(pos) == PAGE_TYPE_LEAF;
}
/**
* Find out if page was saved.
*
* @param pos the position
* @return true if page has been saved
*/
public static boolean isPageSaved(long pos) {
return (pos & ~1L) != 0;
}
/**
* Find out if page was removed.
*
* @param pos the position
* @return true if page has been removed (no longer accessible from the
* current root of the tree)
*/
static boolean isPageRemoved(long pos) {
return pos == 1L;
}
/**
* Get the position of this page. The following information is encoded in
* the position: the chunk id, the page sequential number, 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 composePagePos(int chunkId, int offset, int length, int type) {
assert offset >= 0;
assert type == DataUtils.PAGE_TYPE_LEAF || type == DataUtils.PAGE_TYPE_NODE;
long pos = (long) chunkId << 38;
pos |= (long) offset << 6;
pos |= (long) encodeLength(length) << 1;
pos |= type;
return pos;
}
/**
* Convert tocElement into pagePos by replacing mapId with chunkId.
*
* @param chunkId the chunk id
* @param tocElement the element
* @return the page position
*/
public static long composePagePos(int chunkId, long tocElement) {
return (tocElement & 0x3FFFFFFFFFL) | ((long) chunkId << 38);
}
/**
* Create table of content element. The following information is encoded in it:
* the map id, the page offset, the maximum length, and the type
* (node or leaf).
*
* @param mapId 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 composeTocElement(int mapId, int offset, int length, int type) {
assert mapId >= 0;
assert offset >= 0;
assert type == DataUtils.PAGE_TYPE_LEAF || type == DataUtils.PAGE_TYPE_NODE;
long pos = (long) mapId << 38;
pos |= (long) offset << 6;
pos |= (long) 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) {
Object[] keys = map.keySet().toArray();
Arrays.sort(keys);
for (Object k : keys) {
String key = (String) k;
Object value = map.get(key);
if (value instanceof Long) {
appendMap(buff, key, (long) value);
} else if (value instanceof Integer) {
appendMap(buff, key, (int) value);
} else {
appendMap(buff, key, value.toString());
}
}
return buff;
}
private static StringBuilder appendMapKey(StringBuilder buff, String key) {
if (buff.length() > 0) {
buff.append(',');
}
return buff.append(key).append(':');
}
/**
* 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, String value) {
appendMapKey(buff, key);
if (value.indexOf(',') < 0 && value.indexOf('\"') < 0) {
buff.append(value);
} else {
buff.append('\"');
for (int i = 0, size = value.length(); i < size; i++) {
char c = value.charAt(i);
if (c == '\"') {
buff.append('\\');
}
buff.append(c);
}
buff.append('\"');
}
}
/**
* Append a key-value pair to the string builder. Keys may not contain a
* colon.
*
* @param buff the target buffer
* @param key the key
* @param value the value
*/
public static void appendMap(StringBuilder buff, String key, long value) {
appendMapKey(buff, key).append(Long.toHexString(value));
}
/**
* Append a key-value pair to the string builder. Keys may not contain a
* colon.
*
* @param buff the target buffer
* @param key the key
* @param value the value
*/
public static void appendMap(StringBuilder buff, String key, int value) {
appendMapKey(buff, key).append(Integer.toHexString(value));
}
/**
* @param buff output buffer, should be empty
* @param s parsed string
* @param i offset to parse from
* @param size stop offset (exclusive)
* @return new offset
*/
private static int parseMapValue(StringBuilder buff, String s, int i, int size) {
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 newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
c = s.charAt(i++);
} else if (c == '\"') {
break;
}
buff.append(c);
}
} else {
buff.append(c);
}
}
return i;
}
/**
* Parse a key-value pair list.
*
* @param s the list
* @return the map
* @throws MVStoreException if parsing failed
*/
public static HashMap parseMap(String s) {
HashMap map = new HashMap<>();
StringBuilder buff = new StringBuilder();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
if (i < 0) {
throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
String key = s.substring(startKey, i++);
i = parseMapValue(buff, s, i, size);
map.put(key, buff.toString());
buff.setLength(0);
}
return map;
}
/**
* Parse a key-value pair list and checks its checksum.
*
* @param bytes encoded map
* @return the map without mapping for {@code "fletcher"}, or {@code null} if checksum is wrong
* or parameter do not represent a properly formatted map serialization
*/
static HashMap parseChecksummedMap(byte[] bytes) {
int start = 0, end = bytes.length;
while (start < end && bytes[start] <= ' ') {
start++;
}
while (start < end && bytes[end - 1] <= ' ') {
end--;
}
String s = new String(bytes, start, end - start, StandardCharsets.ISO_8859_1);
HashMap map = new HashMap<>();
StringBuilder buff = new StringBuilder();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
if (i < 0) {
// Corrupted map
return null;
}
if (i - startKey == 8 && s.regionMatches(startKey, "fletcher", 0, 8)) {
parseMapValue(buff, s, i + 1, size);
int check = (int) Long.parseLong(buff.toString(), 16);
if (check == getFletcher32(bytes, start, startKey - 1)) {
return map;
}
// Corrupted map
return null;
}
String key = s.substring(startKey, i++);
i = parseMapValue(buff, s, i, size);
map.put(key, buff.toString());
buff.setLength(0);
}
// Corrupted map
return null;
}
/**
* Parse a name from key-value pair list.
*
* @param s the list
* @return value of name item, or {@code null}
* @throws MVStoreException if parsing failed
*/
public static String getMapName(String s) {
return getFromMap(s, "name");
}
/**
* Parse a specified pair from key-value pair list.
*
* @param s the list
* @param key the name of the key
* @return value of the specified item, or {@code null}
* @throws MVStoreException if parsing failed
*/
public static String getFromMap(String s, String key) {
int keyLength = key.length();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
if (i < 0) {
throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
if (i++ - startKey == keyLength && s.regionMatches(startKey, key, 0, keyLength)) {
StringBuilder buff = new StringBuilder();
parseMapValue(buff, s, i, size);
return buff.toString();
} else {
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 newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
} else if (c == '\"') {
break;
}
}
}
}
}
}
return null;
}
/**
* Calculate the Fletcher32 checksum.
*
* @param bytes the bytes
* @param offset initial offset
* @param length the message length (if odd, 0 is appended)
* @return the checksum
*/
public static int getFletcher32(byte[] bytes, int offset, int length) {
int s1 = 0xffff, s2 = 0xffff;
int i = offset, len = offset + (length & ~1);
while (i < len) {
// reduce after 360 words (each word is two bytes)
for (int end = Math.min(i + 720, len); 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 ((length & 1) != 0) {
// 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 MVStoreException.
*
* @param errorCode the error code
* @param message the message
* @param arguments the arguments
* @return the exception
*/
public static MVStoreException newMVStoreException(
int errorCode, String message, Object... arguments) {
return initCause(new MVStoreException(errorCode,
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 Throwable) {
e.initCause((Throwable) 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
arguments = arguments.clone();
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 + "]";
}
/**
* 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 MVStoreException 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 newMVStoreException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", v, e);
}
}
/**
* Parse an unsigned, hex long.
*
* @param x the string
* @return the parsed value
* @throws MVStoreException 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 newMVStoreException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", x, e);
}
}
/**
* Parse an unsigned, hex long.
*
* @param x the string
* @return the parsed value
* @throws MVStoreException 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 newMVStoreException(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 MVStoreException if parsing fails
*/
static int readHexInt(Map 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 newMVStoreException(ERROR_FILE_CORRUPT,
"Error parsing the value {0}", v, e);
}
}
/**
* Parse the hex-encoded bytes of an entry in the map.
*
* @param map the map
* @param key the key
* @return the byte array, or null if not in the map
*/
static byte[] parseHexBytes(Map map, String key) {
Object v = map.get(key);
if (v == null) {
return null;
}
return StringUtils.convertHexToBytes((String)v);
}
/**
* Get the configuration parameter value, or default.
*
* @param config the configuration
* @param key the key
* @param defaultValue the default
* @return the configured value or default
*/
static int getConfigParam(Map config, String key, int defaultValue) {
Object o = config.get(key);
if (o instanceof Number) {
return ((Number) o).intValue();
} else if (o != null) {
try {
return Integer.decode(o.toString());
} catch (NumberFormatException e) {
// ignore
}
}
return defaultValue;
}
/**
* Convert an exception to an IO exception.
*
* @param e the root cause
* @return the IO exception
*/
public static IOException convertToIOException(Throwable e) {
if (e instanceof IOException) {
return (IOException) e;
}
if (e instanceof JdbcException) {
if (e.getCause() != null) {
e = e.getCause();
}
}
return new IOException(e.toString(), e);
}
}