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.annotation;
018
019import static java.util.Arrays.*;
020import static org.apache.juneau.collections.JsonMap.*;
021import static org.apache.juneau.internal.ArrayUtils.copyOf;
022
023import java.lang.annotation.*;
024import java.lang.reflect.*;
025import java.util.*;
026
027import org.apache.juneau.collections.*;
028import org.apache.juneau.common.utils.*;
029import org.apache.juneau.internal.*;
030
031/**
032 * A concrete implementation of an annotation.
033 *
034 * <p>
035 * Follows the standard Java conventions for equality and hashcode calculation for annotations.
036 * Equivalent annotations defined programmatically and declaratively should match for equality and hashcode calculation.
037 *
038 * <p>
039 * For performance reasons, the hashcode is calculated one time and cached at the end of object creation.
040 * Constructors must call the {@link #postConstruct()} method after all fields have been set to trigger this calculation.
041 *
042 * <h5 class='section'>See Also:</h5><ul>
043 * </ul>
044 */
045public class AnnotationImpl implements Annotation {
046
047   private final Class<? extends Annotation> annotationType;
048   private final String[] description;
049   private int hashCode = -1;
050
051   /**
052    * Constructor.
053    *
054    * @param b The builder used to instantiate the fields of this class.
055    */
056   public AnnotationImpl(AnnotationBuilder<?> b) {
057      this.annotationType = b.annotationType;
058      this.description = copyOf(b.description);
059   }
060
061   /**
062    * This method must be called at the end of initialization to calculate the hashCode one time.
063    */
064   protected void postConstruct() {
065      this.hashCode = AnnotationUtils.hashCode(this);
066   }
067
068   /**
069    * Implements the {@link Annotation#annotationType()} method for child classes.
070    *
071    * @return This class.
072    */
073   @Override /* Annotation */
074   public Class<? extends Annotation> annotationType() {
075      return annotationType;
076   }
077
078   /**
079    * Returns the annotation description.
080    *
081    * @return the annotation description.
082    * @since 9.2.0
083    */
084   public String[] description() {
085       return description;
086   }
087
088   @Override /* Object */
089   public int hashCode() {
090      if (hashCode == -1)
091         throw new IllegalArgumentException("Programming error. postConstruct() was never called on annotation.");
092      return hashCode;
093   }
094
095   @Override /* Object */
096   public boolean equals(Object o) {
097      if (!annotationType.isInstance(o))
098         return false;
099      return AnnotationUtils.equals(this, (Annotation)o);
100   }
101
102   /**
103    * Returns this annotation as a map of key/value pairs.
104    *
105    * <p>
106    * Useful for debugging.
107    *
108    * @return This annotation as a map of key/value pairs.
109    */
110   public JsonMap toMap() {
111      JsonMap m = create();
112      stream(annotationType().getDeclaredMethods())
113         .filter(x->x.getParameterCount() == 0 && x.getDeclaringClass().isAnnotation())
114         .sorted(Comparator.comparing(Method::getName))
115         .forEach(x -> m.append(x.getName(), Utils.safeSupplier(()->x.invoke(this))));
116      return m;
117   }
118
119   @Override /* Object */
120   public String toString() {
121      return toMap().asString();
122   }
123}