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