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