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}