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.CollectionUtils.*;
016
017import java.lang.annotation.*;
018import java.lang.reflect.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.annotation.*;
022
023/**
024 * Lightweight utility class for introspecting information about a field.
025 */
026@BeanIgnore
027public final class FieldInfo implements Comparable<FieldInfo> {
028
029   private final Field f;
030   private ClassInfo declaringClass, type;
031
032   //-----------------------------------------------------------------------------------------------------------------
033   // Instantiation.
034   //-----------------------------------------------------------------------------------------------------------------
035
036   /**
037    * Constructor.
038    *
039    * @param declaringClass The class that declares this method.
040    * @param f The field being wrapped.
041    */
042   protected FieldInfo(ClassInfo declaringClass, Field f) {
043      this.declaringClass = declaringClass;
044      this.f = f;
045   }
046
047   /**
048    * Convenience method for instantiating a {@link FieldInfo};
049    *
050    * @param declaringClass The class that declares this method.
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(ClassInfo declaringClass, Field f) {
055      if (f == null)
056         return null;
057      return new FieldInfo(declaringClass, f);
058   }
059
060   /**
061    * Convenience method for instantiating a {@link FieldInfo};
062    *
063    * @param f The field being wrapped.
064    * @return A new {@link FieldInfo} object, or <jk>null</jk> if the field was null.
065    */
066   public static FieldInfo of(Field f) {
067      if (f == null)
068         return null;
069      return new FieldInfo(null, f);
070   }
071
072   /**
073    * Returns the wrapped field.
074    *
075    * @return The wrapped field.
076    */
077   public Field inner() {
078      return f;
079   }
080
081   /**
082    * Returns metadata about the declaring class.
083    *
084    * @return Metadata about the declaring class.
085    */
086   public ClassInfo getDeclaringClass() {
087      if (declaringClass == null)
088         declaringClass = ClassInfo.of(f.getDeclaringClass());
089      return declaringClass;
090   }
091
092   //-----------------------------------------------------------------------------------------------------------------
093   // Annotations
094   //-----------------------------------------------------------------------------------------------------------------
095
096   /**
097    * Returns the specified annotation on this field.
098    *
099    * @param a The annotation to look for.
100    * @return The annotation, or <jk>null</jk> if not found.
101    */
102   public <T extends Annotation> T getAnnotation(Class<T> a) {
103      return getAnnotation(a, MetaProvider.DEFAULT);
104   }
105
106   /**
107    * Returns the specified annotation on this field.
108    *
109    * @param a The annotation to look for.
110    * @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
111    * @return The annotation, or <jk>null</jk> if not found.
112    */
113   public <T extends Annotation> T getAnnotation(Class<T> a, MetaProvider mp) {
114      return last(mp.getAnnotations(a, f));
115   }
116
117   /**
118    * Returns <jk>true</jk> if the specified annotation is present.
119    *
120    * @param a The annotation to check for.
121    * @return <jk>true</jk> if the specified annotation is present.
122    */
123   public boolean hasAnnotation(Class<? extends Annotation> a) {
124      return f.isAnnotationPresent(a);
125   }
126
127   /**
128    * Returns <jk>true</jk> if the specified annotation is present.
129    *
130    * @param a The annotation to check for.
131    * @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
132    * @return <jk>true</jk> if the specified annotation is present.
133    */
134   public boolean hasAnnotation(Class<? extends Annotation> a, MetaProvider mp) {
135      return ! mp.getAnnotations(a, f).isEmpty();
136   }
137
138   //-----------------------------------------------------------------------------------------------------------------
139   // Characteristics
140   //-----------------------------------------------------------------------------------------------------------------
141
142   /**
143    * Returns <jk>true</jk> if all specified flags are applicable to this field.
144    *
145    * @param flags The flags to test for.
146    * @return <jk>true</jk> if all specified flags are applicable to this field.
147    */
148   public boolean isAll(ReflectFlags...flags) {
149      for (ReflectFlags f : flags) {
150         switch (f) {
151            case DEPRECATED:
152               if (isNotDeprecated())
153                  return false;
154               break;
155            case NOT_DEPRECATED:
156               if (isDeprecated())
157                  return false;
158               break;
159            case PUBLIC:
160               if (isNotPublic())
161                  return false;
162               break;
163            case NOT_PUBLIC:
164               if (isPublic())
165                  return false;
166               break;
167            case STATIC:
168               if (isNotStatic())
169                  return false;
170               break;
171            case NOT_STATIC:
172               if (isStatic())
173                  return false;
174               break;
175            case TRANSIENT:
176               if (isNotTransient())
177                  return false;
178               break;
179            case NOT_TRANSIENT:
180               if (isTransient())
181                  return false;
182               break;
183            default:
184               throw new RuntimeException("Invalid flag for field: " + f);
185         }
186      }
187      return true;
188   }
189
190   /**
191    * Returns <jk>true</jk> if all specified flags are applicable to this field.
192    *
193    * @param flags The flags to test for.
194    * @return <jk>true</jk> if all specified flags are applicable to this field.
195    */
196   public boolean isAny(ReflectFlags...flags) {
197      for (ReflectFlags f : flags) {
198         switch (f) {
199            case DEPRECATED:
200               if (isDeprecated())
201                  return true;
202               break;
203            case NOT_DEPRECATED:
204               if (isNotDeprecated())
205                  return true;
206               break;
207            case PUBLIC:
208               if (isPublic())
209                  return true;
210               break;
211            case NOT_PUBLIC:
212               if (isNotPublic())
213                  return true;
214               break;
215            case STATIC:
216               if (isStatic())
217                  return true;
218               break;
219            case NOT_STATIC:
220               if (isNotStatic())
221                  return true;
222               break;
223            case TRANSIENT:
224               if (isTransient())
225                  return true;
226               break;
227            case NOT_TRANSIENT:
228               if (isNotTransient())
229                  return true;
230               break;
231            default:
232               throw new RuntimeException("Invalid flag for field: " + f);
233         }
234      }
235      return false;
236   }
237
238   /**
239    * Returns <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
240    *
241    * @return <jk>true</jk> if this field has the {@link Deprecated @Deprecated} annotation on it.
242    */
243   public boolean isDeprecated() {
244      return f.isAnnotationPresent(Deprecated.class);
245   }
246
247   /**
248    * Returns <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
249    *
250    * @return <jk>true</jk> if this field doesn't have the {@link Deprecated @Deprecated} annotation on it.
251    */
252   public boolean isNotDeprecated() {
253      return ! f.isAnnotationPresent(Deprecated.class);
254   }
255
256   /**
257    * Returns <jk>true</jk> if this field is public.
258    *
259    * @return <jk>true</jk> if this field is public.
260    */
261   public boolean isPublic() {
262      return Modifier.isPublic(f.getModifiers());
263   }
264
265   /**
266    * Returns <jk>true</jk> if this field is not public.
267    *
268    * @return <jk>true</jk> if this field is not public.
269    */
270   public boolean isNotPublic() {
271      return ! Modifier.isPublic(f.getModifiers());
272   }
273
274   /**
275    * Returns <jk>true</jk> if this field is static.
276    *
277    * @return <jk>true</jk> if this field is static.
278    */
279   public boolean isStatic() {
280      return Modifier.isStatic(f.getModifiers());
281   }
282
283   /**
284    * Returns <jk>true</jk> if this field is not static.
285    *
286    * @return <jk>true</jk> if this field is not static.
287    */
288   public boolean isNotStatic() {
289      return ! Modifier.isStatic(f.getModifiers());
290   }
291
292   /**
293    * Returns <jk>true</jk> if this field is transient.
294    *
295    * @return <jk>true</jk> if this field is transient.
296    */
297   public boolean isTransient() {
298      return Modifier.isTransient(f.getModifiers());
299   }
300
301   /**
302    * Returns <jk>true</jk> if this field is not transient.
303    *
304    * @return <jk>true</jk> if this field is not transient.
305    */
306   public boolean isNotTransient() {
307      return ! Modifier.isTransient(f.getModifiers());
308   }
309
310   /**
311    * Returns <jk>true</jk> if the field has the specified name.
312    *
313    * @param name The name to compare against.
314    * @return <jk>true</jk> if the field has the specified name.
315    */
316   public boolean hasName(String name) {
317      return f.getName().equals(name);
318   }
319
320   //-----------------------------------------------------------------------------------------------------------------
321   // Visibility
322   //-----------------------------------------------------------------------------------------------------------------
323
324   /**
325    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
326    *
327    * @return This object (for method chaining).
328    */
329   public FieldInfo accessible() {
330      setAccessible();
331      return this;
332   }
333
334   /**
335    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
336    *
337    * @return <jk>true</jk> if call was successful.
338    */
339   public boolean setAccessible() {
340      try {
341         if (! (f.isAccessible()))
342            f.setAccessible(true);
343         return true;
344      } catch (SecurityException e) {
345         return false;
346      }
347   }
348
349   /**
350    * Identifies if the specified visibility matches this field.
351    *
352    * @param v The visibility to validate against.
353    * @return <jk>true</jk> if this visibility matches the modifier attribute of this field.
354    */
355   public boolean isVisible(Visibility v) {
356      return v.isVisible(f);
357   }
358
359   //-----------------------------------------------------------------------------------------------------------------
360   // Other methods.
361   //-----------------------------------------------------------------------------------------------------------------
362
363   /**
364    * Returns the type of this field.
365    *
366    * @return The type of this field.
367    */
368   public ClassInfo getType() {
369      if (type == null)
370         type = ClassInfo.of(f.getType());
371      return type;
372   }
373
374   @Override
375   public String toString() {
376      return f.getDeclaringClass().getName() + "." + f.getName();
377   }
378
379   @Override
380   public int compareTo(FieldInfo o) {
381      return getName().compareTo(o.getName());
382   }
383
384   /**
385    * Returns the name of this field.
386    *
387    * @return The name of this field.
388    */
389   public String getName() {
390      return f.getName();
391   }
392
393   /**
394    * Invokes this field on the specified object.
395    *
396    * @param o The object containing the field.
397    * @return The field value.
398    * @throws IllegalAccessException Field was not accessible.
399    * @throws IllegalArgumentException Field does not belong to object.
400    */
401   public Object invoke(Object o) throws IllegalArgumentException, IllegalAccessException {
402      return f.get(o);
403   }
404}