001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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.apache.juneau.objecttools; 018 019import static org.apache.juneau.commons.utils.Utils.*; 020 021import java.io.*; 022import java.lang.reflect.*; 023 024import org.apache.juneau.commons.reflect.*; 025import org.apache.juneau.json.*; 026import org.apache.juneau.parser.*; 027 028/** 029 * POJO method introspector. 030 * 031 * <p> 032 * This class is used to invoke methods on {@code Objects} using arguments in serialized form. 033 * </p> 034 * 035 * <h5 class='section'>Example:</h5> 036 * <p class='bjava'> 037 * String <jv>string1</jv> = <js>"foobar"</js>; 038 * String <jv>string2</jv> = ObjectIntrospector 039 * .create(<jv>string</jv>) 040 * .invoke(String.<jk>class</jk>, <js>"substring(int,int)"</js>, <js>"[3,6]"</js>); <jc>// "bar"</jc> 041 * </p> 042 * <p> 043 * The arguments passed to the identified method are POJOs serialized in JSON format. Arbitrarily complex arguments can be passed 044 * in as arguments. 045 * </p> 046 * <ul> 047 * <li class='warn'>This is an extremely powerful but potentially dangerous tool. Use wisely. 048 * </ul> 049 * 050 */ 051public class ObjectIntrospector { 052 /** 053 * Static creator. 054 * @param o The object on which Java methods will be invoked. 055 * @return A new {@link ObjectIntrospector} object. 056 */ 057 public static ObjectIntrospector create(Object o) { 058 return new ObjectIntrospector(o); 059 } 060 061 /** 062 * Static creator. 063 * @param o The object on which Java methods will be invoked. 064 * @param parser The parser to use to parse the method arguments. 065 * @return A new {@link ObjectIntrospector} object. 066 */ 067 public static ObjectIntrospector create(Object o, ReaderParser parser) { 068 return new ObjectIntrospector(o, parser); 069 } 070 071 private final Object object; 072 private final ReaderParser parser; 073 074 /** 075 * Shortcut for calling <code><jk>new</jk> ObjectIntrospector(o, <jk>null</jk>);</code> 076 * 077 * @param o The object on which Java methods will be invoked. 078 */ 079 public ObjectIntrospector(Object o) { 080 this(o, null); 081 } 082 083 /** 084 * Constructor. 085 * 086 * @param object The object on which Java methods will be invoked. 087 * @param parser The parser to use to parse the method arguments. 088 * If <jk>null</jk>, {@link JsonParser#DEFAULT} is used. 089 */ 090 public ObjectIntrospector(Object object, ReaderParser parser) { 091 if (parser == null) 092 parser = JsonParser.DEFAULT; 093 this.object = object; 094 this.parser = parser; 095 } 096 097 /** 098 * Primary method. 099 * 100 * <p> 101 * Invokes the specified method on this bean. 102 * 103 * @param <T> The return type of the method call. 104 * @param returnType The return type of the method call. 105 * @param method The method being invoked. 106 * @param args 107 * The arguments to pass as parameters to the method. 108 * These will automatically be converted to the appropriate object type if possible. 109 * Can be <jk>null</jk> if method has no arguments. 110 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>. 111 * @throws IllegalAccessException 112 * If the <c>Constructor</c> object enforces Java language access control and the underlying constructor is 113 * inaccessible. 114 * @throws IllegalArgumentException 115 * If one of the following occurs: 116 * <ul class='spaced-list'> 117 * <li> 118 * The number of actual and formal parameters differ. 119 * <li> 120 * An unwrapping conversion for primitive arguments fails. 121 * <li> 122 * A parameter value cannot be converted to the corresponding formal parameter type by a method invocation 123 * conversion. 124 * <li> 125 * The constructor pertains to an enum type. 126 * </ul> 127 * @throws InvocationTargetException If the underlying constructor throws an exception. 128 * @throws ParseException Malformed input encountered. 129 * @throws IOException Thrown by underlying stream. 130 */ 131 public <T> T invokeMethod(Class<T> returnType, Method method, Reader args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, ParseException, IOException { 132 return returnType.cast(invokeMethod(method, args)); 133 } 134 135 /** 136 * Convenience method for invoking argument from method signature (@see {@link MethodInfo#getSignature()}. 137 * 138 * @param <T> The return type of the method call. 139 * @param returnType The return type of the method call. 140 * @param method The method being invoked. 141 * @param args 142 * The arguments to pass as parameters to the method. 143 * These will automatically be converted to the appropriate object type if possible. 144 * Can be <jk>null</jk> if method has no arguments. 145 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>. 146 * @throws NoSuchMethodException If method does not exist. 147 * @throws IllegalAccessException 148 * If the <c>Constructor</c> object enforces Java language access control and 149 * the underlying constructor is inaccessible. 150 * @throws IllegalArgumentException 151 * If one of the following occurs: 152 * <ul class='spaced-list'> 153 * <li> 154 * The number of actual and formal parameters differ. 155 * <li> 156 * An unwrapping conversion for primitive arguments fails. 157 * <li> 158 * A parameter value cannot be converted to the corresponding formal parameter type by a method invocation 159 * conversion. 160 * <li> 161 * The constructor pertains to an enum type. 162 * </ul> 163 * @throws InvocationTargetException If the underlying constructor throws an exception. 164 * @throws ParseException Malformed input encountered. 165 * @throws IOException Thrown by underlying stream. 166 */ 167 public <T> T invokeMethod(Class<T> returnType, String method, String args) 168 throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException, IllegalAccessException, ParseException, IOException { 169 return returnType.cast(invokeMethod(method, args)); 170 } 171 172 /** 173 * Primary method. 174 * 175 * <p> 176 * Invokes the specified method on this bean. 177 * 178 * @param method The method being invoked. 179 * @param args 180 * The arguments to pass as parameters to the method. 181 * These will automatically be converted to the appropriate object type if possible. 182 * Can be <jk>null</jk> if method has no arguments. 183 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>. 184 * @throws IllegalAccessException 185 * If the <c>Constructor</c> object enforces Java language access control and the underlying constructor is 186 * inaccessible. 187 * @throws IllegalArgumentException 188 * If one of the following occurs: 189 * <ul class='spaced-list'> 190 * <li> 191 * The number of actual and formal parameters differ. 192 * <li> 193 * An unwrapping conversion for primitive arguments fails. 194 * <li> 195 * A parameter value cannot be converted to the corresponding formal parameter type by a method invocation 196 * conversion. 197 * <li> 198 * The constructor pertains to an enum type. 199 * </ul> 200 * @throws InvocationTargetException If the underlying constructor throws an exception. 201 * @throws ParseException Malformed input encountered. 202 * @throws IOException Thrown by underlying stream. 203 */ 204 public Object invokeMethod(Method method, Reader args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, ParseException, IOException { 205 if (object == null) 206 return null; 207 Object[] params = args == null ? null : parser.parseArgs(args, method.getGenericParameterTypes()); 208 return method.invoke(object, params); 209 } 210 211 /** 212 * Convenience method for invoking argument from method signature (@see {@link MethodInfo#getSignature()}. 213 * 214 * @param method The method being invoked. 215 * @param args 216 * The arguments to pass as parameters to the method. 217 * These will automatically be converted to the appropriate object type if possible. 218 * Can be <jk>null</jk> if method has no arguments. 219 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>. 220 * @throws NoSuchMethodException If method does not exist. 221 * @throws IllegalAccessException 222 * If the <c>Constructor</c> object enforces Java language access control and 223 * the underlying constructor is inaccessible. 224 * @throws IllegalArgumentException 225 * If one of the following occurs: 226 * <ul class='spaced-list'> 227 * <li> 228 * The number of actual and formal parameters differ. 229 * <li> 230 * An unwrapping conversion for primitive arguments fails. 231 * <li> 232 * A parameter value cannot be converted to the corresponding formal parameter type by a method invocation 233 * conversion. 234 * <li> 235 * The constructor pertains to an enum type. 236 * </ul> 237 * @throws InvocationTargetException If the underlying constructor throws an exception. 238 * @throws ParseException Malformed input encountered. 239 * @throws IOException Thrown by underlying stream. 240 */ 241 public Object invokeMethod(String method, String args) throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException, IllegalAccessException, ParseException, IOException { 242 if (object == null) 243 return null; 244 var m = parser.getBeanContext() 245 .getClassMeta(object.getClass()) 246 .getPublicMethods() 247 .stream() 248 .filter(x -> x.isNotDeprecated() && eq(x.getSignature(), method)) 249 .findFirst() 250 .orElseThrow(() -> new NoSuchMethodException(method)); 251 return invokeMethod(m.inner(), args == null ? null : new StringReader(args)); 252 } 253}