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