1/*
2 * Copyright (c) 2003,2004, Stefan Haustein, Oberhausen, Rhld., Germany
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5 * associated documentation files (the "Software"), to deal in the Software without restriction, including
6 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
8 * following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in all copies or substantial
11 * portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
14 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
15 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
16 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
17 * USE OR OTHER DEALINGS IN THE SOFTWARE.
18 */
19
20package org.ksoap2.serialization;
21
22import org.ksoap2.SoapEnvelope;
23import org.ksoap2.SoapFault;
24import org.ksoap2.SoapFault12;
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlPullParserException;
27import org.xmlpull.v1.XmlSerializer;
28
29import java.io.IOException;
30import java.util.Hashtable;
31import java.util.Vector;
32import java.io.ByteArrayOutputStream;
33
34import org.kxml2.io.*;
35
36/**
37 * @author Stefan Haustein
38 *
39 *         This class extends the SoapEnvelope with Soap Serialization functionality.
40 */
41public class SoapSerializationEnvelope extends SoapEnvelope
42{
43    protected static final int QNAME_TYPE = 1;
44    protected static final int QNAME_NAMESPACE = 0;
45    protected static final int QNAME_MARSHAL = 3;
46    private static final String ANY_TYPE_LABEL = "anyType";
47    private static final String ARRAY_MAPPING_NAME = "Array";
48    private static final String NULL_LABEL = "null";
49    private static final String NIL_LABEL = "nil";
50    private static final String HREF_LABEL = "href";
51    private static final String ID_LABEL = "id";
52    private static final String ROOT_LABEL = "root";
53    private static final String TYPE_LABEL = "type";
54    private static final String ITEM_LABEL = "item";
55    private static final String ARRAY_TYPE_LABEL = "arrayType";
56    static final Marshal DEFAULT_MARSHAL = new DM();
57    public Hashtable properties = new Hashtable();
58
59    Hashtable idMap = new Hashtable();
60    Vector multiRef; // = new Vector();
61
62    /**
63     * Set this variable to true if you don't want that type definitions for complex types/objects
64     * are automatically generated (with type "anyType") in the XML-Request, if you don't call the
65     * Method addMapping. This is needed by some Servers which have problems with these type-definitions.
66     */
67    public boolean implicitTypes;
68
69    /**
70     * Set this variable to true for compatibility with what seems to be the default encoding for
71     * .Net-Services. This feature is an extremely ugly hack. A much better option is to change the
72     * configuration of the .Net-Server to standard Soap Serialization!
73     */
74
75    public boolean dotNet;
76
77    /**
78     * Set this variable to true if you prefer to silently skip unknown properties.
79     * {@link RuntimeException} will be thrown otherwise.
80     */
81    public boolean avoidExceptionForUnknownProperty;
82
83    /**
84     * Map from XML qualified names to Java classes
85     */
86
87    protected Hashtable qNameToClass = new Hashtable();
88
89    /**
90     * Map from Java class names to XML name and namespace pairs
91     */
92
93    protected Hashtable classToQName = new Hashtable();
94
95    /**
96     * Set to true to add and ID and ROOT label to the envelope. Change to false for compatibility with WSDL.
97     */
98    protected boolean addAdornments = true;
99
100    public SoapSerializationEnvelope(int version)
101    {
102        super(version);
103        addMapping(enc, ARRAY_MAPPING_NAME, PropertyInfo.VECTOR_CLASS);
104        DEFAULT_MARSHAL.register(this);
105    }
106
107    /**
108     * @return the addAdornments
109     */
110    public boolean isAddAdornments()
111    {
112        return addAdornments;
113    }
114
115    /**
116     * @param addAdornments
117     *            the addAdornments to set
118     */
119    public void setAddAdornments(boolean addAdornments)
120    {
121        this.addAdornments = addAdornments;
122    }
123
124    /**
125     * Set the bodyOut to be empty so that no un-needed xml is create. The null value for bodyOut will
126     * cause #writeBody to skip writing anything redundant.
127     * @param emptyBody
128     * @see "http://code.google.com/p/ksoap2-android/issues/detail?id=77"
129     */
130    public void setBodyOutEmpty(boolean emptyBody) {
131        if (emptyBody) {
132            bodyOut = null;
133        }
134    }
135
136    public void parseBody(XmlPullParser parser) throws IOException, XmlPullParserException
137    {
138        bodyIn = null;
139        parser.nextTag();
140        if (parser.getEventType() == XmlPullParser.START_TAG && parser.getNamespace().equals(env)
141                && parser.getName().equals("Fault")) {
142            SoapFault fault;
143            if (this.version < SoapEnvelope.VER12) {
144                fault = new SoapFault(this.version);
145            } else {
146                fault = new SoapFault12(this.version);
147            }
148            fault.parse(parser);
149            bodyIn = fault;
150        } else {
151            while (parser.getEventType() == XmlPullParser.START_TAG) {
152                String rootAttr = parser.getAttributeValue(enc, ROOT_LABEL);
153
154                Object o = read(parser, null, -1, parser.getNamespace(), parser.getName(),
155                        PropertyInfo.OBJECT_TYPE);
156                if ("1".equals(rootAttr) || bodyIn == null) {
157                    bodyIn = o;
158                }
159                parser.nextTag();
160            }
161        }
162    }
163
164    /** Read a SoapObject. This extracts any attributes and then reads the object as a KvmSerializable. */
165    protected void readSerializable(XmlPullParser parser, SoapObject obj) throws IOException,
166            XmlPullParserException
167    {
168        for (int counter = 0; counter < parser.getAttributeCount(); counter++) {
169            String attributeName = parser.getAttributeName(counter);
170            String value = parser.getAttributeValue(counter);
171            ((SoapObject) obj).addAttribute(attributeName, value);
172        }
173        readSerializable(parser, (KvmSerializable) obj);
174    }
175
176    /** Read a KvmSerializable. */
177    protected void readSerializable(XmlPullParser parser, KvmSerializable obj) throws IOException,
178            XmlPullParserException
179    {
180        while (parser.nextTag() != XmlPullParser.END_TAG) {
181            String name = parser.getName();
182            if (!implicitTypes || !(obj instanceof SoapObject)) {
183                PropertyInfo info = new PropertyInfo();
184                int propertyCount = obj.getPropertyCount();
185                boolean propertyFound = false;
186
187                for (int i = 0; i < propertyCount && !propertyFound; i++) {
188                    info.clear();
189                    obj.getPropertyInfo(i, properties, info);
190
191                    if ((name.equals(info.name) && info.namespace == null)
192                            ||
193                            (name.equals(info.name) && parser.getNamespace().equals(info.namespace))) {
194                        propertyFound = true;
195                        obj.setProperty(i, read(parser, obj, i, null, null, info));
196                    }
197                }
198
199                if (!propertyFound) {
200                    if (avoidExceptionForUnknownProperty) {
201                        // Dummy loop to read until corresponding END tag
202                        while (parser.next() != XmlPullParser.END_TAG
203                                || !name.equals(parser.getName())) {
204                        }
205                        ;
206                    } else {
207                        throw new RuntimeException("Unknown Property: " + name);
208                    }
209                }
210            } else {
211                // I can only make this work for SoapObjects - hence the check above
212                // I don't understand namespaces well enough to know whether it is correct in the next line...
213                ((SoapObject) obj).addProperty(parser.getName(),
214                        read(parser, obj, obj.getPropertyCount(),
215                                ((SoapObject) obj).getNamespace(), name, PropertyInfo.OBJECT_TYPE));
216            }
217        }
218        parser.require(XmlPullParser.END_TAG, null, null);
219    }
220
221    /**
222     * If the type of the object cannot be determined, and thus no Marshal class can handle the object, this
223     * method is called. It will build either a SoapPrimitive or a SoapObject
224     *
225     * @param parser
226     * @param typeNamespace
227     * @param typeName
228     * @return unknownObject wrapped as a SoapPrimitive or SoapObject
229     * @throws IOException
230     * @throws XmlPullParserException
231     */
232
233    protected Object readUnknown(XmlPullParser parser, String typeNamespace, String typeName)
234            throws IOException, XmlPullParserException {
235        String name = parser.getName();
236        String namespace = parser.getNamespace();
237
238        // cache the attribute info list from the current element before we move on
239        Vector attributeInfoVector = new Vector();
240        for (int attributeCount = 0; attributeCount < parser.getAttributeCount(); attributeCount++) {
241            AttributeInfo attributeInfo = new AttributeInfo();
242            attributeInfo.setName(parser.getAttributeName(attributeCount));
243            attributeInfo.setValue(parser.getAttributeValue(attributeCount));
244            attributeInfo.setNamespace(parser.getAttributeNamespace(attributeCount));
245            attributeInfo.setType(parser.getAttributeType(attributeCount));
246            attributeInfoVector.addElement(attributeInfo);
247        }
248
249        parser.next(); // move to text, inner start tag or end tag
250        Object result = null;
251        String text = null;
252        if (parser.getEventType() == XmlPullParser.TEXT) {
253            text = parser.getText();
254            SoapPrimitive sp = new SoapPrimitive(typeNamespace, typeName, text);
255            result = sp;
256            // apply all the cached attribute info list before we add the property and descend further for parsing
257            for (int i = 0; i < attributeInfoVector.size(); i++) {
258                sp.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
259            }
260            parser.next();
261        } else if (parser.getEventType() == XmlPullParser.END_TAG) {
262            SoapObject so = new SoapObject(typeNamespace, typeName);
263            // apply all the cached attribute info list before we add the property and descend further for parsing
264            for (int i = 0; i < attributeInfoVector.size(); i++) {
265                so.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
266            }
267            result = so;
268        }
269
270        if (parser.getEventType() == XmlPullParser.START_TAG) {
271            if (text != null && text.trim().length() != 0) {
272                throw new RuntimeException("Malformed input: Mixed content");
273            }
274            SoapObject so = new SoapObject(typeNamespace, typeName);
275            // apply all the cached attribute info list before we add the property and descend further for parsing
276            for (int i = 0; i < attributeInfoVector.size(); i++) {
277                so.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
278            }
279
280            while (parser.getEventType() != XmlPullParser.END_TAG) {
281                so.addProperty(parser.getName(),
282                        read(parser, so, so.getPropertyCount(), null, null,
283                                PropertyInfo.OBJECT_TYPE));
284                parser.nextTag();
285            }
286            result = so;
287        }
288        parser.require(XmlPullParser.END_TAG, namespace, name);
289        return result;
290    }
291
292    private int getIndex(String value, int start, int dflt) {
293        if (value == null) {
294            return dflt;
295        }
296        return value.length() - start < 3 ? dflt : Integer.parseInt(value.substring(start + 1,
297                value.length() - 1));
298    }
299
300    protected void readVector(XmlPullParser parser, Vector v, PropertyInfo elementType)
301            throws IOException,
302            XmlPullParserException {
303        String namespace = null;
304        String name = null;
305        int size = v.size();
306        boolean dynamic = true;
307        String type = parser.getAttributeValue(enc, ARRAY_TYPE_LABEL);
308        if (type != null) {
309            int cut0 = type.indexOf(':');
310            int cut1 = type.indexOf("[", cut0);
311            name = type.substring(cut0 + 1, cut1);
312            String prefix = cut0 == -1 ? "" : type.substring(0, cut0);
313            namespace = parser.getNamespace(prefix);
314            size = getIndex(type, cut1, -1);
315            if (size != -1) {
316                v.setSize(size);
317                dynamic = false;
318            }
319        }
320        if (elementType == null) {
321            elementType = PropertyInfo.OBJECT_TYPE;
322        }
323        parser.nextTag();
324        int position = getIndex(parser.getAttributeValue(enc, "offset"), 0, 0);
325        while (parser.getEventType() != XmlPullParser.END_TAG) {
326            // handle position
327            position = getIndex(parser.getAttributeValue(enc, "position"), 0, position);
328            if (dynamic && position >= size) {
329                size = position + 1;
330                v.setSize(size);
331            }
332            // implicit handling of position exceeding specified size
333            v.setElementAt(read(parser, v, position, namespace, name, elementType), position);
334            position++;
335            parser.nextTag();
336        }
337        parser.require(XmlPullParser.END_TAG, null, null);
338    }
339
340    /**
341     * Builds an object from the XML stream. This method is public for usage in conjuction with Marshal
342     * subclasses. Precondition: On the start tag of the object or property, so href can be read.
343     */
344
345    public Object read(XmlPullParser parser, Object owner, int index, String namespace,
346            String name,
347            PropertyInfo expected) throws IOException, XmlPullParserException {
348        String elementName = parser.getName();
349        String href = parser.getAttributeValue(null, HREF_LABEL);
350        Object obj;
351        if (href != null) {
352            if (owner == null) {
353                throw new RuntimeException("href at root level?!?");
354            }
355            href = href.substring(1);
356            obj = idMap.get(href);
357            if (obj == null || obj instanceof FwdRef) {
358                FwdRef f = new FwdRef();
359                f.next = (FwdRef) obj;
360                f.obj = owner;
361                f.index = index;
362                idMap.put(href, f);
363                obj = null;
364            }
365            parser.nextTag(); // start tag
366            parser.require(XmlPullParser.END_TAG, null, elementName);
367        } else {
368            String nullAttr = parser.getAttributeValue(xsi, NIL_LABEL);
369            String id = parser.getAttributeValue(null, ID_LABEL);
370            if (nullAttr == null) {
371                nullAttr = parser.getAttributeValue(xsi, NULL_LABEL);
372            }
373            if (nullAttr != null && SoapEnvelope.stringToBoolean(nullAttr)) {
374                obj = null;
375                parser.nextTag();
376                parser.require(XmlPullParser.END_TAG, null, elementName);
377            } else {
378                String type = parser.getAttributeValue(xsi, TYPE_LABEL);
379                if (type != null) {
380                    int cut = type.indexOf(':');
381                    name = type.substring(cut + 1);
382                    String prefix = cut == -1 ? "" : type.substring(0, cut);
383                    namespace = parser.getNamespace(prefix);
384                } else if (name == null && namespace == null) {
385                    if (parser.getAttributeValue(enc, ARRAY_TYPE_LABEL) != null) {
386                        namespace = enc;
387                        name = ARRAY_MAPPING_NAME;
388                    } else {
389                        Object[] names = getInfo(expected.type, null);
390                        namespace = (String) names[0];
391                        name = (String) names[1];
392                    }
393                }
394                // be sure to set this flag if we don't know the types.
395                if (type == null) {
396                    implicitTypes = true;
397                }
398                obj = readInstance(parser, namespace, name, expected);
399                if (obj == null) {
400                    obj = readUnknown(parser, namespace, name);
401                }
402            }
403            // finally, care about the id....
404            if (id != null) {
405                Object hlp = idMap.get(id);
406                if (hlp instanceof FwdRef) {
407                    FwdRef f = (FwdRef) hlp;
408                    do {
409                        if (f.obj instanceof KvmSerializable) {
410                            ((KvmSerializable) f.obj).setProperty(f.index, obj);
411                        } else {
412                            ((Vector) f.obj).setElementAt(obj, f.index);
413                        }
414                        f = f.next;
415                    } while (f != null);
416                } else if (hlp != null) {
417                    throw new RuntimeException("double ID");
418                }
419                idMap.put(id, obj);
420            }
421        }
422
423        parser.require(XmlPullParser.END_TAG, null, elementName);
424        return obj;
425    }
426
427    /**
428     * Returns a new object read from the given parser. If no mapping is found, null is returned. This method
429     * is used by the SoapParser in order to convert the XML code to Java objects.
430     */
431    public Object readInstance(XmlPullParser parser, String namespace, String name,
432            PropertyInfo expected)
433            throws IOException, XmlPullParserException {
434        Object obj = qNameToClass.get(new SoapPrimitive(namespace, name, null));
435        if (obj == null) {
436            return null;
437        }
438        if (obj instanceof Marshal) {
439            return ((Marshal) obj).readInstance(parser, namespace, name, expected);
440        } else if (obj instanceof SoapObject) {
441            obj = ((SoapObject) obj).newInstance();
442        } else if (obj == SoapObject.class) {
443            obj = new SoapObject(namespace, name);
444        } else {
445            try {
446                obj = ((Class) obj).newInstance();
447            } catch (Exception e) {
448                throw new RuntimeException(e.toString());
449            }
450        }
451        // ok, obj is now the instance, fill it....
452        if (obj instanceof SoapObject) {
453            readSerializable(parser, (SoapObject) obj);
454        } else if (obj instanceof KvmSerializable) {
455            readSerializable(parser, (KvmSerializable) obj);
456        } else if (obj instanceof Vector) {
457            readVector(parser, (Vector) obj, expected.elementType);
458        } else {
459            throw new RuntimeException("no deserializer for " + obj.getClass());
460        }
461        return obj;
462    }
463
464    /**
465     * Returns a string array containing the namespace, name, id and Marshal object for the given java object.
466     * This method is used by the SoapWriter in order to map Java objects to the corresponding SOAP section
467     * five XML code.
468     */
469    public Object[] getInfo(Object type, Object instance) {
470        if (type == null) {
471            if (instance instanceof SoapObject || instance instanceof SoapPrimitive) {
472                type = instance;
473            } else {
474                type = instance.getClass();
475            }
476        }
477        if (type instanceof SoapObject) {
478            SoapObject so = (SoapObject) type;
479            return new Object[] {
480                    so.getNamespace(), so.getName(), null, null
481            };
482        }
483        if (type instanceof SoapPrimitive) {
484            SoapPrimitive sp = (SoapPrimitive) type;
485            return new Object[] {
486                    sp.getNamespace(), sp.getName(), null, DEFAULT_MARSHAL
487            };
488        }
489        if ((type instanceof Class) && type != PropertyInfo.OBJECT_CLASS) {
490            Object[] tmp = (Object[]) classToQName.get(((Class) type).getName());
491            if (tmp != null) {
492                return tmp;
493            }
494        }
495        return new Object[] {
496                xsd, ANY_TYPE_LABEL, null, null
497        };
498    }
499
500    /**
501     * Defines a direct mapping from a namespace and name to a java class (and vice versa), using the given
502     * marshal mechanism
503     */
504    public void addMapping(String namespace, String name, Class clazz, Marshal marshal) {
505        qNameToClass
506                .put(new SoapPrimitive(namespace, name, null), marshal == null ? (Object) clazz
507                        : marshal);
508        classToQName.put(clazz.getName(), new Object[] {
509                namespace, name, null, marshal
510        });
511    }
512
513    /**
514     * Defines a direct mapping from a namespace and name to a java class (and vice versa)
515     */
516    public void addMapping(String namespace, String name, Class clazz) {
517        addMapping(namespace, name, clazz, null);
518    }
519
520    /**
521     * Adds a SoapObject to the class map. During parsing, objects of the given type (namespace/name) will be
522     * mapped to corresponding copies of the given SoapObject, maintaining the structure of the template.
523     */
524    public void addTemplate(SoapObject so) {
525        qNameToClass.put(new SoapPrimitive(so.namespace, so.name, null), so);
526    }
527
528    /**
529     * Response from the soap call. Pulls the object from the wrapper object and returns it.
530     *
531     * @since 2.0.3
532     * @return response from the soap call.
533     * @throws SoapFault
534     */
535    public Object getResponse() throws SoapFault {
536        if (bodyIn instanceof SoapFault) {
537            throw (SoapFault) bodyIn;
538        }
539        KvmSerializable ks = (KvmSerializable) bodyIn;
540
541        if (ks.getPropertyCount() == 0) {
542            return null;
543        } else if (ks.getPropertyCount() == 1) {
544            return ks.getProperty(0);
545        } else {
546            Vector ret = new Vector();
547            for (int i = 0; i < ks.getPropertyCount(); i++) {
548                ret.add(ks.getProperty(i));
549            }
550            return ret;
551        }
552    }
553
554    /**
555     * Serializes the request object to the given XmlSerliazer object
556     *
557     * @param writer
558     *            XmlSerializer object to write the body into.
559     */
560    public void writeBody(XmlSerializer writer) throws IOException {
561        // allow an empty body without any tags in it
562        // see http://code.google.com/p/ksoap2-android/issues/detail?id=77
563        if (bodyOut != null) {
564            multiRef = new Vector();
565            multiRef.addElement(bodyOut);
566            Object[] qName = getInfo(null, bodyOut);
567            writer.startTag((dotNet) ? "" : (String) qName[QNAME_NAMESPACE],
568                    (String) qName[QNAME_TYPE]); //<spp:sppPostDevData
569            if (dotNet) {
570                writer.attribute(null, "xmlns", (String) qName[QNAME_NAMESPACE]);
571            }
572            if (addAdornments) {
573                writer.attribute(null, ID_LABEL, qName[2] == null ? ("o" + 0) : (String) qName[2]);
574                writer.attribute(enc, ROOT_LABEL, "1");
575            }
576            writeElement(writer, bodyOut, null, qName[QNAME_MARSHAL]); //....
577            writer.endTag((dotNet) ? "" : (String) qName[QNAME_NAMESPACE],
578                    (String) qName[QNAME_TYPE]);//</spp:sppPostDevData>
579        }
580    }
581
582    /**
583     * Writes the body of an SoapObject. This method write the attributes and then calls
584     * "writeObjectBody (writer, (KvmSerializable)obj);"
585     */
586    public void writeObjectBody(XmlSerializer writer, SoapObject obj) throws IOException {
587        SoapObject soapObject = (SoapObject) obj;
588        int cnt = soapObject.getAttributeCount();
589        for (int counter = 0; counter < cnt; counter++) {
590            AttributeInfo attributeInfo = new AttributeInfo();
591            soapObject.getAttributeInfo(counter, attributeInfo);
592            writer.attribute(attributeInfo.getNamespace(), attributeInfo.getName(), attributeInfo
593                    .getValue()
594                    .toString());
595        }
596        writeObjectBody(writer, (KvmSerializable) obj);
597    }
598
599    /**
600     * Writes the body of an KvmSerializable object. This method is public for access from Marshal subclasses.
601     */
602    public void writeObjectBody(XmlSerializer writer, KvmSerializable obj) throws IOException {
603        int cnt = obj.getPropertyCount();
604        PropertyInfo propertyInfo = new PropertyInfo();
605        String namespace;
606        String name;
607        String type;
608        for (int i = 0; i < cnt; i++) {
609            // get the property
610            Object prop = obj.getProperty(i);
611            // and importantly also get the property info which holds the name potentially!
612            obj.getPropertyInfo(i, properties, propertyInfo);
613
614            if (!(prop instanceof SoapObject)) {
615                // prop is a PropertyInfo
616                if ((propertyInfo.flags & PropertyInfo.TRANSIENT) == 0) {
617                    writer.startTag(propertyInfo.namespace, propertyInfo.name);
618                    writeProperty(writer, obj.getProperty(i), propertyInfo);
619                    writer.endTag(propertyInfo.namespace, propertyInfo.name);
620                }
621            } else {
622                // prop is a SoapObject
623                SoapObject nestedSoap = (SoapObject) prop;
624                // lets get the info from the soap object itself
625                Object[] qName = getInfo(null, nestedSoap);
626                namespace = (String) qName[QNAME_NAMESPACE];
627                type = (String) qName[QNAME_TYPE];
628
629                // prefer the name from the property info
630                if (propertyInfo.name != null && propertyInfo.name.length() > 0) {
631                    name = propertyInfo.name;
632                } else {
633                    name = (String) qName[QNAME_TYPE];
634                }
635
636                // treat MO data as CDATA
637                if (name.equals("DevInfo") || name.equals("DevDetail")
638                        || name.equals("PerProviderSubscription") || // format v4
639                        name.equals("MgmtTree") // format v6
640                ) {
641                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
642                    XmlSerializer xw = new KXmlSerializer();
643                    xw.setOutput(bos, "UTF-8");
644                    xw.startTag((dotNet) ? "" : namespace, name);
645                    if (!implicitTypes) {
646                        String prefix = writer.getPrefix(namespace, true);
647                        writer.attribute(xsi, TYPE_LABEL, prefix + ":" + type);
648                    }
649                    writeObjectBody(xw, nestedSoap);
650                    xw.endTag((dotNet) ? "" : namespace, name);
651                    xw.flush();
652                    //bos.write('\r');
653                    //bos.write('\n');
654                    bos.flush();
655                    writer.cdsect(bos.toString());
656                }
657                else
658                {
659                    writer.startTag((dotNet) ? "" : namespace, name);
660                    if (!implicitTypes) {
661                        String prefix = writer.getPrefix(namespace, true);
662                        writer.attribute(xsi, TYPE_LABEL, prefix + ":" + type);
663                    }
664                    writeObjectBody(writer, nestedSoap);
665                    writer.endTag((dotNet) ? "" : namespace, name);
666                }
667            }
668        }
669    }
670
671    protected void writeProperty(XmlSerializer writer, Object obj, PropertyInfo type)
672            throws IOException {
673        if (obj == null) {
674            ///M: Modify for HS20
675            //writer.attribute(xsi, version >= VER12 ? NIL_LABEL : NULL_LABEL, "true");
676            return;
677        }
678        Object[] qName = getInfo(null, obj);
679        if (type.multiRef || qName[2] != null) {
680            int i = multiRef.indexOf(obj);
681            if (i == -1) {
682                i = multiRef.size();
683                multiRef.addElement(obj);
684            }
685            writer.attribute(null, HREF_LABEL, qName[2] == null ? ("#o" + i) : "#" + qName[2]);
686        } else {
687            if (!implicitTypes || obj.getClass() != type.type) {
688                String prefix = writer.getPrefix((String) qName[QNAME_NAMESPACE], true);
689                writer.attribute(xsi, TYPE_LABEL, prefix + ":" + qName[QNAME_TYPE]);
690            }
691            writeElement(writer, obj, type, qName[QNAME_MARSHAL]);
692        }
693    }
694
695    private void writeElement(XmlSerializer writer, Object element, PropertyInfo type,
696            Object marshal)
697            throws IOException {
698        if (marshal != null) {
699            ((Marshal) marshal).writeInstance(writer, element);
700        } else if (element instanceof SoapObject) {
701            writeObjectBody(writer, (SoapObject) element);
702        } else if (element instanceof KvmSerializable) {
703            writeObjectBody(writer, (KvmSerializable) element);
704        } else if (element instanceof Vector) {
705            writeVectorBody(writer, (Vector) element, type.elementType);
706        } else {
707            throw new RuntimeException("Cannot serialize: " + element);
708        }
709    }
710
711    protected void writeVectorBody(XmlSerializer writer, Vector vector, PropertyInfo elementType)
712            throws IOException {
713        String itemsTagName = ITEM_LABEL;
714        String itemsNamespace = null;
715
716        if (elementType == null) {
717            elementType = PropertyInfo.OBJECT_TYPE;
718        } else if (elementType instanceof PropertyInfo) {
719            if (elementType.name != null) {
720                itemsTagName = elementType.name;
721                itemsNamespace = elementType.namespace;
722            }
723        }
724
725        int cnt = vector.size();
726        Object[] arrType = getInfo(elementType.type, null);
727
728        // This removes the arrayType attribute from the xml for arrays(required for most .Net services to work)
729        if (!implicitTypes) {
730            writer.attribute(enc, ARRAY_TYPE_LABEL, writer.getPrefix((String) arrType[0], false)
731                    + ":"
732                    + arrType[1] + "[" + cnt + "]");
733        }
734
735        boolean skipped = false;
736        for (int i = 0; i < cnt; i++) {
737            if (vector.elementAt(i) == null) {
738                skipped = true;
739            } else {
740                writer.startTag(itemsNamespace, itemsTagName);
741                if (skipped) {
742                    writer.attribute(enc, "position", "[" + i + "]");
743                    skipped = false;
744                }
745                writeProperty(writer, vector.elementAt(i), elementType);
746                writer.endTag(itemsNamespace, itemsTagName);
747            }
748        }
749    }
750}
751