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.commons.utils; 018 019import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.lang.annotation.*; 024import java.lang.reflect.*; 025import java.util.*; 026import java.util.stream.*; 027 028/** 029 * Annotation utilities. 030 */ 031public class AnnotationUtils { 032 033 /** 034 * Checks if two annotations are equal using the criteria for equality presented in the {@link Annotation#equals(Object)} API docs. 035 * 036 * @param a1 the first Annotation to compare, {@code null} returns {@code false} unless both are {@code null} 037 * @param a2 the second Annotation to compare, {@code null} returns {@code false} unless both are {@code null} 038 * @return {@code true} if the two annotations are {@code equal} or both {@code null} 039 */ 040 public static boolean equals(Annotation a1, Annotation a2) { 041 if (a1 == a2) 042 return true; 043 if (a1 == null || a2 == null) 044 return false; 045 046 var t1 = a1.annotationType(); 047 var t2 = a2.annotationType(); 048 049 if (! t1.equals(t2)) 050 return false; 051 052 return ! getAnnotationMethods(t1).anyMatch(x -> ! memberEquals(x.getReturnType(), safeSupplier(() -> x.invoke(a1)), safeSupplier(() -> x.invoke(a2)))); 053 } 054 055 /** 056 * Generate a hash code for the given annotation using the algorithm presented in the {@link Annotation#hashCode()} API docs. 057 * 058 * @param a the Annotation for a hash code calculation is desired, not {@code null} 059 * @return the calculated hash code 060 * @throws RuntimeException if an {@code Exception} is encountered during annotation member access 061 * @throws IllegalStateException if an annotation method invocation returns {@code null} 062 */ 063 public static int hash(Annotation a) { 064 return getAnnotationMethods(a.annotationType()).mapToInt(x -> hashMember(x.getName(), safeSupplier(() -> x.invoke(a)))).sum(); 065 } 066 067 /** 068 * Returns a stream of nested annotations in a repeated annotation if the specified annotation is a repeated annotation, 069 * or a singleton stream with the same annotation if not. 070 * 071 * <p> 072 * This method is a stream-based alternative to splitting repeated annotations that avoids creating intermediate arrays. 073 * 074 * <p> 075 * <b>Example:</b> 076 * <p class='bjava'> 077 * <jc>// Given an annotation that may be repeatable</jc> 078 * Annotation <jv>annotation</jv> = ...; 079 * 080 * <jc>// Stream individual annotations (expanded if repeatable)</jc> 081 * streamRepeated(<jv>annotation</jv>) 082 * .forEach(<jv>a</jv> -> System.<jsf>out</jsf>.println(<jv>a</jv>)); 083 * </p> 084 * 085 * @param a The annotation to split. 086 * @return A stream of nested annotations, or a singleton stream with the same annotation if it's not repeated. 087 * Never <jk>null</jk>. 088 */ 089 public static Stream<Annotation> streamRepeated(Annotation a) { 090 var ci = info(a.annotationType()); 091 var mi = ci.getRepeatedAnnotationMethod(); 092 if (nn(mi)) { 093 Annotation[] annotations = mi.invoke(a); 094 return Arrays.stream(annotations); 095 } 096 return Stream.of(a); 097 } 098 099 private static boolean annotationArrayMemberEquals(Annotation[] a1, Annotation[] a2) { 100 if (a1.length != a2.length) 101 return false; 102 for (var i = 0; i < a1.length; i++) 103 if (neq(a1[i], a2[i])) 104 return false; 105 return true; 106 } 107 108 private static boolean arrayMemberEquals(Class<?> componentType, Object o1, Object o2) { 109 if (componentType.isAnnotation()) 110 return annotationArrayMemberEquals((Annotation[])o1, (Annotation[])o2); 111 if (componentType.equals(Byte.TYPE)) 112 return Arrays.equals((byte[])o1, (byte[])o2); 113 if (componentType.equals(Short.TYPE)) 114 return Arrays.equals((short[])o1, (short[])o2); 115 if (componentType.equals(Integer.TYPE)) 116 return Arrays.equals((int[])o1, (int[])o2); 117 if (componentType.equals(Character.TYPE)) 118 return Arrays.equals((char[])o1, (char[])o2); 119 if (componentType.equals(Long.TYPE)) 120 return Arrays.equals((long[])o1, (long[])o2); 121 if (componentType.equals(Float.TYPE)) 122 return Arrays.equals((float[])o1, (float[])o2); 123 if (componentType.equals(Double.TYPE)) 124 return Arrays.equals((double[])o1, (double[])o2); 125 if (componentType.equals(Boolean.TYPE)) 126 return Arrays.equals((boolean[])o1, (boolean[])o2); 127 return Arrays.equals((Object[])o1, (Object[])o2); 128 } 129 130 private static int arrayMemberHash(Class<?> componentType, Object o) { 131 if (componentType.equals(Byte.TYPE)) 132 return Arrays.hashCode((byte[])o); 133 if (componentType.equals(Short.TYPE)) 134 return Arrays.hashCode((short[])o); 135 if (componentType.equals(Integer.TYPE)) 136 return Arrays.hashCode((int[])o); 137 if (componentType.equals(Character.TYPE)) 138 return Arrays.hashCode((char[])o); 139 if (componentType.equals(Long.TYPE)) 140 return Arrays.hashCode((long[])o); 141 if (componentType.equals(Float.TYPE)) 142 return Arrays.hashCode((float[])o); 143 if (componentType.equals(Double.TYPE)) 144 return Arrays.hashCode((double[])o); 145 if (componentType.equals(Boolean.TYPE)) 146 return Arrays.hashCode((boolean[])o); 147 return Arrays.hashCode((Object[])o); 148 } 149 150 private static Stream<Method> getAnnotationMethods(Class<? extends Annotation> type) { 151 return l(type.getDeclaredMethods()).stream(); 152 } 153 154 private static int hashMember(String name, Object value) { 155 int part1 = name.hashCode() * 127; 156 if (value == null) 157 return part1; 158 if (isArray(value)) 159 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); 160 if (value instanceof Annotation value2) 161 return part1 ^ hash(value2); 162 return part1 ^ value.hashCode(); 163 } 164 165 private static boolean memberEquals(Class<?> type, Object o1, Object o2) { 166 if (o1 == o2) 167 return true; 168 if (o1 == null || o2 == null) 169 return false; 170 if (type.isArray()) 171 return arrayMemberEquals(type.getComponentType(), o1, o2); 172 if (type.isAnnotation()) 173 return eq((Annotation)o1, (Annotation)o2); 174 return o1.equals(o2); 175 } 176}