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