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 org.jpos.iso.header.BaseHeader; 022import org.jpos.iso.packager.XMLPackager; 023import org.jpos.util.Loggeable; 024 025import java.io.*; 026import java.lang.ref.WeakReference; 027import java.util.*; 028 029/** 030 * implements <b>Composite</b> 031 * within a <b>Composite pattern</b> 032 * 033 * @author apr@cs.com.uy 034 * @version $Id$ 035 * @see ISOComponent 036 * @see ISOField 037 */ 038@SuppressWarnings("unchecked") 039public class ISOMsg extends ISOComponent 040 implements Cloneable, Loggeable, Externalizable 041{ 042 /** Map of field number to field value. */ 043 protected Map<Integer,Object> fields; 044 /** Highest field number currently set in this message. */ 045 protected int maxField; 046 /** The packager used to pack/unpack this message. */ 047 protected ISOPackager packager; 048 /** Dirty flags for tracking state changes. */ 049 protected boolean dirty, maxFieldDirty; 050 /** Message direction: INCOMING or OUTGOING. */ 051 protected int direction; 052 /** Optional ISO header for this message. */ 053 protected ISOHeader header; 054 /** Optional trailer bytes appended to the packed message. */ 055 protected byte[] trailer; 056 /** Field number of this message when nested inside another ISOMsg. */ 057 protected int fieldNumber = -1; 058 /** Constant indicating an incoming message direction. */ 059 public static final int INCOMING = 1; 060 /** Constant indicating an outgoing message direction. */ 061 public static final int OUTGOING = 2; 062 private static final long serialVersionUID = 4306251831901413975L; 063 private WeakReference sourceRef; 064 065 /** 066 * Creates an ISOMsg 067 */ 068 public ISOMsg () { 069 fields = new TreeMap<>(); 070 maxField = -1; 071 dirty = true; 072 maxFieldDirty=true; 073 direction = 0; 074 header = null; 075 trailer = null; 076 } 077 /** 078 * Creates a nested ISOMsg 079 * @param fieldNumber (in the outer ISOMsg) of this nested message 080 */ 081 public ISOMsg (int fieldNumber) { 082 this(); 083 setFieldNumber (fieldNumber); 084 } 085 /** 086 * changes this Component field number<br> 087 * Use with care, this method does not change 088 * any reference held by a Composite. 089 * @param fieldNumber new field number 090 */ 091 @Override 092 public void setFieldNumber (int fieldNumber) { 093 this.fieldNumber = fieldNumber; 094 } 095 /** 096 * Creates an ISOMsg with given mti 097 * @param mti Msg's MTI 098 */ 099 @SuppressWarnings("PMD.EmptyCatchBlock") 100 public ISOMsg (String mti) { 101 this(); 102 try { 103 setMTI (mti); 104 } catch (ISOException ignored) { 105 // Should never happen as this is not an inner message 106 } 107 } 108 /** 109 * Sets the direction information related to this message 110 * @param direction can be either ISOMsg.INCOMING or ISOMsg.OUTGOING 111 */ 112 public void setDirection(int direction) { 113 this.direction = direction; 114 } 115 /** 116 * Sets an optional message header image 117 * @param b header image 118 */ 119 public void setHeader(byte[] b) { 120 header = new BaseHeader (b); 121 } 122 123 /** 124 * Sets the ISO header for this message. 125 * @param header the ISOHeader to set on this message 126 */ 127 public void setHeader (ISOHeader header) { 128 this.header = header; 129 } 130 /** 131 * get optional message header image 132 * @return message header image (may be null) 133 */ 134 public byte[] getHeader() { 135 return header != null ? header.pack() : null; 136 } 137 138 /** 139 * Sets optional trailer data. 140 * <p> 141 * Note: The trailer data requires a customised channel that explicitly handles the trailer data from the ISOMsg. 142 * 143 * @param trailer The trailer data. 144 * @see BaseChannel#getMessageTrailer(ISOMsg) 145 * @see BaseChannel#sendMessageTrailer(ISOMsg, byte[]) 146 */ 147 public void setTrailer(byte[] trailer) { 148 this.trailer = trailer; 149 } 150 151 /** 152 * Get optional trailer image. 153 * 154 * @return message trailer image (may be null) 155 */ 156 public byte[] getTrailer() { 157 return this.trailer; 158 } 159 160 /** 161 * Return this messages ISOHeader 162 * @return header associated with this ISOMsg, can be null 163 */ 164 public ISOHeader getISOHeader() { 165 return header; 166 } 167 /** 168 * Returns the message direction. 169 * @return the direction ({@code ISOMsg.INCOMING} or {@code ISOMsg.OUTGOING}) 170 * @see ISOChannel 171 */ 172 public int getDirection() { 173 return direction; 174 } 175 /** 176 * Returns true if this message was received from a channel. 177 * @return true if this is an incoming message 178 * @see ISOChannel 179 */ 180 public boolean isIncoming() { 181 return direction == INCOMING; 182 } 183 /** 184 * Returns true if this message is to be sent via a channel. 185 * @return true if this is an outgoing message 186 * @see ISOChannel 187 */ 188 public boolean isOutgoing() { 189 return direction == OUTGOING; 190 } 191 /** 192 * Returns the highest field number present in this message. 193 * @return the max field number 194 */ 195 @Override 196 public int getMaxField() { 197 if (maxFieldDirty) 198 recalcMaxField(); 199 return maxField; 200 } 201 private void recalcMaxField() { 202 maxField = 0; 203 for (Object obj : fields.keySet()) { 204 if (obj instanceof Integer) 205 maxField = Math.max(maxField, ((Integer) obj).intValue()); 206 } 207 maxFieldDirty = false; 208 } 209 /** 210 * Sets the packager used to pack/unpack this message. 211 * @param p - a peer packager 212 */ 213 public void setPackager (ISOPackager p) { 214 packager = p; 215 if (packager == null) { 216 for (Object o : fields.values()) { 217 if (o instanceof ISOMsg) 218 ((ISOMsg) o).setPackager(null); 219 } 220 } 221 } 222 /** 223 * Returns the packager associated with this message. 224 * @return the peer packager 225 */ 226 public ISOPackager getPackager () { 227 return packager; 228 } 229 /** 230 * Set a field within this message 231 * @param c - a component 232 */ 233 public void set (ISOComponent c) throws ISOException { 234 if (c != null) { 235 Integer i = (Integer) c.getKey(); 236 fields.put (i, c); 237 if (i > maxField) 238 maxField = i; 239 dirty = true; 240 } 241 } 242 243 /** 244 * Creates an ISOField associated with fldno within this ISOMsg. 245 * 246 * @param fldno field number 247 * @param value field value 248 */ 249 public void set(int fldno, String value) { 250 if (value == null) { 251 unset(fldno); 252 return; 253 } 254 255 try { 256 if (!(packager instanceof ISOBasePackager)) { 257 // No packager is available, we can't tell what the field 258 // might be, so treat as a String! 259 set(new ISOField(fldno, value)); 260 } 261 else { 262 // This ISOMsg has a packager, so use it 263 Object obj = ((ISOBasePackager) packager).getFieldPackager(fldno); 264 if (obj instanceof ISOBinaryFieldPackager) { 265 set(new ISOBinaryField(fldno, ISOUtil.hex2byte(value))); 266 } else { 267 set(new ISOField(fldno, value)); 268 } 269 } 270 } catch (ISOException ex) {}; //NOPMD: never happens for the given arguments of set methods 271 } 272 273 /** 274 * Sets a top-level character field and returns this message for fluent chaining. 275 * 276 * @param fldno field number 277 * @param value field value 278 * @return this message 279 */ 280 public ISOMsg with(int fldno, String value) { 281 set(fldno, value); 282 return this; 283 } 284 285 /** 286 * Creates an ISOField associated with fldno within this ISOMsg. 287 * 288 * @param fpath dot-separated field path (i.e. 63.2) 289 * @param value field value 290 */ 291 public void set(String fpath, String value) { 292 try { 293 if (setDatasetPath(fpath, value)) 294 return; 295 } catch (ISOException e) { 296 throw new IllegalArgumentException(e.getMessage(), e); 297 } 298 StringTokenizer st = new StringTokenizer (fpath, "."); 299 ISOMsg m = this; 300 for (;;) { 301 int fldno = parseInt(st.nextToken()); 302 if (st.hasMoreTokens()) { 303 Object obj = m.getValue(fldno); 304 if (obj instanceof ISOMsg) 305 m = (ISOMsg) obj; 306 else 307 /** 308 * we need to go deeper, however, if the value == null then 309 * there is nothing to do (unset) at the lower levels, so break now and save some processing. 310 */ 311 if (value == null) { 312 break; 313 } else { 314 try { 315 // We have a value to set, so adding a level to hold it is sensible. 316 m.set(m = new ISOMsg (fldno)); 317 } catch (ISOException ex) {} //NOPMD: never happens for the given arguments of set methods 318 } 319 } else { 320 m.set(fldno, value); 321 break; 322 } 323 } 324 } 325 326 /** 327 * Sets a character field by path and returns this message for fluent chaining. 328 * 329 * @param fpath dot-separated field path 330 * @param value field value 331 * @return this message 332 */ 333 public ISOMsg with(String fpath, String value) { 334 set(fpath, value); 335 return this; 336 } 337 338 /** 339 * Creates an ISOField associated with fldno within this ISOMsg 340 * @param fpath dot-separated field path (i.e. 63.2) 341 * @param c component 342 * @throws ISOException on error 343 */ 344 public void set(String fpath, ISOComponent c) throws ISOException { 345 if (setDatasetPath(fpath, c)) 346 return; 347 StringTokenizer st = new StringTokenizer (fpath, "."); 348 ISOMsg m = this; 349 for (;;) { 350 int fldno = parseInt(st.nextToken()); 351 if (st.hasMoreTokens()) { 352 Object obj = m.getValue(fldno); 353 if (obj instanceof ISOMsg) 354 m = (ISOMsg) obj; 355 else 356 /* 357 * we need to go deeper, however, if the value == null then 358 * there is nothing to do (unset) at the lower levels, so break now and save some processing. 359 */ 360 if (c == null) { 361 break; 362 } else { 363 // We have a value to set, so adding a level to hold it is sensible. 364 m.set(m = new ISOMsg(fldno)); 365 } 366 } else { 367 if (c != null) 368 c.setFieldNumber(fldno); 369 m.set(c); 370 break; 371 } 372 } 373 } 374 375 /** 376 * Sets a component by path and returns this message for fluent chaining. 377 * 378 * @param fpath dot-separated field path 379 * @param c component to store 380 * @return this message 381 * @throws ISOException on path or component errors 382 */ 383 public ISOMsg with(String fpath, ISOComponent c) throws ISOException { 384 set(fpath, c); 385 return this; 386 } 387 /** 388 * Creates an ISOField associated with fldno within this ISOMsg. 389 * 390 * @param fpath dot-separated field path (i.e. 63.2) 391 * @param value binary field value 392 */ 393 public void set(String fpath, byte[] value) { 394 try { 395 if (setDatasetPath(fpath, value)) 396 return; 397 } catch (ISOException e) { 398 throw new IllegalArgumentException(e.getMessage(), e); 399 } 400 StringTokenizer st = new StringTokenizer (fpath, "."); 401 ISOMsg m = this; 402 for (;;) { 403 int fldno = parseInt(st.nextToken()); 404 if (st.hasMoreTokens()) { 405 Object obj = m.getValue(fldno); 406 if (obj instanceof ISOMsg) 407 m = (ISOMsg) obj; 408 else 409 try { 410 m.set(m = new ISOMsg (fldno)); 411 } catch (ISOException ex) {} //NOPMD: never happens for the given arguments of set methods 412 } else { 413 m.set(fldno, value); 414 break; 415 } 416 } 417 } 418 419 /** 420 * Sets a top-level binary field and returns this message for fluent chaining. 421 * 422 * @param fldno field number 423 * @param value field value 424 * @return this message 425 */ 426 public ISOMsg with(int fldno, byte[] value) { 427 set(fldno, value); 428 return this; 429 } 430 431 /** 432 * Sets a binary field by path and returns this message for fluent chaining. 433 * 434 * @param fpath dot-separated field path 435 * @param value field value 436 * @return this message 437 */ 438 public ISOMsg with(String fpath, byte[] value) { 439 set(fpath, value); 440 return this; 441 } 442 443 /** 444 * Creates an ISOBinaryField associated with fldno within this ISOMsg. 445 * 446 * @param fldno field number 447 * @param value field value 448 */ 449 public void set(int fldno, byte[] value) { 450 if (value == null) { 451 unset(fldno); 452 return; 453 } 454 455 try { 456 set(new ISOBinaryField(fldno, value)); 457 } catch (ISOException ex) {}; //NOPMD: never happens for the given arguments of set methods 458 } 459 460 461 /** 462 * Unset a field if it exists, otherwise ignore. 463 * @param fldno - the field number 464 */ 465 @Override 466 public void unset (int fldno) { 467 if (fields.remove (fldno) != null) 468 dirty = maxFieldDirty = true; 469 } 470 471 /** 472 * Unsets several fields at once 473 * @param flds - array of fields to be unset from this ISOMsg 474 */ 475 public void unset (int ... flds) { 476 for (int fld : flds) 477 unset(fld); 478 } 479 480 /** 481 * Unset a field referenced by a fpath if it exists, otherwise ignore. 482 * 483 * @param fpath dot-separated field path (i.e. 63.2) 484 */ 485 public void unset(String fpath) { 486 try { 487 if (unsetDatasetPath(fpath)) 488 return; 489 } catch (ISOException e) { 490 throw new IllegalArgumentException(e.getMessage(), e); 491 } 492 StringTokenizer st = new StringTokenizer (fpath, "."); 493 ISOMsg m = this; 494 ISOMsg lastm = m; 495 int fldno = -1 ; 496 int lastfldno ; 497 for (;;) { 498 lastfldno = fldno; 499 fldno = parseInt(st.nextToken()); 500 if (st.hasMoreTokens()) { 501 Object obj = m.getValue(fldno); 502 if (obj instanceof ISOMsg) { 503 lastm = m; 504 m = (ISOMsg) obj; 505 } 506 else { 507 // No real way of unset further subfield, exit. 508 break; 509 } 510 } else { 511 m.unset(fldno); 512 if (!m.hasFields() && lastfldno != -1) { 513 lastm.unset(lastfldno); 514 } 515 break; 516 } 517 } 518 } 519 520 /** 521 * Unset a a set of fields referenced by fpaths if any ot them exist, otherwise ignore. 522 * 523 * @param fpaths dot-separated field paths (i.e. 63.2) 524 */ 525 public void unset(String ... fpaths) { 526 for (String fpath : fpaths) { 527 unset(fpath); 528 } 529 } 530 531 /** 532 * Unsets one or more top-level fields and returns this message for fluent chaining. 533 * 534 * @param flds field numbers to remove 535 * @return this message 536 */ 537 public ISOMsg without(int ... flds) { 538 unset(flds); 539 return this; 540 } 541 542 /** 543 * Unsets one or more field paths, including nested composites and dataset elements, 544 * and returns this message for fluent chaining. 545 * 546 * @param fpaths field paths to remove 547 * @return this message 548 */ 549 public ISOMsg without(String ... fpaths) { 550 unset(fpaths); 551 return this; 552 } 553 /** 554 * In order to interchange <b>Composites</b> and <b>Leafs</b> we use 555 * getComposite(). A <b>Composite component</b> returns itself and 556 * a Leaf returns null. 557 * 558 * @return ISOComponent 559 */ 560 @Override 561 public ISOComponent getComposite() { 562 return this; 563 } 564 /** 565 * setup BitMap 566 * @exception ISOException on error 567 */ 568 public void recalcBitMap () throws ISOException { 569 if (!dirty) 570 return; 571 572 int mf = Math.min (getMaxField(), 192); 573 574 BitSet bmap = new BitSet (mf+62 >>6 <<6); 575 for (int i=1; i<=mf; i++) 576 if (fields.get (i) != null) 577 bmap.set (i); 578 set (new ISOBitMap (-1, bmap)); 579 dirty = false; 580 } 581 /** 582 * clone fields 583 * @return copy of fields 584 */ 585 @Override 586 public Map getChildren() { 587 return (Map) ((TreeMap)fields).clone(); 588 } 589 /** 590 * Packs this message using the configured packager. 591 * @return the packed message 592 * @exception ISOException on packing error 593 */ 594 @Override 595 public byte[] pack() throws ISOException { 596 synchronized (this) { 597 recalcBitMap(); 598 return packager.pack(this); 599 } 600 } 601 /** 602 * Unpacks the raw byte array into this message. 603 * @param b - raw message 604 * @return consumed bytes 605 * @exception ISOException on unpacking error 606 */ 607 @Override 608 public int unpack(byte[] b) throws ISOException { 609 synchronized (this) { 610 return packager.unpack(this, b); 611 } 612 } 613 /** {@inheritDoc} 614 * @throws IOException on I/O failure 615 * @throws ISOException on unpacking error 616 */ 617 @Override 618 public void unpack (InputStream in) throws IOException, ISOException { 619 synchronized (this) { 620 packager.unpack(this, in); 621 } 622 } 623 /** 624 * dump the message to a PrintStream. The output is sorta 625 * XML, intended to be easily parsed. 626 * <br> 627 * Each component is responsible for its own dump function, 628 * ISOMsg just calls dump on every valid field. 629 * @param p - print stream 630 * @param indent - optional indent string 631 */ 632 @Override 633 public void dump (PrintStream p, String indent) { 634 ISOComponent c; 635 p.print (indent + "<" + XMLPackager.ISOMSG_TAG); 636 switch (direction) { 637 case INCOMING: 638 p.print (" direction=\"incoming\""); 639 break; 640 case OUTGOING: 641 p.print (" direction=\"outgoing\""); 642 break; 643 } 644 if (fieldNumber != -1) 645 p.print (" "+XMLPackager.ID_ATTR +"=\""+fieldNumber +"\""); 646 p.println (">"); 647 String newIndent = indent + " "; 648 if (getPackager() != null) { 649 p.println ( 650 newIndent 651 + "<!-- " + getPackager().getDescription() + " -->" 652 ); 653 } 654 if (header instanceof Loggeable) 655 ((Loggeable) header).dump (p, newIndent); 656 657 for (int i : fields.keySet()) { 658 //If you want the bitmap dumped in the log, change the condition from (i >= 0) to (i >= -1). 659 if (i >= 0) { 660 if ((c = (ISOComponent) fields.get(i)) != null) 661 c.dump(p, newIndent); 662 } 663 } 664 665 p.println (indent + "</" + XMLPackager.ISOMSG_TAG+">"); 666 } 667 /** 668 * get the component associated with the given field number 669 * @param fldno the Field Number 670 * @return the Component 671 */ 672 public ISOComponent getComponent(int fldno) { 673 return (ISOComponent) fields.get(fldno); 674 } 675 /** 676 * Return the object value associated with the given field number 677 * @param fldno the Field Number 678 * @return the field Object 679 */ 680 public Object getValue(int fldno) { 681 ISOComponent c = getComponent(fldno); 682 try { 683 return c != null ? c.getValue() : null; 684 } catch (ISOException ex) { 685 return null; //never happens for the given arguments of getValue method 686 } 687 } 688 /** 689 * Return the object value associated with the given field path 690 * @param fpath field path 691 * @return the field Object (may be null) 692 * @throws ISOException on error 693 */ 694 public Object getValue (String fpath) throws ISOException { 695 StringTokenizer st = new StringTokenizer (fpath, "."); 696 ISOMsg m = this; 697 Object obj; 698 for (;;) { 699 int fldno = parseInt(st.nextToken()); 700 obj = m.getValue (fldno); 701 if (obj==null){ 702 // The user will always get a null value for an incorrect path or path not present in the message 703 // no point having the ISOException thrown for fields that were not received. 704 break; 705 } 706 if (st.hasMoreTokens()) { 707 if (obj instanceof ISOMsg) { 708 m = (ISOMsg) obj; 709 } 710 else 711 throw new ISOException ("Invalid path '" + fpath + "'"); 712 } else 713 break; 714 } 715 return obj; 716 } 717 /** 718 * get the component associated with the given field number 719 * @param fpath field path 720 * @return the Component 721 * @throws ISOException on error 722 */ 723 public ISOComponent getComponent (String fpath) throws ISOException { 724 StringTokenizer st = new StringTokenizer (fpath, "."); 725 ISOMsg m = this; 726 ISOComponent obj; 727 for (;;) { 728 int fldno = parseInt(st.nextToken()); 729 obj = m.getComponent(fldno); 730 if (st.hasMoreTokens()) { 731 if (obj instanceof ISOMsg) { 732 m = (ISOMsg) obj; 733 } 734 else 735 break; // 'Quick' exit if hierarchy is not present. 736 } else 737 break; 738 } 739 return obj; 740 } 741 /** 742 * Return the String value associated with the given ISOField number 743 * @param fldno the Field Number 744 * @return field's String value 745 */ 746 public String getString (int fldno) { 747 String s = null; 748 if (hasField (fldno)) { 749 Object obj = getValue(fldno); 750 if (obj instanceof String) 751 s = (String) obj; 752 else if (obj instanceof byte[]) 753 s = ISOUtil.hexString((byte[]) obj); 754 } 755 return s; 756 } 757 /** 758 * Return the String value associated with the given field path 759 * @param fpath field path 760 * @return field's String value (may be null) 761 */ 762 public String getString (String fpath) { 763 String s = null; 764 try { 765 Object obj = getValue(fpath); 766 if (obj instanceof String) 767 s = (String) obj; 768 else if (obj instanceof byte[]) 769 s = ISOUtil.hexString ((byte[]) obj); 770 } catch (ISOException e) { 771 return null; 772 } 773 return s; 774 } 775 /** 776 * Return the byte[] value associated with the given ISOField number 777 * @param fldno the Field Number 778 * @return field's byte[] value or null if ISOException or UnsupportedEncodingException happens 779 */ 780 public byte[] getBytes (int fldno) { 781 byte[] b = null; 782 if (hasField (fldno)) { 783 Object obj = getValue(fldno); 784 if (obj instanceof String) 785 b = ((String) obj).getBytes(ISOUtil.CHARSET); 786 else if (obj instanceof byte[]) 787 b = (byte[]) obj; 788 } 789 return b; 790 } 791 /** 792 * Return the String value associated with the given field path 793 * @param fpath field path 794 * @return field's byte[] value (may be null) 795 */ 796 public byte[] getBytes (String fpath) { 797 byte[] b = null; 798 try { 799 Object obj = getValue(fpath); 800 if (obj instanceof String) 801 b = ((String) obj).getBytes(ISOUtil.CHARSET); 802 else if (obj instanceof byte[]) 803 b = (byte[]) obj; 804 } catch (ISOException ignored) { 805 return null; 806 } 807 return b; 808 } 809 /** 810 * Check if a given field is present 811 * @param fldno the Field Number 812 * @return boolean indicating the existence of the field 813 */ 814 public boolean hasField(int fldno) { 815 return fields.get(fldno) != null; 816 } 817 /** 818 * Check if all fields are present 819 * @param fields an array of fields to check for presence 820 * @return true if all fields are present 821 */ 822 public boolean hasFields (int[] fields) { 823 for (int field : fields) 824 if (!hasField(field)) 825 return false; 826 return true; 827 } 828 829 /** 830 * Check if the message has any of these fields 831 * @param fields an array of fields to check for presence 832 * @return true if at least one field is present 833 */ 834 public boolean hasAny (int[] fields) { 835 for (int field : fields) 836 if (hasField(field)) 837 return true; 838 return false; 839 } 840 /** 841 * Check if the message has any of these fields 842 * @param fields to check for presence 843 * @return true if at least one field is present 844 */ 845 public boolean hasAny (String... fields) { 846 for (String field : fields) 847 if (hasField (field)) 848 return true; 849 return false; 850 } 851 852 /** 853 * Check if a field indicated by a fpath is present 854 * @param fpath dot-separated field path (i.e. 63.2) 855 * @return true if field present 856 */ 857 public boolean hasField (String fpath) { 858 StringTokenizer st = new StringTokenizer (fpath, "."); 859 ISOMsg m = this; 860 for (;;) { 861 int fldno = parseInt(st.nextToken()); 862 if (st.hasMoreTokens()) { 863 Object obj = m.getValue(fldno); 864 if (obj instanceof ISOMsg) { 865 m = (ISOMsg) obj; 866 } 867 else { 868 // No real way of checking for further subfields, return false, perhaps should be ISOException? 869 return false; 870 } 871 } else { 872 return m.hasField(fldno); 873 } 874 } 875 } 876 /** 877 * Returns true if this message has at least one field set. 878 * @return true if at least one field is present 879 */ 880 public boolean hasFields () { 881 return !fields.isEmpty(); 882 } 883 /** 884 * Don't call setValue on an ISOMsg. You'll sure get 885 * an ISOException. It's intended to be used on Leafs 886 * @param obj value to set (not supported on ISOMsg) 887 * @throws org.jpos.iso.ISOException always 888 * @see ISOField 889 * @see ISOException 890 */ 891 @Override 892 public void setValue(Object obj) throws ISOException { 893 throw new ISOException ("setValue N/A in ISOMsg"); 894 } 895 896 @Override 897 public Object clone() { 898 try { 899 ISOMsg m = (ISOMsg) super.clone(); 900 m.fields = (TreeMap) ((TreeMap) fields).clone(); 901 if (header != null) 902 m.header = (ISOHeader) header.clone(); 903 if (trailer != null) 904 m.trailer = trailer.clone(); 905 for (Integer k : fields.keySet()) { 906 ISOComponent c = (ISOComponent) m.fields.get(k); 907 if (c instanceof ISOMsg || c instanceof ISODatasetField) 908 m.fields.put(k, cloneComponent(c)); 909 } 910 return m; 911 } catch (CloneNotSupportedException e) { 912 throw new InternalError(); 913 } catch (ISOException e) { 914 throw new IllegalStateException(e); 915 } 916 } 917 918 /** 919 * Partially clone an ISOMsg 920 * @param fields int array of fields to go 921 * @return new ISOMsg instance 922 */ 923 @SuppressWarnings("PMD.EmptyCatchBlock") 924 public Object clone(int ... fields) { 925 try { 926 ISOMsg m = (ISOMsg) super.clone(); 927 m.fields = new TreeMap(); 928 for (int field : fields) { 929 if (hasField(field)) { 930 try { 931 ISOComponent c = getComponent(field); 932 if (c instanceof ISOMsg || c instanceof ISODatasetField) { 933 m.set(cloneComponent(c)); 934 } else { 935 m.set(c); 936 } 937 } catch (ISOException ignored) { 938 // should never happen 939 } 940 } 941 } 942 return m; 943 } catch (CloneNotSupportedException e) { 944 throw new InternalError(); 945 } 946 } 947 948 /** 949 * Partially clone an ISOMsg by field paths 950 * @param fpaths string array of field paths to copy 951 * @return new ISOMsg instance 952 */ 953 public ISOMsg clone(String ... fpaths) { 954 try { 955 ISOMsg m = (ISOMsg) super.clone(); 956 m.fields = new TreeMap(); 957 for (String fpath : fpaths) { 958 try { 959 ISOComponent component = getComponent(fpath); 960 if (component instanceof ISOMsg || component instanceof ISODatasetField) { 961 m.set(fpath, cloneComponent(component)); 962 } else if (component != null) { 963 m.set(fpath, component); 964 } 965 } catch (ISOException ignored) { 966 //should never happen 967 } 968 } 969 return m; 970 } catch (CloneNotSupportedException e) { 971 throw new InternalError(); 972 } 973 } 974 975 /** 976 * Merges the content of the specified ISOMsg into this ISOMsg instance. 977 * It iterates over the fields of the input message and, for each field that is present, 978 * sets the corresponding component in this message to the value from the input message. 979 * This operation includes all fields that are present in the input message, but does not remove 980 * any existing fields from this message unless they are explicitly overwritten by the input message. 981 * <p> 982 * If the input message contains a header (non-null), this method also clones the header 983 * and sets it as the header of this message. 984 * 985 * @param m The ISOMsg to merge into this ISOMsg. It must not be {@code null}. 986 * The method does nothing if {@code m} is {@code null}. 987 * @param mergeHeader A boolean flag indicating whether to merge the header of the input message into this message. 988 * 989 */ 990 @SuppressWarnings("PMD.EmptyCatchBlock") 991 public void merge (ISOMsg m, boolean mergeHeader) { 992 for (int i : m.fields.keySet()) { 993 try { 994 if (i >= 0 && m.hasField(i)) 995 set(m.getComponent(i)); 996 } catch (ISOException ignored) { 997 // should never happen 998 } 999 } 1000 if (mergeHeader && m.header != null) 1001 header = (ISOHeader) m.header.clone(); 1002 } 1003 1004 /** 1005 * Merges the content of the specified ISOMsg into this ISOMsg instance, excluding the header. 1006 * This method is a convenience wrapper around {@link #merge(ISOMsg, boolean)} with the {@code mergeHeader} 1007 * parameter set to {@code false} for backward compatibility, indicating that the header of the input message 1008 * will not be merged. 1009 * @param m the ISOMsg to merge into this message 1010 */ 1011 public void merge (ISOMsg m) { 1012 merge (m, false); 1013 } 1014 1015 /** 1016 * @return a string suitable for a log 1017 */ 1018 @Override 1019 public String toString() { 1020 StringBuilder s = new StringBuilder(); 1021 if (isIncoming()) 1022 s.append(" In: "); 1023 else if (isOutgoing()) 1024 s.append("Out: "); 1025 else 1026 s.append(" "); 1027 1028 s.append(getString(0)); 1029 if (hasField(11)) { 1030 s.append(' '); 1031 s.append(getString(11)); 1032 } 1033 if (hasField(41)) { 1034 s.append(' '); 1035 s.append(getString(41)); 1036 } 1037 return s.toString(); 1038 } 1039 @Override 1040 public Object getKey() throws ISOException { 1041 if (fieldNumber != -1) 1042 return fieldNumber; 1043 throw new ISOException ("This is not a subField"); 1044 } 1045 /** Returns this message itself as its value. 1046 * @return this ISOMsg 1047 */ 1048 @Override 1049 public Object getValue() { 1050 return this; 1051 } 1052 /** 1053 * Returns true if this is an inner (sub-) message. 1054 * @return true on inner messages 1055 */ 1056 public boolean isInner() { 1057 return fieldNumber > -1; 1058 } 1059 /** 1060 * Sets the message type indicator. 1061 * @param mti new MTI 1062 * @exception ISOException if message is inner message 1063 */ 1064 public void setMTI (String mti) throws ISOException { 1065 if (isInner()) 1066 throw new ISOException ("can't setMTI on inner message"); 1067 set (new ISOField (0, mti)); 1068 } 1069 /** 1070 * moves a field (renumber) 1071 * @param oldFieldNumber old field number 1072 * @param newFieldNumber new field number 1073 * @throws ISOException on error 1074 */ 1075 public void move (int oldFieldNumber, int newFieldNumber) 1076 throws ISOException 1077 { 1078 ISOComponent c = getComponent (oldFieldNumber); 1079 unset (oldFieldNumber); 1080 if (c != null) { 1081 c.setFieldNumber (newFieldNumber); 1082 set (c); 1083 } else 1084 unset (newFieldNumber); 1085 } 1086 1087 @Override 1088 public int getFieldNumber () { 1089 return fieldNumber; 1090 } 1091 1092 /** 1093 * Returns true if this message has an MTI field (field 0) set. 1094 * @return true if MTI is present 1095 * @exception ISOException if this is an inner message 1096 */ 1097 public boolean hasMTI() throws ISOException { 1098 if (isInner()) 1099 throw new ISOException ("can't hasMTI on inner message"); 1100 else 1101 return hasField(0); 1102 } 1103 /** 1104 * Returns the message type indicator. 1105 * @return current MTI 1106 * @exception ISOException on inner message or MTI not set 1107 */ 1108 public String getMTI() throws ISOException { 1109 if (isInner()) 1110 throw new ISOException ("can't getMTI on inner message"); 1111 else if (!hasField(0)) 1112 throw new ISOException ("MTI not available"); 1113 return (String) getValue(0); 1114 } 1115 1116 /** 1117 * Returns true if the MTI suggests this is a request message. 1118 * @return true if message "seems to be" a request 1119 * @exception ISOException on MTI not set 1120 */ 1121 public boolean isRequest() throws ISOException { 1122 return Character.getNumericValue(getMTI().charAt (2))%2 == 0; 1123 } 1124 /** 1125 * Returns true if the MTI suggests this is a response message. 1126 * @return true if message "seems not to be" a request 1127 * @exception ISOException on MTI not set 1128 */ 1129 public boolean isResponse() throws ISOException { 1130 return !isRequest(); 1131 } 1132 /** 1133 * Returns true if this is an authorization message (MTI second digit = 1). 1134 * @return true if message class is "authorization" 1135 * @exception ISOException on MTI not set 1136 */ 1137 public boolean isAuthorization() throws ISOException { 1138 return hasMTI() && getMTI().charAt(1) == '1'; 1139 } 1140 /** 1141 * Returns true if this is a financial message (MTI second digit = 2). 1142 * @return true if message class is "financial" 1143 * @exception ISOException on MTI not set 1144 */ 1145 public boolean isFinancial() throws ISOException { 1146 return hasMTI() && getMTI().charAt(1) == '2'; 1147 } 1148 /** 1149 * Returns true if this is a file action message (MTI second digit = 3). 1150 * @return true if message class is "file action" 1151 * @exception ISOException on MTI not set 1152 */ 1153 public boolean isFileAction() throws ISOException { 1154 return hasMTI() && getMTI().charAt(1) == '3'; 1155 } 1156 /** 1157 * Returns true if this is a reversal message (MTI second digit = 4, last digit 0 or 1). 1158 * @return true if message class is "reversal" 1159 * @exception ISOException on MTI not set 1160 */ 1161 public boolean isReversal() throws ISOException { 1162 return hasMTI() && getMTI().charAt(1) == '4' && (getMTI().charAt(3) == '0' || getMTI().charAt(3) == '1'); 1163 } 1164 /** 1165 * Returns true if this is a chargeback message (MTI second digit = 4, last digit 2 or 3). 1166 * @return true if message class is "chargeback" 1167 * @exception ISOException on MTI not set 1168 */ 1169 public boolean isChargeback() throws ISOException { 1170 return hasMTI() && getMTI().charAt(1) == '4' && (getMTI().charAt(3) == '2' || getMTI().charAt(3) == '3'); 1171 } 1172 /** 1173 * Returns true if this is a reconciliation message (MTI second digit = 5). 1174 * @return true if message class is "reconciliation" 1175 * @exception ISOException on MTI not set 1176 */ 1177 public boolean isReconciliation() throws ISOException { 1178 return hasMTI() && getMTI().charAt(1) == '5'; 1179 } 1180 /** 1181 * Returns true if this is an administrative message (MTI second digit = 6). 1182 * @return true if message class is "administrative" 1183 * @exception ISOException on MTI not set 1184 */ 1185 public boolean isAdministrative() throws ISOException { 1186 return hasMTI() && getMTI().charAt(1) == '6'; 1187 } 1188 /** 1189 * Returns true if this is a fee collection message (MTI second digit = 7). 1190 * @return true if message class is "fee collection" 1191 * @exception ISOException on MTI not set 1192 */ 1193 public boolean isFeeCollection() throws ISOException { 1194 return hasMTI() && getMTI().charAt(1) == '7'; 1195 } 1196 /** 1197 * Returns true if this is a network management message (MTI second digit = 8). 1198 * @return true if message class is "network management" 1199 * @exception ISOException on MTI not set 1200 */ 1201 public boolean isNetworkManagement() throws ISOException { 1202 return hasMTI() && getMTI().charAt(1) == '8'; 1203 } 1204 /** 1205 * Returns true if this is a retransmission (MTI last digit = 1). 1206 * @return true if message is a retransmission 1207 * @exception ISOException on MTI not set 1208 */ 1209 public boolean isRetransmission() throws ISOException { 1210 return getMTI().charAt(3) == '1'; 1211 } 1212 /** 1213 * sets an appropriate response MTI. 1214 * 1215 * i.e. 0100 becomes 0110<br> 1216 * i.e. 0201 becomes 0210<br> 1217 * i.e. 1201 becomes 1210<br> 1218 * @exception ISOException on MTI not set or it is not a request 1219 */ 1220 public void setResponseMTI() throws ISOException { 1221 if (!isRequest()) 1222 throw new ISOException ("not a request - can't set response MTI"); 1223 1224 String mti = getMTI(); 1225 char c1 = mti.charAt(3); 1226 char c2 = '0'; 1227 switch (c1) 1228 { 1229 case '0' : 1230 case '1' : c2='0';break; 1231 case '2' : 1232 case '3' : c2='2';break; 1233 case '4' : 1234 case '5' : c2='4';break; 1235 1236 } 1237 set (new ISOField (0, 1238 mti.substring(0,2) 1239 +(Character.getNumericValue(getMTI().charAt (2))+1) + c2 1240 ) 1241 ); 1242 } 1243 /** 1244 * sets an appropriate retransmission MTI<br> 1245 * @exception ISOException on MTI not set or it is not a request 1246 */ 1247 public void setRetransmissionMTI() throws ISOException { 1248 if (!isRequest()) 1249 throw new ISOException ("not a request"); 1250 1251 set (new ISOField (0, getMTI().substring(0,3) + "1")); 1252 } 1253 /** 1254 * Serializes the message header to the given ObjectOutput. 1255 * @param out the ObjectOutput to write to 1256 * @throws IOException on write error 1257 */ 1258 protected void writeHeader (ObjectOutput out) throws IOException { 1259 int len = header.getLength(); 1260 if (len > 0) { 1261 out.writeByte ('H'); 1262 out.writeShort (len); 1263 out.write (header.pack()); 1264 } 1265 } 1266 1267 /** 1268 * Deserializes the message header from the given ObjectInput. 1269 * @param in the ObjectInput to read from 1270 * @throws IOException on read error 1271 * @throws ClassNotFoundException if a referenced class cannot be found 1272 */ 1273 protected void readHeader (ObjectInput in) 1274 throws IOException, ClassNotFoundException 1275 { 1276 byte[] b = new byte[in.readShort()]; 1277 in.readFully (b); 1278 setHeader (b); 1279 } 1280 /** 1281 * Serializes the packager class name to the given ObjectOutput. 1282 * @param out the ObjectOutput to write to 1283 * @throws IOException on write error 1284 */ 1285 protected void writePackager(ObjectOutput out) throws IOException { 1286 out.writeByte('P'); 1287 String pclass = packager.getClass().getName(); 1288 byte[] b = pclass.getBytes(); 1289 out.writeShort(b.length); 1290 out.write(b); 1291 } 1292 /** 1293 * Deserializes the packager from the given ObjectInput. 1294 * @param in the ObjectInput to read from 1295 * @throws IOException on read error 1296 * @throws ClassNotFoundException if the packager class cannot be found 1297 */ 1298 protected void readPackager(ObjectInput in) throws IOException, 1299 ClassNotFoundException { 1300 byte[] b = new byte[in.readShort()]; 1301 in.readFully(b); 1302 try { 1303 Class mypClass = Class.forName(new String(b)); 1304 ISOPackager myp = (ISOPackager) mypClass.newInstance(); 1305 setPackager(myp); 1306 } catch (Exception e) { 1307 setPackager(null); 1308 } 1309 1310} 1311 /** 1312 * Serializes the message direction to the given ObjectOutput. 1313 * @param out the ObjectOutput to write to 1314 * @throws IOException on write error 1315 */ 1316 protected void writeDirection (ObjectOutput out) throws IOException { 1317 out.writeByte ('D'); 1318 out.writeByte (direction); 1319 } 1320 /** 1321 * Deserializes the message direction from the given ObjectInput. 1322 * @param in the ObjectInput to read from 1323 * @throws IOException on read error 1324 * @throws ClassNotFoundException if a class cannot be found 1325 */ 1326 protected void readDirection (ObjectInput in) 1327 throws IOException, ClassNotFoundException 1328 { 1329 direction = in.readByte(); 1330 } 1331 1332 @Override 1333 public void writeExternal (ObjectOutput out) throws IOException { 1334 out.writeByte (0); // reserved for future expansion (version id) 1335 out.writeShort (fieldNumber); 1336 1337 if (header != null) 1338 writeHeader (out); 1339 if (packager != null) 1340 writePackager(out); 1341 if (direction > 0) 1342 writeDirection (out); 1343 1344 // List keySet = new ArrayList (fields.keySet()); 1345 // Collections.sort (keySet); 1346 for (Object o : fields.values()) { 1347 ISOComponent c = (ISOComponent) o; 1348 if (c instanceof ISOMsg) { 1349 writeExternal(out, 'M', c); 1350 } else if (c instanceof ISOBinaryField) { 1351 writeExternal(out, 'B', c); 1352 } else if (c instanceof ISOAmount) { 1353 writeExternal(out, 'A', c); 1354 } else if (c instanceof ISOField) { 1355 writeExternal(out, 'F', c); 1356 } 1357 } 1358 out.writeByte ('E'); 1359 } 1360 1361 @Override 1362 public void readExternal (ObjectInput in) 1363 throws IOException, ClassNotFoundException 1364 { 1365 in.readByte(); // ignore version for now 1366 fieldNumber = in.readShort(); 1367 byte fieldType; 1368 ISOComponent c; 1369 try { 1370 while ((fieldType = in.readByte()) != 'E') { 1371 c = null; 1372 switch (fieldType) { 1373 case 'F': 1374 c = new ISOField (); 1375 break; 1376 case 'A': 1377 c = new ISOAmount (); 1378 break; 1379 case 'B': 1380 c = new ISOBinaryField (); 1381 break; 1382 case 'M': 1383 c = new ISOMsg (); 1384 break; 1385 case 'H': 1386 readHeader (in); 1387 break; 1388 case 'P': 1389 readPackager(in); 1390 break; 1391 case 'D': 1392 readDirection (in); 1393 break; 1394 default: 1395 throw new IOException ("malformed ISOMsg"); 1396 } 1397 if (c != null) { 1398 ((Externalizable)c).readExternal (in); 1399 set (c); 1400 } 1401 } 1402 } 1403 catch (ISOException e) { 1404 throw new IOException (e.getMessage()); 1405 } 1406 } 1407 /** 1408 * Let this ISOMsg object hold a weak reference to an ISOSource 1409 * (usually used to carry a reference to the incoming ISOChannel) 1410 * @param source an ISOSource 1411 */ 1412 public void setSource (ISOSource source) { 1413 this.sourceRef = new WeakReference (source); 1414 } 1415 /** 1416 * Returns the associated ISOSource (e.g. the channel that received this message). 1417 * @return an ISOSource or null 1418 */ 1419 public ISOSource getSource () { 1420 return sourceRef != null ? (ISOSource) sourceRef.get () : null; 1421 } 1422 private void writeExternal (ObjectOutput out, char b, ISOComponent c) throws IOException { 1423 out.writeByte (b); 1424 ((Externalizable) c).writeExternal (out); 1425 } 1426 private int parseInt (String s) { 1427 return s.startsWith("0x") ? Integer.parseInt(s.substring(2), 16) : Integer.parseInt(s); 1428 } 1429 1430 private boolean setDatasetPath(String fpath, Object value) throws ISOException { 1431 StringTokenizer st = new StringTokenizer(fpath, "."); 1432 if (st.countTokens() < 2) 1433 return false; 1434 1435 int fieldNo = parseInt(st.nextToken()); 1436 ISOFieldPackager fp = null; 1437 if (packager instanceof ISOBasePackager) { 1438 fp = ((ISOBasePackager) packager).getFieldPackager(fieldNo); 1439 } 1440 if (!(fp instanceof DatasetFieldPackager)) 1441 return false; 1442 1443 DatasetFieldPackager dfp = (DatasetFieldPackager) fp; 1444 ISODatasetPackager datasetPackager = dfp.getISODatasetPackager(); 1445 int datasetId; 1446 int elementId; 1447 1448 if (!datasetPackager.hasDatasetEnvelope()) { 1449 if (st.countTokens() != 1) 1450 return false; 1451 datasetId = fieldNo; 1452 elementId = parseInt(st.nextToken()); 1453 } else { 1454 if (st.countTokens() != 2) 1455 return false; 1456 datasetId = parseInt(st.nextToken()); 1457 elementId = parseInt(st.nextToken()); 1458 } 1459 1460 ISODatasetField field; 1461 ISOComponent component = getComponent(fieldNo); 1462 if (component == null) { 1463 field = new ISODatasetField(fieldNo); 1464 set(field); 1465 } else if (component instanceof ISODatasetField) { 1466 field = (ISODatasetField) component; 1467 } else { 1468 throw new ISOException("Field " + fieldNo + " is not a dataset field"); 1469 } 1470 1471 ISODataset dataset = (ISODataset) field.getDataset(datasetId); 1472 if (dataset == null) { 1473 dataset = new ISODataset(datasetId, datasetId <= 0x70 ? DatasetFormat.TLV : DatasetFormat.DBM); 1474 field.addDataset(dataset); 1475 } 1476 dataset.putElement(elementId, toDatasetComponent(elementId, value)); 1477 return true; 1478 } 1479 1480 private boolean unsetDatasetPath(String fpath) throws ISOException { 1481 StringTokenizer st = new StringTokenizer(fpath, "."); 1482 if (st.countTokens() < 2) 1483 return false; 1484 1485 int fieldNo = parseInt(st.nextToken()); 1486 ISOFieldPackager fp = null; 1487 if (packager instanceof ISOBasePackager) { 1488 fp = ((ISOBasePackager) packager).getFieldPackager(fieldNo); 1489 } 1490 if (!(fp instanceof DatasetFieldPackager)) 1491 return false; 1492 1493 DatasetFieldPackager dfp = (DatasetFieldPackager) fp; 1494 ISODatasetPackager datasetPackager = dfp.getISODatasetPackager(); 1495 int datasetId; 1496 int elementId; 1497 1498 if (!datasetPackager.hasDatasetEnvelope()) { 1499 if (st.countTokens() != 1) 1500 return false; 1501 datasetId = fieldNo; 1502 elementId = parseInt(st.nextToken()); 1503 } else { 1504 if (st.countTokens() != 2) 1505 return false; 1506 datasetId = parseInt(st.nextToken()); 1507 elementId = parseInt(st.nextToken()); 1508 } 1509 1510 ISOComponent component = getComponent(fieldNo); 1511 if (component == null) 1512 return true; 1513 if (!(component instanceof ISODatasetField)) 1514 throw new ISOException("Field " + fieldNo + " is not a dataset field"); 1515 1516 ISODatasetField field = (ISODatasetField) component; 1517 ISODataset dataset = (ISODataset) field.getDataset(datasetId); 1518 if (dataset == null) 1519 return true; 1520 1521 dataset.removeElement(elementId); 1522 if (dataset.isEmpty()) { 1523 field.removeDataset(dataset); 1524 if (!field.hasDatasets()) 1525 unset(fieldNo); 1526 } 1527 return true; 1528 } 1529 1530 private ISOComponent toDatasetComponent(int elementId, Object value) throws ISOException { 1531 if (value instanceof ISOComponent) { 1532 ISOComponent component = (ISOComponent) value; 1533 component.setFieldNumber(elementId); 1534 return component; 1535 } 1536 if (value instanceof byte[]) { 1537 return new ISOBinaryField(elementId, (byte[]) value); 1538 } 1539 if (value instanceof String) { 1540 return new ISOField(elementId, (String) value); 1541 } 1542 throw new ISOException("Unsupported dataset value type " + (value != null ? value.getClass().getName() : "null")); 1543 } 1544 1545 private ISOComponent cloneComponent(ISOComponent c) throws ISOException { 1546 if (c instanceof ISOMsg) 1547 return (ISOComponent) ((ISOMsg) c).clone(); 1548 if (c instanceof ISODatasetField) 1549 return cloneDatasetField((ISODatasetField) c); 1550 return c; 1551 } 1552 1553 private ISODatasetField cloneDatasetField(ISODatasetField field) throws ISOException { 1554 ISODatasetField clone = new ISODatasetField(field.getFieldNumber()); 1555 for (Dataset dataset : field.getDatasets()) { 1556 clone.addDataset(cloneDataset(dataset)); 1557 } 1558 return clone; 1559 } 1560 1561 private ISODataset cloneDataset(Dataset dataset) throws ISOException { 1562 ISODataset clone = new ISODataset(dataset.getIdentifier(), dataset.getFormat()); 1563 for (DatasetElement element : dataset.getElements()) { 1564 clone.addElement(element.getId(), cloneDatasetComponent(element.getComponent()), element.isConstructed()); 1565 } 1566 return clone; 1567 } 1568 1569 private ISOComponent cloneDatasetComponent(ISOComponent component) throws ISOException { 1570 if (component instanceof ISOMsg) 1571 return (ISOComponent) ((ISOMsg) component).clone(); 1572 if (component instanceof ISOBinaryField) 1573 return new ISOBinaryField(component.getFieldNumber(), component.getBytes() != null ? component.getBytes().clone() : null); 1574 if (component instanceof ISOField) 1575 return new ISOField(component.getFieldNumber(), (String) component.getValue()); 1576 return component; 1577 } 1578}