View Javadoc
1   /*
2    * Copyright (c) 2012-2024, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.aspects.aj;
31  
32  import com.jcabi.aspects.Immutable;
33  import com.jcabi.aspects.UnitedThrow;
34  import java.lang.reflect.Constructor;
35  import java.lang.reflect.Method;
36  import java.util.Objects;
37  import org.aspectj.lang.ProceedingJoinPoint;
38  import org.aspectj.lang.annotation.Around;
39  import org.aspectj.lang.annotation.Aspect;
40  import org.aspectj.lang.reflect.MethodSignature;
41  
42  /**
43   * Throw single exception out of method.
44   *
45   * @since 0.13
46   * @checkstyle NonStaticMethodCheck (100 lines)
47   */
48  @Aspect
49  @Immutable
50  public final class SingleException {
51  
52      /**
53       * Catch all exceptions and throw a single selected exception.
54       *
55       * <p>Try NOT to change the signature of this method, in order to keep
56       * it backward compatible.
57       *
58       * @param point Joint point
59       * @return The result of call
60       * @throws Throwable If something goes wrong inside
61       */
62      @Around
63          (
64              // @checkstyle StringLiteralsConcatenation (2 lines)
65              "execution(* * (..))"
66              + " && @annotation(com.jcabi.aspects.UnitedThrow)"
67          )
68      @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.PreserveStackTrace"})
69      // @checkstyle IllegalThrowsCheck (1 line)
70      public Object wrap(final ProceedingJoinPoint point) throws Throwable {
71          final Method method =
72              ((MethodSignature) point.getSignature()).getMethod();
73          final UnitedThrow annot = method.getAnnotation(UnitedThrow.class);
74          final Class<? extends Throwable> clz = SingleException.clazz(
75              method,
76              annot
77          );
78          try {
79              return point.proceed();
80              // @checkstyle IllegalCatch (1 line)
81          } catch (final Throwable ex) {
82              Throwable throwable = ex;
83              if (!clz.isAssignableFrom(ex.getClass())) {
84                  if (SingleException.exists(clz)) {
85                      throwable = clz.getConstructor(Throwable.class)
86                          .newInstance(ex);
87                  } else {
88                      throwable = clz.getConstructor().newInstance();
89                  }
90              }
91              throw throwable;
92          }
93      }
94  
95      /**
96       * Check if there is a constructor with single Throwable argument.
97       * @param clz Class to check.
98       * @return Whether constructor exists.
99       */
100     private static boolean exists(final Class<? extends Throwable> clz) {
101         boolean found = false;
102         for (final Constructor<?> ctr : clz.getConstructors()) {
103             if (ctr.getParameterTypes().length == 1
104                 && Objects.equals(ctr.getParameterTypes()[0], Throwable.class)) {
105                 found = true;
106                 break;
107             }
108         }
109         return found;
110     }
111 
112     /**
113      * Get required exception class.
114      * @param method Method declaring exception.
115      * @param annot UnitedThrow annotation.
116      * @return Class of exception.
117      */
118     @SuppressWarnings("unchecked")
119     private static Class<? extends Throwable> clazz(final Method method,
120         final UnitedThrow annot) {
121         Class<? extends Throwable> clz = annot.value();
122         if (Objects.equals(clz, UnitedThrow.None.class)) {
123             if (method.getExceptionTypes().length == 0) {
124                 clz = IllegalStateException.class;
125             } else {
126                 clz = (Class<? extends Throwable>) method
127                     .getExceptionTypes()[0];
128             }
129         }
130         return clz;
131     }
132 }