001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.internal; 014 015import static org.apache.juneau.common.internal.ThrowableUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017 018import java.lang.annotation.*; 019import java.lang.reflect.*; 020import java.util.*; 021import java.util.stream.*; 022 023/** 024 * Annotation utilities. 025 * 026 * <h5 class='section'>See Also:</h5><ul> 027 * </ul> 028 */ 029public class AnnotationUtils { 030 031 /** 032 * Checks if two annotations are equal using the criteria for equality presented in the {@link Annotation#equals(Object)} API docs. 033 * 034 * @param a1 the first Annotation to compare, {@code null} returns {@code false} unless both are {@code null} 035 * @param a2 the second Annotation to compare, {@code null} returns {@code false} unless both are {@code null} 036 * @return {@code true} if the two annotations are {@code equal} or both {@code null} 037 */ 038 public static boolean equals(Annotation a1, Annotation a2) { 039 if (a1 == a2) 040 return true; 041 if (a1 == null || a2 == null) 042 return false; 043 044 Class<? extends Annotation> t1 = a1.annotationType(); 045 Class<? extends Annotation> t2 = a2.annotationType(); 046 047 if (! t1.equals(t2)) 048 return false; 049 050 boolean b= getAnnotationMethods(t1) 051 .anyMatch(x -> ! memberEquals(x.getReturnType(), safeSupplier(()->x.invoke(a1)), safeSupplier(()->x.invoke(a2)))); 052 if (b) 053 return false; 054 055 return true; 056 } 057 058 /** 059 * Generate a hash code for the given annotation using the algorithm presented in the {@link Annotation#hashCode()} API docs. 060 * 061 * @param a the Annotation for a hash code calculation is desired, not {@code null} 062 * @return the calculated hash code 063 * @throws RuntimeException if an {@code Exception} is encountered during annotation member access 064 * @throws IllegalStateException if an annotation method invocation returns {@code null} 065 */ 066 public static int hashCode(Annotation a) { 067 return getAnnotationMethods(a.annotationType()) 068 .mapToInt(x -> hashMember(x.getName(), safeSupplier(()->x.invoke(a)))) 069 .sum(); 070 } 071 072 private static Stream<Method> getAnnotationMethods(Class<? extends Annotation> type) { 073 return alist(type.getDeclaredMethods()) 074 .stream() 075 .filter(x -> x.getParameterCount() == 0 && x.getDeclaringClass().isAnnotation()) 076 ; 077 } 078 079 private static int hashMember(String name, Object value) { 080 int part1 = name.hashCode() * 127; 081 if (value == null) 082 return part1; 083 if (value.getClass().isArray()) 084 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); 085 if (value instanceof Annotation) 086 return part1 ^ hashCode((Annotation) value); 087 return part1 ^ value.hashCode(); 088 } 089 090 private static boolean memberEquals(Class<?> type, Object o1, Object o2) { 091 if (o1 == o2) 092 return true; 093 if (o1 == null || o2 == null) 094 return false; 095 if (type.isArray()) 096 return arrayMemberEquals(type.getComponentType(), o1, o2); 097 if (type.isAnnotation()) 098 return equals((Annotation) o1, (Annotation) o2); 099 return o1.equals(o2); 100 } 101 102 private static boolean arrayMemberEquals(Class<?> componentType, Object o1, Object o2) { 103 if (componentType.isAnnotation()) 104 return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); 105 if (componentType.equals(Byte.TYPE)) 106 return Arrays.equals((byte[]) o1, (byte[]) o2); 107 if (componentType.equals(Short.TYPE)) 108 return Arrays.equals((short[]) o1, (short[]) o2); 109 if (componentType.equals(Integer.TYPE)) 110 return Arrays.equals((int[]) o1, (int[]) o2); 111 if (componentType.equals(Character.TYPE)) 112 return Arrays.equals((char[]) o1, (char[]) o2); 113 if (componentType.equals(Long.TYPE)) 114 return Arrays.equals((long[]) o1, (long[]) o2); 115 if (componentType.equals(Float.TYPE)) 116 return Arrays.equals((float[]) o1, (float[]) o2); 117 if (componentType.equals(Double.TYPE)) 118 return Arrays.equals((double[]) o1, (double[]) o2); 119 if (componentType.equals(Boolean.TYPE)) 120 return Arrays.equals((boolean[]) o1, (boolean[]) o2); 121 return Arrays.equals((Object[]) o1, (Object[]) o2); 122 } 123 124 private static boolean annotationArrayMemberEquals(Annotation[] a1, Annotation[] a2) { 125 if (a1.length != a2.length) 126 return false; 127 for (int i = 0; i < a1.length; i++) 128 if (! equals(a1[i], a2[i])) 129 return false; 130 return true; 131 } 132 133 private static int arrayMemberHash(Class<?> componentType, Object o) { 134 if (componentType.equals(Byte.TYPE)) 135 return Arrays.hashCode((byte[]) o); 136 if (componentType.equals(Short.TYPE)) 137 return Arrays.hashCode((short[]) o); 138 if (componentType.equals(Integer.TYPE)) 139 return Arrays.hashCode((int[]) o); 140 if (componentType.equals(Character.TYPE)) 141 return Arrays.hashCode((char[]) o); 142 if (componentType.equals(Long.TYPE)) 143 return Arrays.hashCode((long[]) o); 144 if (componentType.equals(Float.TYPE)) 145 return Arrays.hashCode((float[]) o); 146 if (componentType.equals(Double.TYPE)) 147 return Arrays.hashCode((double[]) o); 148 if (componentType.equals(Boolean.TYPE)) 149 return Arrays.hashCode((boolean[]) o); 150 return Arrays.hashCode((Object[]) o); 151 } 152}