blob: 7609000952442fa62e05ce5658e6b5ba20f04605 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.CURLY;
import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.LARRY;
import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.MOE;
import static org.apache.commons.lang3.AnnotationUtilsTest.Stooge.SHEMP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
*/
public class AnnotationUtilsTest extends AbstractLangTest {
@TestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
nest = @NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
),
nests = {
@NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object[].class,
types = { Object[].class }
)
},
shortValue = 0,
shortValues = { 0 },
stooge = SHEMP,
stooges = { MOE, LARRY, CURLY },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
)
public Object dummy1;
@TestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
nest = @NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
),
nests = {
@NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object[].class,
types = { Object[].class }
)
},
shortValue = 0,
shortValues = { 0 },
stooge = SHEMP,
stooges = { MOE, LARRY, CURLY },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
)
public Object dummy2;
@TestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
nest = @NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
),
nests = {
@NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object[].class,
types = { Object[].class }
),
//add a second NestAnnotation to break equality:
@NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object[].class,
types = { Object[].class }
)
},
shortValue = 0,
shortValues = { 0 },
stooge = SHEMP,
stooges = { MOE, LARRY, CURLY },
string = "",
strings = { "" },
type = Object.class,
types = { Object.class }
)
public Object dummy3;
@NestAnnotation(
booleanValue = false,
booleanValues = { false },
byteValue = 0,
byteValues = { 0 },
charValue = 0,
charValues = { 0 },
doubleValue = 0,
doubleValues = { 0 },
floatValue = 0,
floatValues = { 0 },
intValue = 0,
intValues = { 0 },
longValue = 0,
longValues = { 0 },
shortValue = 0,
shortValues = { 0 },
stooge = CURLY,
stooges = { MOE, LARRY, SHEMP },
string = "",
strings = { "" },
type = Object[].class,
types = { Object[].class }
)
public Object dummy4;
@Target(FIELD)
@Retention(RUNTIME)
public @interface TestAnnotation {
String string();
String[] strings();
Class<?> type();
Class<?>[] types();
byte byteValue();
byte[] byteValues();
short shortValue();
short[] shortValues();
int intValue();
int[] intValues();
char charValue();
char[] charValues();
long longValue();
long[] longValues();
float floatValue();
float[] floatValues();
double doubleValue();
double[] doubleValues();
boolean booleanValue();
boolean[] booleanValues();
Stooge stooge();
Stooge[] stooges();
NestAnnotation nest();
NestAnnotation[] nests();
}
@Retention(RUNTIME)
public @interface NestAnnotation {
String string();
String[] strings();
Class<?> type();
Class<?>[] types();
byte byteValue();
byte[] byteValues();
short shortValue();
short[] shortValues();
int intValue();
int[] intValues();
char charValue();
char[] charValues();
long longValue();
long[] longValues();
float floatValue();
float[] floatValues();
double doubleValue();
double[] doubleValues();
boolean booleanValue();
boolean[] booleanValues();
Stooge stooge();
Stooge[] stooges();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TestMethodAnnotation {
Class<? extends Throwable> expected() default None.class;
long timeout() default 0L;
class None extends Throwable {
private static final long serialVersionUID = 1L;
}
}
public enum Stooge {
MOE, LARRY, CURLY, JOE, SHEMP
}
private Field field1;
private Field field2;
private Field field3;
private Field field4;
@BeforeEach
public void setup() throws Exception {
field1 = getClass().getDeclaredField("dummy1");
field2 = getClass().getDeclaredField("dummy2");
field3 = getClass().getDeclaredField("dummy3");
field4 = getClass().getDeclaredField("dummy4");
}
@Test
public void testEquivalence() {
assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field2.getAnnotation(TestAnnotation.class)));
assertTrue(AnnotationUtils.equals(field2.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
}
@Test
public void testSameInstance() {
assertTrue(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
}
@Test
public void testNonEquivalentAnnotationsOfSameType() {
assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field3.getAnnotation(TestAnnotation.class)));
assertFalse(AnnotationUtils.equals(field3.getAnnotation(TestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
}
@Test
public void testAnnotationsOfDifferingTypes() {
assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), field4.getAnnotation(NestAnnotation.class)));
assertFalse(AnnotationUtils.equals(field4.getAnnotation(NestAnnotation.class), field1.getAnnotation(TestAnnotation.class)));
}
@Test
public void testOneArgNull() {
assertFalse(AnnotationUtils.equals(field1.getAnnotation(TestAnnotation.class), null));
assertFalse(AnnotationUtils.equals(null, field1.getAnnotation(TestAnnotation.class)));
}
@Test
public void testBothArgsNull() {
assertTrue(AnnotationUtils.equals(null, null));
}
@Test
public void testIsValidAnnotationMemberType() {
for (final Class<?> type : new Class[] { byte.class, short.class, int.class, char.class,
long.class, float.class, double.class, boolean.class, String.class, Class.class,
NestAnnotation.class, TestAnnotation.class, Stooge.class, ElementType.class }) {
assertTrue(AnnotationUtils.isValidAnnotationMemberType(type));
assertTrue(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0)
.getClass()));
}
for (final Class<?> type : new Class[] { Object.class, Map.class, Collection.class }) {
assertFalse(AnnotationUtils.isValidAnnotationMemberType(type));
assertFalse(AnnotationUtils.isValidAnnotationMemberType(Array.newInstance(type, 0)
.getClass()));
}
}
@Test
public void testGeneratedAnnotationEquivalentToRealAnnotation() {
assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
final Test real = getClass().getDeclaredMethod(
"testGeneratedAnnotationEquivalentToRealAnnotation").getAnnotation(Test.class);
final InvocationHandler generatedTestInvocationHandler = (proxy, method, args) -> {
if ("equals".equals(method.getName()) && method.getParameterTypes().length == 1) {
return Boolean.valueOf(proxy == args[0]);
}
if ("hashCode".equals(method.getName()) && method.getParameterTypes().length == 0) {
return Integer.valueOf(System.identityHashCode(proxy));
}
if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0) {
return "Test proxy";
}
return method.invoke(real, args);
};
final Test generated = (Test) Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class[]{Test.class},
generatedTestInvocationHandler);
assertEquals(real, generated);
assertNotEquals(generated, real);
assertTrue(AnnotationUtils.equals(generated, real));
assertTrue(AnnotationUtils.equals(real, generated));
final Test generated2 = (Test) Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class[]{Test.class},
generatedTestInvocationHandler);
assertNotEquals(generated, generated2);
assertNotEquals(generated2, generated);
assertTrue(AnnotationUtils.equals(generated, generated2));
assertTrue(AnnotationUtils.equals(generated2, generated));
});
}
@Test
public void testHashCode() {
assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
final Test test = getClass().getDeclaredMethod("testHashCode").getAnnotation(Test.class);
assertEquals(test.hashCode(), AnnotationUtils.hashCode(test));
final TestAnnotation testAnnotation1 = field1.getAnnotation(TestAnnotation.class);
assertEquals(testAnnotation1.hashCode(), AnnotationUtils.hashCode(testAnnotation1));
final TestAnnotation testAnnotation3 = field3.getAnnotation(TestAnnotation.class);
assertEquals(testAnnotation3.hashCode(), AnnotationUtils.hashCode(testAnnotation3));
});
}
@Test
@TestMethodAnnotation(timeout = 666000)
public void testToString() {
assertTimeoutPreemptively(Duration.ofSeconds(666L), () -> {
final TestMethodAnnotation testAnnotation =
getClass().getDeclaredMethod("testToString").getAnnotation(TestMethodAnnotation.class);
final String annotationString = AnnotationUtils.toString(testAnnotation);
assertTrue(annotationString.startsWith("@org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation("));
assertTrue(annotationString.endsWith(")"));
assertTrue(annotationString.contains("expected=class org.apache.commons.lang3.AnnotationUtilsTest$TestMethodAnnotation$None"));
assertTrue(annotationString.contains("timeout=666000"));
assertTrue(annotationString.contains(", "));
});
}
}