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