001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.commons.encoding; 021 022import java.io.DataInputStream; 023import java.io.DataOutputStream; 024import java.io.IOException; 025import java.io.ObjectInputStream; 026import java.io.ObjectOutputStream; 027import java.io.Serializable; 028import java.lang.reflect.Array; 029import java.lang.reflect.Constructor; 030import java.lang.reflect.InvocationTargetException; 031import java.util.HashMap; 032import java.util.Map; 033 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * Typesafe writing and reading of fields, providing some level of integrity 039 * checking of encoded messages. 040 * 041 * <p> 042 * The {@link #write(DataOutputExtended, Object)} writes out field type and then 043 * the data for that field type. The field type is represented by this 044 * enumberation, with the {@link FieldType#getIdx() index} being what is written 045 * to the stream (hence of type <tt>byte</tt> to keep small). 046 * 047 * <p> 048 * Conversely, the {@link #read(DataInputExtended)} reads the field type and 049 * then the data for that field type. 050 */ 051public abstract class FieldType<T> { 052 053 private static Logger LOG = LoggerFactory.getLogger(FieldType.class); 054 055 private static String LOG_INDENT = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "; 056 private static final int NULL_BIT = 64; // 2 to the 6 057 058 private static Map<Byte, FieldType<?>> cache = new HashMap<Byte, FieldType<?>>(); 059 private static int next = 0; 060 061 static enum Indenting { 062 INDENT_ONLY, INDENT_AND_OUTDENT; 063 } 064 065 public static FieldType<Boolean> BOOLEAN = new FieldType<Boolean>((byte) next++, Boolean.class, Indenting.INDENT_ONLY) { 066 @Override 067 protected void doWrite(final DataOutputExtended output, final Boolean value) throws IOException { 068 try { 069 if (LOG.isDebugEnabled()) { 070 log(this, new StringBuilder().append(value)); 071 } 072 final DataOutputStream outputStream = output.getDataOutputStream(); 073 outputStream.writeBoolean(value); 074 } finally { 075 if (LOG.isDebugEnabled()) { 076 unlog(this); 077 } 078 } 079 } 080 081 @Override 082 protected Boolean doRead(final DataInputExtended input) throws IOException { 083 try { 084 final DataInputStream inputStream = input.getDataInputStream(); 085 final boolean value = inputStream.readBoolean(); 086 if (LOG.isDebugEnabled()) { 087 log(this, new StringBuilder().append(value)); 088 } 089 return value; 090 } finally { 091 if (LOG.isDebugEnabled()) { 092 unlog(this); 093 } 094 } 095 } 096 }; 097 098 public static FieldType<boolean[]> BOOLEAN_ARRAY = new FieldType<boolean[]>((byte) next++, boolean[].class, Indenting.INDENT_AND_OUTDENT) { 099 @Override 100 protected void doWrite(final DataOutputExtended output, final boolean[] values) throws IOException { 101 try { 102 final StringBuilder buf = new StringBuilder(); 103 final DataOutputStream outputStream = output.getDataOutputStream(); 104 outputStream.writeInt(values.length); 105 if (LOG.isDebugEnabled()) { 106 buf.append("length: ").append(values.length); 107 } 108 for (int i = 0; i < values.length; i++) { 109 outputStream.writeBoolean(values[i]); 110 if (LOG.isDebugEnabled()) { 111 buf.append(i == 0 ? ": " : ", "); 112 buf.append(values[i]); 113 } 114 } 115 if (LOG.isDebugEnabled()) { 116 log(this, buf); 117 } 118 } finally { 119 if (LOG.isDebugEnabled()) { 120 unlog(this); 121 } 122 } 123 } 124 125 @Override 126 protected boolean[] doRead(final DataInputExtended input) throws IOException { 127 try { 128 final StringBuilder buf = new StringBuilder(); 129 final DataInputStream inputStream = input.getDataInputStream(); 130 final int length = inputStream.readInt(); 131 if (LOG.isDebugEnabled()) { 132 buf.append("length: ").append(length); 133 } 134 final boolean[] values = new boolean[length]; 135 for (int i = 0; i < values.length; i++) { 136 values[i] = inputStream.readBoolean(); 137 if (LOG.isDebugEnabled()) { 138 buf.append(i == 0 ? ": " : ", "); 139 buf.append(values[i]); 140 } 141 } 142 if (LOG.isDebugEnabled()) { 143 log(this, buf); 144 } 145 return values; 146 } finally { 147 if (LOG.isDebugEnabled()) { 148 unlog(this); 149 } 150 } 151 } 152 }; 153 154 public static FieldType<Byte> BYTE = new FieldType<Byte>((byte) next++, Byte.class, Indenting.INDENT_ONLY) { 155 @Override 156 protected void doWrite(final DataOutputExtended output, final Byte value) throws IOException { 157 try { 158 if (LOG.isDebugEnabled()) { 159 log(this, new StringBuilder().append(value)); 160 } 161 final DataOutputStream outputStream = output.getDataOutputStream(); 162 outputStream.writeByte(value.byteValue()); 163 } finally { 164 if (LOG.isDebugEnabled()) { 165 unlog(this); 166 } 167 } 168 } 169 170 @Override 171 protected Byte doRead(final DataInputExtended input) throws IOException { 172 try { 173 final DataInputStream inputStream = input.getDataInputStream(); 174 final byte value = inputStream.readByte(); 175 if (LOG.isDebugEnabled()) { 176 log(this, new StringBuilder().append(value)); 177 } 178 return value; 179 } finally { 180 if (LOG.isDebugEnabled()) { 181 unlog(this); 182 } 183 } 184 } 185 }; 186 187 public static FieldType<byte[]> BYTE_ARRAY = new FieldType<byte[]>((byte) next++, byte[].class, Indenting.INDENT_AND_OUTDENT) { 188 @Override 189 protected void doWrite(final DataOutputExtended output, final byte[] values) throws IOException { 190 try { 191 final DataOutputStream outputStream = output.getDataOutputStream(); 192 final int length = values.length; 193 outputStream.writeInt(length); 194 if (LOG.isDebugEnabled()) { 195 log(this, new StringBuilder().append("length:").append(length).append(" [BYTE ARRAY]")); 196 } 197 198 // rather than looping through the array, 199 // we take advantage of optimization built into DataOutputStream 200 outputStream.write(values); 201 } finally { 202 if (LOG.isDebugEnabled()) { 203 unlog(this); 204 } 205 } 206 } 207 208 @Override 209 protected byte[] doRead(final DataInputExtended input) throws IOException { 210 try { 211 final DataInputStream inputStream = input.getDataInputStream(); 212 final int length = inputStream.readInt(); 213 if (LOG.isDebugEnabled()) { 214 final StringBuilder msg = new StringBuilder().append("length:").append(length).append(" [BYTE ARRAY]"); 215 log(this, msg); 216 } 217 218 final byte[] bytes = new byte[length]; 219 readBytes(inputStream, bytes); 220 return bytes; 221 } finally { 222 if (LOG.isDebugEnabled()) { 223 unlog(this); 224 } 225 } 226 } 227 228 // rather than looping through the array, 229 // we take advantage of optimization built into DataInputStream 230 private void readBytes(final DataInputStream inputStream, final byte[] bytes) throws IOException { 231 inputStream.read(bytes); 232 } 233 }; 234 235 public static FieldType<Short> SHORT = new FieldType<Short>((byte) next++, Short.class, Indenting.INDENT_ONLY) { 236 @Override 237 protected void doWrite(final DataOutputExtended output, final Short value) throws IOException { 238 try { 239 if (LOG.isDebugEnabled()) { 240 log(this, new StringBuilder().append(value)); 241 } 242 final DataOutputStream outputStream = output.getDataOutputStream(); 243 outputStream.writeShort(value.shortValue()); 244 } finally { 245 if (LOG.isDebugEnabled()) { 246 unlog(this); 247 } 248 } 249 } 250 251 @Override 252 protected Short doRead(final DataInputExtended input) throws IOException { 253 try { 254 final DataInputStream inputStream = input.getDataInputStream(); 255 final short value = inputStream.readShort(); 256 if (LOG.isDebugEnabled()) { 257 log(this, new StringBuilder().append(value)); 258 } 259 return value; 260 } finally { 261 if (LOG.isDebugEnabled()) { 262 unlog(this); 263 } 264 } 265 } 266 }; 267 268 public static FieldType<short[]> SHORT_ARRAY = new FieldType<short[]>((byte) next++, short[].class, Indenting.INDENT_AND_OUTDENT) { 269 @Override 270 protected void doWrite(final DataOutputExtended output, final short[] values) throws IOException { 271 try { 272 final StringBuilder buf = new StringBuilder(); 273 final DataOutputStream outputStream = output.getDataOutputStream(); 274 outputStream.writeInt(values.length); 275 if (LOG.isDebugEnabled()) { 276 buf.append("length: ").append(values.length); 277 } 278 279 for (int i = 0; i < values.length; i++) { 280 outputStream.writeShort(values[i]); 281 if (LOG.isDebugEnabled()) { 282 buf.append(i == 0 ? ": " : ", "); 283 buf.append(values[i]); 284 } 285 } 286 if (LOG.isDebugEnabled()) { 287 log(this, buf); 288 } 289 } finally { 290 if (LOG.isDebugEnabled()) { 291 unlog(this); 292 } 293 } 294 } 295 296 @Override 297 protected short[] doRead(final DataInputExtended input) throws IOException { 298 try { 299 final StringBuilder buf = new StringBuilder(); 300 final DataInputStream inputStream = input.getDataInputStream(); 301 final int length = inputStream.readInt(); 302 if (LOG.isDebugEnabled()) { 303 buf.append("length: ").append(length); 304 } 305 306 final short[] values = new short[length]; 307 for (int i = 0; i < values.length; i++) { 308 values[i] = inputStream.readShort(); 309 if (LOG.isDebugEnabled()) { 310 buf.append(i == 0 ? ": " : ", "); 311 buf.append(values[i]); 312 } 313 } 314 if (LOG.isDebugEnabled()) { 315 log(this, buf); 316 } 317 return values; 318 } finally { 319 if (LOG.isDebugEnabled()) { 320 unlog(this); 321 } 322 } 323 } 324 }; 325 326 public static FieldType<Integer> INTEGER = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { 327 @Override 328 protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { 329 try { 330 if (LOG.isDebugEnabled()) { 331 log(this, new StringBuilder().append(value)); 332 } 333 final DataOutputStream outputStream = output.getDataOutputStream(); 334 outputStream.writeInt(value.intValue()); 335 } finally { 336 if (LOG.isDebugEnabled()) { 337 unlog(this); 338 } 339 } 340 } 341 342 @Override 343 protected Integer doRead(final DataInputExtended input) throws IOException { 344 try { 345 final DataInputStream inputStream = input.getDataInputStream(); 346 final int value = inputStream.readInt(); 347 if (LOG.isDebugEnabled()) { 348 log(this, new StringBuilder().append(value)); 349 } 350 return value; 351 } finally { 352 if (LOG.isDebugEnabled()) { 353 unlog(this); 354 } 355 } 356 } 357 }; 358 359 public static FieldType<Integer> UNSIGNED_BYTE = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { 360 @Override 361 protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { 362 try { 363 if (LOG.isDebugEnabled()) { 364 log(this, new StringBuilder().append(value)); 365 } 366 final DataOutputStream outputStream = output.getDataOutputStream(); 367 outputStream.writeByte(value); 368 } finally { 369 if (LOG.isDebugEnabled()) { 370 unlog(this); 371 } 372 } 373 } 374 375 @Override 376 protected Integer doRead(final DataInputExtended input) throws IOException { 377 try { 378 final DataInputStream inputStream = input.getDataInputStream(); 379 final int value = inputStream.readUnsignedByte(); 380 if (LOG.isDebugEnabled()) { 381 log(this, new StringBuilder().append(value)); 382 } 383 return value; 384 } finally { 385 if (LOG.isDebugEnabled()) { 386 unlog(this); 387 } 388 } 389 } 390 }; 391 392 public static FieldType<Integer> UNSIGNED_SHORT = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { 393 @Override 394 protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { 395 try { 396 if (LOG.isDebugEnabled()) { 397 log(this, new StringBuilder().append(value)); 398 } 399 final DataOutputStream outputStream = output.getDataOutputStream(); 400 outputStream.writeShort(value); 401 } finally { 402 if (LOG.isDebugEnabled()) { 403 unlog(this); 404 } 405 } 406 } 407 408 @Override 409 protected Integer doRead(final DataInputExtended input) throws IOException { 410 try { 411 final DataInputStream inputStream = input.getDataInputStream(); 412 final int value = inputStream.readUnsignedShort(); 413 if (LOG.isDebugEnabled()) { 414 log(this, new StringBuilder().append(value)); 415 } 416 return value; 417 } finally { 418 if (LOG.isDebugEnabled()) { 419 unlog(this); 420 } 421 } 422 } 423 }; 424 425 public static FieldType<int[]> INTEGER_ARRAY = new FieldType<int[]>((byte) next++, int[].class, Indenting.INDENT_AND_OUTDENT) { 426 @Override 427 protected void doWrite(final DataOutputExtended output, final int[] values) throws IOException { 428 try { 429 final StringBuilder buf = new StringBuilder(); 430 final DataOutputStream outputStream = output.getDataOutputStream(); 431 outputStream.writeInt(values.length); 432 if (LOG.isDebugEnabled()) { 433 buf.append("length: ").append(values.length); 434 } 435 436 for (int i = 0; i < values.length; i++) { 437 outputStream.writeInt(values[i]); 438 if (LOG.isDebugEnabled()) { 439 buf.append(i == 0 ? ": " : ", "); 440 buf.append(values[i]); 441 } 442 } 443 if (LOG.isDebugEnabled()) { 444 log(this, buf); 445 } 446 } finally { 447 if (LOG.isDebugEnabled()) { 448 unlog(this); 449 } 450 } 451 } 452 453 @Override 454 protected int[] doRead(final DataInputExtended input) throws IOException { 455 try { 456 final StringBuilder buf = new StringBuilder(); 457 final DataInputStream inputStream = input.getDataInputStream(); 458 final int length = inputStream.readInt(); 459 if (LOG.isDebugEnabled()) { 460 buf.append("length: ").append(length); 461 } 462 463 final int[] values = new int[length]; 464 for (int i = 0; i < values.length; i++) { 465 values[i] = inputStream.readInt(); 466 if (LOG.isDebugEnabled()) { 467 buf.append(i == 0 ? ": " : ", "); 468 buf.append(values[i]); 469 } 470 } 471 if (LOG.isDebugEnabled()) { 472 log(this, buf); 473 } 474 return values; 475 } finally { 476 if (LOG.isDebugEnabled()) { 477 unlog(this); 478 } 479 } 480 } 481 }; 482 483 public static FieldType<Long> LONG = new FieldType<Long>((byte) next++, Long.class, Indenting.INDENT_ONLY) { 484 @Override 485 protected void doWrite(final DataOutputExtended output, final Long value) throws IOException { 486 try { 487 if (LOG.isDebugEnabled()) { 488 log(this, new StringBuilder().append(value)); 489 } 490 final DataOutputStream outputStream = output.getDataOutputStream(); 491 outputStream.writeLong(value.intValue()); 492 } finally { 493 if (LOG.isDebugEnabled()) { 494 unlog(this); 495 } 496 } 497 } 498 499 @Override 500 protected Long doRead(final DataInputExtended input) throws IOException { 501 try { 502 final DataInputStream inputStream = input.getDataInputStream(); 503 final long value = inputStream.readLong(); 504 if (LOG.isDebugEnabled()) { 505 log(this, new StringBuilder().append(value)); 506 } 507 return value; 508 } finally { 509 if (LOG.isDebugEnabled()) { 510 unlog(this); 511 } 512 } 513 } 514 }; 515 public static FieldType<long[]> LONG_ARRAY = new FieldType<long[]>((byte) next++, long[].class, Indenting.INDENT_AND_OUTDENT) { 516 @Override 517 protected void doWrite(final DataOutputExtended output, final long[] values) throws IOException { 518 try { 519 final StringBuilder buf = new StringBuilder(); 520 final DataOutputStream outputStream = output.getDataOutputStream(); 521 outputStream.writeInt(values.length); 522 if (LOG.isDebugEnabled()) { 523 buf.append("length: ").append(values.length); 524 } 525 526 for (int i = 0; i < values.length; i++) { 527 outputStream.writeLong(values[i]); 528 if (LOG.isDebugEnabled()) { 529 buf.append(i == 0 ? ": " : ", "); 530 buf.append(values[i]); 531 } 532 } 533 if (LOG.isDebugEnabled()) { 534 log(this, buf); 535 } 536 } finally { 537 if (LOG.isDebugEnabled()) { 538 unlog(this); 539 } 540 } 541 } 542 543 @Override 544 protected long[] doRead(final DataInputExtended input) throws IOException { 545 try { 546 final StringBuilder buf = new StringBuilder(); 547 548 final DataInputStream inputStream = input.getDataInputStream(); 549 final int length = inputStream.readInt(); 550 if (LOG.isDebugEnabled()) { 551 buf.append("length: ").append(length); 552 } 553 554 final long[] values = new long[length]; 555 for (int i = 0; i < values.length; i++) { 556 values[i] = inputStream.readLong(); 557 if (LOG.isDebugEnabled()) { 558 buf.append(i == 0 ? ": " : ", "); 559 buf.append(values[i]); 560 } 561 } 562 if (LOG.isDebugEnabled()) { 563 log(this, buf); 564 } 565 return values; 566 } finally { 567 if (LOG.isDebugEnabled()) { 568 unlog(this); 569 } 570 } 571 } 572 }; 573 574 public static FieldType<Character> CHAR = new FieldType<Character>((byte) next++, Character.class, Indenting.INDENT_ONLY) { 575 @Override 576 protected void doWrite(final DataOutputExtended output, final Character value) throws IOException { 577 try { 578 if (LOG.isDebugEnabled()) { 579 log(this, new StringBuilder().append(value)); 580 } 581 final DataOutputStream outputStream = output.getDataOutputStream(); 582 outputStream.writeLong(value.charValue()); 583 } finally { 584 if (LOG.isDebugEnabled()) { 585 unlog(this); 586 } 587 } 588 } 589 590 @Override 591 protected Character doRead(final DataInputExtended input) throws IOException { 592 try { 593 final DataInputStream inputStream = input.getDataInputStream(); 594 final char value = inputStream.readChar(); 595 if (LOG.isDebugEnabled()) { 596 log(this, new StringBuilder().append(value)); 597 } 598 return value; 599 } finally { 600 if (LOG.isDebugEnabled()) { 601 unlog(this); 602 } 603 } 604 } 605 }; 606 607 public static FieldType<char[]> CHAR_ARRAY = new FieldType<char[]>((byte) next++, char[].class, Indenting.INDENT_AND_OUTDENT) { 608 // TODO: could perhaps optimize by writing out as a string 609 @Override 610 protected void doWrite(final DataOutputExtended output, final char[] values) throws IOException { 611 try { 612 final StringBuilder buf = new StringBuilder(); 613 final DataOutputStream outputStream = output.getDataOutputStream(); 614 outputStream.writeInt(values.length); 615 if (LOG.isDebugEnabled()) { 616 buf.append("length: ").append(values.length); 617 } 618 619 for (int i = 0; i < values.length; i++) { 620 outputStream.writeChar(values[i]); 621 if (LOG.isDebugEnabled()) { 622 buf.append(i == 0 ? ": " : ", "); 623 buf.append(values[i]); 624 } 625 } 626 if (LOG.isDebugEnabled()) { 627 log(this, buf); 628 } 629 } finally { 630 if (LOG.isDebugEnabled()) { 631 unlog(this); 632 } 633 } 634 } 635 636 @Override 637 protected char[] doRead(final DataInputExtended input) throws IOException { 638 try { 639 final StringBuilder buf = new StringBuilder(); 640 final DataInputStream inputStream = input.getDataInputStream(); 641 final int length = inputStream.readInt(); 642 if (LOG.isDebugEnabled()) { 643 buf.append("length: ").append(length); 644 } 645 646 final char[] values = new char[length]; 647 for (int i = 0; i < values.length; i++) { 648 if (LOG.isDebugEnabled()) { 649 buf.append(i == 0 ? ": " : ", "); 650 buf.append(values[i]); 651 } 652 values[i] = inputStream.readChar(); 653 } 654 if (LOG.isDebugEnabled()) { 655 log(this, buf); 656 } 657 return values; 658 } finally { 659 if (LOG.isDebugEnabled()) { 660 unlog(this); 661 } 662 } 663 } 664 }; 665 666 public static FieldType<Float> FLOAT = new FieldType<Float>((byte) next++, Float.class, Indenting.INDENT_ONLY) { 667 @Override 668 protected void doWrite(final DataOutputExtended output, final Float value) throws IOException { 669 try { 670 if (LOG.isDebugEnabled()) { 671 log(this, new StringBuilder().append(value)); 672 } 673 final DataOutputStream outputStream = output.getDataOutputStream(); 674 outputStream.writeFloat(value); 675 } finally { 676 if (LOG.isDebugEnabled()) { 677 unlog(this); 678 } 679 } 680 } 681 682 @Override 683 protected Float doRead(final DataInputExtended input) throws IOException { 684 try { 685 final DataInputStream inputStream = input.getDataInputStream(); 686 final float value = inputStream.readFloat(); 687 if (LOG.isDebugEnabled()) { 688 log(this, new StringBuilder().append(value)); 689 } 690 return value; 691 } finally { 692 if (LOG.isDebugEnabled()) { 693 unlog(this); 694 } 695 } 696 } 697 }; 698 699 public static FieldType<float[]> FLOAT_ARRAY = new FieldType<float[]>((byte) next++, float[].class, Indenting.INDENT_AND_OUTDENT) { 700 @Override 701 protected void doWrite(final DataOutputExtended output, final float[] values) throws IOException { 702 try { 703 final StringBuilder buf = new StringBuilder(); 704 final DataOutputStream outputStream = output.getDataOutputStream(); 705 outputStream.writeInt(values.length); 706 if (LOG.isDebugEnabled()) { 707 buf.append("length: ").append(values.length); 708 } 709 710 for (int i = 0; i < values.length; i++) { 711 outputStream.writeFloat(values[i]); 712 if (LOG.isDebugEnabled()) { 713 buf.append(i == 0 ? ": " : ", "); 714 buf.append(values[i]); 715 } 716 } 717 if (LOG.isDebugEnabled()) { 718 log(this, buf); 719 } 720 } finally { 721 if (LOG.isDebugEnabled()) { 722 unlog(this); 723 } 724 } 725 } 726 727 @Override 728 protected float[] doRead(final DataInputExtended input) throws IOException { 729 try { 730 final StringBuilder buf = new StringBuilder(); 731 final DataInputStream inputStream = input.getDataInputStream(); 732 final int length = inputStream.readInt(); 733 if (LOG.isDebugEnabled()) { 734 buf.append("length: ").append(length); 735 } 736 737 final float[] values = new float[length]; 738 for (int i = 0; i < values.length; i++) { 739 values[i] = inputStream.readFloat(); 740 if (LOG.isDebugEnabled()) { 741 buf.append(i == 0 ? ": " : ", "); 742 buf.append(values[i]); 743 } 744 } 745 if (LOG.isDebugEnabled()) { 746 log(this, buf); 747 } 748 return values; 749 } finally { 750 if (LOG.isDebugEnabled()) { 751 unlog(this); 752 } 753 } 754 } 755 }; 756 757 public static FieldType<Double> DOUBLE = new FieldType<Double>((byte) next++, Double.class, Indenting.INDENT_ONLY) { 758 @Override 759 protected void doWrite(final DataOutputExtended output, final Double value) throws IOException { 760 try { 761 if (LOG.isDebugEnabled()) { 762 log(this, new StringBuilder().append(value)); 763 } 764 final DataOutputStream outputStream = output.getDataOutputStream(); 765 outputStream.writeDouble(value); 766 } finally { 767 if (LOG.isDebugEnabled()) { 768 unlog(this); 769 } 770 } 771 } 772 773 @Override 774 protected Double doRead(final DataInputExtended input) throws IOException { 775 try { 776 final DataInputStream inputStream = input.getDataInputStream(); 777 final double value = inputStream.readDouble(); 778 if (LOG.isDebugEnabled()) { 779 log(this, new StringBuilder().append(value)); 780 } 781 return value; 782 } finally { 783 if (LOG.isDebugEnabled()) { 784 unlog(this); 785 } 786 } 787 } 788 }; 789 790 public static FieldType<double[]> DOUBLE_ARRAY = new FieldType<double[]>((byte) next++, double[].class, Indenting.INDENT_AND_OUTDENT) { 791 @Override 792 protected void doWrite(final DataOutputExtended output, final double[] values) throws IOException { 793 try { 794 final StringBuilder buf = new StringBuilder(); 795 final DataOutputStream outputStream = output.getDataOutputStream(); 796 outputStream.writeInt(values.length); 797 if (LOG.isDebugEnabled()) { 798 buf.append("length: ").append(values.length); 799 } 800 801 for (int i = 0; i < values.length; i++) { 802 outputStream.writeDouble(values[i]); 803 if (LOG.isDebugEnabled()) { 804 buf.append(i == 0 ? ": " : ", "); 805 buf.append(values[i]); 806 } 807 } 808 if (LOG.isDebugEnabled()) { 809 log(this, buf); 810 } 811 } finally { 812 if (LOG.isDebugEnabled()) { 813 unlog(this); 814 } 815 } 816 } 817 818 @Override 819 protected double[] doRead(final DataInputExtended input) throws IOException { 820 try { 821 final StringBuilder buf = new StringBuilder(); 822 final DataInputStream inputStream = input.getDataInputStream(); 823 final int length = inputStream.readInt(); 824 if (LOG.isDebugEnabled()) { 825 buf.append("length: ").append(length); 826 } 827 828 final double[] values = new double[length]; 829 for (int i = 0; i < values.length; i++) { 830 values[i] = inputStream.readDouble(); 831 if (LOG.isDebugEnabled()) { 832 buf.append(i == 0 ? ": " : ", "); 833 buf.append(values[i]); 834 } 835 } 836 if (LOG.isDebugEnabled()) { 837 log(this, buf); 838 } 839 return values; 840 } finally { 841 if (LOG.isDebugEnabled()) { 842 unlog(this); 843 } 844 } 845 } 846 }; 847 848 public static FieldType<String> STRING = new FieldType<String>((byte) next++, String.class, Indenting.INDENT_ONLY) { 849 @Override 850 protected void doWrite(final DataOutputExtended output, final String value) throws IOException { 851 try { 852 if (LOG.isDebugEnabled()) { 853 log(this, new StringBuilder().append(value)); 854 } 855 final DataOutputStream outputStream = output.getDataOutputStream(); 856 outputStream.writeUTF(value); 857 } finally { 858 if (LOG.isDebugEnabled()) { 859 unlog(this); 860 } 861 } 862 } 863 864 @Override 865 protected String doRead(final DataInputExtended input) throws IOException { 866 try { 867 final DataInputStream inputStream = input.getDataInputStream(); 868 final String value = inputStream.readUTF(); 869 if (LOG.isDebugEnabled()) { 870 log(this, new StringBuilder().append(value)); 871 } 872 return value; 873 } finally { 874 if (LOG.isDebugEnabled()) { 875 unlog(this); 876 } 877 } 878 } 879 }; 880 public static FieldType<String[]> STRING_ARRAY = new FieldType<String[]>((byte) next++, String[].class, Indenting.INDENT_AND_OUTDENT) { 881 @Override 882 protected void doWrite(final DataOutputExtended output, final String[] values) throws IOException { 883 try { 884 final StringBuilder buf = new StringBuilder(); 885 final DataOutputStream outputStream = output.getDataOutputStream(); 886 outputStream.writeInt(values.length); 887 if (LOG.isDebugEnabled()) { 888 buf.append("length: ").append(values.length); 889 } 890 891 for (int i = 0; i < values.length; i++) { 892 // using FieldType to write out takes care of null handling 893 FieldType.STRING.write(output, values[i]); 894 if (LOG.isDebugEnabled()) { 895 buf.append(i == 0 ? ": " : ", "); 896 buf.append(values[i]); 897 } 898 } 899 if (LOG.isDebugEnabled()) { 900 log(this, buf); 901 } 902 } finally { 903 if (LOG.isDebugEnabled()) { 904 unlog(this); 905 } 906 } 907 } 908 909 @Override 910 protected String[] doRead(final DataInputExtended input) throws IOException { 911 try { 912 final StringBuilder buf = new StringBuilder(); 913 final DataInputStream inputStream = input.getDataInputStream(); 914 final int length = inputStream.readInt(); 915 if (LOG.isDebugEnabled()) { 916 buf.append("length: ").append(length); 917 } 918 919 final String[] values = new String[length]; 920 for (int i = 0; i < values.length; i++) { 921 // using FieldType to read in takes care of null handling 922 values[i] = FieldType.STRING.read(input); 923 if (LOG.isDebugEnabled()) { 924 buf.append(i == 0 ? ": " : ", "); 925 buf.append(values[i]); 926 } 927 } 928 if (LOG.isDebugEnabled()) { 929 log(this, buf); 930 } 931 return values; 932 } finally { 933 if (LOG.isDebugEnabled()) { 934 unlog(this); 935 } 936 } 937 } 938 }; 939 940 public static FieldType<Encodable> ENCODABLE = new FieldType<Encodable>((byte) next++, Encodable.class, Indenting.INDENT_AND_OUTDENT) { 941 @Override 942 protected void doWrite(final DataOutputExtended output, final Encodable encodable) throws IOException { 943 try { 944 // write out class 945 final String className = encodable.getClass().getName(); 946 if (LOG.isDebugEnabled()) { 947 log(this, new StringBuilder().append(className)); 948 } 949 output.writeUTF(className); 950 951 // recursively encode 952 encodable.encode(output); 953 } finally { 954 if (LOG.isDebugEnabled()) { 955 unlog(this); 956 } 957 } 958 } 959 960 @Override 961 protected Encodable doRead(final DataInputExtended input) throws IOException { 962 try { 963 // read in class name ... 964 final String className = input.readUTF(); 965 if (LOG.isDebugEnabled()) { 966 log(this, new StringBuilder().append(className)); 967 } 968 969 Class<?> cls; 970 try { 971 // ...obtain constructor 972 cls = Thread.currentThread().getContextClassLoader().loadClass(className); 973 974 final Constructor<?> constructor = cls.getConstructor(new Class[] { DataInputExtended.class }); 975 976 // recursively decode 977 return (Encodable) constructor.newInstance(new Object[] { input }); 978 } catch (final ClassNotFoundException ex) { 979 throw new FailedToDecodeException(ex); 980 } catch (final IllegalArgumentException ex) { 981 throw new FailedToDecodeException(ex); 982 } catch (final InstantiationException ex) { 983 throw new FailedToDecodeException(ex); 984 } catch (final IllegalAccessException ex) { 985 throw new FailedToDecodeException(ex); 986 } catch (final InvocationTargetException ex) { 987 throw new FailedToDecodeException(ex); 988 } catch (final SecurityException ex) { 989 throw new FailedToDecodeException(ex); 990 } catch (final NoSuchMethodException ex) { 991 throw new FailedToDecodeException(ex); 992 } 993 994 } finally { 995 if (LOG.isDebugEnabled()) { 996 unlog(this); 997 } 998 } 999 } 1000 1001 @Override 1002 protected boolean checksStream() { 1003 return false; 1004 } 1005 }; 1006 1007 public static FieldType<Encodable[]> ENCODABLE_ARRAY = new FieldType<Encodable[]>((byte) next++, Encodable[].class, Indenting.INDENT_AND_OUTDENT) { 1008 @Override 1009 protected void doWrite(final DataOutputExtended output, final Encodable[] values) throws IOException { 1010 try { 1011 final DataOutputStream outputStream = output.getDataOutputStream(); 1012 outputStream.writeInt(values.length); 1013 if (LOG.isDebugEnabled()) { 1014 log(this, new StringBuilder().append("length: ").append(values.length)); 1015 } 1016 for (final Encodable encodable : values) { 1017 // using FieldType to write out takes care of null handling 1018 FieldType.ENCODABLE.write(output, encodable); 1019 } 1020 } finally { 1021 if (LOG.isDebugEnabled()) { 1022 unlog(this); 1023 } 1024 } 1025 } 1026 1027 @SuppressWarnings("unchecked") 1028 @Override 1029 protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { 1030 try { 1031 final DataInputStream inputStream = input.getDataInputStream(); 1032 final int length = inputStream.readInt(); 1033 if (LOG.isDebugEnabled()) { 1034 log(this, new StringBuilder().append("length: ").append(length)); 1035 } 1036 1037 final Q[] values = (Q[]) Array.newInstance(elementType, length); 1038 for (int i = 0; i < values.length; i++) { 1039 // using FieldType to read in takes care of null handling 1040 values[i] = (Q) FieldType.ENCODABLE.read(input); 1041 } 1042 return values; 1043 } finally { 1044 if (LOG.isDebugEnabled()) { 1045 unlog(this); 1046 } 1047 } 1048 } 1049 1050 @Override 1051 protected boolean checksStream() { 1052 return false; 1053 } 1054 }; 1055 1056 public static FieldType<Serializable> SERIALIZABLE = new FieldType<Serializable>((byte) next++, Serializable.class, Indenting.INDENT_ONLY) { 1057 @Override 1058 protected void doWrite(final DataOutputExtended output, final Serializable value) throws IOException { 1059 try { 1060 if (LOG.isDebugEnabled()) { 1061 log(this, new StringBuilder().append("[SERIALIZABLE]")); 1062 } 1063 1064 // write out as blob of bytes 1065 final ObjectOutputStream oos = new ObjectOutputStream(output.getDataOutputStream()); 1066 oos.writeObject(value); 1067 oos.flush(); 1068 } finally { 1069 if (LOG.isDebugEnabled()) { 1070 unlog(this); 1071 } 1072 } 1073 } 1074 1075 @Override 1076 protected Serializable doRead(final DataInputExtended input) throws IOException { 1077 try { 1078 if (LOG.isDebugEnabled()) { 1079 log(this, new StringBuilder().append("[SERIALIZABLE]")); 1080 } 1081 1082 // read in a blob of bytes 1083 final ObjectInputStream ois = new ObjectInputStream(input.getDataInputStream()); 1084 try { 1085 return (Serializable) ois.readObject(); 1086 } catch (final ClassNotFoundException ex) { 1087 throw new FailedToDeserializeException(ex); 1088 } 1089 } finally { 1090 if (LOG.isDebugEnabled()) { 1091 unlog(this); 1092 } 1093 } 1094 } 1095 1096 @Override 1097 protected boolean checksStream() { 1098 return false; 1099 } 1100 }; 1101 1102 public static FieldType<Serializable[]> SERIALIZABLE_ARRAY = new FieldType<Serializable[]>((byte) next++, Serializable[].class, Indenting.INDENT_AND_OUTDENT) { 1103 @Override 1104 protected void doWrite(final DataOutputExtended output, final Serializable[] values) throws IOException { 1105 try { 1106 final DataOutputStream outputStream = output.getDataOutputStream(); 1107 outputStream.writeInt(values.length); 1108 if (LOG.isDebugEnabled()) { 1109 log(this, new StringBuilder().append("length: ").append(values.length)); 1110 } 1111 1112 for (final Serializable value : values) { 1113 // using FieldType to write out takes care of null handling 1114 FieldType.SERIALIZABLE.write(output, value); 1115 } 1116 } finally { 1117 if (LOG.isDebugEnabled()) { 1118 unlog(this); 1119 } 1120 } 1121 } 1122 1123 @Override 1124 @SuppressWarnings("unchecked") 1125 protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { 1126 try { 1127 final DataInputStream inputStream = input.getDataInputStream(); 1128 final int length = inputStream.readInt(); 1129 if (LOG.isDebugEnabled()) { 1130 log(this, new StringBuilder().append("length: ").append(length)); 1131 } 1132 1133 final Q[] values = (Q[]) Array.newInstance(elementType, length); 1134 for (int i = 0; i < values.length; i++) { 1135 // using FieldType to read in takes care of null handling 1136 values[i] = (Q) FieldType.SERIALIZABLE.read(input); 1137 } 1138 return values; 1139 } finally { 1140 if (LOG.isDebugEnabled()) { 1141 unlog(this); 1142 } 1143 } 1144 } 1145 1146 @Override 1147 protected boolean checksStream() { 1148 return false; 1149 } 1150 }; 1151 1152 public static FieldType<?> get(final byte idx) { 1153 return cache.get(idx); 1154 } 1155 1156 private final byte idx; 1157 private final Class<T> cls; 1158 private final Indenting indenting; 1159 1160 private FieldType(final byte idx, final Class<T> cls, final Indenting indenting) { 1161 this.idx = idx; 1162 this.cls = cls; 1163 this.indenting = indenting; 1164 cache.put(idx, this); 1165 } 1166 1167 public byte getIdx() { 1168 return idx; 1169 } 1170 1171 public Class<T> getCls() { 1172 return cls; 1173 } 1174 1175 /** 1176 * Whether this implementation checks ordering in the stream. 1177 * 1178 * <p> 1179 * Broadly, the type safe ones do, the {@link Encodable} and 1180 * {@link Serializable} ones do not. 1181 */ 1182 protected boolean checksStream() { 1183 return true; 1184 } 1185 1186 public final T read(final DataInputExtended input) throws IOException { 1187 final DataInputStream inputStream = input.getDataInputStream(); 1188 final byte fieldTypeIdxAndNullability = inputStream.readByte(); 1189 1190 final boolean isNull = fieldTypeIdxAndNullability >= NULL_BIT; 1191 final byte fieldTypeIdx = (byte) (fieldTypeIdxAndNullability - (isNull ? NULL_BIT : 0)); 1192 try { 1193 final FieldType<?> fieldType = FieldType.get(fieldTypeIdx); 1194 if (fieldType == null || (fieldType.checksStream() && fieldType != this)) { 1195 throw new IllegalStateException("Mismatch in stream: expected " + this + " but got " + fieldType + " (" + fieldTypeIdx + ")"); 1196 } 1197 1198 if (isNull && LOG.isDebugEnabled()) { 1199 // only log if reading a null; otherwise actual value read 1200 // logged later 1201 log(this, new StringBuilder().append("(null)")); 1202 } 1203 1204 if (isNull) { 1205 return null; 1206 } else { 1207 return doRead(input); 1208 } 1209 } finally { 1210 if (isNull && LOG.isDebugEnabled()) { 1211 // only unlog if reading a null 1212 unlog(this); 1213 } 1214 } 1215 } 1216 1217 public final <Q> Q[] readArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { 1218 final DataInputStream inputStream = input.getDataInputStream(); 1219 final byte fieldTypeIdxAndNullability = inputStream.readByte(); 1220 1221 final boolean isNull = fieldTypeIdxAndNullability >= NULL_BIT; 1222 final byte fieldTypeIdx = (byte) (fieldTypeIdxAndNullability - (isNull ? NULL_BIT : 0)); 1223 try { 1224 final FieldType<?> fieldType = FieldType.get(fieldTypeIdx); 1225 if (fieldType.checksStream() && fieldType != this) { 1226 throw new IllegalStateException("Mismatch in stream: expected " + this + " but got " + fieldType); 1227 } 1228 1229 if (isNull && LOG.isDebugEnabled()) { 1230 // only log if reading a null; otherwise actual value read 1231 // logged later 1232 log(this, new StringBuilder().append("(null)")); 1233 } 1234 1235 if (isNull) { 1236 return null; 1237 } else { 1238 return doReadArray(input, elementType); 1239 } 1240 1241 } finally { 1242 if (isNull && LOG.isDebugEnabled()) { 1243 // only unlog if reading a null 1244 unlog(this); 1245 } 1246 } 1247 1248 } 1249 1250 public final void write(final DataOutputExtended output, final T value) throws IOException { 1251 byte fieldTypeIdxAndNullability = getIdx(); 1252 final boolean isNull = value == null; 1253 if (isNull) { 1254 // set high order bit 1255 fieldTypeIdxAndNullability += NULL_BIT; 1256 } 1257 try { 1258 1259 final DataOutputStream outputStream = output.getDataOutputStream(); 1260 1261 outputStream.write(fieldTypeIdxAndNullability); 1262 if (isNull && LOG.isDebugEnabled()) { 1263 // only log if writing a null; otherwise actual value logged 1264 // later 1265 log(this, new StringBuilder().append("(null)")); 1266 } 1267 1268 if (!isNull) { 1269 doWrite(output, value); 1270 } 1271 } finally { 1272 if (isNull && LOG.isDebugEnabled()) { 1273 // only unlog if writing a null 1274 unlog(this); 1275 } 1276 } 1277 } 1278 1279 protected T doRead(final DataInputExtended input) throws IOException { 1280 throw new UnsupportedOperationException("not supported for this field type"); 1281 } 1282 1283 protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { 1284 throw new UnsupportedOperationException("not supported for this field type"); 1285 } 1286 1287 protected abstract void doWrite(DataOutputExtended output, T value) throws IOException; 1288 1289 private boolean isIndentingAndOutdenting() { 1290 return indenting == Indenting.INDENT_AND_OUTDENT; 1291 } 1292 1293 // /////////////////////////////////////////////////////// 1294 // debugging 1295 // /////////////////////////////////////////////////////// 1296 1297 private static ThreadLocal<int[]> debugIndent = new ThreadLocal<int[]>(); 1298 1299 private static void log(final FieldType<?> fieldType, final StringBuilder buf) { 1300 buf.insert(0, ": "); 1301 buf.insert(0, fieldType); 1302 if (fieldType.isIndentingAndOutdenting()) { 1303 buf.insert(0, "> "); 1304 } 1305 buf.insert(0, spaces(currentDebugLevel())); 1306 incrementDebugLevel(); 1307 LOG.debug(buf.toString()); 1308 } 1309 1310 private static void unlog(final FieldType<?> fieldType) { 1311 unlog(fieldType, new StringBuilder()); 1312 } 1313 1314 private static void unlog(final FieldType<?> fieldType, final StringBuilder buf) { 1315 if (fieldType.isIndentingAndOutdenting()) { 1316 buf.insert(0, "< "); 1317 } 1318 decrementDebugLevel(); 1319 if (fieldType.isIndentingAndOutdenting()) { 1320 buf.insert(0, spaces(currentDebugLevel())); 1321 LOG.debug(buf.toString()); 1322 } 1323 } 1324 1325 private static String spaces(final int num) { 1326 return LOG_INDENT.substring(0, num); 1327 } 1328 1329 private static int currentDebugLevel() { 1330 return debugIndent()[0]; 1331 } 1332 1333 private static void incrementDebugLevel() { 1334 final int[] indentLevel = debugIndent(); 1335 indentLevel[0] += 2; 1336 } 1337 1338 private static void decrementDebugLevel() { 1339 final int[] indentLevel = debugIndent(); 1340 indentLevel[0] -= 2; 1341 } 1342 1343 private static int[] debugIndent() { 1344 int[] indentLevel = debugIndent.get(); 1345 if (indentLevel == null) { 1346 indentLevel = new int[1]; 1347 debugIndent.set(indentLevel); 1348 } 1349 return indentLevel; 1350 } 1351 1352 // /////////////////////////////////////////////////////// 1353 // toString 1354 // /////////////////////////////////////////////////////// 1355 1356 @Override 1357 public String toString() { 1358 return getCls().getSimpleName(); 1359 } 1360 1361}