001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.iso; 020 021import java.math.BigDecimal; 022import java.nio.ByteBuffer; 023import java.nio.charset.Charset; 024import java.nio.charset.StandardCharsets; 025import java.text.DecimalFormat; 026import java.time.Duration; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.BitSet; 030import java.util.List; 031import java.util.Random; 032import java.util.StringJoiner; 033import java.util.StringTokenizer; 034import java.util.concurrent.locks.LockSupport; 035import java.util.regex.Pattern; 036import java.util.stream.Collectors; 037 038/** 039 * various functions needed to pack/unpack ISO-8583 fields 040 * 041 * @author apr@jpos.org 042 * @author Hani S. Kirollos 043 * @author Alwyn Schoeman 044 * @author Federico Gonzalez 045 * @version $Id$ 046 * @see ISOComponent 047 */ 048@SuppressWarnings("unused") 049public class ISOUtil { 050 /** 051 * All methods in this class are static, so there's usually no need to instantiate it 052 * We provide this public constructor in order to deal with some legacy script integration 053 * that needs an instance of this class in a rendering context. 054 */ 055 public ISOUtil() { 056 super(); 057 } 058 /** Pre-computed hex strings for each byte value (00-FF). */ 059 public static final String[] hexStrings; 060 061 static { 062 hexStrings = new String[256]; 063 for (int i = 0; i < 256; i++ ) { 064 StringBuilder d = new StringBuilder(2); 065 char ch = Character.forDigit((byte)i >> 4 & 0x0F, 16); 066 d.append(Character.toUpperCase(ch)); 067 ch = Character.forDigit((byte)i & 0x0F, 16); 068 d.append(Character.toUpperCase(ch)); 069 hexStrings[i] = d.toString(); 070 } 071 072 } 073 074 /** 075 * Default encoding (charset) for bytes transmissions over network 076 * @deprecated use {@link #CHARSET} instead 077 */ 078 @Deprecated 079 public static final String ENCODING = "ISO8859_1"; 080 /** Pattern for matching Unicode escape sequences. */ 081 public static final Pattern unicodePattern = Pattern.compile("u00([0-9a-fA-F]{2})+"); 082 083 /** 084 * Default charset for bytes transmissions over network 085 */ 086 public static final Charset CHARSET = StandardCharsets.ISO_8859_1; 087 /** EBCDIC charset (IBM1047). */ 088 public static final Charset EBCDIC = Charset.forName("IBM1047"); 089 090 /** ASCII Start of Text (STX) control character. */ 091 public static final byte STX = 0x02; 092 /** ASCII File Separator (FS) control character. */ 093 public static final byte FS = 0x1C; 094 /** ASCII Unit Separator (US) control character. */ 095 public static final byte US = 0x1F; 096 /** ASCII Record Separator (RS) control character. */ 097 public static final byte RS = 0x1D; 098 /** ASCII Group Separator (GS) control character. */ 099 public static final byte GS = 0x1E; 100 /** ASCII End of Text (ETX) control character. */ 101 public static final byte ETX = 0x03; 102 103 /** 104 * Converts an EBCDIC byte array to an ASCII string. 105 * 106 * @param e EBCDIC-encoded byte array 107 * @return decoded ASCII string 108 */ 109 public static String ebcdicToAscii(byte[] e) { 110 return EBCDIC.decode(ByteBuffer.wrap(e)).toString(); 111 } 112 /** 113 * Converts a portion of an EBCDIC byte array to an ASCII string. 114 * 115 * @param e EBCDIC-encoded byte array 116 * @param offset start offset within the array 117 * @param len number of bytes to convert 118 * @return decoded ASCII string 119 */ 120 public static String ebcdicToAscii(byte[] e, int offset, int len) { 121 return EBCDIC.decode(ByteBuffer.wrap(e, offset, len)).toString(); 122 } 123 124 /** 125 * Converts an EBCDIC byte array to ASCII bytes. 126 * 127 * @param e EBCDIC-encoded byte array 128 * @return ASCII-encoded byte array 129 */ 130 public static byte[] ebcdicToAsciiBytes (byte[] e) { 131 return ebcdicToAsciiBytes (e, 0, e.length); 132 } 133 134 /** 135 * Converts a portion of an EBCDIC byte array to ASCII bytes. 136 * 137 * @param e EBCDIC-encoded byte array 138 * @param offset start offset within the array 139 * @param len number of bytes to convert 140 * @return ASCII-encoded byte array 141 */ 142 public static byte[] ebcdicToAsciiBytes (byte[] e, int offset, int len) { 143 return ebcdicToAscii(e, offset, len).getBytes(CHARSET); 144 } 145 146 /** 147 * Converts an ASCII string to an EBCDIC byte array. 148 * 149 * @param s ASCII string to encode 150 * @return EBCDIC-encoded byte array 151 */ 152 public static byte[] asciiToEbcdic(String s) { 153 return EBCDIC.encode(s).array(); 154 } 155 156 /** 157 * Converts an ASCII byte array to an EBCDIC byte array. 158 * 159 * @param a ASCII-encoded byte array 160 * @return EBCDIC-encoded byte array 161 */ 162 public static byte[] asciiToEbcdic(byte[] a) { 163 return EBCDIC.encode(new String(a, CHARSET)).array(); 164 } 165 166 /** 167 * Converts an ASCII string to EBCDIC and copies the result into the destination array. 168 * 169 * @param s ASCII string to encode 170 * @param e destination byte array 171 * @param offset start offset in the destination array 172 */ 173 public static void asciiToEbcdic(String s, byte[] e, int offset) { 174 System.arraycopy (asciiToEbcdic(s), 0, e, offset, s.length()); 175 } 176 177 /** 178 * Converts an ASCII byte array to EBCDIC and copies the result into the destination array. 179 * 180 * @param s ASCII-encoded source byte array 181 * @param e destination byte array 182 * @param offset start offset in the destination array 183 */ 184 public static void asciiToEbcdic(byte[] s, byte[] e, int offset) { 185 asciiToEbcdic(new String(s, CHARSET), e, offset); 186 } 187 188 /** 189 * pad to the left 190 * @param s - original string 191 * @param len - desired len 192 * @param c - padding char 193 * @return padded string 194 * @throws ISOException on error 195 */ 196 public static String padleft(String s, int len, char c) 197 throws ISOException 198 { 199 s = s.trim(); 200 if (s.length() > len) 201 throw new ISOException("invalid len " +s.length() + "/" +len); 202 StringBuilder d = new StringBuilder (len); 203 int fill = len - s.length(); 204 while (fill-- > 0) 205 d.append (c); 206 d.append(s); 207 return d.toString(); 208 } 209 210 /** 211 * pad to the right 212 * 213 * @param s - 214 * original string 215 * @param len - 216 * desired len 217 * @param c - 218 * padding char 219 * @return padded string 220 * @throws ISOException if String's length greater than pad length 221 */ 222 public static String padright(String s, int len, char c) throws ISOException { 223 s = s.trim(); 224 if (s.length() > len) 225 throw new ISOException("invalid len " + s.length() + "/" + len); 226 StringBuilder d = new StringBuilder(len); 227 int fill = len - s.length(); 228 d.append(s); 229 while (fill-- > 0) 230 d.append(c); 231 return d.toString(); 232 } 233 234 /** 235 * trim String (if not null) 236 * @param s String to trim 237 * @return String (may be null) 238 */ 239 public static String trim (String s) { 240 return s != null ? s.trim() : null; 241 } 242 243 /** 244 * left pad with '0' 245 * @param s - original string 246 * @param len - desired len 247 * @return zero padded string 248 * @throws ISOException if string's length greater than len 249 */ 250 public static String zeropad(String s, int len) throws ISOException { 251 return padleft(s, len, '0'); 252 } 253 254 /** 255 * zeropads a long without throwing an ISOException (performs modulus operation) 256 * 257 * @param l the long 258 * @param len the length 259 * @return zeropadded value 260 */ 261 public static String zeropad(long l, int len) { 262 try { 263 return padleft (Long.toString ((long) (l % Math.pow (10, len))), len, '0'); 264 } catch (ISOException ignored) { } 265 return null; // should never happen 266 } 267 268 /** 269 * pads to the right 270 * @param s - original string 271 * @param len - desired len 272 * @return space padded string 273 */ 274 public static String strpad(String s, int len) { 275 StringBuilder d = new StringBuilder(s); 276 while (d.length() < len) 277 d.append(' '); 278 return d.toString(); 279 } 280 /** 281 * Pads a string to the right with zeros. 282 * 283 * @param s original string 284 * @param len desired length 285 * @return zero-padded string (zeros appended on right) 286 */ 287 public static String zeropadRight (String s, int len) { 288 StringBuilder d = new StringBuilder(s); 289 while (d.length() < len) 290 d.append('0'); 291 return d.toString(); 292 } 293 294 /** 295 * Pads {@code data} as per ISO/IEC 9797-1, method 2. 296 * @param data Data to be padded. 297 * @return Returns {@code data} padded as per ISO/IEC 9797-1, method 2. 298 */ 299 public static byte[] padISO9797Method2(byte[] data) { 300 byte[] t = new byte[data.length - data.length % 8 + 8]; 301 System.arraycopy(data, 0, t, 0, data.length); 302 for (int i = data.length; i < t.length; i++) 303 t[i] = (byte) (i == data.length ? 0x80 : 0x00); 304 data = t; 305 return data; 306 } 307 308 /** 309 * converts to BCD 310 * @param s - the number 311 * @param padLeft - flag indicating left/right padding 312 * @param d The byte array to copy into. 313 * @param offset Where to start copying into. 314 * @return BCD representation of the number 315 */ 316 public static byte[] str2bcd(String s, boolean padLeft, byte[] d, int offset) { 317 int len = s.length(); 318 int start = (len & 1) == 1 && padLeft ? 1 : 0; 319 for (int i=start; i < len+start; i++) 320 d [offset + (i >> 1)] |= s.charAt(i-start)-'0' << ((i & 1) == 1 ? 0 : 4); 321 return d; 322 } 323 324 /** 325 * converts to BCD 326 * @param s - the number 327 * @param padLeft - flag indicating left/right padding 328 * @param d The byte array to copy into. 329 * @param offset Where to start copying into. 330 * @return BCD representation of the number 331 */ 332 public static byte[] str2hex(String s, boolean padLeft, byte[] d, int offset) { 333 int len = s.length(); 334 int start = (len & 1) == 1 && padLeft ? 1 : 0; 335 for (int i=start; i < len+start; i++) 336 d [offset + (i >> 1)] |= Character.digit(s.charAt(i-start),16) << ((i & 1) == 1 ? 0 : 4); 337 return d; 338 } 339 340 /** 341 * converts to BCD 342 * @param s - the number 343 * @param padLeft - flag indicating left/right padding 344 * @return BCD representation of the number 345 */ 346 public static byte[] str2bcd(String s, boolean padLeft) { 347 int len = s.length(); 348 byte[] d = new byte[ len+1 >> 1 ]; 349 return str2bcd(s, padLeft, d, 0); 350 } 351 /** 352 * converts to BCD 353 * @param s - the number 354 * @param padLeft - flag indicating left/right padding 355 * @param fill - fill value 356 * @return BCD representation of the number 357 */ 358 public static byte[] str2bcd(String s, boolean padLeft, byte fill) { 359 int len = s.length(); 360 byte[] d = new byte[ len+1 >> 1 ]; 361 if (d.length > 0) { 362 if (padLeft) 363 d[0] = (byte) ((fill & 0xF) << 4); 364 int start = (len & 1) == 1 && padLeft ? 1 : 0; 365 int i; 366 for (i=start; i < len+start; i++) 367 d [i >> 1] |= s.charAt(i-start)-'0' << ((i & 1) == 1 ? 0 : 4); 368 if ((i & 1) == 1) 369 d [i >> 1] |= fill & 0xF; 370 } 371 return d; 372 } 373 /** 374 * converts a BCD representation of a number to a String 375 * @param b - BCD representation 376 * @param offset - starting offset 377 * @param len - BCD field len 378 * @param padLeft - was padLeft packed? 379 * @return the String representation of the number 380 */ 381 public static String bcd2str(byte[] b, int offset, 382 int len, boolean padLeft) 383 { 384 StringBuilder d = new StringBuilder(len); 385 int start = (len & 1) == 1 && padLeft ? 1 : 0; 386 for (int i=start; i < len+start; i++) { 387 int shift = (i & 1) == 1 ? 0 : 4; 388 char c = Character.forDigit ( 389 b[offset+ (i>>1)] >> shift & 0x0F, 16); 390 if (c == 'd') 391 c = '='; 392 d.append (Character.toUpperCase (c)); 393 } 394 return d.toString(); 395 } 396 /** 397 * converts a a byte array to a String with padding support 398 * @param b - HEX representation 399 * @param offset - starting offset 400 * @param len - BCD field len 401 * @param padLeft - was padLeft packed? 402 * @return the String representation of the number 403 */ 404 public static String hex2str(byte[] b, int offset, 405 int len, boolean padLeft) 406 { 407 StringBuilder d = new StringBuilder(len); 408 int start = (len & 1) == 1 && padLeft ? 1 : 0; 409 410 for (int i=start; i < len+start; i++) { 411 int shift = (i & 1) == 1 ? 0 : 4; 412 char c = Character.forDigit ( 413 b[offset+ (i>>1)] >> shift & 0x0F, 16); 414 d.append (Character.toUpperCase (c)); 415 } 416 return d.toString(); 417 } 418 419 /** 420 * converts a byte array to hex string 421 * (suitable for dumps and ASCII packaging of Binary fields 422 * @param b - byte array 423 * @return String representation 424 */ 425 public static String hexString(byte[] b) { 426 StringBuilder d = new StringBuilder(b.length * 2); 427 for (byte aB : b) { 428 d.append(hexStrings[(int) aB & 0xFF]); 429 } 430 return d.toString(); 431 } 432 /** 433 * converts a byte array to printable characters 434 * @param b - byte array 435 * @return String representation 436 */ 437 public static String dumpString(byte[] b) { 438 StringBuilder d = new StringBuilder(b.length * 2); 439 for (byte aB : b) { 440 char c = (char) aB; 441 if (Character.isISOControl(c)) { 442 switch (c) { 443 case '\r': 444 d.append("{CR}"); 445 break; 446 case '\n': 447 d.append("{LF}"); 448 break; 449 case '\000': 450 d.append("{NULL}"); 451 break; 452 case '\001': 453 d.append("{SOH}"); 454 break; 455 case '\002': 456 d.append("{STX}"); 457 break; 458 case '\003': 459 d.append("{ETX}"); 460 break; 461 case '\004': 462 d.append("{EOT}"); 463 break; 464 case '\005': 465 d.append("{ENQ}"); 466 break; 467 case '\006': 468 d.append("{ACK}"); 469 break; 470 case '\007': 471 d.append("{BEL}"); 472 break; 473 case '\020': 474 d.append("{DLE}"); 475 break; 476 case '\025': 477 d.append("{NAK}"); 478 break; 479 case '\026': 480 d.append("{SYN}"); 481 break; 482 case '\034': 483 d.append("{FS}"); 484 break; 485 case '\036': 486 d.append("{RS}"); 487 break; 488 default: 489 d.append('['); 490 d.append(hexStrings[(int) aB & 0xFF]); 491 d.append(']'); 492 break; 493 } 494 } else 495 d.append(c); 496 497 } 498 return d.toString(); 499 } 500 /** 501 * converts a byte array to hex string 502 * (suitable for dumps and ASCII packaging of Binary fields 503 * @param b - byte array 504 * @param offset - starting position 505 * @param len the length 506 * @return String representation 507 */ 508 public static String hexString(byte[] b, int offset, int len) { 509 StringBuilder d = new StringBuilder(len * 2); 510 len += offset; 511 for (int i=offset; i<len; i++) { 512 d.append(hexStrings[(int)b[i] & 0xFF]); 513 } 514 return d.toString(); 515 } 516 517 /** 518 * bit representation of a BitSet 519 * suitable for dumps and debugging 520 * @param b - the BitSet 521 * @return string representing the bits (i.e. 011010010...) 522 */ 523 public static String bitSet2String (BitSet b) { 524 int len = b.size(); // BBB Should be length()? 525 len = len > 128 ? 128: len; // BBB existence of 3rd bitmap not considered here 526 StringBuilder d = new StringBuilder(len); 527 for (int i=0; i<len; i++) 528 d.append (b.get(i) ? '1' : '0'); 529 return d.toString(); 530 } 531 /** 532 * converts a BitSet into a binary field 533 * used in pack routines 534 * 535 * This method will set bits 0 (and 65) if there's a secondary (and tertiary) bitmap 536 * (i.e., if the bitmap length is > 64 (and > 128)) 537 * 538 * @param b - the BitSet 539 * @return binary representation 540 */ 541 public static byte[] bitSet2byte (BitSet b) 542 { 543 int len = b.length()+62 >>6 <<6; // +62 because we don't use bit 0 in the BitSet 544 byte[] d = new byte[len >> 3]; 545 for (int i=0; i<len; i++) 546 if (b.get(i+1)) // +1 because we don't use bit 0 of the BitSet 547 d[i >> 3] |= 0x80 >> i % 8; 548 if (len>64) 549 d[0] |= 0x80; 550 if (len>128) 551 d[8] |= 0x80; 552 return d; 553 } 554 555 /** 556 * converts a BitSet into a binary field 557 * used in pack routines 558 * 559 * This method will set bits 0 (and 65) if there's a secondary (and tertiary) bitmap 560 * (i.e., if the bitmap length is > 64 (and > 128)) 561 * 562 * @param b - the BitSet 563 * @param bytes - number of bytes to return 564 * @return binary representation 565 */ 566 public static byte[] bitSet2byte (BitSet b, int bytes) 567 { 568 int len = bytes * 8; 569 570 byte[] d = new byte[bytes]; 571 for (int i=0; i<len; i++) 572 if (b.get(i+1)) // +1 because we don't use bit 0 of the BitSet 573 d[i >> 3] |= 0x80 >> i % 8; 574 if (len>64) 575 d[0] |= 0x80; 576 if (len>128) 577 d[8] |= 0x80; 578 return d; 579 } 580 581 /** 582 * Converts a BitSet to an int value. 583 * 584 * @param bs the BitSet to convert 585 * @return integer representation of the BitSet 586 */ 587 public static int bitSet2Int(BitSet bs) { 588 int total = 0; 589 int b = bs.length() - 1; 590 if (b > 0) { 591 int value = (int) Math.pow(2,b); 592 for (int i = 0; i <= b; i++) { 593 if (bs.get(i)) 594 total += value; 595 596 value = value >> 1; 597 } 598 } 599 600 return total; 601 } 602 603 /** 604 * Converts an int value to a BitSet. 605 * 606 * @param value the integer to convert 607 * @return BitSet representation of the value 608 */ 609 public static BitSet int2BitSet(int value) { 610 611 return int2BitSet(value,0); 612 } 613 614 /** 615 * Converts an int value to a BitSet with a given bit offset. 616 * 617 * @param value the integer to convert 618 * @param offset bit offset to apply when setting bits 619 * @return BitSet representation of the value at the given offset 620 */ 621 public static BitSet int2BitSet(int value, int offset) { 622 623 BitSet bs = new BitSet(); 624 625 String hex = Integer.toHexString(value); 626 hex2BitSet(bs, hex.getBytes(), offset); 627 628 return bs; 629 } 630 631 /** 632 * Converts a binary representation of a Bitmap field 633 * into a Java BitSet 634 * @param b - binary representation 635 * @param offset - staring offset 636 * @param bitZeroMeansExtended - true for ISO-8583 637 * @return java BitSet object 638 */ 639 public static BitSet byte2BitSet 640 (byte[] b, int offset, boolean bitZeroMeansExtended) 641 { 642 int len = bitZeroMeansExtended ? 643 (b[offset] & 0x80) == 0x80 ? 128 : 64 : 64; 644 BitSet bmap = new BitSet (len); 645 for (int i=0; i<len; i++) 646 if ((b[offset + (i >> 3)] & 0x80 >> i % 8) > 0) 647 bmap.set(i+1); 648 return bmap; 649 } 650 /** 651 * Converts a binary representation of a Bitmap field 652 * into a Java BitSet 653 * @param b - binary representation 654 * @param offset - staring offset 655 * @param maxBits - max number of bits (supports 64,128 or 192) 656 * @return java BitSet object 657 */ 658 public static BitSet byte2BitSet (byte[] b, int offset, int maxBits) { 659 boolean b1= (b[offset] & 0x80) == 0x80; 660 boolean b65= (b.length > offset+8) && ((b[offset+8] & 0x80) == 0x80); 661 662 int len= (maxBits > 128 && b1 && b65) ? 192 : 663 (maxBits > 64 && b1) ? 128 : 664 (maxBits < 64) ? maxBits : 64; 665 666 BitSet bmap = new BitSet (len); 667 for (int i=0; i<len; i++) 668 if ((b[offset + (i >> 3)] & 0x80 >> i % 8) > 0) 669 bmap.set(i+1); 670 return bmap; 671 } 672 673 /** 674 * Converts a binary representation of a Bitmap field 675 * into a Java BitSet. 676 * 677 * The byte[] will be fully consumed, and fed into the given BitSet starting at bitOffset+1 678 * 679 * @param bmap - BitSet 680 * @param b - hex representation 681 * @param bitOffset - (i.e. 0 for primary bitmap, 64 for secondary) 682 * @return the same java BitSet object given as first argument 683 */ 684 public static BitSet byte2BitSet (BitSet bmap, byte[] b, int bitOffset) 685 { 686 int len = b.length << 3; 687 for (int i=0; i<len; i++) 688 if ((b[i >> 3] & 0x80 >> i % 8) > 0) 689 bmap.set(bitOffset + i + 1); 690 return bmap; 691 } 692 693 /** 694 * Converts an ASCII representation of a Bitmap field 695 * into a Java BitSet 696 * @param b - hex representation 697 * @param offset - starting offset 698 * @param bitZeroMeansExtended - true for ISO-8583 699 * @return java BitSet object 700 */ 701 public static BitSet hex2BitSet 702 (byte[] b, int offset, boolean bitZeroMeansExtended) 703 { 704 int len = bitZeroMeansExtended ? 705 (Character.digit((char)b[offset],16) & 0x08) == 8 ? 128 : 64 : 706 64; 707 BitSet bmap = new BitSet (len); 708 for (int i=0; i<len; i++) { 709 int digit = Character.digit((char)b[offset + (i >> 2)], 16); 710 if ((digit & 0x08 >> i%4) > 0) 711 bmap.set(i+1); 712 } 713 return bmap; 714 } 715 716 /** 717 * Converts an ASCII representation of a Bitmap field 718 * into a Java BitSet 719 * @param b - hex representation 720 * @param offset - starting offset 721 * @param maxBits - max number of bits (supports 8, 16, 24, 32, 48, 52, 64,.. 128 or 192) 722 * @return java BitSet object 723 */ 724 public static BitSet hex2BitSet (byte[] b, int offset, int maxBits) { 725 int len = maxBits > 64 ? 726 (Character.digit((char)b[offset],16) & 0x08) == 8 ? 128 : 64 : 727 maxBits; 728 if (len > 64 && maxBits > 128 && 729 b.length > offset+16 && 730 (Character.digit((char)b[offset+16],16) & 0x08) == 8) 731 { 732 len = 192; 733 } 734 BitSet bmap = new BitSet (len); 735 for (int i=0; i<len; i++) { 736 int digit = Character.digit((char)b[offset + (i >> 2)], 16); 737 if ((digit & 0x08 >> i%4) > 0) { 738 bmap.set(i+1); 739 if (i==65 && maxBits > 128) // BBB this is redundant (check already done outside 740 len = 192; // BBB of the loop), but I'll leave it for now.. 741 } 742 } 743 return bmap; 744 } 745 746 /** 747 * Converts an ASCII representation of a Bitmap field 748 * into a Java BitSet 749 * @param bmap - BitSet 750 * @param b - hex representation 751 * @param bitOffset - (i.e. 0 for primary bitmap, 64 for secondary) 752 * @return java BitSet object 753 */ 754 public static BitSet hex2BitSet (BitSet bmap, byte[] b, int bitOffset) 755 { 756 int len = b.length << 2; 757 for (int i=0; i<len; i++) { 758 int digit = Character.digit((char)b[i >> 2], 16); 759 if ((digit & 0x08 >> i%4) > 0) 760 bmap.set (bitOffset + i + 1); 761 } 762 return bmap; 763 } 764 765 /** 766 * Converts a hex-encoded byte array to a binary byte array. 767 * @param b source byte array containing hex digits 768 * @param offset starting offset 769 * @param len number of bytes in destination (processes len*2) 770 * @return byte[len] 771 */ 772 public static byte[] hex2byte (byte[] b, int offset, int len) { 773 byte[] d = new byte[len]; 774 for (int i=0; i<len*2; i++) { 775 int shift = i%2 == 1 ? 0 : 4; 776 d[i>>1] |= Character.digit((char) b[offset+i], 16) << shift; 777 } 778 return d; 779 } 780 /** 781 * Converts a hex string into a byte array 782 * @param s source string (with Hex representation) 783 * @return byte array 784 */ 785 public static byte[] hex2byte (String s) { 786 return hex2byte (s, CHARSET); 787 } 788 /** 789 * Converts a hex string into a byte array 790 * @param s source string (with Hex representation) 791 * @param charset character set to be used 792 * @return byte array 793 */ 794 public static byte[] hex2byte (String s, Charset charset) { 795 if (charset == null) charset = CHARSET; 796 if (s.length() % 2 == 0) { 797 return hex2byte (s.getBytes(charset), 0, s.length() >> 1); 798 } else { 799 // Padding left zero to make it even size #Bug raised by tommy 800 return hex2byte("0"+s, charset); 801 } 802 } 803 804 /** 805 * Converts a byte array into a hex string 806 * @param bs source byte array 807 * @return hexadecimal representation of bytes 808 */ 809 public static String byte2hex(byte[] bs) { 810 return byte2hex(bs, 0, bs.length); 811 } 812 813 /** 814 * Converts an integer into a minimal big-endian byte array. 815 * 816 * @param value the integer to convert 817 * @return bytes representation of integer 818 */ 819 public static byte[] int2byte(int value) { 820 if (value < 0) { 821 return new byte[]{(byte) (value >>> 24 & 0xFF), (byte) (value >>> 16 & 0xFF), 822 (byte) (value >>> 8 & 0xFF), (byte) (value & 0xFF)}; 823 } else if (value <= 0xFF) { 824 return new byte[]{(byte) (value & 0xFF)}; 825 } else if (value <= 0xFFFF) { 826 return new byte[]{(byte) (value >>> 8 & 0xFF), (byte) (value & 0xFF)}; 827 } else if (value <= 0xFFFFFF) { 828 return new byte[]{(byte) (value >>> 16 & 0xFF), (byte) (value >>> 8 & 0xFF), 829 (byte) (value & 0xFF)}; 830 } else { 831 return new byte[]{(byte) (value >>> 24 & 0xFF), (byte) (value >>> 16 & 0xFF), 832 (byte) (value >>> 8 & 0xFF), (byte) (value & 0xFF)}; 833 } 834 } 835 836 /** 837 * Converts a big-endian byte array to an integer. 838 * 839 * @param bytes the byte array to convert 840 * @return integer representation of bytes 841 */ 842 public static int byte2int(byte[] bytes) { 843 if (bytes == null || bytes.length == 0) { 844 return 0; 845 } 846 ByteBuffer byteBuffer = ByteBuffer.allocate(4); 847 for (int i = 0; i < 4 - bytes.length; i++) { 848 byteBuffer.put((byte) 0); 849 } 850 for (int i = 0; i < bytes.length; i++) { 851 byteBuffer.put(bytes[i]); 852 } 853 byteBuffer.position(0); 854 return byteBuffer.getInt(); 855 } 856 857 /** 858 * Converts a byte array into a string of lower case hex chars. 859 * 860 * @param bs A byte array 861 * @param off The index of the first byte to read 862 * @param length The number of bytes to read. 863 * @return the string of hex chars. 864 */ 865 public static String byte2hex(byte[] bs, int off, int length) { 866 if (bs.length <= off || bs.length < off + length) 867 throw new IllegalArgumentException(); 868 StringBuilder sb = new StringBuilder(length * 2); 869 byte2hexAppend(bs, off, length, sb); 870 return sb.toString(); 871 } 872 873 private static void byte2hexAppend(byte[] bs, int off, int length, StringBuilder sb) { 874 if (bs.length <= off || bs.length < off + length) 875 throw new IllegalArgumentException(); 876 sb.ensureCapacity(sb.length() + length * 2); 877 for (int i = off; i < off + length; i++) { 878 sb.append(Character.forDigit(bs[i] >>> 4 & 0xf, 16)); 879 sb.append(Character.forDigit(bs[i] & 0xf, 16)); 880 } 881 } 882 883 /** 884 * format double value 885 * @param d the amount 886 * @param len the field len 887 * @return a String of fieldLen characters (right justified) 888 */ 889 public static String formatDouble(double d, int len) { 890 String prefix = Long.toString((long) d); 891 String suffix = Integer.toString ( 892 (int) (Math.round(d * 100f) % 100) ); 893 try { 894 if (len > 3) 895 prefix = ISOUtil.padleft(prefix,len-3,' '); 896 suffix = ISOUtil.zeropad(suffix, 2); 897 } catch (ISOException e) { 898 // should not happen 899 } 900 return prefix + "." + suffix; 901 } 902 /** 903 * prepare long value used as amount for display 904 * (implicit 2 decimals) 905 * @param l value 906 * @param len display len 907 * @return formated field 908 * @exception ISOException if the formatted value exceeds the requested length 909 */ 910 public static String formatAmount(long l, int len) throws ISOException { 911 String buf = Long.toString(l); 912 if (l < 100) 913 buf = zeropad(buf, 3); 914 StringBuilder s = new StringBuilder(padleft (buf, len-1, ' ') ); 915 s.insert(len-3, '.'); 916 return s.toString(); 917 } 918 919 /** 920 * XML normalizer 921 * @param s source String 922 * @param canonical true if we want to normalize \r and \n as well 923 * @return normalized string suitable for XML Output 924 */ 925 public static String normalize (String s, boolean canonical) { 926 StringBuilder str = new StringBuilder(); 927 928 int len = s != null ? s.length() : 0; 929 for (int i = 0; i < len; i++) { 930 char ch = s.charAt(i); 931 switch (ch) { 932 case '<': 933 str.append("<"); 934 break; 935 case '>': 936 str.append(">"); 937 break; 938 case '&': 939 str.append("&"); 940 break; 941 case '"': 942 str.append("""); 943 break; 944 case '\'': 945 str.append("'"); 946 break; 947 case '\r': 948 case '\n': 949 if (!canonical) { 950 str.append("&#"); 951 str.append(Integer.toString(ch & 0xFF)); 952 str.append(';'); 953 break; 954 } 955 // else, default append char 956 default: 957 if (ch < 0x20) { 958 str.append(String.format("\\u%04x", (int) (ch & 0xFF))); 959 } else { 960 str.append(ch); 961 } 962 } 963 } 964 return str.toString(); 965 } 966 967 /** 968 * Reverses the {@code \\uXXXX} escapes produced by {@code escapeUnicode}-style 969 * helpers, returning the original characters. 970 * 971 * @param s string possibly containing {@code \\uXXXX} escapes; may be {@code null} 972 * @return decoded string, or empty string when {@code s} is {@code null} 973 */ 974 public static String stripUnicode (String s) { 975 StringBuilder sb = new StringBuilder(); 976 int len = s != null ? s.length() : 0; 977 boolean escape = false; 978 for (int i = 0; i < len; i++) { 979 char ch = s.charAt(i); 980 if (ch == '\\' && i < len-5 && isInternalUnicodeSequence(s.substring(i+1, i+6))) { 981 sb.append((char) (Character.digit(s.charAt(i + 4), 16) << 4 | (Character.digit(s.charAt(i + 5), 16)))); 982 i += 5; 983 } else 984 sb.append(ch); 985 } 986 return sb.toString(); 987 } 988 989 private static boolean isInternalUnicodeSequence(String s) { 990 return unicodePattern.matcher(s).matches(); 991 } 992 /** 993 * XML normalizer (default canonical) 994 * @param s source String 995 * @return normalized string suitable for XML Output 996 */ 997 public static String normalize (String s) { 998 return normalize(s, true); 999 } 1000 /** 1001 * Protects PAN, Track2, CVC (suitable for logs). 1002 * 1003 * <pre> 1004 * "40000101010001" is converted to "400001____0001" 1005 * "40000101010001=020128375" is converted to "400001____0001=0201_____" 1006 * "40000101010001D020128375" is converted to "400001____0001D0201_____" 1007 * "123" is converted to "___" 1008 * </pre> 1009 * @param s string to be protected 1010 * @param mask char used to protect the string 1011 * @return 'protected' String 1012 */ 1013 1014 public static String protect(String s, char mask) { 1015 // Validation for minimum length 1016 if (s.length() <= 4) { 1017 char[] maskedArray = new char[s.length()]; 1018 Arrays.fill(maskedArray, mask);// 6 (BIN) + 4 (last digits) = 10 1019 return new String(maskedArray); 1020 } 1021 StringBuilder ps = new StringBuilder(s); 1022 1023 // Identify the positions of separators (^ and =) 1024 String separator = s.contains("^") ? "^" : s.contains("=") ? "=" : null; 1025 int firstSeparatorIndex = separator != null ? ps.indexOf(separator) : s.length(); 1026 if (firstSeparatorIndex < 6) { 1027 return s; // nothing to do 1028 } 1029 int lastDigitIndex = firstSeparatorIndex - 4; 1030 1031 // Replace characters with underscore except BIN, last four digits and separators 1032 for (int i = 6; i < lastDigitIndex; i++) { 1033 ps.setCharAt(i, mask); 1034 } 1035 if (separator != null) { 1036 for (int i = firstSeparatorIndex + 1; i < ps.length(); i++) { 1037 char c = ps.charAt(i); 1038 if ((c != '=' && c != '^')) { 1039 ps.setCharAt(i, mask); 1040 } 1041 } 1042 } 1043 return ps.toString(); 1044 } 1045 /** 1046 * Protects PAN, Track2, CVC using the default underscore mask character. 1047 * 1048 * @param s string to be protected 1049 * @return 'protected' String 1050 */ 1051 public static String protect(String s) { 1052 return protect(s, '_'); 1053 } 1054 1055 /** 1056 * Converts a whitespace-delimited string of numbers to an int array. 1057 * 1058 * @param s whitespace-delimited string of integer values 1059 * @return array of parsed integers 1060 */ 1061 public static int[] toIntArray(String s) { 1062 StringTokenizer st = new StringTokenizer (s); 1063 int[] array = new int [st.countTokens()]; 1064 for (int i=0; st.hasMoreTokens(); i++) 1065 array[i] = Integer.parseInt (st.nextToken()); 1066 return array; 1067 } 1068 1069 /** 1070 * Converts a whitespace-delimited string of tokens to a String array. 1071 * 1072 * @param s whitespace-delimited string of tokens 1073 * @return array of string tokens 1074 */ 1075 public static String[] toStringArray(String s) { 1076 StringTokenizer st = new StringTokenizer (s); 1077 String[] array = new String [st.countTokens()]; 1078 for (int i=0; st.hasMoreTokens(); i++) 1079 array[i] = st.nextToken(); 1080 return array; 1081 } 1082 /** 1083 * Bitwise XOR between corresponding bytes 1084 * @param op1 byteArray1 1085 * @param op2 byteArray2 1086 * @return an array of length = the smallest between op1 and op2 1087 */ 1088 public static byte[] xor (byte[] op1, byte[] op2) { 1089 byte[] result; 1090 // Use the smallest array 1091 if (op2.length > op1.length) { 1092 result = new byte[op1.length]; 1093 } 1094 else { 1095 result = new byte[op2.length]; 1096 } 1097 for (int i = 0; i < result.length; i++) { 1098 result[i] = (byte)(op1[i] ^ op2[i]); 1099 } 1100 return result; 1101 } 1102 /** 1103 * Bitwise XOR between corresponding byte arrays represented in hex 1104 * @param op1 hexstring 1 1105 * @param op2 hexstring 2 1106 * @return an array of length = the smallest between op1 and op2 1107 */ 1108 public static String hexor (String op1, String op2) { 1109 byte[] xor = xor (hex2byte (op1), hex2byte (op2)); 1110 return hexString (xor); 1111 } 1112 1113 /** 1114 * Trims a byte[] to a certain length 1115 * @param array the byte[] to be trimmed 1116 * @param length the wanted length 1117 * @return the trimmed byte[] 1118 */ 1119 public static byte[] trim (byte[] array, int length) { 1120 byte[] trimmedArray = new byte[length]; 1121 System.arraycopy(array, 0, trimmedArray, 0, length); 1122 return trimmedArray; 1123 } 1124 1125 /** 1126 * Concatenates two byte arrays (array1 and array2) 1127 * @param array1 first part 1128 * @param array2 last part 1129 * @return the concatenated array 1130 */ 1131 public static byte[] concat (byte[] array1, byte[] array2) { 1132 byte[] concatArray = new byte[array1.length + array2.length]; 1133 System.arraycopy(array1, 0, concatArray, 0, array1.length); 1134 System.arraycopy(array2, 0, concatArray, array1.length, array2.length); 1135 return concatArray; 1136 } 1137 1138 /** 1139 * Concatenates two byte arrays (array1 and array2) 1140 * @param array1 first part 1141 * @param beginIndex1 initial index 1142 * @param length1 length 1143 * @param array2 last part 1144 * @param beginIndex2 last part index 1145 * @param length2 last part length 1146 * @return the concatenated array 1147 */ 1148 public static byte[] concat (byte[] array1, int beginIndex1, int length1, byte[] array2, 1149 int beginIndex2, int length2) { 1150 byte[] concatArray = new byte[length1 + length2]; 1151 System.arraycopy(array1, beginIndex1, concatArray, 0, length1); 1152 System.arraycopy(array2, beginIndex2, concatArray, length1, length2); 1153 return concatArray; 1154 } 1155 /** 1156 * Causes the currently executing thread to sleep (temporarily cease 1157 * execution) for the specified number of milliseconds. The thread 1158 * does not lose ownership of any monitors. 1159 * 1160 * @param millis the length of time to sleep in milliseconds. 1161 */ 1162 public static void sleep (long millis) { 1163 if (millis > 0) 1164 LockSupport.parkNanos(Duration.ofMillis(millis).toNanos()); 1165 else if (millis == 0) 1166 Thread.yield(); 1167 else 1168 throw new IllegalArgumentException ("timeout value is negative"); 1169 } 1170 1171 /** 1172 * Left unPad with '0' 1173 * @param s - original string 1174 * @return zero unPadded string 1175 */ 1176 public static String zeroUnPad( String s ) { 1177 return unPadLeft(s, '0'); 1178 } 1179 /** 1180 * Right unPad with ' ' 1181 * @param s - original string 1182 * @return blank unPadded string 1183 */ 1184 public static String blankUnPad( String s ) { 1185 return unPadRight(s, ' '); 1186 } 1187 1188 /** 1189 * Unpad from right. 1190 * @param s - original string 1191 * @param c - padding char 1192 * @return unPadded string. 1193 */ 1194 public static String unPadRight(String s, char c) { 1195 int end = s.length(); 1196 if (end == 0) 1197 return s; 1198 while ( 0 < end && s.charAt(end-1) == c) end --; 1199 return 0 < end ? s.substring( 0, end ): s.substring( 0, 1 ); 1200 } 1201 1202 /** 1203 * Unpad from left. 1204 * @param s - original string 1205 * @param c - padding char 1206 * @return unPadded string. 1207 */ 1208 public static String unPadLeft(String s, char c) { 1209 int fill = 0, end = s.length(); 1210 if (end == 0) 1211 return s; 1212 while ( fill < end && s.charAt(fill) == c) fill ++; 1213 return fill < end ? s.substring( fill, end ): s.substring( fill-1, end ); 1214 } 1215 1216 /** 1217 * Returns true if the string is zero-filled (all '0' characters). 1218 * @param s the string to test 1219 * @return true if the string is zero-filled ( 0 char filled ) 1220 **/ 1221 public static boolean isZero( String s ) { 1222 int i = 0, len = s.length(); 1223 while ( i < len && s.charAt( i ) == '0'){ 1224 i++; 1225 } 1226 return i >= len; 1227 } 1228 1229 /** 1230 * Returns true if the string is blank-filled (all space characters). 1231 * @param s the string to test 1232 * @return true if the string is blank filled (space char filled) 1233 */ 1234 public static boolean isBlank( String s ){ 1235 return s.trim().length() == 0; 1236 } 1237 1238 /** 1239 * Returns true if the string contains only alphanumeric characters. 1240 * @param s the string to test 1241 * @return true if the string is alphanumeric 1242 **/ 1243 public static boolean isAlphaNumeric ( String s ) { 1244 int len = s.length(); 1245 for (int i=0; i<len; i++) { 1246 char c = s.charAt(i); 1247 if (!Character.isLetterOrDigit(s.charAt(i))) 1248 return false; 1249 } 1250 return len > 0; 1251 } 1252 1253 /** 1254 * Returns true if the string represents a number in the specified radix. 1255 * @param s the string to test 1256 * @param radix the radix to use for digit validation 1257 * @return true if the string represents a valid number in the given radix 1258 **/ 1259 public static boolean isNumeric ( String s, int radix ) { 1260 int i = 0, len = s.length(); 1261 while ( i < len && Character.digit(s.charAt(i), radix) > -1 ){ 1262 i++; 1263 } 1264 return i >= len && len > 0; 1265 } 1266 1267 /** 1268 * Converts a BitSet into an extended binary field 1269 * used in pack routines. The result is always in the 1270 * extended format: (16 bytes of length) 1271 * <br><br> 1272 * @param b the BitSet 1273 * @return binary representation 1274 */ 1275 public static byte[] bitSet2extendedByte ( BitSet b ){ 1276 int len = 128; 1277 byte[] d = new byte[len >> 3]; 1278 for ( int i=0; i<len; i++ ) 1279 if (b.get(i+1)) 1280 d[i >> 3] |= 0x80 >> i % 8; 1281 d[0] |= 0x80; 1282 return d; 1283 } 1284 1285 /** 1286 * Converts a String to an integer of base radix. 1287 * <br><br> 1288 * String constraints are: 1289 * <ul> 1290 * <li>Number must be less than 10 digits</li> 1291 * <li>Number must be positive</li> 1292 * </ul> 1293 * @param s String representation of number 1294 * @param radix Number base to use 1295 * @return integer value of number 1296 * @throws NumberFormatException if the string contains non-digit characters or exceeds 9 digits 1297 */ 1298 public static int parseInt (String s, int radix) throws NumberFormatException { 1299 int length = s.length(); 1300 if (length > 9) 1301 throw new NumberFormatException ("Number can have maximum 9 digits"); 1302 int result; 1303 int index = 0; 1304 int digit = Character.digit (s.charAt(index++), radix); 1305 if (digit == -1) 1306 throw new NumberFormatException ("String contains non-digit"); 1307 result = digit; 1308 while (index < length) { 1309 result *= radix; 1310 digit = Character.digit (s.charAt(index++), radix); 1311 if (digit == -1) 1312 throw new NumberFormatException ("String contains non-digit"); 1313 result += digit; 1314 } 1315 return result; 1316 } 1317 1318 /** 1319 * Converts a String to an integer of radix 10. 1320 * <br><br> 1321 * String constraints are: 1322 * <ul> 1323 * <li>Number must be less than 10 digits</li> 1324 * <li>Number must be positive</li> 1325 * </ul> 1326 * @param s String representation of number 1327 * @return integer value of number 1328 * @throws NumberFormatException if the string contains non-digit characters or exceeds 9 digits 1329 */ 1330 public static int parseInt (String s) throws NumberFormatException { 1331 return parseInt (s, 10); 1332 } 1333 1334 /** 1335 * Converts a character array to an integer of base radix. 1336 * <br><br> 1337 * Array constraints are: 1338 * <ul> 1339 * <li>Number must be less than 10 digits</li> 1340 * <li>Number must be positive</li> 1341 * </ul> 1342 * @param cArray Character Array representation of number 1343 * @param radix Number base to use 1344 * @return integer value of number 1345 * @throws NumberFormatException if the array contains non-digit characters or exceeds 9 digits 1346 */ 1347 public static int parseInt (char[] cArray, int radix) throws NumberFormatException { 1348 int length = cArray.length; 1349 if (length > 9) 1350 throw new NumberFormatException ("Number can have maximum 9 digits"); 1351 int result; 1352 int index = 0; 1353 int digit = Character.digit(cArray[index++], radix); 1354 if (digit == -1) 1355 throw new NumberFormatException ("Char array contains non-digit"); 1356 result = digit; 1357 while (index < length) { 1358 result *= radix; 1359 digit = Character.digit(cArray[index++],radix); 1360 if (digit == -1) 1361 throw new NumberFormatException ("Char array contains non-digit"); 1362 result += digit; 1363 } 1364 return result; 1365 } 1366 1367 /** 1368 * Converts a character array to an integer of radix 10. 1369 * <br><br> 1370 * Array constraints are: 1371 * <ul> 1372 * <li>Number must be less than 10 digits</li> 1373 * <li>Number must be positive</li> 1374 * </ul> 1375 * @param cArray Character Array representation of number 1376 * @return integer value of number 1377 * @throws NumberFormatException if the array contains non-digit characters or exceeds 9 digits 1378 */ 1379 public static int parseInt (char[] cArray) throws NumberFormatException { 1380 return parseInt (cArray,10); 1381 } 1382 1383 /** 1384 * Converts a byte array to an integer of base radix. 1385 * <br><br> 1386 * Array constraints are: 1387 * <ul> 1388 * <li>Number must be less than 10 digits</li> 1389 * <li>Number must be positive</li> 1390 * </ul> 1391 * @param bArray Byte Array representation of number 1392 * @param radix Number base to use 1393 * @return integer value of number 1394 * @throws NumberFormatException if the array contains non-digit characters or exceeds 9 digits 1395 */ 1396 public static int parseInt (byte[] bArray, int radix) throws NumberFormatException { 1397 int length = bArray.length; 1398 if (length > 9) 1399 throw new NumberFormatException ("Number can have maximum 9 digits"); 1400 int result; 1401 int index = 0; 1402 int digit = Character.digit((char)bArray[index++], radix); 1403 if (digit == -1) 1404 throw new NumberFormatException ("Byte array contains non-digit"); 1405 result = digit; 1406 while (index < length) { 1407 result *= radix; 1408 digit = Character.digit((char)bArray[index++],radix); 1409 if (digit == -1) 1410 throw new NumberFormatException ("Byte array contains non-digit"); 1411 result += digit; 1412 } 1413 return result; 1414 } 1415 1416 /** 1417 * Converts a byte array to an integer of radix 10. 1418 * <br><br> 1419 * Array constraints are: 1420 * <ul> 1421 * <li>Number must be less than 10 digits</li> 1422 * <li>Number must be positive</li> 1423 * </ul> 1424 * @param bArray Byte Array representation of number 1425 * @return integer value of number 1426 * @throws NumberFormatException if the array contains non-digit characters or exceeds 9 digits 1427 */ 1428 public static int parseInt (byte[] bArray) throws NumberFormatException { 1429 return parseInt (bArray,10); 1430 } 1431 private static String hexOffset (int i) { 1432 i = i>>4 << 4; 1433 int w = i > 0xFFFF ? 8 : 4; 1434 try { 1435 return zeropad (Integer.toString (i, 16), w); 1436 } catch (ISOException e) { 1437 // should not happen 1438 return e.getMessage(); 1439 } 1440 } 1441 1442 /** 1443 * Returns a formatted hexdump of a byte buffer. 1444 * @param b a byte[] buffer 1445 * @return hexdump string 1446 */ 1447 public static String hexdump (byte[] b) { 1448 return hexdump (b, 0, b.length); 1449 } 1450 1451 /** 1452 * Returns a formatted hexdump of a byte buffer starting at the given offset. 1453 * @param b a byte[] buffer 1454 * @param offset starting offset 1455 * @return hexdump string from offset to end of buffer 1456 */ 1457 public static String hexdump (byte[] b, int offset) { 1458 return hexdump (b, offset, b.length-offset); 1459 } 1460 1461 /** 1462 * Returns a formatted hexdump of a byte buffer region. 1463 * @param b a byte[] buffer 1464 * @param offset starting offset 1465 * @param len the number of bytes to dump 1466 * @return hexdump string for the specified region 1467 */ 1468 public static String hexdump (byte[] b, int offset, int len) { 1469 StringBuilder sb = new StringBuilder (); 1470 StringBuilder hex = new StringBuilder (); 1471 StringBuilder ascii = new StringBuilder (); 1472 String sep = " "; 1473 String lineSep = System.getProperty ("line.separator"); 1474 len = offset + len; 1475 1476 for (int i=offset; i<len; i++) { 1477 hex.append(hexStrings[(int)b[i] & 0xFF]); 1478 hex.append (' '); 1479 char c = (char) b[i]; 1480 ascii.append (c >= 32 && c < 127 ? c : '.'); 1481 1482 int j = i % 16; 1483 switch (j) { 1484 case 7 : 1485 hex.append (' '); 1486 break; 1487 case 15 : 1488 sb.append (hexOffset (i)); 1489 sb.append (sep); 1490 sb.append (hex.toString()); 1491 sb.append (' '); 1492 sb.append (ascii.toString()); 1493 sb.append (lineSep); 1494 hex = new StringBuilder (); 1495 ascii = new StringBuilder (); 1496 break; 1497 } 1498 } 1499 if (hex.length() > 0) { 1500 while (hex.length () < 49) 1501 hex.append (' '); 1502 1503 sb.append (hexOffset (len)); 1504 sb.append (sep); 1505 sb.append (hex.toString()); 1506 sb.append (' '); 1507 sb.append (ascii.toString()); 1508 sb.append (lineSep); 1509 } 1510 return sb.toString(); 1511 } 1512 /** 1513 * pads a string with 'F's (useful for pinoffset management) 1514 * @param s an [hex]string 1515 * @param len desired length 1516 * @return string right padded with 'F's 1517 */ 1518 public static String strpadf (String s, int len) { 1519 StringBuilder d = new StringBuilder(s); 1520 while (d.length() < len) 1521 d.append('F'); 1522 return d.toString(); 1523 } 1524 /** 1525 * reverse the effect of strpadf 1526 * @param s F padded string 1527 * @return trimmed string 1528 */ 1529 public static String trimf (String s) { 1530 if (s != null) { 1531 int l = s.length(); 1532 if (l > 0) { 1533 while (--l >= 0) { 1534 if (s.charAt (l) != 'F') 1535 break; 1536 } 1537 s = l == 0 ? "" : s.substring (0, l+1); 1538 } 1539 } 1540 return s; 1541 } 1542 1543 /** 1544 * Returns the last n characters of the passed String, left-padding with '0' where required. 1545 * 1546 * @param s String to take from 1547 * @param n number of characters to take 1548 * @return String (may be null) 1549 * @throws ISOException if zero-padding fails 1550 */ 1551 public static String takeLastN(String s,int n) throws ISOException { 1552 if (s.length()>n) { 1553 return s.substring(s.length()-n); 1554 } 1555 else { 1556 if (s.length()<n){ 1557 return zeropad(s,n); 1558 } 1559 else { 1560 return s; 1561 } 1562 } 1563 } 1564 1565 /** 1566 * Returns the first n characters of the passed String, left-padding with '0' where required. 1567 * 1568 * @param s String to take from 1569 * @param n number of characters to take 1570 * @return String (may be null) 1571 * @throws ISOException if zero-padding fails 1572 */ 1573 public static String takeFirstN(String s,int n) throws ISOException { 1574 if (s.length()>n) { 1575 return s.substring(0,n); 1576 } 1577 else { 1578 if (s.length()<n){ 1579 return zeropad(s,n); 1580 } 1581 else { 1582 return s; 1583 } 1584 } 1585 } 1586 /** 1587 * Converts a duration in milliseconds to a human-readable string (e.g. "1d 2h 3m 4s 500ms"). 1588 * 1589 * @param millis duration in milliseconds 1590 * @return human-readable duration string 1591 */ 1592 public static String millisToString (long millis) { 1593 StringBuilder sb = new StringBuilder(); 1594 if (millis < 0) { 1595 millis = -millis; 1596 sb.append('-'); 1597 } 1598 int ms = (int) (millis % 1000); 1599 millis /= 1000; 1600 int dd = (int) (millis/86400); 1601 millis -= dd * 86400; 1602 int hh = (int) (millis/3600); 1603 millis -= hh * 3600; 1604 int mm = (int) (millis/60); 1605 millis -= mm * 60; 1606 int ss = (int) millis; 1607 if (dd > 0) { 1608 sb.append (Long.toString(dd)); 1609 sb.append ("d "); 1610 } 1611 sb.append (zeropad (hh, 2)); 1612 sb.append (':'); 1613 sb.append (zeropad (mm, 2)); 1614 sb.append (':'); 1615 sb.append (zeropad (ss, 2)); 1616 sb.append ('.'); 1617 sb.append (zeropad (ms, 3)); 1618 return sb.toString(); 1619 } 1620 1621 /** 1622 * Format a string containing a amount conversion rate in the proper format 1623 * <p> 1624 * Format: 1625 * The leftmost digit (i.e., position 1) of this data element denotes the number of 1626 * positions the decimal separator must be moved from the right. Positions 2–8 of 1627 * this data element specify the rate. For example, a conversion rate value of 1628 * 91234567 in this data element would equate to 0.001234567. 1629 * 1630 * @param convRate amount conversion rate 1631 * @return a string containing a amount conversion rate in the proper format, 1632 * which is suitable for creating fields 10 and 11 1633 * @throws ISOException if zero-padding fails during formatting 1634 */ 1635 public static String formatAmountConversionRate(double convRate) throws ISOException { 1636 if (convRate == 0) 1637 return null; 1638 BigDecimal cr = new BigDecimal(convRate); 1639 int x = 7 - cr.precision() + cr.scale(); 1640 String bds = cr.movePointRight(cr.scale()).toString(); 1641 if (x > 9) 1642 bds = ISOUtil.zeropad(bds, bds.length() + x - 9); 1643 String ret = ISOUtil.zeropadRight(bds, 7); 1644 return Math.min(9, x) + ISOUtil.takeFirstN(ret, 7); 1645 } 1646 1647 /** 1648 * Parse currency amount conversion rate string 1649 * <p> 1650 * Suitble for parse fields 10 and 11 1651 * 1652 * @param convRate amount conversion rate string (8 characters) 1653 * @return parsed currency amount conversion rate as a double 1654 * @throws IllegalArgumentException if the string is null or not exactly 8 characters 1655 */ 1656 public static double parseAmountConversionRate(String convRate) { 1657 if (convRate == null || convRate.length() != 8) 1658 throw new IllegalArgumentException("Invalid amount converion rate argument: '" + 1659 convRate + "'"); 1660 BigDecimal bd = new BigDecimal(convRate); 1661 int pow = bd.movePointLeft(7).intValue(); 1662 bd = new BigDecimal(convRate.substring(1)); 1663 return bd.movePointLeft(pow).doubleValue(); 1664 } 1665 1666 1667 /** 1668 * Converts a string[] or multiple strings into one comma-delimited String. 1669 * 1670 * Takes care of escaping commas using a backlash 1671 * @see ISOUtil#commaDecode(String) 1672 * @param ss string array to be comma encoded 1673 * @return comma encoded string 1674 */ 1675 public static String commaEncode (String... ss) { 1676 return charEncode(',', ss); 1677 } 1678 1679 /** 1680 * Decodes a comma encoded String as encoded by commaEncode 1681 * @see ISOUtil#commaEncode(String[]) 1682 * @param s the command encoded String 1683 * @return String[] 1684 */ 1685 public static String[] commaDecode (String s) { 1686 return charDecode(',', s); 1687 } 1688 1689 /** 1690 * Decodes a comma encoded String returning element in position i 1691 * @param s comma encoded string 1692 * @param i position (starts at 0) 1693 * @return element in position i of comma encoded string, or null 1694 */ 1695 public static String commaDecode (String s, int i) { 1696 String[] ss = commaDecode(s); 1697 int l = ss.length; 1698 return i >= 0 && i < l ? ss[i] : null; 1699 } 1700 1701 /** 1702 * Compute card's check digit (LUHN) 1703 * @param p PAN (without checkdigit) 1704 * @return the checkdigit 1705 */ 1706 public static char calcLUHN (String p) { 1707 int i, crc; 1708 int odd = p.length() % 2; 1709 1710 for (i=crc=0; i<p.length(); i++) { 1711 char c = p.charAt(i); 1712 if (!Character.isDigit (c)) { 1713 throw new IllegalArgumentException("Invalid PAN " + p); 1714 } 1715 c = (char) (c - '0'); 1716 if (i % 2 != odd) 1717 crc+= c*2 >= 10 ? c*2 -9 : c*2; 1718 else 1719 crc+=c; 1720 } 1721 1722 return (char) ((crc % 10 == 0 ? 0 : 10 - crc % 10) + '0'); 1723 } 1724 1725 /** 1726 * Generates a string of {@code l} random digits using the given radix. 1727 * 1728 * @param r random number generator to use 1729 * @param l number of digits to generate 1730 * @param radix the radix (base) for digit generation 1731 * @return string of {@code l} random digits 1732 */ 1733 public static String getRandomDigits(Random r, int l, int radix) { 1734 StringBuilder sb = new StringBuilder(); 1735 for (int i=0; i<l; i++) { 1736 sb.append (r.nextInt(radix)); 1737 } 1738 return sb.toString(); 1739 } 1740 1741 // See http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc 1742 // and http://physics.nist.gov/cuu/Units/binary.html 1743 /** 1744 * Formats a file size in bytes as a human-readable string (e.g. "1.5 MiB"). 1745 * 1746 * @param size file size in bytes 1747 * @return human-readable file size string 1748 */ 1749 public static String readableFileSize(long size) { 1750 if(size <= 0) return "0"; 1751 final String[] units = new String[] { "Bi", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; 1752 int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); 1753 return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 1754 } 1755 1756 /** 1757 * At times when the charset is not the default usual one the dump will have more unprintable characters than printable. 1758 * The charset will allow printing of more printable character. Usually when your data is in EBCDIC format you will run into this. 1759 * 1760 * The standard hexdump that exists would print a byte array of F0F1F2 as 1761 * F0 F1 F2 ... 1762 * 1763 * This hexdump, if the Charset.forName("IBM1047") is passedin as charset will print 1764 * F0 F1 F2 | 123 1765 * 1766 * 1767 * @param array 1768 * the array that needs to be dumped. 1769 * @param offset 1770 * From where the data needs to be dumped. 1771 * @param length 1772 * The number of byte that ned to be dumped. 1773 * @param charSet 1774 * The Charset encoding the array is i. 1775 * @return The hexdump string. 1776 */ 1777 public static String hexDump(byte[] array, int offset, int length, Charset charSet) { 1778 // https://gist.github.com/jen20/906db194bd97c14d91df 1779 final int width = 16; 1780 1781 StringBuilder builder = new StringBuilder(); 1782 1783 for (int rowOffset = offset; rowOffset < offset + length; rowOffset += width) { 1784 builder.append(String.format("%06d: ", rowOffset)); 1785 1786 for (int index = 0; index < width; index++) { 1787 if (rowOffset + index < array.length) { 1788 builder.append(String.format("%02x ", array[rowOffset + index]).toUpperCase()); 1789 } 1790 else { 1791 builder.append(" "); 1792 } 1793 } 1794 1795 if (rowOffset < array.length) { 1796 int asciiWidth = Math.min(width, array.length - rowOffset); 1797 builder.append(" | "); 1798 builder.append(new String(array, rowOffset, asciiWidth, charSet).replaceAll("\r\n", " ") 1799 .replaceAll("\n", " ")); 1800 } 1801 1802 builder.append(String.format("%n")); 1803 } 1804 1805 return builder.toString(); 1806 } 1807 1808 /** 1809 * Decodes a hex dump string (as produced by hexdump) back into a byte array. 1810 * 1811 * @param s hex dump string to decode 1812 * @return decoded byte array 1813 */ 1814 public static byte[] decodeHexDump(String s) { 1815 return hex2byte( 1816 Arrays.stream(s.split("\\r\\n|[\\r\\n]")) 1817 .map(x -> 1818 x.replaceAll("^.{4} ", ""). 1819 replaceAll("\\s\\s", " "). 1820 replaceAll("(([0-9A-F][0-9A-F]\\s){1,16}).*$", "$1"). 1821 replaceAll("\\s", "") 1822 ).collect(Collectors.joining())); 1823 } 1824 1825 /** 1826 * Converts a string[] or multiple strings into one char-delimited String. 1827 * 1828 * Takes care of escaping char using a backlash 1829 * 1830 * NOTE: for backward compatibility, an empty String returns a zero-length array 1831 * 1832 * @see ISOUtil#charDecode(char, String) 1833 * @param delimiter char used to delimit 1834 * @param ss string array to be char encoded 1835 * @return char encoded string 1836 */ 1837 public static String charEncode (char delimiter, String... ss) { 1838 StringJoiner sj = new StringJoiner(String.valueOf(delimiter)); 1839 for (String s : ss) { 1840 if (s == null || s.length() == 0) { 1841 sj.add(""); 1842 continue; 1843 } 1844 StringBuilder sb = new StringBuilder(); 1845 for (char c : s.toCharArray()) { 1846 if (c == delimiter || c == '\\') { 1847 sb.append("\\"); 1848 } 1849 sb.append(c); 1850 } 1851 sj.add(sb.toString()); 1852 } 1853 return sj.toString(); 1854 } 1855 1856 /** 1857 * Decodes a char encoded String as encoded by charEncode 1858 * @see ISOUtil#charEncode(char, String[]) 1859 * @param delimiter char used to delimit 1860 * @param s the char encoded String 1861 * @return String[] 1862 */ 1863 public static String[] charDecode (char delimiter, String s) { 1864 if (s == null) { 1865 return null; 1866 } 1867 if (s.length() == 0) { 1868 return new String[0]; 1869 } 1870 List<String> l = new ArrayList<>(); 1871 StringBuilder sb = new StringBuilder(); 1872 boolean escaped = false; 1873 for (int i = 0; i < s.length(); i++) { 1874 char c = s.charAt(i); 1875 if (!escaped) { 1876 if (c == '\\'){ 1877 escaped = true; 1878 continue; 1879 } 1880 if (c == delimiter){ 1881 l.add(sb.toString()); 1882 sb = new StringBuilder(); 1883 continue; 1884 } 1885 } 1886 sb.append(c); 1887 escaped = false; 1888 } 1889 l.add(sb.toString()); 1890 return l.toArray(new String[l.size()]); 1891 } 1892 1893 /** 1894 * Decodes a char encoded String returning element in position i 1895 * @param s comma encoded string 1896 * @param delimiter char used to delimit 1897 * @param i position (starts at 0) 1898 * @return element in position i of comma encoded string, or empty 1899 */ 1900 public static String charDecode(String s, char delimiter, int i) { 1901 if (s == null || s.length() == 0) { 1902 return ""; 1903 } 1904 String[] split = charDecode(delimiter, s); 1905 if (split.length <= 0) { 1906 return ""; 1907 } else { 1908 return i < split.length ? split[i] : ""; 1909 } 1910 } 1911 1912 /** 1913 * Converts each character in the string to its Unicode escape sequence (\uXXXX format). 1914 * 1915 * @param input the string to convert 1916 * @return string with all characters encoded as Unicode escapes 1917 */ 1918 public static String toUnicodeString(String input) { 1919 StringBuilder sb = new StringBuilder(); 1920 for (char c : input.toCharArray()) { 1921 sb.append(String.format("\\u%04x", (int) c)); 1922 } 1923 return sb.toString(); 1924 } 1925 1926 /** 1927 * Converts a string to ASCII by replacing characters above 0x7F with a space. 1928 * 1929 * @param s the string to convert 1930 * @return ASCII-safe string 1931 */ 1932 public static String toASCII(String s) { 1933 return toSingleByte (s, 0x7F); 1934 } 1935 1936 /** 1937 * Converts a string to Latin-1 by replacing characters above 0xFF with a space. 1938 * 1939 * @param s the string to convert 1940 * @return Latin-1-safe string 1941 */ 1942 public static String toLatin(String s) { 1943 return toSingleByte (s, 0xFF); 1944 } 1945 1946 private static String toSingleByte(String s, int mask) { 1947 StringBuilder sb = new StringBuilder(); 1948 s.codePoints().forEach(cp -> { 1949 if (cp > mask) { 1950 sb.append(" "); 1951 if (cp > 0xFFFF) // multi-byte character 1952 sb.append(" "); 1953 } else 1954 sb.appendCodePoint(cp); // Append the single-byte character 1955 }); 1956 return sb.toString(); 1957 } 1958 1959}