1 /* 2 * BeDecoder.java 3 * 4 * Created on May 30, 2003, 2:44 PM 5 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 */ 19 20 package org.gudy.azureus2.core3.util; 21 22 import java.io.*; 23 import java.nio.ByteBuffer; 24 import java.nio.CharBuffer; 25 import java.nio.charset.CharsetDecoder; 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.Map; 32 33 import com.aelitis.azureus.util.JSONUtils; 34 35 /** 36 * A set of utility methods to decode a bencoded array of byte into a Map. 37 * integer are represented as Long, String as byte[], dictionnaries as Map, and list as List. 38 * 39 * @author TdC_VgA 40 * 41 */ 42 public class BDecoder 43 { 44 public static final int MAX_BYTE_ARRAY_SIZE = 100*1024*1024; 45 private static final int MAX_MAP_KEY_SIZE = 64*1024; 46 47 private static final boolean TRACE = false; 48 49 private boolean recovery_mode; 50 private boolean verify_map_order; 51 52 private final static byte[] PORTABLE_ROOT; 53 54 static{ 55 byte[] portable = null; 56 57 try{ 58 String root = System.getProperty( "azureus.portable.root", "" ); 59 60 if ( root.length() > 0 ){ 61 62 portable = root.getBytes( "UTF-8" ); 63 } 64 }catch( Throwable e ){ 65 66 e.printStackTrace(); 67 } 68 69 PORTABLE_ROOT = portable; 70 } 71 72 public static Map<String,Object> decode( byte[] data )73 decode( 74 byte[] data ) 75 76 throws IOException 77 { 78 return( new BDecoder().decodeByteArray( data )); 79 } 80 81 public static Map<String,Object> decode( byte[] data, int offset, int length )82 decode( 83 byte[] data, 84 int offset, 85 int length ) 86 87 throws IOException 88 { 89 return( new BDecoder().decodeByteArray( data, offset, length )); 90 } 91 92 public static Map<String,Object> decode( BufferedInputStream is )93 decode( 94 BufferedInputStream is ) 95 96 throws IOException 97 { 98 return( new BDecoder().decodeStream( is )); 99 } 100 101 102 public BDecoder()103 BDecoder() 104 { 105 } 106 107 public Map<String,Object> decodeByteArray( byte[] data)108 decodeByteArray( 109 byte[] data) 110 111 throws IOException 112 { 113 return( decode(new BDecoderInputStreamArray(data),true)); 114 } 115 116 public Map<String, Object> decodeByteArray( byte[] data, int offset, int length )117 decodeByteArray( 118 byte[] data, 119 int offset, 120 int length ) 121 122 throws IOException 123 { 124 return( decode(new BDecoderInputStreamArray(data, offset, length ),true)); 125 } 126 127 public Map<String, Object> decodeByteArray( byte[] data, int offset, int length, boolean internKeys)128 decodeByteArray( 129 byte[] data, 130 int offset, 131 int length, 132 boolean internKeys) 133 134 throws IOException 135 { 136 return( decode(new BDecoderInputStreamArray(data, offset, length ),internKeys)); 137 } 138 139 // used externally decodeByteBuffer(ByteBuffer buffer, boolean internKeys)140 public Map<String, Object> decodeByteBuffer(ByteBuffer buffer, boolean internKeys) throws IOException { 141 InputStream is = new BDecoderInputStreamArray(buffer); 142 Map<String,Object> result = decode(is,internKeys); 143 buffer.position(buffer.limit()-is.available()); 144 return result; 145 } 146 147 public Map<String, Object> decodeStream( BufferedInputStream data )148 decodeStream( 149 BufferedInputStream data ) 150 151 throws IOException 152 { 153 return decodeStream(data, true); 154 } 155 156 public Map<String, Object> decodeStream( BufferedInputStream data, boolean internKeys)157 decodeStream( 158 BufferedInputStream data, 159 boolean internKeys) 160 161 throws IOException 162 { 163 Object res = decodeInputStream(data, "", 0, internKeys); 164 165 if ( res == null ){ 166 167 throw( new BEncodingException( "BDecoder: zero length file" )); 168 169 }else if ( !(res instanceof Map )){ 170 171 throw( new BEncodingException( "BDecoder: top level isn't a Map" )); 172 } 173 174 return((Map<String,Object>)res ); 175 } 176 177 private Map<String, Object> decode( InputStream data, boolean internKeys )178 decode( 179 InputStream data, boolean internKeys ) 180 181 throws IOException 182 { 183 Object res = decodeInputStream(data, "", 0, internKeys); 184 185 if ( res == null ){ 186 187 throw( new BEncodingException( "BDecoder: zero length file" )); 188 189 }else if ( !(res instanceof Map )){ 190 191 throw( new BEncodingException( "BDecoder: top level isn't a Map" )); 192 } 193 194 return((Map<String, Object>)res ); 195 } 196 197 // reuseable objects for key decoding 198 private ByteBuffer keyBytesBuffer = ByteBuffer.allocate(32); 199 private CharBuffer keyCharsBuffer = CharBuffer.allocate(32); 200 private CharsetDecoder keyDecoder = Constants.BYTE_CHARSET.newDecoder(); 201 202 private Object decodeInputStream( InputStream dbis, String context, int nesting, boolean internKeys)203 decodeInputStream( 204 InputStream dbis, 205 String context, 206 int nesting, 207 boolean internKeys) 208 209 throws IOException 210 { 211 if (nesting == 0 && !dbis.markSupported()) { 212 213 throw new IOException("InputStream must support the mark() method"); 214 } 215 216 //set a mark 217 218 dbis.mark(1); 219 220 //read a byte 221 222 int tempByte = dbis.read(); 223 224 //decide what to do 225 226 switch (tempByte) { 227 case 'd' : 228 //create a new dictionary object 229 230 LightHashMap tempMap = new LightHashMap(); 231 232 try{ 233 byte[] prev_key = null; 234 235 //get the key 236 237 while (true) { 238 239 dbis.mark(1); 240 241 tempByte = dbis.read(); 242 if(tempByte == 'e' || tempByte == -1) 243 break; // end of map 244 245 dbis.reset(); 246 247 // decode key strings manually so we can reuse the bytebuffer 248 249 int keyLength = (int)getPositiveNumberFromStream(dbis, ':'); 250 251 int skipBytes = 0; 252 253 if ( keyLength > MAX_MAP_KEY_SIZE ){ 254 skipBytes = keyLength - MAX_MAP_KEY_SIZE; 255 keyLength = MAX_MAP_KEY_SIZE; 256 //new Exception().printStackTrace(); 257 //throw( new IOException( msg )); 258 } 259 260 if(keyLength < keyBytesBuffer.capacity()) 261 { 262 keyBytesBuffer.position(0).limit(keyLength); 263 keyCharsBuffer.position(0).limit(keyLength); 264 } else { 265 keyBytesBuffer = ByteBuffer.allocate(keyLength); 266 keyCharsBuffer = CharBuffer.allocate(keyLength); 267 } 268 269 getByteArrayFromStream(dbis, keyLength, keyBytesBuffer.array()); 270 271 if (skipBytes > 0) { 272 dbis.skip(skipBytes); 273 } 274 275 if ( verify_map_order ){ 276 277 byte[] current_key = new byte[keyLength]; 278 279 System.arraycopy( keyBytesBuffer.array(), 0, current_key, 0, keyLength ); 280 281 if ( prev_key != null ){ 282 283 int len = Math.min( prev_key.length, keyLength ); 284 285 int state = 0; 286 287 for ( int i=0;i<len;i++){ 288 289 int cb = current_key[i]&0x00ff; 290 int pb = prev_key[i]&0x00ff; 291 292 if ( cb > pb ){ 293 state = 1; 294 break; 295 }else if ( cb < pb ){ 296 state = 2; 297 break; 298 } 299 } 300 301 if ( state == 0){ 302 if ( prev_key.length > keyLength ){ 303 304 state = 2; 305 } 306 } 307 308 if ( state == 2 ){ 309 310 // Debug.out( "Dictionary order incorrect: prev=" + new String( prev_key ) + ", current=" + new String( current_key )); 311 312 if (!( tempMap instanceof LightHashMapEx )){ 313 314 LightHashMapEx x = new LightHashMapEx( tempMap ); 315 316 x.setFlag( LightHashMapEx.FL_MAP_ORDER_INCORRECT, true ); 317 318 tempMap = x; 319 } 320 } 321 } 322 323 prev_key = current_key; 324 } 325 326 keyDecoder.reset(); 327 keyDecoder.decode(keyBytesBuffer,keyCharsBuffer,true); 328 keyDecoder.flush(keyCharsBuffer); 329 String key = new String(keyCharsBuffer.array(),0,keyCharsBuffer.limit()); 330 331 // keys often repeat a lot - intern to save space 332 if (internKeys) 333 key = StringInterner.intern( key ); 334 335 336 //decode value 337 338 Object value = decodeInputStream(dbis,key,nesting+1,internKeys); 339 340 // value interning is too CPU-intensive, let's skip that for now 341 /*if(value instanceof byte[] && ((byte[])value).length < 17) 342 value = StringInterner.internBytes((byte[])value);*/ 343 344 if ( TRACE ){ 345 System.out.println( key + "->" + value + ";" ); 346 } 347 348 // recover from some borked encodings that I have seen whereby the value has 349 // not been encoded. This results in, for example, 350 // 18:azureus_propertiesd0:e 351 // we only get null back here if decoding has hit an 'e' or end-of-file 352 // that is, there is no valid way for us to get a null 'value' here 353 354 if ( value == null ){ 355 356 System.err.println( "Invalid encoding - value not serialsied for '" + key + "' - ignoring: map so far=" + tempMap + ",loc=" + Debug.getCompressedStackTrace()); 357 358 break; 359 } 360 361 if (skipBytes > 0) { 362 363 String msg = "dictionary key is too large - " 364 + (keyLength + skipBytes) + ":, max=" + MAX_MAP_KEY_SIZE 365 + ": skipping key starting with " + new String(key.substring(0, 128)); 366 System.err.println( msg ); 367 368 } else { 369 370 if ( tempMap.put( key, value) != null ){ 371 372 Debug.out( "BDecoder: key '" + key + "' already exists!" ); 373 } 374 } 375 } 376 377 /* 378 if ( tempMap.size() < 8 ){ 379 380 tempMap = new CompactMap( tempMap ); 381 }*/ 382 383 dbis.mark(1); 384 tempByte = dbis.read(); 385 dbis.reset(); 386 if ( nesting > 0 && tempByte == -1 ){ 387 388 throw( new BEncodingException( "BDecoder: invalid input data, 'e' missing from end of dictionary")); 389 } 390 }catch( Throwable e ){ 391 392 if ( !recovery_mode ){ 393 394 if ( e instanceof IOException ){ 395 396 throw((IOException)e); 397 } 398 399 throw( new IOException( Debug.getNestedExceptionMessage(e))); 400 } 401 } 402 403 tempMap.compactify(-0.9f); 404 405 //return the map 406 407 return tempMap; 408 409 case 'l' : 410 //create the list 411 412 ArrayList tempList = new ArrayList(); 413 414 try{ 415 //create the key 416 417 String context2 = PORTABLE_ROOT==null?context:(context+"[]"); 418 419 Object tempElement = null; 420 while ((tempElement = decodeInputStream(dbis, context2, nesting+1, internKeys)) != null) { 421 //add the element 422 tempList.add(tempElement); 423 } 424 425 tempList.trimToSize(); 426 dbis.mark(1); 427 tempByte = dbis.read(); 428 dbis.reset(); 429 if ( nesting > 0 && tempByte == -1 ){ 430 431 throw( new BEncodingException( "BDecoder: invalid input data, 'e' missing from end of list")); 432 } 433 }catch( Throwable e ){ 434 435 if ( !recovery_mode ){ 436 437 if ( e instanceof IOException ){ 438 439 throw((IOException)e); 440 } 441 442 throw( new IOException( Debug.getNestedExceptionMessage(e))); 443 } 444 } 445 //return the list 446 return tempList; 447 448 case 'e' : 449 case -1 : 450 return null; 451 452 case 'i' : 453 return Long.valueOf(getNumberFromStream(dbis, 'e')); 454 455 case '0' : 456 case '1' : 457 case '2' : 458 case '3' : 459 case '4' : 460 case '5' : 461 case '6' : 462 case '7' : 463 case '8' : 464 case '9' : 465 //move back one 466 dbis.reset(); 467 //get the string 468 return getByteArrayFromStream(dbis, context ); 469 470 default :{ 471 472 int rem_len = dbis.available(); 473 474 if ( rem_len > 256 ){ 475 476 rem_len = 256; 477 } 478 479 byte[] rem_data = new byte[rem_len]; 480 481 dbis.read( rem_data ); 482 483 throw( new BEncodingException( 484 "BDecoder: unknown command '" + tempByte + ", remainder = " + new String( rem_data ))); 485 } 486 } 487 } 488 489 /* 490 private long getNumberFromStream(InputStream dbis, char parseChar) throws IOException { 491 StringBuffer sb = new StringBuffer(3); 492 493 int tempByte = dbis.read(); 494 while ((tempByte != parseChar) && (tempByte >= 0)) { 495 sb.append((char)tempByte); 496 tempByte = dbis.read(); 497 } 498 499 //are we at the end of the stream? 500 if (tempByte < 0) { 501 return -1; 502 } 503 504 String str = sb.toString(); 505 506 // support some borked impls that sometimes don't bother encoding anything 507 508 if ( str.length() == 0 ){ 509 510 return( 0 ); 511 } 512 513 return Long.parseLong(str); 514 } 515 */ 516 517 /** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method) 518 */ 519 private final char[] numberChars = new char[32]; 520 521 /** 522 * @note will break (likely return a negative) if number > 523 * {@link Integer#MAX_VALUE}. This check is intentionally skipped to 524 * increase performance 525 */ 526 private int getPositiveNumberFromStream( InputStream dbis, char parseChar)527 getPositiveNumberFromStream( 528 InputStream dbis, 529 char parseChar) 530 531 throws IOException 532 { 533 int tempByte = dbis.read(); 534 if (tempByte < 0) { 535 return -1; 536 } 537 if (tempByte != parseChar) { 538 539 int value = tempByte - '0'; 540 541 tempByte = dbis.read(); 542 // optimized for single digit cases 543 if (tempByte == parseChar) { 544 return value; 545 } 546 if (tempByte < 0) { 547 return -1; 548 } 549 550 while (true) { 551 // Base10 shift left --> v*8 + v*2 = v*10 552 value = (value << 3) + (value << 1) + (tempByte - '0'); 553 // For bounds check: 554 // if (value < 0) return something; 555 tempByte = dbis.read(); 556 if (tempByte == parseChar) { 557 return value; 558 } 559 if (tempByte < 0) { 560 return -1; 561 } 562 } 563 } else { 564 return 0; 565 } 566 } 567 568 private long getNumberFromStream( InputStream dbis, char parseChar)569 getNumberFromStream( 570 InputStream dbis, 571 char parseChar) 572 573 throws IOException 574 { 575 576 577 int tempByte = dbis.read(); 578 579 int pos = 0; 580 581 while ((tempByte != parseChar) && (tempByte >= 0)) { 582 numberChars[pos++] = (char)tempByte; 583 if ( pos == numberChars.length ){ 584 throw( new NumberFormatException( "Number too large: " + new String(numberChars,0,pos) + "..." )); 585 } 586 tempByte = dbis.read(); 587 } 588 589 //are we at the end of the stream? 590 591 if (tempByte < 0) { 592 593 return -1; 594 595 }else if ( pos == 0 ){ 596 // support some borked impls that sometimes don't bother encoding anything 597 598 return(0); 599 } 600 601 try{ 602 return( parseLong( numberChars, 0, pos )); 603 604 }catch( NumberFormatException e ){ 605 606 String temp = new String( numberChars, 0, pos ); 607 608 try{ 609 double d = Double.parseDouble( temp ); 610 611 long l = (long)d; 612 613 Debug.out( "Invalid number '" + temp + "' - decoding as " + l + " and attempting recovery" ); 614 615 return( l ); 616 617 }catch( Throwable f ){ 618 } 619 620 throw( e ); 621 } 622 } 623 624 // This is similar to Long.parseLong(String) source 625 // It is also used in projects external to azureus2/azureus3 hence it is public 626 public static long parseLong( char[] chars, int start, int length )627 parseLong( 628 char[] chars, 629 int start, 630 int length ) 631 { 632 if ( length > 0 ){ 633 // Short Circuit: We don't support octal parsing, so if it 634 // starts with 0, it's 0 635 if (chars[start] == '0') { 636 637 return 0; 638 } 639 640 long result = 0; 641 642 boolean negative = false; 643 644 int i = start; 645 646 long limit; 647 648 if ( chars[i] == '-' ){ 649 650 negative = true; 651 652 limit = Long.MIN_VALUE; 653 654 i++; 655 656 }else{ 657 // Short Circuit: If we are only processing one char, 658 // and it wasn't a '-', just return that digit instead 659 // of doing the negative junk 660 if (length == 1) { 661 int digit = chars[i] - '0'; 662 663 if ( digit < 0 || digit > 9 ){ 664 665 throw new NumberFormatException(new String(chars,start,length)); 666 667 }else{ 668 669 return digit; 670 } 671 } 672 673 limit = -Long.MAX_VALUE; 674 } 675 676 int max = start + length; 677 678 if ( i < max ){ 679 680 int digit = chars[i++] - '0'; 681 682 if ( digit < 0 || digit > 9 ){ 683 684 throw new NumberFormatException(new String(chars,start,length)); 685 686 }else{ 687 688 result = -digit; 689 } 690 } 691 692 long multmin = limit / 10; 693 694 while ( i < max ){ 695 696 // Accumulating negatively avoids surprises near MAX_VALUE 697 698 int digit = chars[i++] - '0'; 699 700 if ( digit < 0 || digit > 9 ){ 701 702 throw new NumberFormatException(new String(chars,start,length)); 703 } 704 705 if ( result < multmin ){ 706 707 throw new NumberFormatException(new String(chars,start,length)); 708 } 709 710 result *= 10; 711 712 if ( result < limit + digit ){ 713 714 throw new NumberFormatException(new String(chars,start,length)); 715 } 716 717 result -= digit; 718 } 719 720 if ( negative ){ 721 722 if ( i > start+1 ){ 723 724 return result; 725 726 }else{ /* Only got "-" */ 727 728 throw new NumberFormatException(new String(chars,start,length)); 729 } 730 }else{ 731 732 return -result; 733 } 734 }else{ 735 736 throw new NumberFormatException(new String(chars,start,length)); 737 } 738 739 } 740 741 742 743 // This one causes lots of "Query Information" calls to the filesystem 744 /* 745 private long getNumberFromStreamOld(InputStream dbis, char parseChar) throws IOException { 746 int length = 0; 747 748 //place a mark 749 dbis.mark(???); 1 wouldn't work here ;) 750 751 int tempByte = dbis.read(); 752 while ((tempByte != parseChar) && (tempByte >= 0)) { 753 tempByte = dbis.read(); 754 length++; 755 } 756 757 //are we at the end of the stream? 758 if (tempByte < 0) { 759 return -1; 760 } 761 762 //reset the mark 763 dbis.reset(); 764 765 //get the length 766 byte[] tempArray = new byte[length]; 767 int count = 0; 768 int len = 0; 769 770 //get the string 771 while (count != length && (len = dbis.read(tempArray, count, length - count)) > 0) { 772 count += len; 773 } 774 775 //jump ahead in the stream to compensate for the : 776 dbis.skip(1); 777 778 //return the value 779 780 CharBuffer cb = Constants.DEFAULT_CHARSET.decode(ByteBuffer.wrap(tempArray)); 781 782 String str_value = new String(cb.array(),0,cb.limit()); 783 784 return Long.parseLong(str_value); 785 } 786 */ 787 788 private byte[] getByteArrayFromStream( InputStream dbis, String context )789 getByteArrayFromStream( 790 InputStream dbis, 791 String context ) 792 793 throws IOException 794 { 795 int length = (int) getPositiveNumberFromStream(dbis, ':'); 796 797 if (length < 0) { 798 return null; 799 } 800 801 // note that torrent hashes can be big (consider a 55GB file with 2MB pieces 802 // this generates a pieces hash of 1/2 meg 803 804 if ( length > MAX_BYTE_ARRAY_SIZE ){ 805 806 throw( new IOException( "Byte array length too large (" + length + ")")); 807 } 808 809 byte[] tempArray = new byte[length]; 810 811 getByteArrayFromStream(dbis, length, tempArray); 812 813 if ( PORTABLE_ROOT != null && length >= PORTABLE_ROOT.length && tempArray[1] == ':' && tempArray[2] == '\\' && context != null ){ 814 815 boolean mismatch = false; 816 817 for ( int i=2;i<PORTABLE_ROOT.length;i++){ 818 819 if ( tempArray[i] != PORTABLE_ROOT[i] ){ 820 821 mismatch = true; 822 823 break; 824 } 825 } 826 827 if ( !mismatch ){ 828 829 context = context.toLowerCase( Locale.US ); 830 831 // always a chance a hash will match the root so we just pick on relevant looking 832 // entries... 833 834 if ( context.contains( "file" ) || 835 context.contains( "link" ) || 836 context.contains( "dir" ) || 837 context.contains( "folder" ) || 838 context.contains( "path" ) || 839 context.contains( "save" ) || 840 context.contains( "torrent" )){ 841 842 tempArray[0] = PORTABLE_ROOT[0]; 843 844 /* 845 String test = new String( tempArray, 0, tempArray.length > 80?80:tempArray.length ); 846 847 System.out.println( "mapped " + context + "->" + tempArray.length + ": " + test ); 848 */ 849 850 }else{ 851 852 String test = new String( tempArray, 0, tempArray.length > 80?80:tempArray.length ); 853 854 System.out.println( "Portable: not mapping " + context + "->" + tempArray.length + ": " + test ); 855 } 856 } 857 } 858 859 return tempArray; 860 } 861 getByteArrayFromStream(InputStream dbis, int length, byte[] targetArray)862 private void getByteArrayFromStream(InputStream dbis, int length, byte[] targetArray) throws IOException { 863 864 int count = 0; 865 int len = 0; 866 //get the string 867 while (count != length && (len = dbis.read(targetArray, count, length - count)) > 0) 868 count += len; 869 870 if (count != length) 871 throw (new IOException("BDecoder::getByteArrayFromStream: truncated")); 872 } 873 874 public void setVerifyMapOrder( boolean b )875 setVerifyMapOrder( 876 boolean b ) 877 { 878 verify_map_order = b; 879 } 880 881 public void setRecoveryMode( boolean r )882 setRecoveryMode( 883 boolean r ) 884 { 885 recovery_mode = r; 886 } 887 888 public static void print( Object obj )889 print( 890 Object obj ) 891 { 892 StringWriter sw = new StringWriter(); 893 894 PrintWriter pw = new PrintWriter( sw ); 895 896 print( pw, obj ); 897 898 pw.flush(); 899 900 System.out.println( sw.toString()); 901 } 902 903 public static void print( PrintWriter writer, Object obj )904 print( 905 PrintWriter writer, 906 Object obj ) 907 { 908 print( writer, obj, "", false ); 909 } 910 911 private static void print( PrintWriter writer, Object obj, String indent, boolean skip_indent )912 print( 913 PrintWriter writer, 914 Object obj, 915 String indent, 916 boolean skip_indent ) 917 { 918 String use_indent = skip_indent?"":indent; 919 920 if ( obj instanceof Long ){ 921 922 writer.println( use_indent + obj ); 923 924 }else if ( obj instanceof byte[]){ 925 926 byte[] b = (byte[])obj; 927 928 if ( b.length==20 ){ 929 writer.println( use_indent + " { "+ ByteFormatter.nicePrint( b )+ " }" ); 930 }else if ( b.length < 64 ){ 931 writer.println( new String(b) + " [" + ByteFormatter.encodeString( b ) + "]" ); 932 }else{ 933 writer.println( "[byte array length " + b.length ); 934 } 935 936 }else if ( obj instanceof String ){ 937 938 writer.println( use_indent + obj ); 939 940 }else if ( obj instanceof List ){ 941 942 List l = (List)obj; 943 944 writer.println( use_indent + "[" ); 945 946 for (int i=0;i<l.size();i++){ 947 948 writer.print( indent + " (" + i + ") " ); 949 950 print( writer, l.get(i), indent + " ", true ); 951 } 952 953 writer.println( indent + "]" ); 954 955 }else{ 956 957 Map m = (Map)obj; 958 959 Iterator it = m.keySet().iterator(); 960 961 while( it.hasNext()){ 962 963 String key = (String)it.next(); 964 965 if ( key.length() > 256 ){ 966 writer.print( indent + key.substring(0,256) + "... = " ); 967 }else{ 968 writer.print( indent + key + " = " ); 969 } 970 971 print( writer, m.get(key), indent + " ", true ); 972 } 973 } 974 } 975 976 /** 977 * Converts any byte[] entries into UTF-8 strings. 978 * REPLACES EXISTING MAP VALUES 979 * 980 * @param map 981 * @return 982 */ 983 984 public static Map decodeStrings( Map map )985 decodeStrings( 986 Map map ) 987 { 988 if (map == null ){ 989 990 return( null ); 991 } 992 993 Iterator it = map.entrySet().iterator(); 994 995 while( it.hasNext()){ 996 997 Map.Entry entry = (Map.Entry)it.next(); 998 999 Object value = entry.getValue(); 1000 1001 if ( value instanceof byte[]){ 1002 1003 try{ 1004 entry.setValue( new String((byte[])value,"UTF-8" )); 1005 1006 }catch( Throwable e ){ 1007 1008 System.err.println(e); 1009 } 1010 }else if ( value instanceof Map ){ 1011 1012 decodeStrings((Map)value ); 1013 }else if ( value instanceof List ){ 1014 1015 decodeStrings((List)value ); 1016 } 1017 } 1018 1019 return( map ); 1020 } 1021 1022 /** 1023 * Decodes byte arrays into strings. 1024 * REPLACES EXISTING LIST VALUES 1025 * 1026 * @param list 1027 * @return the same list passed in 1028 */ 1029 public static List decodeStrings( List list )1030 decodeStrings( 1031 List list ) 1032 { 1033 if ( list == null ){ 1034 1035 return( null ); 1036 } 1037 1038 for (int i=0;i<list.size();i++){ 1039 1040 Object value = list.get(i); 1041 1042 if ( value instanceof byte[]){ 1043 1044 try{ 1045 String str = new String((byte[])value, "UTF-8" ); 1046 1047 list.set( i, str ); 1048 1049 }catch( Throwable e ){ 1050 1051 System.err.println(e); 1052 } 1053 }else if ( value instanceof Map ){ 1054 1055 decodeStrings((Map)value ); 1056 1057 }else if ( value instanceof List ){ 1058 1059 decodeStrings((List)value ); 1060 } 1061 } 1062 1063 return( list ); 1064 } 1065 1066 private static void print( File f, File output )1067 print( 1068 File f, 1069 File output ) 1070 { 1071 try{ 1072 BDecoder decoder = new BDecoder(); 1073 1074 decoder.setRecoveryMode( false ); 1075 1076 PrintWriter pw = new PrintWriter( new FileWriter( output )); 1077 1078 print( pw, decoder.decodeStream( new BufferedInputStream( new FileInputStream( f )))); 1079 1080 pw.flush(); 1081 1082 }catch( Throwable e ){ 1083 1084 e.printStackTrace(); 1085 } 1086 } 1087 1088 // JSON 1089 1090 private static Object decodeFromJSONGeneric( Object obj )1091 decodeFromJSONGeneric( 1092 Object obj ) 1093 { 1094 if ( obj == null ){ 1095 1096 return( null ); 1097 1098 }else if ( obj instanceof Map ){ 1099 1100 return( decodeFromJSONObject((Map)obj)); 1101 1102 }else if ( obj instanceof List ){ 1103 1104 return( decodeFromJSONArray((List)obj)); 1105 1106 }else if ( obj instanceof String ){ 1107 1108 String s = (String)obj; 1109 1110 try{ 1111 1112 int len = s.length(); 1113 1114 if ( len >= 6 && s.startsWith( "\\x" ) && s.endsWith( "\\x" )){ 1115 1116 byte[] result = new byte[(len-4)/2]; 1117 1118 int pos = 2; 1119 1120 for ( int i=0;i<result.length;i++){ 1121 1122 result[i] = (byte)Integer.parseInt( s.substring( pos, pos+2 ), 16 ); 1123 1124 pos += 2; 1125 } 1126 1127 return( result ); 1128 } 1129 1130 return(s.getBytes( "UTF-8" )); 1131 1132 }catch( Throwable e ){ 1133 1134 return(s.getBytes()); 1135 } 1136 1137 }else if ( obj instanceof Long ){ 1138 1139 return( obj ); 1140 1141 }else if ( obj instanceof Boolean ){ 1142 1143 return( new Long(((Boolean)obj)?1:0 )); 1144 1145 }else if ( obj instanceof Double ){ 1146 1147 return( String.valueOf((Double)obj)); 1148 1149 }else{ 1150 1151 System.err.println( "Unexpected JSON value type: " + obj.getClass()); 1152 1153 return( obj ); 1154 } 1155 } 1156 1157 public static List decodeFromJSONArray( List j_list )1158 decodeFromJSONArray( 1159 List j_list ) 1160 { 1161 List b_list = new ArrayList(); 1162 1163 for ( Object o: j_list ){ 1164 1165 b_list.add( decodeFromJSONGeneric( o )); 1166 } 1167 1168 return( b_list ); 1169 } 1170 1171 1172 public static Map decodeFromJSONObject( Map<Object,Object> j_map )1173 decodeFromJSONObject( 1174 Map<Object,Object> j_map ) 1175 { 1176 Map b_map = new HashMap(); 1177 1178 for ( Map.Entry<Object,Object> entry: j_map.entrySet()){ 1179 1180 Object key = entry.getKey(); 1181 Object val = entry.getValue(); 1182 1183 b_map.put((String)key, decodeFromJSONGeneric( val )); 1184 } 1185 1186 return( b_map ); 1187 } 1188 1189 public static Map decodeFromJSON( String json )1190 decodeFromJSON( 1191 String json ) 1192 { 1193 Map j_map = JSONUtils.decodeJSON(json); 1194 1195 return( decodeFromJSONObject( j_map )); 1196 } 1197 1198 1199 /* 1200 private interface 1201 BDecoderInputStream 1202 { 1203 public int 1204 read() 1205 1206 throws IOException; 1207 1208 public int 1209 read( 1210 byte[] buffer ) 1211 1212 throws IOException; 1213 1214 public int 1215 read( 1216 byte[] buffer, 1217 int offset, 1218 int length ) 1219 1220 throws IOException; 1221 1222 public int 1223 available() 1224 1225 throws IOException; 1226 1227 public boolean 1228 markSupported(); 1229 1230 public void 1231 mark( 1232 int limit ); 1233 1234 public void 1235 reset() 1236 1237 throws IOException; 1238 } 1239 1240 private class 1241 BDecoderInputStreamStream 1242 1243 implements BDecoderInputStream 1244 { 1245 final private BufferedInputStream is; 1246 1247 private 1248 BDecoderInputStreamStream( 1249 BufferedInputStream _is ) 1250 { 1251 is = _is; 1252 } 1253 1254 public int 1255 read() 1256 1257 throws IOException 1258 { 1259 return( is.read()); 1260 } 1261 1262 public int 1263 read( 1264 byte[] buffer ) 1265 1266 throws IOException 1267 { 1268 return( is.read( buffer )); 1269 } 1270 1271 public int 1272 read( 1273 byte[] buffer, 1274 int offset, 1275 int length ) 1276 1277 throws IOException 1278 { 1279 return( is.read( buffer, offset, length )); 1280 } 1281 1282 public int 1283 available() 1284 1285 throws IOException 1286 { 1287 return( is.available()); 1288 } 1289 1290 public boolean 1291 markSupported() 1292 { 1293 return( is.markSupported()); 1294 } 1295 1296 public void 1297 mark( 1298 int limit ) 1299 { 1300 is.mark( limit ); 1301 } 1302 1303 public void 1304 reset() 1305 1306 throws IOException 1307 { 1308 is.reset(); 1309 } 1310 } 1311 */ 1312 private static class 1313 BDecoderInputStreamArray 1314 1315 extends InputStream 1316 { 1317 final private byte[] bytes; 1318 private int pos = 0; 1319 private int markPos; 1320 private int overPos; 1321 1322 BDecoderInputStreamArray(ByteBuffer buffer)1323 public BDecoderInputStreamArray(ByteBuffer buffer) { 1324 bytes = buffer.array(); 1325 pos = buffer.arrayOffset() + buffer.position(); 1326 overPos = pos + buffer.remaining(); 1327 } 1328 1329 1330 private BDecoderInputStreamArray( byte[] _buffer )1331 BDecoderInputStreamArray( 1332 byte[] _buffer ) 1333 { 1334 bytes = _buffer; 1335 overPos = bytes.length; 1336 } 1337 1338 private BDecoderInputStreamArray( byte[] _buffer, int _offset, int _length )1339 BDecoderInputStreamArray( 1340 byte[] _buffer, 1341 int _offset, 1342 int _length ) 1343 { 1344 if (_offset == 0) { 1345 bytes = _buffer; 1346 overPos = _length; 1347 } else { 1348 bytes = _buffer; 1349 pos = _offset; 1350 overPos = Math.min(_offset + _length, bytes.length); 1351 } 1352 } 1353 1354 public int read()1355 read() 1356 1357 throws IOException 1358 { 1359 if (pos < overPos) { 1360 return bytes[pos++] & 0xFF; 1361 } 1362 return -1; 1363 } 1364 1365 public int read( byte[] buffer )1366 read( 1367 byte[] buffer ) 1368 1369 throws IOException 1370 { 1371 return( read( buffer, 0, buffer.length )); 1372 } 1373 1374 public int read( byte[] b, int offset, int length )1375 read( 1376 byte[] b, 1377 int offset, 1378 int length ) 1379 1380 throws IOException 1381 { 1382 1383 if (pos < overPos) { 1384 int toRead = Math.min(length, overPos - pos); 1385 System.arraycopy(bytes, pos, b, offset, toRead); 1386 pos += toRead; 1387 return toRead; 1388 } 1389 return -1; 1390 1391 } 1392 1393 public int available()1394 available() 1395 1396 throws IOException 1397 { 1398 return overPos - pos; 1399 } 1400 1401 public boolean markSupported()1402 markSupported() 1403 { 1404 return( true ); 1405 } 1406 1407 public void mark( int limit )1408 mark( 1409 int limit ) 1410 { 1411 markPos = pos; 1412 } 1413 1414 public void reset()1415 reset() 1416 1417 throws IOException 1418 { 1419 pos = markPos; 1420 } 1421 } 1422 1423 1424 public static void main( String[] args )1425 main( 1426 String[] args ) 1427 { 1428 print( new File( "C:\\Temp\\tables.config" ), 1429 new File( "C:\\Temp\\tables.txt" )); 1430 } 1431 } 1432