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
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * Contributor(s): John D. Beatty, Dave Dash, Andre Gerard, F. Hunter, Renaud
23 * Tognelli
24 */
25
26package org.ksoap2.serialization;
27
28import java.util.*;
29
30/**
31 * A simple dynamic object that can be used to build soap calls without
32 * implementing KvmSerializable
33 * <p/>
34 * Essentially, this is what goes inside the body of a soap envelope - it is the
35 * direct subelement of the body and all further subelements
36 * <p/>
37 * Instead of this this class, custom classes can be used if they implement the
38 * KvmSerializable interface.
39 */
40
41public class SoapObject extends AttributeContainer implements KvmSerializable {
42
43    private static final String EMPTY_STRING = "";
44    /**
45     * The namespace of this soap object.
46     */
47    protected String namespace;
48    /**
49     * The name of this soap object.
50     */
51    protected String name;
52    /**
53     * The Vector of properties (can contain PropertyInfo and SoapObject)
54     */
55    protected Vector properties = new Vector();
56
57    // TODO: accessing properties and attributes would work much better if we
58    // kept a list of known properties instead of iterating through the list
59    // each time
60
61    /**
62     * Creates a new <code>SoapObject</code> instance.
63     */
64
65    public SoapObject() {
66        this("", "");
67    }
68
69    /**
70     * Creates a new <code>SoapObject</code> instance.
71     *
72     * @param namespace
73     *            the namespace for the soap object
74     * @param name
75     *            the name of the soap object
76     */
77
78    public SoapObject(String namespace, String name) {
79        this.namespace = namespace;
80        this.name = name;
81    }
82
83    public boolean equals(Object obj) {
84        if (!(obj instanceof SoapObject)) {
85            return false;
86        }
87
88        SoapObject otherSoapObject = (SoapObject) obj;
89
90        if (!name.equals(otherSoapObject.name)
91                || !namespace.equals(otherSoapObject.namespace)) {
92            return false;
93        }
94
95        int numProperties = properties.size();
96        if (numProperties != otherSoapObject.properties.size()) {
97            return false;
98        }
99
100        // SoapObjects are only considered the same if properties equals and in the same order
101        for (int propIndex = 0; propIndex < numProperties; propIndex++) {
102            Object thisProp = this.properties.elementAt(propIndex);
103            if (!otherSoapObject.isPropertyEqual(thisProp, propIndex)) {
104                return false;
105            }
106        }
107
108        return attributesAreEqual(otherSoapObject);
109    }
110
111    /**
112     * Helper function for SoapObject.equals
113     * Checks if a given property and index are the same as in this
114     *
115     *  @param otherProp, index
116     *  @return
117     */
118    public boolean isPropertyEqual(Object otherProp, int index) {
119        if (index >= getPropertyCount()) {
120            return false;
121        }
122        Object thisProp = this.properties.elementAt(index);
123        if (otherProp instanceof PropertyInfo &&
124                thisProp instanceof PropertyInfo) {
125            // Get both PropertInfos and compare values
126            PropertyInfo otherPropInfo = (PropertyInfo) otherProp;
127            PropertyInfo thisPropInfo = (PropertyInfo) thisProp;
128            return otherPropInfo.getName().equals(thisPropInfo.getName()) &&
129                    otherPropInfo.getValue().equals(thisPropInfo.getValue());
130        } else if (otherProp instanceof SoapObject && thisProp instanceof SoapObject) {
131            SoapObject otherPropSoap = (SoapObject) otherProp;
132            SoapObject thisPropSoap = (SoapObject) thisProp;
133            return otherPropSoap.equals(thisPropSoap);
134        }
135        return false;
136    }
137
138    public String getName() {
139        return name;
140    }
141
142    public String getNamespace() {
143        return namespace;
144    }
145
146    /**
147     * @inheritDoc
148     */
149    public Object getProperty(int index) {
150        Object prop = properties.elementAt(index);
151        if (prop instanceof PropertyInfo) {
152            return ((PropertyInfo) prop).getValue();
153        } else {
154            return ((SoapObject) prop);
155        }
156    }
157
158    /**
159     * Get the toString value of the property.
160     *
161     * @param index
162     * @return
163     */
164    public String getPropertyAsString(int index) {
165        PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(index);
166        return propertyInfo.getValue().toString();
167    }
168
169    /**
170     * Get the property with the given name
171     *
172     * @throws java.lang.RuntimeException
173     *             if the property does not exist
174     */
175    public Object getProperty(String name) {
176        Integer index = propertyIndex(name);
177        if (index != null) {
178            return getProperty(index.intValue());
179        } else {
180            throw new RuntimeException("illegal property: " + name);
181        }
182    }
183
184    /**
185     * Get the toString value of the property.
186     *
187     * @param name
188     * @return
189     */
190
191    public String getPropertyAsString(String name) {
192        Integer index = propertyIndex(name);
193        if (index != null) {
194            return getProperty(index.intValue()).toString();
195        } else {
196            throw new RuntimeException("illegal property: " + name);
197        }
198    }
199
200    /**
201     * Knows whether the given property exists
202     */
203    public boolean hasProperty(final String name) {
204        if (propertyIndex(name) != null) {
205            return true;
206        } else {
207            return false;
208        }
209    }
210
211    /**
212     * Get a property without chance of throwing an exception
213     *
214     * @return the property if it exists; if not, {@link NullSoapObject} is
215     *         returned
216     */
217    public Object getPropertySafely(final String name) {
218        Integer i = propertyIndex(name);
219        if (i != null) {
220            return getProperty(i.intValue());
221        } else {
222            return new NullSoapObject();
223        }
224    }
225
226    /**
227     * Get the toString value of a property without chance of throwing an
228     * exception
229     *
230     * @return the string value of the property if it exists; if not, #EMPTY_STRING is
231     *         returned
232     */
233    public String getPropertySafelyAsString(final String name) {
234        Integer i = propertyIndex(name);
235        if (i != null) {
236            Object foo = getProperty(i.intValue());
237            if (foo == null) {
238                return EMPTY_STRING;
239            } else {
240                return foo.toString();
241            }
242        } else {
243            return EMPTY_STRING;
244        }
245    }
246
247    /**
248     * Get a property without chance of throwing an exception. An object can be
249     * provided to this method; if the property is not found, this object will
250     * be returned.
251     *
252     * @param defaultThing
253     *            the object to return if the property is not found
254     * @return the property if it exists; defaultThing if the property does not
255     *         exist
256     */
257    public Object getPropertySafely(final String name, final Object defaultThing) {
258        Integer i = propertyIndex(name);
259        if (i != null) {
260            return getProperty(i.intValue());
261        } else {
262            return defaultThing;
263        }
264    }
265
266    /**
267     * Get the toString value of a property without chance of throwing an
268     * exception. An object can be provided to this method; if the property is
269     * not found, this object's string representation will be returned.
270     *
271     * @param defaultThing
272     *            toString of the object to return if the property is not found
273     * @return the property toString if it exists; defaultThing toString if the
274     *         property does not exist, if the defaultThing is null #EMPTY_STRING
275     *         is returned
276     */
277    public String getPropertySafelyAsString(final String name,
278            final Object defaultThing) {
279        Integer i = propertyIndex(name);
280        if (i != null) {
281            Object property = getProperty(i.intValue());
282            if (property != null) {
283                return property.toString();
284            } else {
285                return EMPTY_STRING;
286            }
287        } else {
288            if (defaultThing != null) {
289                return defaultThing.toString();
290            } else {
291                return EMPTY_STRING;
292            }
293        }
294    }
295
296    /**
297     * Get the primitive property with the given name.
298     *
299     * @param name
300     * @return PropertyInfo containing an empty string if property either complex or empty
301     */
302    public Object getPrimitiveProperty(final String name) {
303        Integer index = propertyIndex(name);
304        if (index != null) {
305            PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(index.intValue());
306            if (propertyInfo.getType() != SoapObject.class) {
307                return propertyInfo.getValue();
308            } else {
309                propertyInfo = new PropertyInfo();
310                propertyInfo.setType(String.class);
311                propertyInfo.setValue(EMPTY_STRING);
312                propertyInfo.setName(name);
313                return (Object) propertyInfo.getValue();
314            }
315        } else {
316            throw new RuntimeException("illegal property: " + name);
317        }
318    }
319
320    /**
321     * Get the toString value of the primitive property with the given name.
322     * Returns empty string if property either complex or empty
323     *
324     * @param name
325     * @return the string value of the property
326     */
327    public String getPrimitivePropertyAsString(final String name) {
328        Integer index = propertyIndex(name);
329        if (index != null) {
330            PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(index.intValue());
331            if (propertyInfo.getType() != SoapObject.class) {
332                return propertyInfo.getValue().toString();
333            } else {
334                return EMPTY_STRING;
335            }
336        } else {
337            throw new RuntimeException("illegal property: " + name);
338        }
339    }
340
341    /**
342     * Get the toString value of a primitive property without chance of throwing an
343     * exception
344     *
345     * @param name
346     * @return the string value of the property if it exists and is primitive; if not, #EMPTY_STRING is
347     *         returned
348     */
349    public Object getPrimitivePropertySafely(final String name) {
350        Integer index = propertyIndex(name);
351        if (index != null) {
352            PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(index.intValue());
353            if (propertyInfo.getType() != SoapObject.class) {
354                return propertyInfo.getValue().toString();
355            } else {
356                propertyInfo = new PropertyInfo();
357                propertyInfo.setType(String.class);
358                propertyInfo.setValue(EMPTY_STRING);
359                propertyInfo.setName(name);
360                return (Object) propertyInfo.getValue();
361            }
362        } else {
363            return new NullSoapObject();
364        }
365    }
366
367    /**
368     * Get the toString value of a primitive property without chance of throwing an
369     * exception
370     *
371     * @param name
372     * @return the string value of the property if it exists and is primitive; if not, #EMPTY_STRING is
373     *         returned
374     */
375    public String getPrimitivePropertySafelyAsString(final String name) {
376        Integer index = propertyIndex(name);
377        if (index != null) {
378            PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(index.intValue());
379            if (propertyInfo.getType() != SoapObject.class) {
380                return propertyInfo.getValue().toString();
381            } else {
382                return EMPTY_STRING;
383            }
384        } else {
385            return EMPTY_STRING;
386        }
387    }
388
389    private Integer propertyIndex(String name) {
390        if (name != null) {
391            for (int i = 0; i < properties.size(); i++) {
392                if (name.equals(((PropertyInfo) properties.elementAt(i)).getName())) {
393                    return new Integer(i);
394                }
395            }
396        }
397        return null;
398    }
399
400    /**
401     * Returns the number of properties
402     *
403     * @return the number of properties
404     */
405    public int getPropertyCount() {
406        return properties.size();
407    }
408
409    /**
410     * Places PropertyInfo of desired property into a designated PropertyInfo
411     * object. Just calls #getPropertyInfo and discards any provided properties.
412     *
413     * @param index
414     *            index of desired property
415     * @param properties
416     *            this parameter is ignored
417     * @param propertyInfo
418     *            designated retainer of desired property
419     */
420    public void getPropertyInfo(int index, Hashtable properties, PropertyInfo propertyInfo) {
421        getPropertyInfo(index, propertyInfo);
422    }
423
424    /**
425     * Places PropertyInfo of desired property into a designated PropertyInfo
426     * object
427     *
428     * @param index
429     *            index of desired property
430     * @param propertyInfo
431     *            designated retainer of desired property
432     */
433    public void getPropertyInfo(int index, PropertyInfo propertyInfo) {
434        Object element = properties.elementAt(index);
435        if (element instanceof PropertyInfo) {
436            PropertyInfo p = (PropertyInfo) element;
437            propertyInfo.name = p.name;
438            propertyInfo.namespace = p.namespace;
439            propertyInfo.flags = p.flags;
440            propertyInfo.type = p.type;
441            propertyInfo.elementType = p.elementType;
442            propertyInfo.value = p.value;
443            propertyInfo.multiRef = p.multiRef;
444        } else {
445            // SoapObject
446            propertyInfo.name = null;
447            propertyInfo.namespace = null;
448            propertyInfo.flags = 0;
449            propertyInfo.type = null;
450            propertyInfo.elementType = null;
451            propertyInfo.value = element;
452            propertyInfo.multiRef = false;
453        }
454    }
455
456    /**
457     * Creates a new SoapObject based on this, allows usage of SoapObjects as
458     * templates. One application is to set the expected return type of a soap
459     * call if the server does not send explicit type information.
460     *
461     * @return a copy of this.
462     */
463    public SoapObject newInstance() {
464        SoapObject o = new SoapObject(namespace, name);
465        for (int propIndex = 0; propIndex < properties.size(); propIndex++) {
466            Object prop = properties.elementAt(propIndex);
467            if (prop instanceof PropertyInfo) {
468                PropertyInfo propertyInfo = (PropertyInfo) properties.elementAt(propIndex);
469                PropertyInfo propertyInfoClonned = (PropertyInfo) propertyInfo.clone();
470                o.addProperty(propertyInfoClonned);
471            } else if (prop instanceof SoapObject) {
472                o.addSoapObject(((SoapObject) prop).newInstance());
473            }
474        }
475        for (int attribIndex = 0; attribIndex < getAttributeCount(); attribIndex++) {
476            AttributeInfo newAI = new AttributeInfo();
477            getAttributeInfo(attribIndex, newAI);
478            AttributeInfo attributeInfo = newAI; // (AttributeInfo)
479                                                 // attributes.elementAt(attribIndex);
480            o.addAttribute(attributeInfo);
481        }
482        return o;
483    }
484
485    /**
486     * Sets a specified property to a certain value.
487     *
488     * @param index
489     *            the index of the specified property
490     * @param value
491     *            the new value of the property
492     */
493    public void setProperty(int index, Object value) {
494        Object prop = properties.elementAt(index);
495        if (prop instanceof PropertyInfo) {
496            ((PropertyInfo) prop).setValue(value);
497        }
498        // TODO: not sure how you want to handle an exception here if the index points to a SoapObject
499    }
500
501    /**
502     * Adds a property (parameter) to the object. This is essentially a sub
503     * element.
504     *
505     * @param name
506     *            The name of the property
507     * @param value
508     *            the value of the property
509     */
510    public SoapObject addProperty(String name, Object value) {
511        PropertyInfo propertyInfo = new PropertyInfo();
512        propertyInfo.name = name;
513        propertyInfo.type = value == null ? PropertyInfo.OBJECT_CLASS : value
514                .getClass();
515        propertyInfo.value = value;
516        propertyInfo.namespace = namespace;
517        ///M: HS20 modify by Jungo
518        return addProperty(propertyInfo);
519    }
520
521    /**
522     * Add a property only if the value is not null.
523     *
524     * @param name
525     * @param value
526     * @return
527     */
528    public SoapObject addPropertyIfValue(String name, Object value) {
529        if (value != null) {
530            return addProperty(name, value);
531        } else {
532            return this;
533        }
534    }
535
536    /**
537     * Add a property only if the value is not null.
538     *
539     * @param propertyInfo
540     * @param value
541     * @return
542     */
543    public SoapObject addPropertyIfValue(PropertyInfo propertyInfo, Object value) {
544        if (value != null) {
545            propertyInfo.setValue(value);
546            return addProperty(propertyInfo);
547        } else {
548            return this;
549        }
550    }
551
552    /**
553     * Adds a property (parameter) to the object. This is essentially a sub
554     * element.
555     *
556     * @param propertyInfo
557     *            designated retainer of desired property
558     */
559    public SoapObject addProperty(PropertyInfo propertyInfo) {
560        properties.addElement(propertyInfo);
561        return this;
562    }
563
564    /**
565     * Ad the propertyInfo only if the value of it is not null.
566     *
567     * @param propertyInfo
568     * @return
569     */
570    public SoapObject addPropertyIfValue(PropertyInfo propertyInfo) {
571        if (propertyInfo.value != null) {
572            properties.addElement(propertyInfo);
573            return this;
574        } else {
575            return this;
576        }
577    }
578
579    /**
580     * Adds a SoapObject the properties array. This is a sub element to
581     * allow nested SoapObjects
582     *
583     * @param soapObject
584     *            to be added as a property of the current object
585     */
586    public SoapObject addSoapObject(SoapObject soapObject) {
587        properties.addElement(soapObject);
588        return this;
589    }
590
591    /**
592     * Generate a {@code String} describing this object.
593     *
594     * @return
595     */
596    public String toString() {
597        StringBuffer buf = new StringBuffer(EMPTY_STRING + name + "{");
598        for (int i = 0; i < getPropertyCount(); i++) {
599            Object prop = properties.elementAt(i);
600            if (prop instanceof PropertyInfo) {
601                buf.append(EMPTY_STRING)
602                        .append(((PropertyInfo) prop).getName())
603                        .append("=")
604                        .append(getProperty(i))
605                        .append("; ");
606            } else {
607                buf.append(((SoapObject) prop).toString());
608            }
609        }
610        buf.append("}");
611        return buf.toString();
612    }
613}
614