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("&lt;");
934                    break;
935                case '>': 
936                    str.append("&gt;");
937                    break;
938                case '&': 
939                    str.append("&amp;");
940                    break;
941                case '"': 
942                    str.append("&quot;");
943                    break;
944                case '\'':
945                    str.append("&apos;");
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 (&#92;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}