blob: 30554597a4b13e39ad6a99689e23f0fea1010869 [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.juneau.internal;
import static org.apache.juneau.common.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
/**
* Annotation utilities.
*
* <h5 class='section'>See Also:</h5><ul>
* </ul>
*/
public class AnnotationUtils {
/**
* Checks if two annotations are equal using the criteria for equality presented in the {@link Annotation#equals(Object)} API docs.
*
* @param a1 the first Annotation to compare, {@code null} returns {@code false} unless both are {@code null}
* @param a2 the second Annotation to compare, {@code null} returns {@code false} unless both are {@code null}
* @return {@code true} if the two annotations are {@code equal} or both {@code null}
*/
public static boolean equals(Annotation a1, Annotation a2) {
if (a1 == a2)
return true;
if (a1 == null || a2 == null)
return false;
Class<? extends Annotation> t1 = a1.annotationType();
Class<? extends Annotation> t2 = a2.annotationType();
if (! t1.equals(t2))
return false;
boolean b= getAnnotationMethods(t1)
.anyMatch(x -> ! memberEquals(x.getReturnType(), safeSupplier(()->x.invoke(a1)), safeSupplier(()->x.invoke(a2))));
if (b)
return false;
return true;
}
/**
* Generate a hash code for the given annotation using the algorithm presented in the {@link Annotation#hashCode()} API docs.
*
* @param a the Annotation for a hash code calculation is desired, not {@code null}
* @return the calculated hash code
* @throws RuntimeException if an {@code Exception} is encountered during annotation member access
* @throws IllegalStateException if an annotation method invocation returns {@code null}
*/
public static int hashCode(Annotation a) {
return getAnnotationMethods(a.annotationType())
.mapToInt(x -> hashMember(x.getName(), safeSupplier(()->x.invoke(a))))
.sum();
}
private static Stream<Method> getAnnotationMethods(Class<? extends Annotation> type) {
return alist(type.getDeclaredMethods())
.stream()
.filter(x -> x.getParameterCount() == 0 && x.getDeclaringClass().isAnnotation())
;
}
private static int hashMember(String name, Object value) {
int part1 = name.hashCode() * 127;
if (value == null)
return part1;
if (value.getClass().isArray())
return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
if (value instanceof Annotation)
return part1 ^ hashCode((Annotation) value);
return part1 ^ value.hashCode();
}
private static boolean memberEquals(Class<?> type, Object o1, Object o2) {
if (o1 == o2)
return true;
if (o1 == null || o2 == null)
return false;
if (type.isArray())
return arrayMemberEquals(type.getComponentType(), o1, o2);
if (type.isAnnotation())
return equals((Annotation) o1, (Annotation) o2);
return o1.equals(o2);
}
private static boolean arrayMemberEquals(Class<?> componentType, Object o1, Object o2) {
if (componentType.isAnnotation())
return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
if (componentType.equals(Byte.TYPE))
return Arrays.equals((byte[]) o1, (byte[]) o2);
if (componentType.equals(Short.TYPE))
return Arrays.equals((short[]) o1, (short[]) o2);
if (componentType.equals(Integer.TYPE))
return Arrays.equals((int[]) o1, (int[]) o2);
if (componentType.equals(Character.TYPE))
return Arrays.equals((char[]) o1, (char[]) o2);
if (componentType.equals(Long.TYPE))
return Arrays.equals((long[]) o1, (long[]) o2);
if (componentType.equals(Float.TYPE))
return Arrays.equals((float[]) o1, (float[]) o2);
if (componentType.equals(Double.TYPE))
return Arrays.equals((double[]) o1, (double[]) o2);
if (componentType.equals(Boolean.TYPE))
return Arrays.equals((boolean[]) o1, (boolean[]) o2);
return Arrays.equals((Object[]) o1, (Object[]) o2);
}
private static boolean annotationArrayMemberEquals(Annotation[] a1, Annotation[] a2) {
if (a1.length != a2.length)
return false;
for (int i = 0; i < a1.length; i++)
if (! equals(a1[i], a2[i]))
return false;
return true;
}
private static int arrayMemberHash(Class<?> componentType, Object o) {
if (componentType.equals(Byte.TYPE))
return Arrays.hashCode((byte[]) o);
if (componentType.equals(Short.TYPE))
return Arrays.hashCode((short[]) o);
if (componentType.equals(Integer.TYPE))
return Arrays.hashCode((int[]) o);
if (componentType.equals(Character.TYPE))
return Arrays.hashCode((char[]) o);
if (componentType.equals(Long.TYPE))
return Arrays.hashCode((long[]) o);
if (componentType.equals(Float.TYPE))
return Arrays.hashCode((float[]) o);
if (componentType.equals(Double.TYPE))
return Arrays.hashCode((double[]) o);
if (componentType.equals(Boolean.TYPE))
return Arrays.hashCode((boolean[]) o);
return Arrays.hashCode((Object[]) o);
}
}