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}