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}