001/**
002 *
003 * Copyright 2019-2022 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.provider;
018
019import java.io.IOException;
020import java.text.ParseException;
021
022import org.jivesoftware.smack.packet.IQ;
023import org.jivesoftware.smack.packet.IqData;
024import org.jivesoftware.smack.packet.XmlEnvironment;
025import org.jivesoftware.smack.parsing.SmackParsingException;
026import org.jivesoftware.smack.util.ParserUtils;
027import org.jivesoftware.smack.xml.XmlPullParser;
028import org.jivesoftware.smack.xml.XmlPullParserException;
029
030/**
031 * An abstract class for parsing custom {@link IQ} packets. Each IqProvider must be registered with the {@link
032 * ProviderManager} for it to be used. Every implementation of this abstract class <b>must</b> have a public,
033 * no-argument constructor.
034 * <h2>Custom IQ Provider Example</h2>
035 * <p>
036 * Let us assume you want to write a provider for a new, unsupported IQ in Smack.
037 * </p>
038 * <pre>{@code
039 * <iq type='set' from='juliet@capulet.example/balcony' to='romeo@montage.example'>
040 *   <myiq xmlns='example:iq:foo' token='secret'>
041 *     <user age='42'>John Doe</user>
042 *     <location>New York</location>
043 *   </myiq>
044 * </iq>
045 * }</pre>
046 * The custom IQ provider may look like the follows
047 * <pre>{@code
048 * public class MyIQProvider extends IQProvider<MyIQ> {
049 *
050 *   {@literal @}Override
051 *   public MyIQ parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException {
052 *     // Define the data we are trying to collect with sane defaults
053 *     int age = -1;
054 *     String user = null;
055 *     String location = null;
056 *
057 *     // Start parsing loop
058 *     outerloop: while(true) {
059 *       XmlPullParser.Event eventType = parser.next();
060 *       switch(eventType) {
061 *       case START_ELEMENT:
062 *         String elementName = parser.getName();
063 *         switch (elementName) {
064 *         case "user":
065 *           age = ParserUtils.getIntegerAttribute(parser, "age");
066 *           user = parser.nextText();
067 *           break;
068 *         case "location"
069 *           location = parser.nextText();
070 *           break;
071 *         }
072 *         break;
073 *       case END_ELEMENT:
074 *         // Abort condition: if the are on a end tag (closing element) of the same depth
075 *         if (parser.getDepth() == initialDepth) {
076 *           break outerloop;
077 *         }
078 *         break;
079 *       default:
080 *         // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
081 *         break;
082 *       }
083 *     }
084 *
085 *     // Construct the IQ instance at the end of parsing, when all data has been collected
086 *     return new MyIQ(user, age, location);
087 *   }
088 * }
089 * }</pre>
090 *
091 * @param <I> the {@link IQ} that is parsed by implementations.
092 */
093public abstract class IqProvider<I extends IQ> extends AbstractProvider<I> {
094
095    public final I parse(XmlPullParser parser, IqData iqCommon)
096                    throws XmlPullParserException, IOException, SmackParsingException {
097        return parse(parser, iqCommon, null);
098    }
099
100    public final I parse(XmlPullParser parser, IqData iqData, XmlEnvironment outerXmlEnvironment)
101                    throws XmlPullParserException, IOException, SmackParsingException {
102        final int initialDepth = parser.getDepth();
103        final XmlEnvironment xmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
104
105        I i = wrapExceptions(() -> parse(parser, initialDepth, iqData, xmlEnvironment));
106
107        // Parser should be at end tag of the consumed/parsed element
108        ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
109        return i;
110    }
111
112    public abstract I parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment)
113                    throws XmlPullParserException, IOException, SmackParsingException, ParseException;
114
115}