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}