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> -&gt; 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}