1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2007 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smack.util;
22
23import java.io.ByteArrayInputStream;
24import java.io.IOException;
25import java.io.ObjectInputStream;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Map;
31
32import org.jivesoftware.smack.Connection;
33import org.jivesoftware.smack.packet.Authentication;
34import org.jivesoftware.smack.packet.Bind;
35import org.jivesoftware.smack.packet.DefaultPacketExtension;
36import org.jivesoftware.smack.packet.IQ;
37import org.jivesoftware.smack.packet.Message;
38import org.jivesoftware.smack.packet.Packet;
39import org.jivesoftware.smack.packet.PacketExtension;
40import org.jivesoftware.smack.packet.Presence;
41import org.jivesoftware.smack.packet.Registration;
42import org.jivesoftware.smack.packet.RosterPacket;
43import org.jivesoftware.smack.packet.StreamError;
44import org.jivesoftware.smack.packet.XMPPError;
45import org.jivesoftware.smack.provider.IQProvider;
46import org.jivesoftware.smack.provider.PacketExtensionProvider;
47import org.jivesoftware.smack.provider.ProviderManager;
48import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
49import org.xmlpull.v1.XmlPullParser;
50import org.xmlpull.v1.XmlPullParserException;
51
52/**
53 * Utility class that helps to parse packets. Any parsing packets method that must be shared
54 * between many clients must be placed in this utility class.
55 *
56 * @author Gaston Dombiak
57 */
58public class PacketParserUtils {
59
60    /**
61     * Namespace used to store packet properties.
62     */
63    private static final String PROPERTIES_NAMESPACE =
64            "http://www.jivesoftware.com/xmlns/xmpp/properties";
65
66    /**
67     * Parses a message packet.
68     *
69     * @param parser the XML parser, positioned at the start of a message packet.
70     * @return a Message packet.
71     * @throws Exception if an exception occurs while parsing the packet.
72     */
73    public static Packet parseMessage(XmlPullParser parser) throws Exception {
74        Message message = new Message();
75        String id = parser.getAttributeValue("", "id");
76        message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
77        message.setTo(parser.getAttributeValue("", "to"));
78        message.setFrom(parser.getAttributeValue("", "from"));
79        message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
80        String language = getLanguageAttribute(parser);
81
82        // determine message's default language
83        String defaultLanguage = null;
84        if (language != null && !"".equals(language.trim())) {
85            message.setLanguage(language);
86            defaultLanguage = language;
87        }
88        else {
89            defaultLanguage = Packet.getDefaultLanguage();
90        }
91
92        // Parse sub-elements. We include extra logic to make sure the values
93        // are only read once. This is because it's possible for the names to appear
94        // in arbitrary sub-elements.
95        boolean done = false;
96        String thread = null;
97        Map<String, Object> properties = null;
98        while (!done) {
99            int eventType = parser.next();
100            if (eventType == XmlPullParser.START_TAG) {
101                String elementName = parser.getName();
102                String namespace = parser.getNamespace();
103                if (elementName.equals("subject")) {
104                    String xmlLang = getLanguageAttribute(parser);
105                    if (xmlLang == null) {
106                        xmlLang = defaultLanguage;
107                    }
108
109                    String subject = parseContent(parser);
110
111                    if (message.getSubject(xmlLang) == null) {
112                        message.addSubject(xmlLang, subject);
113                    }
114                }
115                else if (elementName.equals("body")) {
116                    String xmlLang = getLanguageAttribute(parser);
117                    if (xmlLang == null) {
118                        xmlLang = defaultLanguage;
119                    }
120
121                    String body = parseContent(parser);
122
123                    if (message.getBody(xmlLang) == null) {
124                        message.addBody(xmlLang, body);
125                    }
126                }
127                else if (elementName.equals("thread")) {
128                    if (thread == null) {
129                        thread = parser.nextText();
130                    }
131                }
132                else if (elementName.equals("error")) {
133                    message.setError(parseError(parser));
134                }
135                else if (elementName.equals("properties") &&
136                        namespace.equals(PROPERTIES_NAMESPACE))
137                {
138                    properties = parseProperties(parser);
139                }
140                // Otherwise, it must be a packet extension.
141                else {
142                    message.addExtension(
143                    PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
144                }
145            }
146            else if (eventType == XmlPullParser.END_TAG) {
147                if (parser.getName().equals("message")) {
148                    done = true;
149                }
150            }
151        }
152
153        message.setThread(thread);
154        // Set packet properties.
155        if (properties != null) {
156            for (String name : properties.keySet()) {
157                message.setProperty(name, properties.get(name));
158            }
159        }
160        return message;
161    }
162
163    /**
164     * Returns the content of a tag as string regardless of any tags included.
165     *
166     * @param parser the XML pull parser
167     * @return the content of a tag as string
168     * @throws XmlPullParserException if parser encounters invalid XML
169     * @throws IOException if an IO error occurs
170     */
171    private static String parseContent(XmlPullParser parser)
172                    throws XmlPullParserException, IOException {
173        StringBuffer content = new StringBuffer();
174        int parserDepth = parser.getDepth();
175        while (!(parser.next() == XmlPullParser.END_TAG && parser
176                        .getDepth() == parserDepth)) {
177            content.append(parser.getText());
178        }
179        return content.toString();
180    }
181
182    /**
183     * Parses a presence packet.
184     *
185     * @param parser the XML parser, positioned at the start of a presence packet.
186     * @return a Presence packet.
187     * @throws Exception if an exception occurs while parsing the packet.
188     */
189    public static Presence parsePresence(XmlPullParser parser) throws Exception {
190        Presence.Type type = Presence.Type.available;
191        String typeString = parser.getAttributeValue("", "type");
192        if (typeString != null && !typeString.equals("")) {
193            try {
194                type = Presence.Type.valueOf(typeString);
195            }
196            catch (IllegalArgumentException iae) {
197                System.err.println("Found invalid presence type " + typeString);
198            }
199        }
200        Presence presence = new Presence(type);
201        presence.setTo(parser.getAttributeValue("", "to"));
202        presence.setFrom(parser.getAttributeValue("", "from"));
203        String id = parser.getAttributeValue("", "id");
204        presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
205
206        String language = getLanguageAttribute(parser);
207        if (language != null && !"".equals(language.trim())) {
208        	presence.setLanguage(language);
209        }
210        presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
211
212        // Parse sub-elements
213        boolean done = false;
214        while (!done) {
215            int eventType = parser.next();
216            if (eventType == XmlPullParser.START_TAG) {
217                String elementName = parser.getName();
218                String namespace = parser.getNamespace();
219                if (elementName.equals("status")) {
220                    presence.setStatus(parser.nextText());
221                }
222                else if (elementName.equals("priority")) {
223                    try {
224                        int priority = Integer.parseInt(parser.nextText());
225                        presence.setPriority(priority);
226                    }
227                    catch (NumberFormatException nfe) {
228                        // Ignore.
229                    }
230                    catch (IllegalArgumentException iae) {
231                        // Presence priority is out of range so assume priority to be zero
232                        presence.setPriority(0);
233                    }
234                }
235                else if (elementName.equals("show")) {
236                    String modeText = parser.nextText();
237                    try {
238                        presence.setMode(Presence.Mode.valueOf(modeText));
239                    }
240                    catch (IllegalArgumentException iae) {
241                        System.err.println("Found invalid presence mode " + modeText);
242                    }
243                }
244                else if (elementName.equals("error")) {
245                    presence.setError(parseError(parser));
246                }
247                else if (elementName.equals("properties") &&
248                        namespace.equals(PROPERTIES_NAMESPACE))
249                {
250                    Map<String,Object> properties = parseProperties(parser);
251                    // Set packet properties.
252                    for (String name : properties.keySet()) {
253                        presence.setProperty(name, properties.get(name));
254                    }
255                }
256                // Otherwise, it must be a packet extension.
257                else {
258                	try {
259                        presence.addExtension(PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
260                	}
261                	catch (Exception e) {
262                		System.err.println("Failed to parse extension packet in Presence packet.");
263                	}
264                }
265            }
266            else if (eventType == XmlPullParser.END_TAG) {
267                if (parser.getName().equals("presence")) {
268                    done = true;
269                }
270            }
271        }
272        return presence;
273    }
274
275    /**
276     * Parses an IQ packet.
277     *
278     * @param parser the XML parser, positioned at the start of an IQ packet.
279     * @return an IQ object.
280     * @throws Exception if an exception occurs while parsing the packet.
281     */
282    public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception {
283        IQ iqPacket = null;
284
285        String id = parser.getAttributeValue("", "id");
286        String to = parser.getAttributeValue("", "to");
287        String from = parser.getAttributeValue("", "from");
288        IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
289        XMPPError error = null;
290
291        boolean done = false;
292        while (!done) {
293            int eventType = parser.next();
294
295            if (eventType == XmlPullParser.START_TAG) {
296                String elementName = parser.getName();
297                String namespace = parser.getNamespace();
298                if (elementName.equals("error")) {
299                    error = PacketParserUtils.parseError(parser);
300                }
301                else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) {
302                    iqPacket = parseAuthentication(parser);
303                }
304                else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) {
305                    iqPacket = parseRoster(parser);
306                }
307                else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
308                    iqPacket = parseRegistration(parser);
309                }
310                else if (elementName.equals("bind") &&
311                        namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
312                    iqPacket = parseResourceBinding(parser);
313                }
314                // Otherwise, see if there is a registered provider for
315                // this element name and namespace.
316                else {
317                    Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace);
318                    if (provider != null) {
319                        if (provider instanceof IQProvider) {
320                            iqPacket = ((IQProvider)provider).parseIQ(parser);
321                        }
322                        else if (provider instanceof Class) {
323                            iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
324                                    (Class<?>)provider, parser);
325                        }
326                    }
327                    // Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood
328                    // have to be answered with an IQ error response. See the code a few lines below
329                    else if (IQ.Type.RESULT == type){
330                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
331                        // so that the content of the IQ can be examined later on
332                        iqPacket = new UnparsedResultIQ(parseContent(parser));
333                    }
334                }
335            }
336            else if (eventType == XmlPullParser.END_TAG) {
337                if (parser.getName().equals("iq")) {
338                    done = true;
339                }
340            }
341        }
342        // Decide what to do when an IQ packet was not understood
343        if (iqPacket == null) {
344            if (IQ.Type.GET == type || IQ.Type.SET == type ) {
345                // If the IQ stanza is of type "get" or "set" containing a child element
346                // qualified by a namespace it does not understand, then answer an IQ of
347                // type "error" with code 501 ("feature-not-implemented")
348                iqPacket = new IQ() {
349                    @Override
350                    public String getChildElementXML() {
351                        return null;
352                    }
353                };
354                iqPacket.setPacketID(id);
355                iqPacket.setTo(from);
356                iqPacket.setFrom(to);
357                iqPacket.setType(IQ.Type.ERROR);
358                iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented));
359                connection.sendPacket(iqPacket);
360                return null;
361            }
362            else {
363                // If an IQ packet wasn't created above, create an empty IQ packet.
364                iqPacket = new IQ() {
365                    @Override
366                    public String getChildElementXML() {
367                        return null;
368                    }
369                };
370            }
371        }
372
373        // Set basic values on the iq packet.
374        iqPacket.setPacketID(id);
375        iqPacket.setTo(to);
376        iqPacket.setFrom(from);
377        iqPacket.setType(type);
378        iqPacket.setError(error);
379
380        return iqPacket;
381    }
382
383    private static Authentication parseAuthentication(XmlPullParser parser) throws Exception {
384        Authentication authentication = new Authentication();
385        boolean done = false;
386        while (!done) {
387            int eventType = parser.next();
388            if (eventType == XmlPullParser.START_TAG) {
389                if (parser.getName().equals("username")) {
390                    authentication.setUsername(parser.nextText());
391                }
392                else if (parser.getName().equals("password")) {
393                    authentication.setPassword(parser.nextText());
394                }
395                else if (parser.getName().equals("digest")) {
396                    authentication.setDigest(parser.nextText());
397                }
398                else if (parser.getName().equals("resource")) {
399                    authentication.setResource(parser.nextText());
400                }
401            }
402            else if (eventType == XmlPullParser.END_TAG) {
403                if (parser.getName().equals("query")) {
404                    done = true;
405                }
406            }
407        }
408        return authentication;
409    }
410
411    private static RosterPacket parseRoster(XmlPullParser parser) throws Exception {
412        RosterPacket roster = new RosterPacket();
413        boolean done = false;
414        RosterPacket.Item item = null;
415        while (!done) {
416        	if(parser.getEventType()==XmlPullParser.START_TAG &&
417        			parser.getName().equals("query")){
418        		String version = parser.getAttributeValue(null, "ver");
419        		roster.setVersion(version);
420        	}
421            int eventType = parser.next();
422            if (eventType == XmlPullParser.START_TAG) {
423                if (parser.getName().equals("item")) {
424                    String jid = parser.getAttributeValue("", "jid");
425                    String name = parser.getAttributeValue("", "name");
426                    // Create packet.
427                    item = new RosterPacket.Item(jid, name);
428                    // Set status.
429                    String ask = parser.getAttributeValue("", "ask");
430                    RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask);
431                    item.setItemStatus(status);
432                    // Set type.
433                    String subscription = parser.getAttributeValue("", "subscription");
434                    RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
435                    item.setItemType(type);
436                }
437                if (parser.getName().equals("group") && item!= null) {
438                    final String groupName = parser.nextText();
439                    if (groupName != null && groupName.trim().length() > 0) {
440                        item.addGroupName(groupName);
441                    }
442                }
443            }
444            else if (eventType == XmlPullParser.END_TAG) {
445                if (parser.getName().equals("item")) {
446                    roster.addRosterItem(item);
447                }
448                if (parser.getName().equals("query")) {
449                    done = true;
450                }
451            }
452        }
453        return roster;
454    }
455
456     private static Registration parseRegistration(XmlPullParser parser) throws Exception {
457        Registration registration = new Registration();
458        Map<String, String> fields = null;
459        boolean done = false;
460        while (!done) {
461            int eventType = parser.next();
462            if (eventType == XmlPullParser.START_TAG) {
463                // Any element that's in the jabber:iq:register namespace,
464                // attempt to parse it if it's in the form <name>value</name>.
465                if (parser.getNamespace().equals("jabber:iq:register")) {
466                    String name = parser.getName();
467                    String value = "";
468                    if (fields == null) {
469                        fields = new HashMap<String, String>();
470                    }
471
472                    if (parser.next() == XmlPullParser.TEXT) {
473                        value = parser.getText();
474                    }
475                    // Ignore instructions, but anything else should be added to the map.
476                    if (!name.equals("instructions")) {
477                        fields.put(name, value);
478                    }
479                    else {
480                        registration.setInstructions(value);
481                    }
482                }
483                // Otherwise, it must be a packet extension.
484                else {
485                    registration.addExtension(
486                        PacketParserUtils.parsePacketExtension(
487                            parser.getName(),
488                            parser.getNamespace(),
489                            parser));
490                }
491            }
492            else if (eventType == XmlPullParser.END_TAG) {
493                if (parser.getName().equals("query")) {
494                    done = true;
495                }
496            }
497        }
498        registration.setAttributes(fields);
499        return registration;
500    }
501
502    private static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
503            XmlPullParserException {
504        Bind bind = new Bind();
505        boolean done = false;
506        while (!done) {
507            int eventType = parser.next();
508            if (eventType == XmlPullParser.START_TAG) {
509                if (parser.getName().equals("resource")) {
510                    bind.setResource(parser.nextText());
511                }
512                else if (parser.getName().equals("jid")) {
513                    bind.setJid(parser.nextText());
514                }
515            } else if (eventType == XmlPullParser.END_TAG) {
516                if (parser.getName().equals("bind")) {
517                    done = true;
518                }
519            }
520        }
521
522        return bind;
523    }
524
525    /**
526     * Parse the available SASL mechanisms reported from the server.
527     *
528     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
529     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
530     * @throws Exception if an exception occurs while parsing the stanza.
531     */
532    public static Collection<String> parseMechanisms(XmlPullParser parser) throws Exception {
533        List<String> mechanisms = new ArrayList<String>();
534        boolean done = false;
535        while (!done) {
536            int eventType = parser.next();
537
538            if (eventType == XmlPullParser.START_TAG) {
539                String elementName = parser.getName();
540                if (elementName.equals("mechanism")) {
541                    mechanisms.add(parser.nextText());
542                }
543            }
544            else if (eventType == XmlPullParser.END_TAG) {
545                if (parser.getName().equals("mechanisms")) {
546                    done = true;
547                }
548            }
549        }
550        return mechanisms;
551    }
552
553    /**
554     * Parse the available compression methods reported from the server.
555     *
556     * @param parser the XML parser, positioned at the start of the compression stanza.
557     * @return a collection of Stings with the methods included in the compression stanza.
558     * @throws Exception if an exception occurs while parsing the stanza.
559     */
560    public static Collection<String> parseCompressionMethods(XmlPullParser parser)
561            throws IOException, XmlPullParserException {
562        List<String> methods = new ArrayList<String>();
563        boolean done = false;
564        while (!done) {
565            int eventType = parser.next();
566
567            if (eventType == XmlPullParser.START_TAG) {
568                String elementName = parser.getName();
569                if (elementName.equals("method")) {
570                    methods.add(parser.nextText());
571                }
572            }
573            else if (eventType == XmlPullParser.END_TAG) {
574                if (parser.getName().equals("compression")) {
575                    done = true;
576                }
577            }
578        }
579        return methods;
580    }
581
582    /**
583     * Parse a properties sub-packet. If any errors occur while de-serializing Java object
584     * properties, an exception will be printed and not thrown since a thrown
585     * exception will shut down the entire connection. ClassCastExceptions will occur
586     * when both the sender and receiver of the packet don't have identical versions
587     * of the same class.
588     *
589     * @param parser the XML parser, positioned at the start of a properties sub-packet.
590     * @return a map of the properties.
591     * @throws Exception if an error occurs while parsing the properties.
592     */
593    public static Map<String, Object> parseProperties(XmlPullParser parser) throws Exception {
594        Map<String, Object> properties = new HashMap<String, Object>();
595        while (true) {
596            int eventType = parser.next();
597            if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
598                // Parse a property
599                boolean done = false;
600                String name = null;
601                String type = null;
602                String valueText = null;
603                Object value = null;
604                while (!done) {
605                    eventType = parser.next();
606                    if (eventType == XmlPullParser.START_TAG) {
607                        String elementName = parser.getName();
608                        if (elementName.equals("name")) {
609                            name = parser.nextText();
610                        }
611                        else if (elementName.equals("value")) {
612                            type = parser.getAttributeValue("", "type");
613                            valueText = parser.nextText();
614                        }
615                    }
616                    else if (eventType == XmlPullParser.END_TAG) {
617                        if (parser.getName().equals("property")) {
618                            if ("integer".equals(type)) {
619                                value = Integer.valueOf(valueText);
620                            }
621                            else if ("long".equals(type))  {
622                                value = Long.valueOf(valueText);
623                            }
624                            else if ("float".equals(type)) {
625                                value = Float.valueOf(valueText);
626                            }
627                            else if ("double".equals(type)) {
628                                value = Double.valueOf(valueText);
629                            }
630                            else if ("boolean".equals(type)) {
631                                value = Boolean.valueOf(valueText);
632                            }
633                            else if ("string".equals(type)) {
634                                value = valueText;
635                            }
636                            else if ("java-object".equals(type)) {
637                                try {
638                                    byte [] bytes = StringUtils.decodeBase64(valueText);
639                                    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
640                                    value = in.readObject();
641                                }
642                                catch (Exception e) {
643                                    e.printStackTrace();
644                                }
645                            }
646                            if (name != null && value != null) {
647                                properties.put(name, value);
648                            }
649                            done = true;
650                        }
651                    }
652                }
653            }
654            else if (eventType == XmlPullParser.END_TAG) {
655                if (parser.getName().equals("properties")) {
656                    break;
657                }
658            }
659        }
660        return properties;
661    }
662
663    /**
664     * Parses SASL authentication error packets.
665     *
666     * @param parser the XML parser.
667     * @return a SASL Failure packet.
668     * @throws Exception if an exception occurs while parsing the packet.
669     */
670    public static Failure parseSASLFailure(XmlPullParser parser) throws Exception {
671        String condition = null;
672        boolean done = false;
673        while (!done) {
674            int eventType = parser.next();
675
676            if (eventType == XmlPullParser.START_TAG) {
677                if (!parser.getName().equals("failure")) {
678                    condition = parser.getName();
679                }
680            }
681            else if (eventType == XmlPullParser.END_TAG) {
682                if (parser.getName().equals("failure")) {
683                    done = true;
684                }
685            }
686        }
687        return new Failure(condition);
688    }
689
690    /**
691     * Parses stream error packets.
692     *
693     * @param parser the XML parser.
694     * @return an stream error packet.
695     * @throws Exception if an exception occurs while parsing the packet.
696     */
697    public static StreamError parseStreamError(XmlPullParser parser) throws IOException,
698            XmlPullParserException {
699    StreamError streamError = null;
700    boolean done = false;
701    while (!done) {
702        int eventType = parser.next();
703
704        if (eventType == XmlPullParser.START_TAG) {
705            streamError = new StreamError(parser.getName());
706        }
707        else if (eventType == XmlPullParser.END_TAG) {
708            if (parser.getName().equals("error")) {
709                done = true;
710            }
711        }
712    }
713    return streamError;
714}
715
716    /**
717     * Parses error sub-packets.
718     *
719     * @param parser the XML parser.
720     * @return an error sub-packet.
721     * @throws Exception if an exception occurs while parsing the packet.
722     */
723    public static XMPPError parseError(XmlPullParser parser) throws Exception {
724        final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
725    	String errorCode = "-1";
726        String type = null;
727        String message = null;
728        String condition = null;
729        List<PacketExtension> extensions = new ArrayList<PacketExtension>();
730
731        // Parse the error header
732        for (int i=0; i<parser.getAttributeCount(); i++) {
733            if (parser.getAttributeName(i).equals("code")) {
734                errorCode = parser.getAttributeValue("", "code");
735            }
736            if (parser.getAttributeName(i).equals("type")) {
737            	type = parser.getAttributeValue("", "type");
738            }
739        }
740        boolean done = false;
741        // Parse the text and condition tags
742        while (!done) {
743            int eventType = parser.next();
744            if (eventType == XmlPullParser.START_TAG) {
745                if (parser.getName().equals("text")) {
746                    message = parser.nextText();
747                }
748                else {
749                	// Condition tag, it can be xmpp error or an application defined error.
750                    String elementName = parser.getName();
751                    String namespace = parser.getNamespace();
752                    if (errorNamespace.equals(namespace)) {
753                    	condition = elementName;
754                    }
755                    else {
756                    	extensions.add(parsePacketExtension(elementName, namespace, parser));
757                    }
758                }
759            }
760                else if (eventType == XmlPullParser.END_TAG) {
761                    if (parser.getName().equals("error")) {
762                        done = true;
763                    }
764                }
765        }
766        // Parse the error type.
767        XMPPError.Type errorType = XMPPError.Type.CANCEL;
768        try {
769            if (type != null) {
770                errorType = XMPPError.Type.valueOf(type.toUpperCase());
771            }
772        }
773        catch (IllegalArgumentException iae) {
774            // Print stack trace. We shouldn't be getting an illegal error type.
775            iae.printStackTrace();
776        }
777        return new XMPPError(Integer.parseInt(errorCode), errorType, condition, message, extensions);
778    }
779
780    /**
781     * Parses a packet extension sub-packet.
782     *
783     * @param elementName the XML element name of the packet extension.
784     * @param namespace the XML namespace of the packet extension.
785     * @param parser the XML parser, positioned at the starting element of the extension.
786     * @return a PacketExtension.
787     * @throws Exception if a parsing error occurs.
788     */
789    public static PacketExtension parsePacketExtension(String elementName, String namespace, XmlPullParser parser)
790            throws Exception
791    {
792        // See if a provider is registered to handle the extension.
793        Object provider = ProviderManager.getInstance().getExtensionProvider(elementName, namespace);
794        if (provider != null) {
795            if (provider instanceof PacketExtensionProvider) {
796                return ((PacketExtensionProvider)provider).parseExtension(parser);
797            }
798            else if (provider instanceof Class) {
799                return (PacketExtension)parseWithIntrospection(
800                        elementName, (Class<?>)provider, parser);
801            }
802        }
803        // No providers registered, so use a default extension.
804        DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
805        boolean done = false;
806        while (!done) {
807            int eventType = parser.next();
808            if (eventType == XmlPullParser.START_TAG) {
809                String name = parser.getName();
810                // If an empty element, set the value with the empty string.
811                if (parser.isEmptyElementTag()) {
812                    extension.setValue(name,"");
813                }
814                // Otherwise, get the the element text.
815                else {
816                    eventType = parser.next();
817                    if (eventType == XmlPullParser.TEXT) {
818                        String value = parser.getText();
819                        extension.setValue(name, value);
820                    }
821                }
822            }
823            else if (eventType == XmlPullParser.END_TAG) {
824                if (parser.getName().equals(elementName)) {
825                    done = true;
826                }
827            }
828        }
829        return extension;
830    }
831
832    private static String getLanguageAttribute(XmlPullParser parser) {
833    	for (int i = 0; i < parser.getAttributeCount(); i++) {
834            String attributeName = parser.getAttributeName(i);
835            if ( "xml:lang".equals(attributeName) ||
836                    ("lang".equals(attributeName) &&
837                            "xml".equals(parser.getAttributePrefix(i)))) {
838    			return parser.getAttributeValue(i);
839    		}
840    	}
841    	return null;
842    }
843
844    public static Object parseWithIntrospection(String elementName,
845            Class<?> objectClass, XmlPullParser parser) throws Exception
846    {
847        boolean done = false;
848        Object object = objectClass.newInstance();
849        while (!done) {
850            int eventType = parser.next();
851            if (eventType == XmlPullParser.START_TAG) {
852                String name = parser.getName();
853                String stringValue = parser.nextText();
854                Class propertyType = object.getClass().getMethod(
855                    "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1)).getReturnType();
856                // Get the value of the property by converting it from a
857                // String to the correct object type.
858                Object value = decode(propertyType, stringValue);
859                // Set the value of the bean.
860                object.getClass().getMethod("set" + Character.toUpperCase(name.charAt(0)) + name.substring(1), propertyType)
861                .invoke(object, value);
862            }
863            else if (eventType == XmlPullParser.END_TAG) {
864                if (parser.getName().equals(elementName)) {
865                    done = true;
866                }
867            }
868        }
869        return object;
870            }
871
872    /**
873     * Decodes a String into an object of the specified type. If the object
874     * type is not supported, null will be returned.
875     *
876     * @param type the type of the property.
877     * @param value the encode String value to decode.
878     * @return the String value decoded into the specified type.
879     * @throws Exception If decoding failed due to an error.
880     */
881    private static Object decode(Class<?> type, String value) throws Exception {
882        if (type.getName().equals("java.lang.String")) {
883            return value;
884        }
885        if (type.getName().equals("boolean")) {
886            return Boolean.valueOf(value);
887        }
888        if (type.getName().equals("int")) {
889            return Integer.valueOf(value);
890        }
891        if (type.getName().equals("long")) {
892            return Long.valueOf(value);
893        }
894        if (type.getName().equals("float")) {
895            return Float.valueOf(value);
896        }
897        if (type.getName().equals("double")) {
898            return Double.valueOf(value);
899        }
900        if (type.getName().equals("java.lang.Class")) {
901            return Class.forName(value);
902        }
903        return null;
904    }
905
906    /**
907     * This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider
908     * was found for the IQ element.
909     *
910     * The child elements can be examined with the getChildElementXML() method.
911     *
912     */
913    public static class UnparsedResultIQ extends IQ {
914        public UnparsedResultIQ(String content) {
915            this.str = content;
916        }
917
918        private final String str;
919
920        @Override
921        public String getChildElementXML() {
922            return this.str;
923        }
924    }
925}
926