1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.mvstore; |
7 | |
8 | import java.io.EOFException; |
9 | import java.io.IOException; |
10 | import java.io.OutputStream; |
11 | import java.nio.ByteBuffer; |
12 | import java.nio.channels.FileChannel; |
13 | import java.nio.charset.Charset; |
14 | import java.text.MessageFormat; |
15 | import java.util.ArrayList; |
16 | import java.util.Collections; |
17 | import java.util.ConcurrentModificationException; |
18 | import java.util.HashMap; |
19 | import java.util.Map; |
20 | |
21 | import org.h2.engine.Constants; |
22 | import org.h2.util.New; |
23 | |
24 | /** |
25 | * Utility methods |
26 | */ |
27 | public class DataUtils { |
28 | |
29 | /** |
30 | * An error occurred while reading from the file. |
31 | */ |
32 | public static final int ERROR_READING_FAILED = 1; |
33 | |
34 | /** |
35 | * An error occurred when trying to write to the file. |
36 | */ |
37 | public static final int ERROR_WRITING_FAILED = 2; |
38 | |
39 | /** |
40 | * An internal error occurred. This could be a bug, or a memory corruption |
41 | * (for example caused by out of memory). |
42 | */ |
43 | public static final int ERROR_INTERNAL = 3; |
44 | |
45 | /** |
46 | * The object is already closed. |
47 | */ |
48 | public static final int ERROR_CLOSED = 4; |
49 | |
50 | /** |
51 | * The file format is not supported. |
52 | */ |
53 | public static final int ERROR_UNSUPPORTED_FORMAT = 5; |
54 | |
55 | /** |
56 | * The file is corrupt or (for encrypted files) the encryption key is wrong. |
57 | */ |
58 | public static final int ERROR_FILE_CORRUPT = 6; |
59 | |
60 | /** |
61 | * The file is locked. |
62 | */ |
63 | public static final int ERROR_FILE_LOCKED = 7; |
64 | |
65 | /** |
66 | * An error occurred when serializing or de-serializing. |
67 | */ |
68 | public static final int ERROR_SERIALIZATION = 8; |
69 | |
70 | /** |
71 | * The application was trying to read data from a chunk that is no longer |
72 | * available. |
73 | */ |
74 | public static final int ERROR_CHUNK_NOT_FOUND = 9; |
75 | |
76 | /** |
77 | * The block in the stream store was not found. |
78 | */ |
79 | public static final int ERROR_BLOCK_NOT_FOUND = 50; |
80 | |
81 | /** |
82 | * The transaction store is corrupt. |
83 | */ |
84 | public static final int ERROR_TRANSACTION_CORRUPT = 100; |
85 | |
86 | /** |
87 | * An entry is still locked by another transaction. |
88 | */ |
89 | public static final int ERROR_TRANSACTION_LOCKED = 101; |
90 | |
91 | /** |
92 | * There are too many open transactions. |
93 | */ |
94 | public static final int ERROR_TOO_MANY_OPEN_TRANSACTIONS = 102; |
95 | |
96 | /** |
97 | * The transaction store is in an illegal state (for example, not yet |
98 | * initialized). |
99 | */ |
100 | public static final int ERROR_TRANSACTION_ILLEGAL_STATE = 103; |
101 | |
102 | /** |
103 | * The type for leaf page. |
104 | */ |
105 | public static final int PAGE_TYPE_LEAF = 0; |
106 | |
107 | /** |
108 | * The type for node page. |
109 | */ |
110 | public static final int PAGE_TYPE_NODE = 1; |
111 | |
112 | /** |
113 | * The bit mask for compressed pages (compression level fast). |
114 | */ |
115 | public static final int PAGE_COMPRESSED = 2; |
116 | |
117 | /** |
118 | * The bit mask for compressed pages (compression level high). |
119 | */ |
120 | public static final int PAGE_COMPRESSED_HIGH = 2 + 4; |
121 | |
122 | /** |
123 | * The maximum length of a variable size int. |
124 | */ |
125 | public static final int MAX_VAR_INT_LEN = 5; |
126 | |
127 | /** |
128 | * The maximum length of a variable size long. |
129 | */ |
130 | public static final int MAX_VAR_LONG_LEN = 10; |
131 | |
132 | /** |
133 | * The maximum integer that needs less space when using variable size |
134 | * encoding (only 3 bytes instead of 4). |
135 | */ |
136 | public static final int COMPRESSED_VAR_INT_MAX = 0x1fffff; |
137 | |
138 | /** |
139 | * The maximum long that needs less space when using variable size |
140 | * encoding (only 7 bytes instead of 8). |
141 | */ |
142 | public static final long COMPRESSED_VAR_LONG_MAX = 0x1ffffffffffffL; |
143 | |
144 | /** |
145 | * The estimated number of bytes used per page object. |
146 | */ |
147 | public static final int PAGE_MEMORY = 128; |
148 | |
149 | /** |
150 | * The estimated number of bytes used per child entry. |
151 | */ |
152 | public static final int PAGE_MEMORY_CHILD = 16; |
153 | |
154 | /** |
155 | * The marker size of a very large page. |
156 | */ |
157 | public static final int PAGE_LARGE = 2 * 1024 * 1024; |
158 | |
159 | /** |
160 | * The UTF-8 character encoding format. |
161 | */ |
162 | public static final Charset UTF8 = Charset.forName("UTF-8"); |
163 | |
164 | /** |
165 | * The ISO Latin character encoding format. |
166 | */ |
167 | public static final Charset LATIN = Charset.forName("ISO-8859-1"); |
168 | |
169 | /** |
170 | * An 0-size byte array. |
171 | */ |
172 | private static final byte[] EMPTY_BYTES = {}; |
173 | |
174 | /** |
175 | * The maximum byte to grow a buffer at a time. |
176 | */ |
177 | private static final int MAX_GROW = 16 * 1024 * 1024; |
178 | |
179 | /** |
180 | * Get the length of the variable size int. |
181 | * |
182 | * @param x the value |
183 | * @return the length in bytes |
184 | */ |
185 | public static int getVarIntLen(int x) { |
186 | if ((x & (-1 << 7)) == 0) { |
187 | return 1; |
188 | } else if ((x & (-1 << 14)) == 0) { |
189 | return 2; |
190 | } else if ((x & (-1 << 21)) == 0) { |
191 | return 3; |
192 | } else if ((x & (-1 << 28)) == 0) { |
193 | return 4; |
194 | } |
195 | return 5; |
196 | } |
197 | |
198 | /** |
199 | * Get the length of the variable size long. |
200 | * |
201 | * @param x the value |
202 | * @return the length in bytes |
203 | */ |
204 | public static int getVarLongLen(long x) { |
205 | int i = 1; |
206 | while (true) { |
207 | x >>>= 7; |
208 | if (x == 0) { |
209 | return i; |
210 | } |
211 | i++; |
212 | } |
213 | } |
214 | |
215 | /** |
216 | * Read a variable size int. |
217 | * |
218 | * @param buff the source buffer |
219 | * @return the value |
220 | */ |
221 | public static int readVarInt(ByteBuffer buff) { |
222 | int b = buff.get(); |
223 | if (b >= 0) { |
224 | return b; |
225 | } |
226 | // a separate function so that this one can be inlined |
227 | return readVarIntRest(buff, b); |
228 | } |
229 | |
230 | private static int readVarIntRest(ByteBuffer buff, int b) { |
231 | int x = b & 0x7f; |
232 | b = buff.get(); |
233 | if (b >= 0) { |
234 | return x | (b << 7); |
235 | } |
236 | x |= (b & 0x7f) << 7; |
237 | b = buff.get(); |
238 | if (b >= 0) { |
239 | return x | (b << 14); |
240 | } |
241 | x |= (b & 0x7f) << 14; |
242 | b = buff.get(); |
243 | if (b >= 0) { |
244 | return x | b << 21; |
245 | } |
246 | x |= ((b & 0x7f) << 21) | (buff.get() << 28); |
247 | return x; |
248 | } |
249 | |
250 | /** |
251 | * Read a variable size long. |
252 | * |
253 | * @param buff the source buffer |
254 | * @return the value |
255 | */ |
256 | public static long readVarLong(ByteBuffer buff) { |
257 | long x = buff.get(); |
258 | if (x >= 0) { |
259 | return x; |
260 | } |
261 | x &= 0x7f; |
262 | for (int s = 7; s < 64; s += 7) { |
263 | long b = buff.get(); |
264 | x |= (b & 0x7f) << s; |
265 | if (b >= 0) { |
266 | break; |
267 | } |
268 | } |
269 | return x; |
270 | } |
271 | |
272 | /** |
273 | * Write a variable size int. |
274 | * |
275 | * @param out the output stream |
276 | * @param x the value |
277 | */ |
278 | public static void writeVarInt(OutputStream out, int x) throws IOException { |
279 | while ((x & ~0x7f) != 0) { |
280 | out.write((byte) (0x80 | (x & 0x7f))); |
281 | x >>>= 7; |
282 | } |
283 | out.write((byte) x); |
284 | } |
285 | |
286 | /** |
287 | * Write a variable size int. |
288 | * |
289 | * @param buff the source buffer |
290 | * @param x the value |
291 | */ |
292 | public static void writeVarInt(ByteBuffer buff, int x) { |
293 | while ((x & ~0x7f) != 0) { |
294 | buff.put((byte) (0x80 | (x & 0x7f))); |
295 | x >>>= 7; |
296 | } |
297 | buff.put((byte) x); |
298 | } |
299 | |
300 | /** |
301 | * Write characters from a string (without the length). |
302 | * |
303 | * @param buff the target buffer |
304 | * @param s the string |
305 | * @param len the number of characters |
306 | * @return the byte buffer |
307 | */ |
308 | public static ByteBuffer writeStringData(ByteBuffer buff, |
309 | String s, int len) { |
310 | buff = DataUtils.ensureCapacity(buff, 3 * len); |
311 | for (int i = 0; i < len; i++) { |
312 | int c = s.charAt(i); |
313 | if (c < 0x80) { |
314 | buff.put((byte) c); |
315 | } else if (c >= 0x800) { |
316 | buff.put((byte) (0xe0 | (c >> 12))); |
317 | buff.put((byte) (((c >> 6) & 0x3f))); |
318 | buff.put((byte) (c & 0x3f)); |
319 | } else { |
320 | buff.put((byte) (0xc0 | (c >> 6))); |
321 | buff.put((byte) (c & 0x3f)); |
322 | } |
323 | } |
324 | return buff; |
325 | } |
326 | |
327 | /** |
328 | * Read a string. |
329 | * |
330 | * @param buff the source buffer |
331 | * @param len the number of characters |
332 | * @return the value |
333 | */ |
334 | public static String readString(ByteBuffer buff, int len) { |
335 | char[] chars = new char[len]; |
336 | for (int i = 0; i < len; i++) { |
337 | int x = buff.get() & 0xff; |
338 | if (x < 0x80) { |
339 | chars[i] = (char) x; |
340 | } else if (x >= 0xe0) { |
341 | chars[i] = (char) (((x & 0xf) << 12) |
342 | + ((buff.get() & 0x3f) << 6) + (buff.get() & 0x3f)); |
343 | } else { |
344 | chars[i] = (char) (((x & 0x1f) << 6) + (buff.get() & 0x3f)); |
345 | } |
346 | } |
347 | return new String(chars); |
348 | } |
349 | |
350 | /** |
351 | * Write a variable size long. |
352 | * |
353 | * @param buff the target buffer |
354 | * @param x the value |
355 | */ |
356 | public static void writeVarLong(ByteBuffer buff, long x) { |
357 | while ((x & ~0x7f) != 0) { |
358 | buff.put((byte) (0x80 | (x & 0x7f))); |
359 | x >>>= 7; |
360 | } |
361 | buff.put((byte) x); |
362 | } |
363 | |
364 | /** |
365 | * Write a variable size long. |
366 | * |
367 | * @param out the output stream |
368 | * @param x the value |
369 | */ |
370 | public static void writeVarLong(OutputStream out, long x) |
371 | throws IOException { |
372 | while ((x & ~0x7f) != 0) { |
373 | out.write((byte) (0x80 | (x & 0x7f))); |
374 | x >>>= 7; |
375 | } |
376 | out.write((byte) x); |
377 | } |
378 | |
379 | /** |
380 | * Copy the elements of an array, with a gap. |
381 | * |
382 | * @param src the source array |
383 | * @param dst the target array |
384 | * @param oldSize the size of the old array |
385 | * @param gapIndex the index of the gap |
386 | */ |
387 | public static void copyWithGap(Object src, Object dst, int oldSize, |
388 | int gapIndex) { |
389 | if (gapIndex > 0) { |
390 | System.arraycopy(src, 0, dst, 0, gapIndex); |
391 | } |
392 | if (gapIndex < oldSize) { |
393 | System.arraycopy(src, gapIndex, dst, gapIndex + 1, oldSize |
394 | - gapIndex); |
395 | } |
396 | } |
397 | |
398 | /** |
399 | * Copy the elements of an array, and remove one element. |
400 | * |
401 | * @param src the source array |
402 | * @param dst the target array |
403 | * @param oldSize the size of the old array |
404 | * @param removeIndex the index of the entry to remove |
405 | */ |
406 | public static void copyExcept(Object src, Object dst, int oldSize, |
407 | int removeIndex) { |
408 | if (removeIndex > 0 && oldSize > 0) { |
409 | System.arraycopy(src, 0, dst, 0, removeIndex); |
410 | } |
411 | if (removeIndex < oldSize) { |
412 | System.arraycopy(src, removeIndex + 1, dst, removeIndex, oldSize |
413 | - removeIndex - 1); |
414 | } |
415 | } |
416 | |
417 | /** |
418 | * Read from a file channel until the buffer is full. |
419 | * The buffer is rewind after reading. |
420 | * |
421 | * @param file the file channel |
422 | * @param pos the absolute position within the file |
423 | * @param dst the byte buffer |
424 | * @throws IllegalStateException if some data could not be read |
425 | */ |
426 | public static void readFully(FileChannel file, long pos, ByteBuffer dst) { |
427 | try { |
428 | do { |
429 | int len = file.read(dst, pos); |
430 | if (len < 0) { |
431 | throw new EOFException(); |
432 | } |
433 | pos += len; |
434 | } while (dst.remaining() > 0); |
435 | dst.rewind(); |
436 | } catch (IOException e) { |
437 | long size; |
438 | try { |
439 | size = file.size(); |
440 | } catch (IOException e2) { |
441 | size = -1; |
442 | } |
443 | throw newIllegalStateException( |
444 | ERROR_READING_FAILED, |
445 | "Reading from {0} failed; file length {1} " + |
446 | "read length {2} at {3}", |
447 | file, size, dst.remaining(), pos, e); |
448 | } |
449 | } |
450 | |
451 | /** |
452 | * Write to a file channel. |
453 | * |
454 | * @param file the file channel |
455 | * @param pos the absolute position within the file |
456 | * @param src the source buffer |
457 | */ |
458 | public static void writeFully(FileChannel file, long pos, ByteBuffer src) { |
459 | try { |
460 | int off = 0; |
461 | do { |
462 | int len = file.write(src, pos + off); |
463 | off += len; |
464 | } while (src.remaining() > 0); |
465 | } catch (IOException e) { |
466 | throw newIllegalStateException( |
467 | ERROR_WRITING_FAILED, |
468 | "Writing to {0} failed; length {1} at {2}", |
469 | file, src.remaining(), pos, e); |
470 | } |
471 | } |
472 | |
473 | /** |
474 | * Convert the length to a length code 0..31. 31 means more than 1 MB. |
475 | * |
476 | * @param len the length |
477 | * @return the length code |
478 | */ |
479 | public static int encodeLength(int len) { |
480 | if (len <= 32) { |
481 | return 0; |
482 | } |
483 | int code = Integer.numberOfLeadingZeros(len); |
484 | int remaining = len << (code + 1); |
485 | code += code; |
486 | if ((remaining & (1 << 31)) != 0) { |
487 | code--; |
488 | } |
489 | if ((remaining << 1) != 0) { |
490 | code--; |
491 | } |
492 | code = Math.min(31, 52 - code); |
493 | // alternative code (slower): |
494 | // int x = len; |
495 | // int shift = 0; |
496 | // while (x > 3) { |
497 | // shift++; |
498 | // x = (x >>> 1) + (x & 1); |
499 | // } |
500 | // shift = Math.max(0, shift - 4); |
501 | // int code = (shift << 1) + (x & 1); |
502 | // code = Math.min(31, code); |
503 | return code; |
504 | } |
505 | |
506 | /** |
507 | * Get the chunk id from the position. |
508 | * |
509 | * @param pos the position |
510 | * @return the chunk id |
511 | */ |
512 | public static int getPageChunkId(long pos) { |
513 | return (int) (pos >>> 38); |
514 | } |
515 | |
516 | /** |
517 | * Get the maximum length for the given code. |
518 | * For the code 31, PAGE_LARGE is returned. |
519 | * |
520 | * @param pos the position |
521 | * @return the maximum length |
522 | */ |
523 | public static int getPageMaxLength(long pos) { |
524 | int code = (int) ((pos >> 1) & 31); |
525 | if (code == 31) { |
526 | return PAGE_LARGE; |
527 | } |
528 | return (2 + (code & 1)) << ((code >> 1) + 4); |
529 | } |
530 | |
531 | /** |
532 | * Get the offset from the position. |
533 | * |
534 | * @param pos the position |
535 | * @return the offset |
536 | */ |
537 | public static int getPageOffset(long pos) { |
538 | return (int) (pos >> 6); |
539 | } |
540 | |
541 | /** |
542 | * Get the page type from the position. |
543 | * |
544 | * @param pos the position |
545 | * @return the page type (PAGE_TYPE_NODE or PAGE_TYPE_LEAF) |
546 | */ |
547 | public static int getPageType(long pos) { |
548 | return ((int) pos) & 1; |
549 | } |
550 | |
551 | /** |
552 | * Get the position of this page. The following information is encoded in |
553 | * the position: the chunk id, the offset, the maximum length, and the type |
554 | * (node or leaf). |
555 | * |
556 | * @param chunkId the chunk id |
557 | * @param offset the offset |
558 | * @param length the length |
559 | * @param type the page type (1 for node, 0 for leaf) |
560 | * @return the position |
561 | */ |
562 | public static long getPagePos(int chunkId, int offset, |
563 | int length, int type) { |
564 | long pos = (long) chunkId << 38; |
565 | pos |= (long) offset << 6; |
566 | pos |= encodeLength(length) << 1; |
567 | pos |= type; |
568 | return pos; |
569 | } |
570 | |
571 | /** |
572 | * Calculate a check value for the given integer. A check value is mean to |
573 | * verify the data is consistent with a high probability, but not meant to |
574 | * protect against media failure or deliberate changes. |
575 | * |
576 | * @param x the value |
577 | * @return the check value |
578 | */ |
579 | public static short getCheckValue(int x) { |
580 | return (short) ((x >> 16) ^ x); |
581 | } |
582 | |
583 | /** |
584 | * Append a map to the string builder, sorted by key. |
585 | * |
586 | * @param buff the target buffer |
587 | * @param map the map |
588 | * @return the string builder |
589 | */ |
590 | public static StringBuilder appendMap(StringBuilder buff, |
591 | HashMap<String, ?> map) { |
592 | ArrayList<String> list = New.arrayList(map.keySet()); |
593 | Collections.sort(list); |
594 | for (String k : list) { |
595 | appendMap(buff, k, map.get(k)); |
596 | } |
597 | return buff; |
598 | } |
599 | |
600 | /** |
601 | * Append a key-value pair to the string builder. Keys may not contain a |
602 | * colon. Values that contain a comma or a double quote are enclosed in |
603 | * double quotes, with special characters escaped using a backslash. |
604 | * |
605 | * @param buff the target buffer |
606 | * @param key the key |
607 | * @param value the value |
608 | */ |
609 | public static void appendMap(StringBuilder buff, String key, Object value) { |
610 | if (buff.length() > 0) { |
611 | buff.append(','); |
612 | } |
613 | buff.append(key).append(':'); |
614 | String v; |
615 | if (value instanceof Long) { |
616 | v = Long.toHexString((Long) value); |
617 | } else if (value instanceof Integer) { |
618 | v = Integer.toHexString((Integer) value); |
619 | } else { |
620 | v = value.toString(); |
621 | } |
622 | if (v.indexOf(',') < 0 && v.indexOf('\"') < 0) { |
623 | buff.append(v); |
624 | } else { |
625 | buff.append('\"'); |
626 | for (int i = 0, size = v.length(); i < size; i++) { |
627 | char c = v.charAt(i); |
628 | if (c == '\"') { |
629 | buff.append('\\'); |
630 | } |
631 | buff.append(c); |
632 | } |
633 | buff.append('\"'); |
634 | } |
635 | } |
636 | |
637 | /** |
638 | * Parse a key-value pair list. |
639 | * |
640 | * @param s the list |
641 | * @return the map |
642 | * @throws IllegalStateException if parsing failed |
643 | */ |
644 | public static HashMap<String, String> parseMap(String s) { |
645 | HashMap<String, String> map = New.hashMap(); |
646 | for (int i = 0, size = s.length(); i < size;) { |
647 | int startKey = i; |
648 | i = s.indexOf(':', i); |
649 | if (i < 0) { |
650 | throw DataUtils.newIllegalStateException( |
651 | DataUtils.ERROR_FILE_CORRUPT, "Not a map: {0}", s); |
652 | } |
653 | String key = s.substring(startKey, i++); |
654 | StringBuilder buff = new StringBuilder(); |
655 | while (i < size) { |
656 | char c = s.charAt(i++); |
657 | if (c == ',') { |
658 | break; |
659 | } else if (c == '\"') { |
660 | while (i < size) { |
661 | c = s.charAt(i++); |
662 | if (c == '\\') { |
663 | if (i == size) { |
664 | throw DataUtils.newIllegalStateException( |
665 | DataUtils.ERROR_FILE_CORRUPT, |
666 | "Not a map: {0}", s); |
667 | } |
668 | c = s.charAt(i++); |
669 | } else if (c == '\"') { |
670 | break; |
671 | } |
672 | buff.append(c); |
673 | } |
674 | } else { |
675 | buff.append(c); |
676 | } |
677 | } |
678 | map.put(key, buff.toString()); |
679 | } |
680 | return map; |
681 | } |
682 | |
683 | /** |
684 | * Calculate the Fletcher32 checksum. |
685 | * |
686 | * @param bytes the bytes |
687 | * @param length the message length (if odd, 0 is appended) |
688 | * @return the checksum |
689 | */ |
690 | public static int getFletcher32(byte[] bytes, int length) { |
691 | int s1 = 0xffff, s2 = 0xffff; |
692 | int i = 0, evenLength = length / 2 * 2; |
693 | while (i < evenLength) { |
694 | // reduce after 360 words (each word is two bytes) |
695 | for (int end = Math.min(i + 720, evenLength); i < end;) { |
696 | int x = ((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff); |
697 | s2 += s1 += x; |
698 | } |
699 | s1 = (s1 & 0xffff) + (s1 >>> 16); |
700 | s2 = (s2 & 0xffff) + (s2 >>> 16); |
701 | } |
702 | if (i < length) { |
703 | // odd length: append 0 |
704 | int x = (bytes[i] & 0xff) << 8; |
705 | s2 += s1 += x; |
706 | } |
707 | s1 = (s1 & 0xffff) + (s1 >>> 16); |
708 | s2 = (s2 & 0xffff) + (s2 >>> 16); |
709 | return (s2 << 16) | s1; |
710 | } |
711 | |
712 | /** |
713 | * Throw an IllegalArgumentException if the argument is invalid. |
714 | * |
715 | * @param test true if the argument is valid |
716 | * @param message the message |
717 | * @param arguments the arguments |
718 | * @throws IllegalArgumentException if the argument is invalid |
719 | */ |
720 | public static void checkArgument(boolean test, String message, |
721 | Object... arguments) { |
722 | if (!test) { |
723 | throw newIllegalArgumentException(message, arguments); |
724 | } |
725 | } |
726 | |
727 | /** |
728 | * Create a new IllegalArgumentException. |
729 | * |
730 | * @param message the message |
731 | * @param arguments the arguments |
732 | * @return the exception |
733 | */ |
734 | public static IllegalArgumentException newIllegalArgumentException( |
735 | String message, Object... arguments) { |
736 | return initCause(new IllegalArgumentException( |
737 | formatMessage(0, message, arguments)), |
738 | arguments); |
739 | } |
740 | |
741 | /** |
742 | * Create a new UnsupportedOperationException. |
743 | * |
744 | * @param message the message |
745 | * @return the exception |
746 | */ |
747 | public static UnsupportedOperationException |
748 | newUnsupportedOperationException(String message) { |
749 | return new UnsupportedOperationException(formatMessage(0, message)); |
750 | } |
751 | |
752 | /** |
753 | * Create a new ConcurrentModificationException. |
754 | * |
755 | * @param message the message |
756 | * @return the exception |
757 | */ |
758 | public static ConcurrentModificationException |
759 | newConcurrentModificationException(String message) { |
760 | return new ConcurrentModificationException(formatMessage(0, message)); |
761 | } |
762 | |
763 | /** |
764 | * Create a new IllegalStateException. |
765 | * |
766 | * @param errorCode the error code |
767 | * @param message the message |
768 | * @param arguments the arguments |
769 | * @return the exception |
770 | */ |
771 | public static IllegalStateException newIllegalStateException( |
772 | int errorCode, String message, Object... arguments) { |
773 | return initCause(new IllegalStateException( |
774 | formatMessage(errorCode, message, arguments)), |
775 | arguments); |
776 | } |
777 | |
778 | private static <T extends Exception> T initCause(T e, Object... arguments) { |
779 | int size = arguments.length; |
780 | if (size > 0) { |
781 | Object o = arguments[size - 1]; |
782 | if (o instanceof Exception) { |
783 | e.initCause((Exception) o); |
784 | } |
785 | } |
786 | return e; |
787 | } |
788 | |
789 | /** |
790 | * Format an error message. |
791 | * |
792 | * @param errorCode the error code |
793 | * @param message the message |
794 | * @param arguments the arguments |
795 | * @return the formatted message |
796 | */ |
797 | public static String formatMessage(int errorCode, String message, |
798 | Object... arguments) { |
799 | // convert arguments to strings, to avoid locale specific formatting |
800 | for (int i = 0; i < arguments.length; i++) { |
801 | Object a = arguments[i]; |
802 | if (!(a instanceof Exception)) { |
803 | String s = a == null ? "null" : a.toString(); |
804 | if (s.length() > 1000) { |
805 | s = s.substring(0, 1000) + "..."; |
806 | } |
807 | arguments[i] = s; |
808 | } |
809 | } |
810 | return MessageFormat.format(message, arguments) + |
811 | " [" + Constants.VERSION_MAJOR + "." + |
812 | Constants.VERSION_MINOR + "." + Constants.BUILD_ID + |
813 | "/" + errorCode + "]"; |
814 | } |
815 | |
816 | /** |
817 | * Get the error code from an exception message. |
818 | * |
819 | * @param m the message |
820 | * @return the error code, or 0 if none |
821 | */ |
822 | public static int getErrorCode(String m) { |
823 | if (m != null && m.endsWith("]")) { |
824 | int dash = m.lastIndexOf('/'); |
825 | if (dash >= 0) { |
826 | String s = m.substring(dash + 1, m.length() - 1); |
827 | try { |
828 | return Integer.parseInt(s); |
829 | } catch (NumberFormatException e) { |
830 | // no error code |
831 | } |
832 | } |
833 | } |
834 | return 0; |
835 | } |
836 | |
837 | /** |
838 | * Create an array of bytes with the given size. If this is not possible |
839 | * because not enough memory is available, an OutOfMemoryError with the |
840 | * requested size in the message is thrown. |
841 | * <p> |
842 | * This method should be used if the size of the array is user defined, or |
843 | * stored in a file, so wrong size data can be distinguished from regular |
844 | * out-of-memory. |
845 | * |
846 | * @param len the number of bytes requested |
847 | * @return the byte array |
848 | * @throws OutOfMemoryError if the allocation was too large |
849 | */ |
850 | public static byte[] newBytes(int len) { |
851 | if (len == 0) { |
852 | return EMPTY_BYTES; |
853 | } |
854 | try { |
855 | return new byte[len]; |
856 | } catch (OutOfMemoryError e) { |
857 | Error e2 = new OutOfMemoryError("Requested memory: " + len); |
858 | e2.initCause(e); |
859 | throw e2; |
860 | } |
861 | } |
862 | |
863 | /** |
864 | * Ensure the byte buffer has the given capacity, plus 1 KB. If not, a new, |
865 | * larger byte buffer is created and the data is copied. |
866 | * |
867 | * @param buff the byte buffer |
868 | * @param len the minimum remaining capacity |
869 | * @return the byte buffer (possibly a new one) |
870 | */ |
871 | public static ByteBuffer ensureCapacity(ByteBuffer buff, int len) { |
872 | len += 1024; |
873 | if (buff.remaining() > len) { |
874 | return buff; |
875 | } |
876 | return grow(buff, len); |
877 | } |
878 | |
879 | private static ByteBuffer grow(ByteBuffer buff, int len) { |
880 | len = buff.remaining() + len; |
881 | int capacity = buff.capacity(); |
882 | len = Math.max(len, Math.min(capacity + MAX_GROW, capacity * 2)); |
883 | ByteBuffer temp = ByteBuffer.allocate(len); |
884 | buff.flip(); |
885 | temp.put(buff); |
886 | return temp; |
887 | } |
888 | |
889 | /** |
890 | * Read a hex long value from a map. |
891 | * |
892 | * @param map the map |
893 | * @param key the key |
894 | * @param defaultValue if the value is null |
895 | * @return the parsed value |
896 | * @throws IllegalStateException if parsing fails |
897 | */ |
898 | public static long readHexLong(Map<String, ? extends Object> map, |
899 | String key, long defaultValue) { |
900 | Object v = map.get(key); |
901 | if (v == null) { |
902 | return defaultValue; |
903 | } else if (v instanceof Long) { |
904 | return (Long) v; |
905 | } |
906 | try { |
907 | return parseHexLong((String) v); |
908 | } catch (NumberFormatException e) { |
909 | throw newIllegalStateException(ERROR_FILE_CORRUPT, |
910 | "Error parsing the value {0}", v, e); |
911 | } |
912 | } |
913 | |
914 | /** |
915 | * Parse an unsigned, hex long. |
916 | * |
917 | * @param x the string |
918 | * @return the parsed value |
919 | * @throws IllegalStateException if parsing fails |
920 | */ |
921 | public static long parseHexLong(String x) { |
922 | try { |
923 | if (x.length() == 16) { |
924 | // avoid problems with overflow |
925 | // in Java 8, this special case is not needed |
926 | return (Long.parseLong(x.substring(0, 8), 16) << 32) | |
927 | Long.parseLong(x.substring(8, 16), 16); |
928 | } |
929 | return Long.parseLong(x, 16); |
930 | } catch (NumberFormatException e) { |
931 | throw newIllegalStateException(ERROR_FILE_CORRUPT, |
932 | "Error parsing the value {0}", x, e); |
933 | } |
934 | } |
935 | |
936 | /** |
937 | * Parse an unsigned, hex long. |
938 | * |
939 | * @param x the string |
940 | * @return the parsed value |
941 | * @throws IllegalStateException if parsing fails |
942 | */ |
943 | public static int parseHexInt(String x) { |
944 | try { |
945 | // avoid problems with overflow |
946 | // in Java 8, we can use Integer.parseLong(x, 16); |
947 | return (int) Long.parseLong(x, 16); |
948 | } catch (NumberFormatException e) { |
949 | throw newIllegalStateException(ERROR_FILE_CORRUPT, |
950 | "Error parsing the value {0}", x, e); |
951 | } |
952 | } |
953 | |
954 | /** |
955 | * Read a hex int value from a map. |
956 | * |
957 | * @param map the map |
958 | * @param key the key |
959 | * @param defaultValue if the value is null |
960 | * @return the parsed value |
961 | * @throws IllegalStateException if parsing fails |
962 | */ |
963 | public static int readHexInt(HashMap<String, ? extends Object> map, |
964 | String key, int defaultValue) { |
965 | Object v = map.get(key); |
966 | if (v == null) { |
967 | return defaultValue; |
968 | } else if (v instanceof Integer) { |
969 | return (Integer) v; |
970 | } |
971 | try { |
972 | // support unsigned hex value |
973 | return (int) Long.parseLong((String) v, 16); |
974 | } catch (NumberFormatException e) { |
975 | throw newIllegalStateException(ERROR_FILE_CORRUPT, |
976 | "Error parsing the value {0}", v, e); |
977 | } |
978 | } |
979 | |
980 | /** |
981 | * An entry of a map. |
982 | * |
983 | * @param <K> the key type |
984 | * @param <V> the value type |
985 | */ |
986 | public static class MapEntry<K, V> implements Map.Entry<K, V> { |
987 | |
988 | private final K key; |
989 | private V value; |
990 | |
991 | public MapEntry(K key, V value) { |
992 | this.key = key; |
993 | this.value = value; |
994 | } |
995 | |
996 | @Override |
997 | public K getKey() { |
998 | return key; |
999 | } |
1000 | |
1001 | @Override |
1002 | public V getValue() { |
1003 | return value; |
1004 | } |
1005 | |
1006 | @Override |
1007 | public V setValue(V value) { |
1008 | throw DataUtils.newUnsupportedOperationException( |
1009 | "Updating the value is not supported"); |
1010 | } |
1011 | |
1012 | } |
1013 | |
1014 | } |