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}