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