1717package org .apache .commons .lang3 .external ;
1818
1919import org .apache .commons .lang3 .AnnotationUtils ;
20- import org .junit .jupiter .api .Assertions ;
2120import org .junit .jupiter .api .Test ;
2221
22+ import java .lang .annotation .Annotation ;
2323import java .lang .annotation .Retention ;
2424import java .lang .annotation .RetentionPolicy ;
25+ import java .lang .reflect .InvocationTargetException ;
2526
26- // CAUTION: in order to reproduce https://issues.apache.org/jira/browse/LANG-1815,
27- // this test MUST be located OUTSIDE org.apache.commons.lang3 package.
28- // Do NOT move it to the org.apache.commons.lang3 package!
27+ import static org .junit .jupiter .api .Assertions .assertEquals ;
28+ import static org .junit .jupiter .api .Assertions .assertInstanceOf ;
29+ import static org .junit .jupiter .api .Assertions .assertThrows ;
30+ import static org .junit .jupiter .api .Assertions .assertTrue ;
31+
32+ /**
33+ * Regression test for <a href="https://issues.apache.org/jira/browse/LANG-1815">LANG-1815</a>.
34+ * <p>
35+ * Verifies that {@code AnnotationUtils.equals(Annotation, Annotation)} treats two equal
36+ * package-private annotations as equal, and also wraps a possible ReflectiveOperationException.
37+ * </p>
38+ *
39+ * <h2>Important</h2>
40+ * <p>
41+ * This test relies on reflective access rules that differ depending on the caller's package.
42+ * To reproduce the original bug, this class <strong>must remain outside</strong> the
43+ * {@code org.apache.commons.lang3} package.
44+ * </p>
45+ * <p>
46+ * Do <strong>not</strong> move this class into {@code org.apache.commons.lang3},
47+ * otherwise the test may no longer exercise the failing scenario from LANG-1815.
48+ * </p>
49+ */
2950public class AnnotationEqualsTest {
3051 @ Retention (RetentionPolicy .RUNTIME )
3152 @interface Tag {
3253 String value ();
3354 }
3455
56+ static class ThrowingTag implements Tag {
57+ @ Override
58+ public String value () {
59+ throw new IllegalArgumentException ("boom" );
60+ }
61+
62+ @ Override
63+ public Class <? extends Annotation > annotationType () {
64+ return Tag .class ;
65+ }
66+ }
67+
3568 @ Tag ("value" )
3669 private final Object a = new Object ();
3770 @ Tag ("value" )
@@ -41,6 +74,19 @@ public class AnnotationEqualsTest {
4174 void equalsWorksOnPackagePrivateAnnotations () throws Exception {
4275 Tag tagA = getClass ().getDeclaredField ("a" ).getAnnotation (Tag .class );
4376 Tag tagB = getClass ().getDeclaredField ("b" ).getAnnotation (Tag .class );
44- Assertions . assertTrue (AnnotationUtils .equals (tagA , tagB ));
77+ assertTrue (AnnotationUtils .equals (tagA , tagB ));
4578 }
79+
80+ @ Test
81+ void equalsWrapsReflectiveOperationException () throws Exception {
82+ // Proxy annotation instances: calling Tag#value() will throw at runtime
83+ final Tag tagA = new ThrowingTag ();
84+ final Tag tagB = getClass ().getDeclaredField ("b" ).getAnnotation (Tag .class );
85+
86+ final IllegalStateException ex =
87+ assertThrows (IllegalStateException .class , () -> AnnotationUtils .equals (tagA , tagB ));
88+ assertInstanceOf (InvocationTargetException .class , ex .getCause ());
89+ assertEquals ("boom" , ((InvocationTargetException ) ex .getCause ()).getTargetException ().getMessage ());
90+ }
91+
4692}
0 commit comments