001package io.avaje.inject.aop;
002
003import java.lang.reflect.Method;
004import java.util.function.Consumer;
005import java.util.function.Function;
006
007/**
008 * Method invocation using in {@link MethodInterceptor#invoke(Invocation)} for Aspects.
009 * <p>
010 * Represents a method invocation that can be intercepted with additional before and after
011 * invocation logic.
012 */
013public interface Invocation {
014
015  /**
016   * Invoke the underlying method returning the result.
017   *
018   * @return The result of the method call. This will return null for void methods.
019   * @throws Throwable Exception thrown by underlying method
020   */
021  Object invoke() throws Throwable;
022
023  /**
024   * Invoke the underlying method returning the result. Checked exceptions will be caught and
025   * rethrown as {@code InvocationException}s.
026   *
027   * @return The result of the method call. This will return null for void methods.
028   */
029  default Object invokeUnchecked() {
030    try {
031      return invoke();
032    } catch (final RuntimeException e) {
033      throw e;
034    } catch (final Throwable e) {
035      throw new InvocationException(e);
036    }
037  }
038
039  /**
040   * Set the result that will be returned to the caller.
041   * <p>
042   * This will replace a prior result set by calling {@code #invoke} or can be used
043   * to provide a result allowing to skip calling {@code #invoke} altogether.
044   *
045   * @param result The result that will be returned to the caller.
046   */
047  void result(Object result);
048
049  /**
050   * Return the arguments used for this invocation.
051   */
052  Object[] arguments();
053
054  /**
055   * Return the method being called for this invocation.
056   */
057  Method method();
058
059  /**
060   * Return the 'this' instance of the invocation.
061   * <p>
062   * This is typically used when invoking fallback/recovery methods.
063   */
064  Object instance();
065
066  /**
067   * Return whether this invocation has a registered recovery method
068   */
069  boolean hasRecoveryMethod();
070
071  /**
072   * Invoke the recovery method associated for this invocation and return the result.
073   *
074   * @return The result of the method call. This will return null for void methods.
075   * @throws IllegalStateException if no fallback method is configured with this invocation
076   */
077  Object invokeRecoveryMethod(Throwable t);
078
079  /**
080   * Invocation base type for both callable and runnable methods.
081   *
082   * @param <T> The result type
083   */
084  abstract class Base<T> implements Invocation {
085
086    protected Method method;
087    protected Object[] args;
088    protected Object instance;
089    protected T result;
090
091    /**
092     * Set the instance, method and arguments for the invocation.
093     */
094    public Base<T> with(Object instance, Method method, Object... args) {
095      this.instance = instance;
096      this.method = method;
097      this.args = args;
098      return this;
099    }
100
101    @SuppressWarnings("unchecked")
102    @Override
103    public void result(Object result) {
104      this.result = (T) result;
105    }
106
107    /**
108     * Return the final invocation result.
109     */
110    public T finalResult() {
111      return result;
112    }
113
114    @Override
115    public Object[] arguments() {
116      return args;
117    }
118
119    @Override
120    public Method method() {
121      return method;
122    }
123
124    @Override
125    public Object instance() {
126      return instance;
127    }
128
129    /**
130     * Wrap this invocation using a methodInterceptor returning the wrapped call.
131     * <p>
132     * This invocation is effectively nested inside the returned invocation.
133     *
134     * @param methodInterceptor The method interceptor to use to wrap this call with
135     * @return The wrapped call
136     */
137    public abstract Base<T> wrap(MethodInterceptor methodInterceptor);
138
139    protected void noRecovery(Object recover) {
140      if (recover == null) {
141        throw new IllegalStateException("No recovery method available for this invocation");
142      }
143    }
144  }
145
146  /**
147   * Runnable based Invocation.
148   */
149  final class Run extends Base<Void> {
150
151    private final CheckedRunnable delegate;
152    private Consumer<Throwable> fallback;
153
154    /**
155     * Create with a given closure to run.
156     */
157    public Run(CheckedRunnable delegate) {
158      this.delegate = delegate;
159    }
160
161    @Override
162    public Object invoke() throws Throwable {
163      delegate.invoke();
164      return null;
165    }
166
167    /**
168     * Register a fallback method which can be used to recover from an exception
169     * while intercepting an invocation
170     */
171    public Run fallback(Consumer<Throwable> fallback) {
172      this.fallback = fallback;
173      return this;
174    }
175
176    @Override
177    public Base<Void> wrap(MethodInterceptor methodInterceptor) {
178      return new Invocation.Run(() -> methodInterceptor.invoke(this)).with(instance, method, args);
179    }
180
181    @Override
182    public boolean hasRecoveryMethod() {
183      return fallback != null;
184    }
185
186    @Override
187    public Object invokeRecoveryMethod(Throwable t) {
188      noRecovery(fallback);
189      fallback.accept(t);
190      return null;
191    }
192
193    @Override
194    public Run with(Object instance, Method method, Object... args) {
195      super.with(instance, method, args);
196      return this;
197    }
198  }
199
200  /**
201   * Callable based Invocation with checked exceptions.
202   */
203  final class Call<T> extends Base<T> {
204
205    private final CheckedSupplier<T> delegate;
206    private Function<Throwable, T> fallback;
207
208    /**
209     * Create with a given supplier.
210     */
211    public Call(CheckedSupplier<T> delegate) {
212      this.delegate = delegate;
213    }
214
215    @Override
216    public Object invoke() throws Throwable {
217      result = delegate.invoke();
218      return result;
219    }
220
221    @Override
222    public T finalResult() {
223      return result;
224    }
225
226    @Override
227    public Call<T> with(Object instance, Method method, Object... args) {
228      super.with(instance, method, args);
229      return this;
230    }
231
232    /**
233     * register a fallback method which can be used to recover from an exception while intercepting
234     * an invocation
235     */
236    public Call<T> fallback(Function<Throwable, T> fallback) {
237      this.fallback = fallback;
238      return this;
239    }
240
241    @Override
242    public Base<T> wrap(MethodInterceptor methodInterceptor) {
243      return new Invocation.Call<>(() -> {
244        final Call<T> delegate = this;
245        methodInterceptor.invoke(delegate);
246        return delegate.finalResult();
247      }).with(instance, method, args);
248    }
249
250    @Override
251    public boolean hasRecoveryMethod() {
252      return fallback != null;
253    }
254
255    @Override
256    public Object invokeRecoveryMethod(Throwable t) {
257      noRecovery(fallback);
258      var result = fallback.apply(t);
259      super.result(result);
260      return result;
261    }
262  }
263
264  /**
265   * Runnable with checked exceptions.
266   */
267  @FunctionalInterface
268  interface CheckedRunnable {
269
270    /**
271     * Invoke the method.
272     */
273    void invoke() throws Throwable;
274  }
275
276  /**
277   * Callable with checked exceptions.
278   *
279   * @param <T> The result type
280   */
281  @FunctionalInterface
282  interface CheckedSupplier<T> {
283
284    /**
285     * Invoke the method returning the result.
286     */
287    T invoke() throws Throwable;
288  }
289}