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}