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.inject;
018
019import static java.lang.annotation.ElementType.*;
020import static java.lang.annotation.RetentionPolicy.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022
023import java.lang.annotation.*;
024import java.util.*;
025
026import org.apache.juneau.commons.*;
027import org.apache.juneau.commons.reflect.*;
028
029/**
030 * Utility classes and methods for the {@link Bean @Bean} annotation.
031 *
032 */
033public class BeanAnnotation {
034
035   /**
036    * Private constructor to prevent instantiation.
037    */
038   private BeanAnnotation() {
039      // Utility class - prevent instantiation
040   }
041
042   /**
043    * A collection of {@link Bean @Bean} annotations.
044    */
045   @Documented
046   @Target({ FIELD, METHOD, TYPE })
047   @Retention(RUNTIME)
048   @Inherited
049   public static @interface Array {
050
051      /**
052       * The child annotations.
053       *
054       * @return The annotation value.
055       */
056      Bean[] value();
057   }
058
059   /**
060    * Builder class.
061    *
062    * <h5 class='section'>See Also:</h5><ul>
063    *    <li class='jm'>{@code MarshallingContext.Builder<?>#annotations(Annotation...)}
064    * </ul>
065    */
066   public static class Builder extends AppliedAnnotationObject.BuilderM {
067
068      private String[] description = {};
069      private String name;
070      private String value;
071      private String[] methodScope;
072
073      /**
074       * Constructor.
075       */
076      protected Builder() {
077         super(Bean.class);
078      }
079
080      /**
081       * Instantiates a new {@link Bean @Bean} object initialized with this builder.
082       *
083       * @return A new {@link Bean @Bean} object.
084       */
085      public Bean build() {
086         return new Object(this);
087      }
088
089      /**
090       * Sets the description property on this annotation.
091       *
092       * @param value The new value for this property.  Can be <jk>null</jk>, in which case an empty array is used.
093       * @return This object.
094       */
095      public Builder description(String...value) {
096         description = value;
097         return this;
098      }
099
100      /**
101       * Sets the {@link Bean#methodScope()} property on this annotation.
102       *
103       * @param value The new value for this property.  Can be <jk>null</jk>, in which case an empty array is used.
104       * @return This object.
105       */
106      public Builder methodScope(String...value) {
107         methodScope = value;
108         return this;
109      }
110
111      /**
112       * Sets the {@link Bean#name()} property on this annotation.
113       *
114       * @param value The new value for this property.  Can be <jk>null</jk>, in which case an empty string is used.
115       * @return This object.
116       */
117      public Builder name(String value) {
118         name = value;
119         return this;
120      }
121
122      /**
123       * Sets the {@link Bean#value()} property on this annotation.
124       *
125       * @param value The new value for this property.  Can be <jk>null</jk>, in which case an empty string is used.
126       * @return This object.
127       */
128      public Builder value(String value) {
129         this.value = value;
130         return this;
131      }
132
133      @Override /* Overridden from AppliedAnnotationObject.Builder */
134      public Builder on(String...value) {
135         super.on(value);
136         return this;
137      }
138      @Override /* Overridden from AppliedAnnotationObject.BuilderM */
139      public Builder on(java.lang.reflect.Method...value) {
140         super.on(value);
141         return this;
142      }
143
144
145      @Override /* Overridden from AppliedAnnotationObject.BuilderM */
146      public Builder on(org.apache.juneau.commons.reflect.MethodInfo...value) {
147         super.on(value);
148         return this;
149      }
150
151   }
152
153   @SuppressWarnings({
154      "annotationSuperInterface", // Eclipse JDT: intentional concrete implementation of annotation interface for runtime-built annotation instances
155      "java:S2160" // equals() inherited from AnnotationObject compares all annotation interface methods; subclass fields are accessed via those methods
156   })
157   private static class Object extends AppliedAnnotationObject implements Bean {
158
159      private static final String[] EMPTY_STRINGS = new String[0];
160
161      private final String[] description;
162      private final String name;
163      private final String value;
164      private final String[] methodScope;
165
166      Object(BeanAnnotation.Builder b) {
167         super(b);
168         // Mirror the compile-time defaults declared on @Bean (empty strings / empty arrays) so
169         // callers iterating the runtime instance see annotation-equivalent values instead of nulls.
170         description = b.description != null ? cp(b.description) : EMPTY_STRINGS;
171         name = b.name != null ? b.name : "";
172         value = b.value != null ? b.value : "";
173         methodScope = b.methodScope != null ? cp(b.methodScope) : EMPTY_STRINGS;
174      }
175
176      @Override /* Overridden from Bean */
177      public String[] methodScope() {
178         return cp(methodScope);
179      }
180
181      @Override /* Overridden from Bean */
182      public String name() {
183         return name;
184      }
185
186      @Override /* Overridden from Bean */
187      public String value() {
188         return value;
189      }
190
191      @Override /* Overridden from annotation */
192      public String[] description() {
193         return cp(description);
194      }
195
196      @Override /* Overridden from Bean */
197      public int priority() {
198         return Integer.MAX_VALUE / 2;
199      }
200   }
201
202   /** Default value */
203   public static final Bean DEFAULT = create().build();
204
205   /**
206    * Instantiates a new builder for this class.
207    *
208    * @return A new builder object.
209    */
210   public static Builder create() {
211      return new Builder();
212   }
213
214   /**
215    * Pulls the name/value attribute from a {@link Bean} annotation.
216    *
217    * @param a The annotation to check.  Can be <jk>null</jk> (returns an empty string).
218    * @return The annotation value, or an empty string if the annotation is <jk>null</jk>.
219    */
220   public static String name(Bean a) {
221      if (a == null)
222         return "";
223      if (! a.name().isEmpty())
224         return a.name();
225      return a.value();
226   }
227
228   /**
229    * Finds the {@link Bean @Bean} annotation that applies to the specified method.
230    *
231    * <p>
232    * The result is the method's <em>own</em> declared {@link Bean @Bean}, or else the one declared on a method it
233    * truly overrides.  A parent method only counts as overridden when it is non-private, non-static, not a
234    * bridge/synthetic method, and (when package-private) declared in the same package as the method.
235    * This is stricter than {@link MethodInfo#getAnnotations()}, which also merges annotations from same-signature
236    * private or static parent methods that are not overrides.
237    *
238    * <p>
239    * Bridge and synthetic methods always return {@link Optional#empty()}.
240    *
241    * <h5 class='section'>Example:</h5>
242    * <p class='bjava'>
243    *    <jk>public class</jk> Parent {
244    *       <ja>@Bean</ja>(name=<js>"db"</js>)
245    *       <jk>public</jk> HealthIndicator dbIndicator() { ... }
246    *    }
247    *
248    *    <jk>public class</jk> Child <jk>extends</jk> Parent {
249    *       <ja>@Override</ja>
250    *       <jk>public</jk> HealthIndicator dbIndicator() { ... }  <jc>// Not annotated, but overrides an annotated method.</jc>
251    *    }
252    *
253    *    MethodInfo <jv>mi</jv> = ClassInfo.<jsm>of</jsm>(Child.<jk>class</jk>).getDeclaredMethod(<jv>x</jv> -&gt; <jv>x</jv>.hasName(<js>"dbIndicator"</js>)).get();
254    *    Optional&lt;Bean&gt; <jv>bean</jv> = BeanAnnotation.<jsm>find</jsm>(<jv>mi</jv>);  <jc>// Present, name="db".</jc>
255    * </p>
256    *
257    * @param method The method to inspect.  Must not be <jk>null</jk>.
258    * @return The applicable annotation, or {@link Optional#empty()} if none applies.
259    */
260   public static Optional<Bean> find(MethodInfo method) {
261      reqnn("method", method);
262      if (method.isBridge() || method.isSynthetic())
263         return Optional.empty();
264      var own = method.inner().getAnnotation(Bean.class);
265      if (own != null)
266         return Optional.of(own);
267      if (method.isPrivate() || method.isStatic())
268         return Optional.empty();
269      var pkg = method.inner().getDeclaringClass().getPackageName();
270      var matching = method.getMatchingMethods();
271      for (var i = 1; i < matching.size(); i++) {
272         var parent = matching.get(i);
273         if (! isInheritableFrom(parent, pkg))
274            continue;
275         var a = parent.inner().getAnnotation(Bean.class);
276         if (a != null)
277            return Optional.of(a);
278      }
279      return Optional.empty();
280   }
281
282   private static boolean isInheritableFrom(MethodInfo parent, String pkg) {
283      if (parent.isPrivate() || parent.isStatic() || parent.isBridge() || parent.isSynthetic())
284         return false;
285      return parent.isPublic() || parent.isProtected() || pkg.equals(parent.inner().getDeclaringClass().getPackageName());
286   }
287}