001package io.avaje.inject.aop;
002
003import java.lang.reflect.Method;
004import java.util.Arrays;
005
006/**
007 * Method invocation using in {@link MethodInterceptor#invoke(Invocation)} for Aspects.
008 * <p>
009 * Represents a method invocation that can be intercepted with additional before and after
010 * invocation logic.
011 */
012public interface Invocation {
013
014  /**
015   * Invoke the underlying method returning the result.
016   *
017   * @return The result of the method call. This will return null for void methods.
018   * @throws Throwable Exception thrown by underlying method
019   */
020  Object invoke() throws Throwable;
021
022  /**
023   * Invoke the underlying method returning the result. Checked exceptions will be caught and
024   * rethrown as {@code InvocationException}s.
025   *
026   * @return The result of the method call. This will return null for void methods.
027   */
028  default Object invokeUnchecked() {
029    try {
030      return invoke();
031    } catch (final RuntimeException e) {
032      throw e;
033    } catch (final Throwable e) {
034      throw new InvocationException(e);
035    }
036  }
037
038  /**
039   * Set the result that will be returned to the caller.
040   * <p>
041   * This will replace a prior result set by calling {@code #invoke} or can be used
042   * to provide a result allowing to skip calling {@code #invoke} altogether.
043   *
044   * @param result The result that will be returned to the caller.
045   */
046  void result(Object result);
047
048  /**
049   * Return the arguments used for this invocation.
050   */
051  Object[] arguments();
052
053  /**
054   * Return the arguments additionally appending the throwable.
055   */
056  Object[] arguments(Throwable e);
057
058  /**
059   * Return the method being called for this invocation.
060   */
061  Method method();
062
063  /**
064   * Return the 'this' instance of the invocation.
065   * <p>
066   * This is typically used when invoking fallback/recovery methods.
067   */
068  Object instance();
069
070  /**
071   * Invocation base type for both callable and runnable methods.
072   *
073   * @param <T> The result type
074   */
075  abstract class Base<T> implements Invocation {
076
077    protected Method method;
078    protected Object[] args;
079    protected Object instance;
080    protected T result;
081
082    /**
083     * Set the instance, method and arguments for the invocation.
084     */
085    public Base<T> with(Object instance, Method method, Object... args) {
086      this.instance = instance;
087      this.method = method;
088      this.args = args;
089      return this;
090    }
091
092    @SuppressWarnings("unchecked")
093    @Override
094    public void result(Object result) {
095      this.result = (T) result;
096    }
097
098    /**
099     * Return the final invocation result.
100     */
101    public T finalResult() {
102      return result;
103    }
104
105    @Override
106    public Object[] arguments() {
107      return args;
108    }
109
110    @Override
111    public Object[] arguments(Throwable e) {
112      if (args == null || args.length == 0) {
113        return new Object[]{e};
114      } else {
115        Object[] newArgs = Arrays.copyOf(args, args.length + 1);
116        newArgs[args.length] = e;
117        return newArgs;
118      }
119    }
120
121    @Override
122    public Method method() {
123      return method;
124    }
125
126    @Override
127    public Object instance() {
128      return instance;
129    }
130
131    /**
132     * Wrap this invocation using a methodInterceptor returning the wrapped call.
133     * <p>
134     * This invocation is effectively nested inside the returned invocation.
135     *
136     * @param methodInterceptor The method interceptor to use to wrap this call with
137     * @return The wrapped call
138     */
139    public abstract Base<T> wrap(MethodInterceptor methodInterceptor);
140  }
141
142  /**
143   * Runnable based Invocation.
144   */
145  final class Run extends Base<Void> {
146
147    private final CheckedRunnable delegate;
148
149    /**
150     * Create with a given closure to run.
151     */
152    public Run(CheckedRunnable delegate) {
153      this.delegate = delegate;
154    }
155
156    @Override
157    public Object invoke() throws Throwable {
158      delegate.invoke();
159      return null;
160    }
161
162    @Override
163    public Base<Void> wrap(MethodInterceptor methodInterceptor) {
164      return new Invocation.Run(() -> methodInterceptor.invoke(this))
165        .with(instance, method, args);
166    }
167
168  }
169
170  /**
171   * Callable based Invocation with checked exceptions.
172   */
173  final class Call<T> extends Base<T> {
174
175    private final CheckedSupplier<T> delegate;
176
177    /**
178     * Create with a given supplier.
179     */
180    public Call(CheckedSupplier<T> delegate) {
181      this.delegate = delegate;
182    }
183
184    @Override
185    public Object invoke() throws Throwable {
186      result = delegate.invoke();
187      return result;
188    }
189
190    @Override
191    public T finalResult() {
192      return result;
193    }
194
195    @Override
196    public Base<T> wrap(MethodInterceptor methodInterceptor) {
197      return new Invocation.Call<T>(() -> {
198        final Call<T> delegate = this;
199        methodInterceptor.invoke(delegate);
200        return delegate.finalResult();
201      }).with(instance, method, args);
202    }
203  }
204
205  /**
206   * Runnable with checked exceptions.
207   */
208  @FunctionalInterface
209  interface CheckedRunnable {
210
211    /**
212     * Invoke the method.
213     */
214    void invoke() throws Throwable;
215  }
216
217  /**
218   * Callable with checked exceptions.
219   *
220   * @param <T> The result type
221   */
222  @FunctionalInterface
223  interface CheckedSupplier<T> {
224
225    /**
226     * Invoke the method returning the result.
227     */
228    T invoke() throws Throwable;
229  }
230}