001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.reflect; 014 015import static org.apache.juneau.internal.ConsumerUtils.*; 016import java.lang.annotation.*; 017import java.lang.reflect.*; 018import java.util.*; 019import java.util.function.*; 020 021import org.apache.juneau.*; 022 023/** 024 * Lightweight utility class for introspecting information about a field. 025 * 026 * <h5 class='section'>See Also:</h5><ul> 027 * </ul> 028 */ 029public final class FieldInfo implements Comparable<FieldInfo> { 030 031 //----------------------------------------------------------------------------------------------------------------- 032 // Static 033 //----------------------------------------------------------------------------------------------------------------- 034 035 /** 036 * Convenience method for instantiating a {@link FieldInfo}; 037 * 038 * @param declaringClass The class that declares this method. 039 * @param f The field being wrapped. 040 * @return A new {@link FieldInfo} object, or <jk>null</jk> if the field was null. 041 */ 042 public static FieldInfo of(ClassInfo declaringClass, Field f) { 043 if (f == null) 044 return null; 045 return ClassInfo.of(declaringClass).getFieldInfo(f); 046 } 047 048 /** 049 * Convenience method for instantiating a {@link FieldInfo}; 050 * 051 * @param f The field being wrapped. 052 * @return A new {@link FieldInfo} object, or <jk>null</jk> if the field was null. 053 */ 054 public static FieldInfo of(Field f) { 055 if (f == null) 056 return null; 057 return ClassInfo.of(f.getDeclaringClass()).getFieldInfo(f); 058 } 059 060 //----------------------------------------------------------------------------------------------------------------- 061 // Instance 062 //----------------------------------------------------------------------------------------------------------------- 063 064 private final Field f; 065 private final ClassInfo declaringClass; 066 private volatile ClassInfo type; 067 068 /** 069 * Constructor. 070 * 071 * @param declaringClass The class that declares this method. 072 * @param f The field being wrapped. 073 */ 074 protected FieldInfo(ClassInfo declaringClass, Field f) { 075 this.declaringClass = declaringClass; 076 this.f = f; 077 } 078 079 /** 080 * Returns the wrapped field. 081 * 082 * @return The wrapped field. 083 */ 084 public Field inner() { 085 return f; 086 } 087 088 /** 089 * Returns metadata about the declaring class. 090 * 091 * @return Metadata about the declaring class. 092 */ 093 public ClassInfo getDeclaringClass() { 094 return declaringClass; 095 } 096 097 //----------------------------------------------------------------------------------------------------------------- 098 // Annotations 099 //----------------------------------------------------------------------------------------------------------------- 100 101 /** 102 * Returns the specified annotation on this field. 103 * 104 * @param <A> The annotation type to look for. 105 * @param type The annotation to look for. 106 * @return The annotation, or <jk>null</jk> if not found. 107 */ 108 public <A extends Annotation> A getAnnotation(Class<A> type) { 109 return getAnnotation(AnnotationProvider.DEFAULT, type); 110 } 111 112 /** 113 * Returns the specified annotation on this field. 114 * 115 * @param <A> The annotation type to look for. 116 * @param annotationProvider The annotation provider. 117 * @param type The annotation to look for. 118 * @return The annotation, or <jk>null</jk> if not found. 119 */ 120 public <A extends Annotation> A getAnnotation(AnnotationProvider annotationProvider, Class<A> type) { 121 Value<A> t = Value.empty(); 122 annotationProvider.forEachAnnotation(type, f, x -> true, x -> t.set(x)); 123 return t.orElse(null); 124 } 125 126 /** 127 * Returns <jk>true</jk> if the specified annotation is present. 128 * 129 * @param <A> The annotation type to look for. 130 * @param type The annotation to look for. 131 * @return <jk>true</jk> if the specified annotation is present. 132 */ 133 public <A extends Annotation> boolean hasAnnotation(Class<A> type) { 134 return f.isAnnotationPresent(type); 135 } 136 137 /** 138 * Returns <jk>true</jk> if the specified annotation is not present on this field. 139 * 140 * @param <A> The annotation type to look for. 141 * @param type The annotation to look for. 142 * @return <jk>true</jk> if the specified annotation is not present on this field. 143 */ 144 public <A extends Annotation> boolean hasNoAnnotation(Class<A> type) { 145 return ! hasAnnotation(type); 146 } 147 148 /** 149 * Returns <jk>true</jk> if the specified annotation is present. 150 * 151 * @param <A> The annotation type to look for. 152 * @param annotationProvider The annotation provider. 153 * @param type The annotation to look for. 154 * @return <jk>true</jk> if the specified annotation is present. 155 */ 156 public <A extends Annotation> boolean hasAnnotation(AnnotationProvider annotationProvider, Class<A> type) { 157 return annotationProvider.firstAnnotation(type, f, x -> true) != null; 158 } 159 160 /** 161 * Returns <jk>true</jk> if the specified annotation is not present. 162 * 163 * @param <A> The annotation type to look for. 164 * @param annotationProvider The annotation provider. 165 * @param type The annotation to look for. 166 * @return <jk>true</jk> if the specified annotation is not present. 167 */ 168 public <A extends Annotation> boolean hasNoAnnotation(AnnotationProvider annotationProvider, Class<A> type) { 169 return ! hasAnnotation(annotationProvider, type); 170 } 171 172 //----------------------------------------------------------------------------------------------------------------- 173 // Characteristics 174 //----------------------------------------------------------------------------------------------------------------- 175 176 /** 177 * Returns <jk>true</jk> if all specified flags are applicable to this field. 178 * 179 * @param flags The flags to test for. 180 * @return <jk>true</jk> if all specified flags are applicable to this field. 181 */ 182 public boolean isAll(ReflectFlags...flags) { 183 for (ReflectFlags f : flags) { 184 switch (f) { 185 case DEPRECATED: 186 if (isNotDeprecated()) 187 return false; 188 break; 189 case NOT_DEPRECATED: 190 if (isDeprecated()) 191 return false; 192 break; 193 case PUBLIC: 194 if (isNotPublic()) 195 return false; 196 break; 197 case NOT_PUBLIC: 198 if (isPublic()) 199 return false; 200 break; 201 case STATIC: 202 if (isNotStatic()) 203 return false; 204 break; 205 case NOT_STATIC: 206 if (isStatic()) 207 return false; 208 break; 209 case TRANSIENT: 210 if (isNotTransient()) 211 return false; 212 break; 213 case NOT_TRANSIENT: 214 if (isTransient()) 215 return false; 216 break; 217 default: 218 throw new BasicRuntimeException("Invalid flag for field: {0}", f); 219 } 220 } 221 return true; 222 } 223 224 /** 225 * Returns <jk>true</jk> if all specified flags are applicable to this field. 226 * 227 * @param flags The flags to test for. 228 * @return <jk>true</jk> if all specified flags are applicable to this field. 229 */ 230 public boolean isAny(ReflectFlags...flags) { 231 for (ReflectFlags f : flags) { 232 switch (f) { 233 case DEPRECATED: 234 if (isDeprecated()) 235 return true; 236 break; 237 case NOT_DEPRECATED: 238 if (isNotDeprecated()) 239 return true; 240 break; 241 case PUBLIC: 242 if (isPublic()) 243 return true; 244 break; 245 case NOT_PUBLIC: 246 if (isNotPublic()) 247 return true; 248 break; 249 case STATIC: 250 if (isStatic()) 251 return true; 252 break; 253 case NOT_STATIC: 254 if (isNotStatic()) 255 return true; 256 break; 257 case TRANSIENT: 258 if (isTransient()) 259 return true; 260 break; 261 case NOT_TRANSIENT: 262 if (isNotTransient()) 263 return true; 264 break; 265 default: 266 throw new BasicRuntimeException("Invalid flag for field: {0}", f); 267 } 268 } 269 return false; 270 } 271 272 /** 273 * Returns <jk>true</jk> if all specified flags are applicable to this field. 274 * 275 * @param flags The flags to test for. 276 * @return <jk>true</jk> if all specified flags are applicable to this field. 277 */ 278 public boolean is(ReflectFlags...flags) { 279 return isAll(flags); 280 } 281 282 /** 283 * Returns <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it. 284 * 285 * @return <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it. 286 */ 287 public boolean isDeprecated() { 288 return f.isAnnotationPresent(Deprecated.class); 289 } 290 291 /** 292 * Returns <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it. 293 * 294 * @return <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it. 295 */ 296 public boolean isNotDeprecated() { 297 return ! f.isAnnotationPresent(Deprecated.class); 298 } 299 300 /** 301 * Returns <jk>true</jk> if this field is public. 302 * 303 * @return <jk>true</jk> if this field is public. 304 */ 305 public boolean isPublic() { 306 return Modifier.isPublic(f.getModifiers()); 307 } 308 309 /** 310 * Returns <jk>true</jk> if this field is not public. 311 * 312 * @return <jk>true</jk> if this field is not public. 313 */ 314 public boolean isNotPublic() { 315 return ! Modifier.isPublic(f.getModifiers()); 316 } 317 318 /** 319 * Returns <jk>true</jk> if this field is static. 320 * 321 * @return <jk>true</jk> if this field is static. 322 */ 323 public boolean isStatic() { 324 return Modifier.isStatic(f.getModifiers()); 325 } 326 327 /** 328 * Returns <jk>true</jk> if this field is not static. 329 * 330 * @return <jk>true</jk> if this field is not static. 331 */ 332 public boolean isNotStatic() { 333 return ! Modifier.isStatic(f.getModifiers()); 334 } 335 336 /** 337 * Returns <jk>true</jk> if this field is transient. 338 * 339 * @return <jk>true</jk> if this field is transient. 340 */ 341 public boolean isTransient() { 342 return Modifier.isTransient(f.getModifiers()); 343 } 344 345 /** 346 * Returns <jk>true</jk> if this field is not transient. 347 * 348 * @return <jk>true</jk> if this field is not transient. 349 */ 350 public boolean isNotTransient() { 351 return ! Modifier.isTransient(f.getModifiers()); 352 } 353 354 /** 355 * Returns <jk>true</jk> if the field has the specified name. 356 * 357 * @param name The name to compare against. 358 * @return <jk>true</jk> if the field has the specified name. 359 */ 360 public boolean hasName(String name) { 361 return f.getName().equals(name); 362 } 363 364 //----------------------------------------------------------------------------------------------------------------- 365 // Visibility 366 //----------------------------------------------------------------------------------------------------------------- 367 368 /** 369 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 370 * 371 * @return This object. 372 */ 373 public FieldInfo accessible() { 374 setAccessible(); 375 return this; 376 } 377 378 /** 379 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 380 * 381 * @return <jk>true</jk> if call was successful. 382 */ 383 public boolean setAccessible() { 384 try { 385 if (f != null) 386 f.setAccessible(true); 387 return true; 388 } catch (SecurityException e) { 389 return false; 390 } 391 } 392 393 /** 394 * Identifies if the specified visibility matches this field. 395 * 396 * @param v The visibility to validate against. 397 * @return <jk>true</jk> if this visibility matches the modifier attribute of this field. 398 */ 399 public boolean isVisible(Visibility v) { 400 return v.isVisible(f); 401 } 402 403 //----------------------------------------------------------------------------------------------------------------- 404 // Other methods 405 //----------------------------------------------------------------------------------------------------------------- 406 407 /** 408 * Returns <jk>true</jk> if this object passes the specified predicate test. 409 * 410 * @param test The test to perform. 411 * @return <jk>true</jk> if this object passes the specified predicate test. 412 */ 413 public boolean matches(Predicate<FieldInfo> test) { 414 return test(test, this); 415 } 416 417 /** 418 * Performs an action on this object if the specified predicate test passes. 419 * 420 * @param test A test to apply to determine if action should be executed. Can be <jk>null</jk>. 421 * @param action An action to perform on this object. 422 * @return This object. 423 */ 424 public FieldInfo accept(Predicate<FieldInfo> test, Consumer<FieldInfo> action) { 425 if (matches(test)) 426 action.accept(this); 427 return this; 428 } 429 430 /** 431 * Returns the type of this field. 432 * 433 * @return The type of this field. 434 */ 435 public ClassInfo getType() { 436 if (type == null) { 437 synchronized(this) { 438 type = ClassInfo.of(f.getType()); 439 } 440 } 441 return type; 442 } 443 444 @Override 445 public String toString() { 446 return f.getDeclaringClass().getName() + "." + f.getName(); 447 } 448 449 @Override 450 public int compareTo(FieldInfo o) { 451 return getName().compareTo(o.getName()); 452 } 453 454 /** 455 * Returns the name of this field. 456 * 457 * @return The name of this field. 458 */ 459 public String getName() { 460 return f.getName(); 461 } 462 463 /** 464 * Returns the full name of this field. 465 * 466 * <h5 class='section'>Examples:</h5> 467 * <ul> 468 * <li><js>"com.foo.MyClass.myField"</js> - Method. 469 * </ul> 470 * 471 * @return The underlying executable name. 472 */ 473 public String getFullName() { 474 StringBuilder sb = new StringBuilder(128); 475 ClassInfo dc = declaringClass; 476 Package p = dc.getPackage(); 477 if (p != null) 478 sb.append(p.getName()).append('.'); 479 dc.appendShortName(sb); 480 sb.append(".").append(getName()); 481 return sb.toString(); 482 } 483 484 /** 485 * Returns the field value on the specified object. 486 * 487 * @param o The object containing the field. 488 * @param <T> The object type to retrieve. 489 * @return The field value. 490 * @throws BeanRuntimeException Field was not accessible or field does not belong to object. 491 */ 492 @SuppressWarnings("unchecked") 493 public <T> T get(Object o) throws BeanRuntimeException { 494 try { 495 f.setAccessible(true); 496 return (T)f.get(o); 497 } catch (Exception e) { 498 throw new BeanRuntimeException(e); 499 } 500 } 501 502 /** 503 * Same as {@link #get(Object)} but wraps the results in an {@link Optional}. 504 * 505 * @param o The object containing the field. 506 * @param <T> The object type to retrieve. 507 * @return The field value. 508 * @throws BeanRuntimeException Field was not accessible or field does not belong to object. 509 */ 510 public <T> Optional<T> getOptional(Object o) throws BeanRuntimeException { 511 return Optional.ofNullable(get(o)); 512 } 513 514 /** 515 * Sets the field value on the specified object. 516 * 517 * @param o The object containing the field. 518 * @param value The new field value. 519 * @throws BeanRuntimeException Field was not accessible or field does not belong to object. 520 */ 521 public void set(Object o, Object value) throws BeanRuntimeException { 522 try { 523 f.setAccessible(true); 524 f.set(o, value); 525 } catch (Exception e) { 526 throw new BeanRuntimeException(e); 527 } 528 } 529 530 /** 531 * Sets the field value on the specified object if the value is <jk>null</jk>. 532 * 533 * @param o The object containing the field. 534 * @param value The new field value. 535 * @throws BeanRuntimeException Field was not accessible or field does not belong to object. 536 */ 537 public void setIfNull(Object o, Object value) { 538 Object v = get(o); 539 if (v == null) 540 set(o, value); 541 } 542}