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> -> <jv>x</jv>.hasName(<js>"dbIndicator"</js>)).get(); 254 * Optional<Bean> <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}