View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.external;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.lang.annotation.Annotation;
25  import java.lang.annotation.Retention;
26  import java.lang.annotation.RetentionPolicy;
27  import java.lang.reflect.InvocationTargetException;
28  
29  import org.apache.commons.lang3.AnnotationUtils;
30  import org.junit.jupiter.api.Test;
31  
32  /**
33   * Regression test for <a href="https://issues.apache.org/jira/browse/LANG-1815">LANG-1815</a>.
34   * <p>
35   * Verifies that {@link AnnotationUtils} can reflectively read package-private annotation members
36   * for {@code equals}, {@code hashCode}, and {@code toString}, and that reflective invocation failures
37   * are not treated as inequality.
38   * </p>
39   *
40   * <h2>Important</h2>
41   * <p>
42   * This test relies on reflective access rules that differ depending on the caller's package.
43   * To reproduce the original bug, this class <strong>must remain outside</strong> the
44   * {@code org.apache.commons.lang3} package.
45   * </p>
46   * <p>
47   * Do <strong>not</strong> move this class into {@code org.apache.commons.lang3},
48   * otherwise the test may no longer exercise the failing scenario from LANG-1815.
49   * </p>
50   */
51  public class AnnotationEqualsTest {
52      @Retention(RetentionPolicy.RUNTIME)
53      @interface Tag {
54          String value();
55      }
56  
57      static class ThrowingTag implements Tag {
58          @Override
59          public Class<? extends Annotation> annotationType() {
60              return Tag.class;
61          }
62  
63          @Override
64          public String value() {
65              throw new IllegalArgumentException("boom");
66          }
67      }
68  
69      @Tag("value")
70      private final Object a = new Object();
71      @Tag("value")
72      private final Object b = new Object();
73  
74      @Test
75      void equalsWorksOnPackagePrivateAnnotations() throws Exception {
76          final Tag tagA = getClass().getDeclaredField("a").getAnnotation(Tag.class);
77          final Tag tagB = getClass().getDeclaredField("b").getAnnotation(Tag.class);
78          assertTrue(AnnotationUtils.equals(tagA, tagB));
79      }
80  
81      @Test
82      void equalsWrapsReflectiveOperationException() throws Exception {
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  
92      @Test
93      void hashCodeWorksOnPackagePrivateAnnotations() throws Exception {
94          final Tag tag = getClass().getDeclaredField("a").getAnnotation(Tag.class);
95          assertEquals(tag.hashCode(), AnnotationUtils.hashCode(tag));
96      }
97  
98      @Test
99      void toStringWorksOnPackagePrivateAnnotations() throws Exception {
100         final Tag tag = getClass().getDeclaredField("a").getAnnotation(Tag.class);
101         final String text = AnnotationUtils.toString(tag);
102         assertTrue(text.contains("value=value"), text);
103     }
104 
105 }