001/**
002 *
003 * Copyright © 2014-2019 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.lang.reflect.InvocationTargetException;
021
022import org.jivesoftware.smack.packet.ExtensionElement;
023import org.jivesoftware.smack.packet.IQ;
024import org.jivesoftware.smack.packet.XmlEnvironment;
025import org.jivesoftware.smack.util.ParserUtils;
026import org.jivesoftware.smack.xml.XmlPullParser;
027import org.jivesoftware.smack.xml.XmlPullParserException;
028
029public class IntrospectionProvider{
030
031    // Unfortunately, we have to create two introspection providers, with the exactly the same code here
032
033    public abstract static class IQIntrospectionProvider<I extends IQ> extends IQProvider<I> {
034        private final Class<I> elementClass;
035
036        protected IQIntrospectionProvider(Class<I> elementClass) {
037            this.elementClass = elementClass;
038        }
039
040        @SuppressWarnings("unchecked")
041        @Override
042        public I parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
043            try {
044                return (I) parseWithIntrospection(elementClass, parser, initialDepth);
045            }
046            catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
047                            | IllegalArgumentException | InvocationTargetException | ClassNotFoundException e) {
048                // TODO: Should probably be SmackParsingException (once it exists).
049                throw new IOException(e);
050            }
051        }
052    }
053
054    public abstract static class PacketExtensionIntrospectionProvider<PE extends ExtensionElement> extends ExtensionElementProvider<PE> {
055        private final Class<PE> elementClass;
056
057        protected PacketExtensionIntrospectionProvider(Class<PE> elementClass) {
058            this.elementClass = elementClass;
059        }
060
061        @SuppressWarnings("unchecked")
062        @Override
063        public PE parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
064            try {
065                return (PE) parseWithIntrospection(elementClass, parser, initialDepth);
066            }
067            catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
068                            | IllegalArgumentException | InvocationTargetException | ClassNotFoundException e) {
069                // TODO: Should probably be SmackParsingException (once it exists).
070                throw new IOException(e);
071            }
072        }
073    }
074
075    public static Object parseWithIntrospection(Class<?> objectClass,
076                    XmlPullParser parser, final int initialDepth) throws NoSuchMethodException, SecurityException,
077                    InstantiationException, IllegalAccessException, XmlPullParserException,
078                    IOException, IllegalArgumentException, InvocationTargetException,
079                    ClassNotFoundException {
080        ParserUtils.assertAtStartTag(parser);
081        Object object = objectClass.getConstructor().newInstance();
082        outerloop: while (true) {
083            XmlPullParser.Event eventType = parser.next();
084            switch (eventType) {
085            case START_ELEMENT:
086                String name = parser.getName();
087                String stringValue = parser.nextText();
088                Class<?> propertyType = object.getClass().getMethod(
089                                "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1)).getReturnType();
090                // Get the value of the property by converting it from a
091                // String to the correct object type.
092                Object value = decode(propertyType, stringValue);
093                // Set the value of the bean.
094                object.getClass().getMethod(
095                                "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1),
096                                propertyType).invoke(object, value);
097                break;
098
099            case  END_ELEMENT:
100                if (parser.getDepth() == initialDepth) {
101                    break outerloop;
102                }
103                break;
104            default:
105                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
106                break;
107            }
108        }
109        ParserUtils.assertAtEndTag(parser);
110        return object;
111    }
112
113    /**
114     * Decodes a String into an object of the specified type. If the object
115     * type is not supported, null will be returned.
116     *
117     * @param type the type of the property.
118     * @param value the encode String value to decode.
119     * @return the String value decoded into the specified type.
120     * @throws ClassNotFoundException
121     */
122    private static Object decode(Class<?> type, String value) throws ClassNotFoundException {
123        String name = type.getName();
124        switch (name) {
125        case "java.lang.String":
126            return value;
127        case "boolean":
128            // CHECKSTYLE:OFF
129            return Boolean.valueOf(value);
130            // CHECKSTYLE:ON
131        case "int":
132            return Integer.valueOf(value);
133        case "long":
134            return Long.valueOf(value);
135        case "float":
136            return Float.valueOf(value);
137        case "double":
138            return Double.valueOf(value);
139        case "short":
140            return Short.valueOf(value);
141        case "byte":
142            return Byte.valueOf(value);
143        case "java.lang.Class":
144            return Class.forName(value);
145        }
146        return null;
147    }
148}