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}