View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.orchestra.dynaForm.metadata.impl.ejb;
20  
21  import java.lang.reflect.AccessibleObject;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Modifier;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.EnumSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.Stack;
31  import java.util.TreeSet;
32  
33  import javax.persistence.Basic;
34  import javax.persistence.Column;
35  import javax.persistence.Embeddable;
36  import javax.persistence.Embedded;
37  import javax.persistence.EmbeddedId;
38  import javax.persistence.Entity;
39  import javax.persistence.GeneratedValue;
40  import javax.persistence.Id;
41  import javax.persistence.ManyToMany;
42  import javax.persistence.ManyToOne;
43  import javax.persistence.MappedSuperclass;
44  import javax.persistence.OneToMany;
45  import javax.persistence.OneToOne;
46  import javax.persistence.Temporal;
47  import javax.persistence.Transient;
48  import javax.persistence.Version;
49  
50  import org.apache.commons.lang.StringUtils;
51  import org.apache.myfaces.orchestra.dynaForm.annot.ui.ComponentHandler;
52  import org.apache.myfaces.orchestra.dynaForm.annot.ui.DataProvider;
53  import org.apache.myfaces.orchestra.dynaForm.annot.ui.DisplayOnly;
54  import org.apache.myfaces.orchestra.dynaForm.annot.ui.DisplaySize;
55  import org.apache.myfaces.orchestra.dynaForm.annot.ui.IgnoreProperty;
56  import org.apache.myfaces.orchestra.dynaForm.annot.ui.Length;
57  import org.apache.myfaces.orchestra.dynaForm.annot.ui.Max;
58  import org.apache.myfaces.orchestra.dynaForm.annot.ui.MetaAttribute;
59  import org.apache.myfaces.orchestra.dynaForm.annot.ui.Min;
60  import org.apache.myfaces.orchestra.dynaForm.annot.ui.NotNull;
61  import org.apache.myfaces.orchestra.dynaForm.annot.ui.Range;
62  import org.apache.myfaces.orchestra.dynaForm.annot.ui.ReadOnly;
63  import org.apache.myfaces.orchestra.dynaForm.annot.ui.UIComponent;
64  import org.apache.myfaces.orchestra.dynaForm.metadata.Extractor;
65  import org.apache.myfaces.orchestra.dynaForm.metadata.MetaDataWritable;
66  import org.apache.myfaces.orchestra.dynaForm.metadata.MetaFieldWritable;
67  import org.apache.myfaces.orchestra.dynaForm.metadata.RelationType;
68  import org.apache.myfaces.orchestra.dynaForm.metadata.Selection;
69  
70  /**
71   * Extract metadata from ejb3 beans.
72   * <p>
73   * This class makes a whole lot of assumptions. 
74   * <p>
75   * It assumes that if there is a method getFoo(), and a field foo exists on the class
76   * then that field is a property.
77   */
78  public class EjbExtractor implements Extractor
79  {
80      private final static Set<String> SYSTEM_METHODS = new TreeSet<String>(
81          Arrays.asList(new String[]
82              {"hashCode", "getClass", "wait", "equals", "notify", "notifyAll",
83                  "toString"}));
84  
85      // Info about how to introspect a particular class
86      protected static class ContextInfo
87      {
88          private final Boolean accessField;
89          private final String name;
90          private final boolean embedded;
91          private final boolean id;
92  
93          protected ContextInfo(final String name, final Boolean accessField, final boolean embedded, final boolean id)
94          {
95              super();
96              this.name = name;
97              this.accessField = accessField;
98              this.embedded = embedded;
99              this.id = id;
100         }
101     }
102 
103     // Info about how to introspect the "current class" within a hierarchy
104     // of Class objects.
105     protected static class Context
106     {
107         private boolean accessField = false;
108         private Stack<ContextInfo> accessFields = new Stack<ContextInfo>();
109         private int embeddLevel = 0;
110 
111         // Are persistent properties detected by inspecting fields, or
112         // accessor-methods?
113         public void setAccessField(boolean accessField)
114         {
115             this.accessField = accessField;
116         }
117 
118         public Boolean popAccessType()
119         {
120             return accessFields.pop().accessField;
121         }
122 
123         // Defaults to false.
124         public boolean getAccessField()
125         {
126             return accessField;
127         }
128 
129         // Start processing an embedded persistent class.
130         protected void startEmbedded(final String name, final boolean embedded, final boolean id)
131         {
132             embeddLevel++;
133 
134             String contextName = name;
135             if (!accessFields.isEmpty())
136             {
137                 contextName = accessFields.peek().name + "." + name;
138             }
139 
140             accessFields.push(new ContextInfo(contextName, accessField, embedded, id));
141         }
142 
143         protected void endEmbedded()
144         {
145             embeddLevel--;
146             accessField = popAccessType();
147         }
148 
149         public String getContextName()
150         {
151             if (accessFields.isEmpty())
152             {
153                 return null;
154             }
155 
156             return accessFields.peek().name;
157         }
158 
159         public boolean isEmbedded()
160         {
161             if (accessFields.isEmpty())
162             {
163                 return true;
164             }
165 
166             return accessFields.peek().embedded;
167         }
168 
169         public boolean isId()
170         {
171             if (accessFields.isEmpty())
172             {
173                 return true;
174             }
175 
176             return accessFields.peek().id;
177         }
178     }
179 
180     public EjbExtractor()
181     {
182     }
183 
184     /**
185      * Implement the Extractor interface.
186      * <p>
187      * The entity parameter must be a String containing the fully-qualified name of a
188      * java class.
189      * <p>
190      * The specified class and its ancestors are inspected for JPA annotations, and
191      */
192     public void getMetaData(MetaDataWritable metaData, Object entity)
193     {
194         if (!(entity instanceof String))
195         {
196             throw new IllegalArgumentException("passed entity argument not a string: " + entity);
197         }
198 
199         Class<?> entityClass;
200         try
201         {
202             entityClass = Class.forName(entity.toString());
203         }
204         catch (ClassNotFoundException e)
205         {
206             throw new RuntimeException(e);
207         }
208 
209         Context context = new Context();
210 
211         create(context, metaData, entityClass);
212     }
213 
214     /**
215      * get all super classes needed to be parsed.
216      * <p>
217      * Populates the classes list with the complete superclass chain of the specified
218      * class. At return the first entry added to the list is the furthest ancestor that
219      * is NOT Object.class, and the last entry in the list is the clazz parameter itself.
220      */
221     protected void createClassList(List<Class<?>> classes, Class<?> clazz)
222     {
223         Class<?> superClass = clazz.getSuperclass();
224         // todo: rewrite this funky recursive method as a normal loop.
225         if (superClass != null && !superClass.equals(Object.class))
226         {
227             createClassList(classes, superClass);
228         }
229 
230         classes.add(clazz);
231     }
232 
233     /**
234      * create the metadata for the given class
235      */
236     @SuppressWarnings("unchecked")
237     protected void create(Context context, MetaDataWritable metaData, Class entityClass)
238     {
239         /* do not check if this is really a entity. this allows us to parse any given bean
240         if (!entityClass.isAnnotationPresent(Entity.class))
241         {
242             throw new IllegalArgumentException(entityClass.getName()
243                     + " is not a ejb3 bean");
244         }
245         */
246 
247         List<Class<?>> classes = new ArrayList<Class<?>>(10);
248         createClassList(classes, entityClass);
249 
250         for (Class clazz : classes)
251         {
252             // Note that accessByField defaults to false. That means that
253             // initFromMethods is called.
254             boolean accessByField = context.getAccessField();
255 
256             Boolean determinedAccessByField = determineAccessByField(entityClass);
257             if (determinedAccessByField != null)
258             {
259                 accessByField = determinedAccessByField.booleanValue();
260             }
261 
262             if (accessByField)
263             {
264                 context.setAccessField(true);
265                 initFromFields(context, metaData, getFields(clazz));
266             }
267             else
268             {
269                 context.setAccessField(false);
270                 initFromMethods(context, metaData, getMethods(clazz));
271             }
272         }
273     }
274 
275     // Check whether we should look at the persistent fields of this class, or the
276     // accessor-methods. This is done by looking for JPA annotations.
277     //
278     // TODO: this only works when annotations are used, but annotations are optional in
279     // JPA; external xml config can also be used.
280     //
281     // Returns Boolean.TRUE if we should definitely look at the fields.
282     // Returns Boolean.FALSE if we should definitely look at the methods
283     // Returns null if there is no way to tell.
284     protected Boolean determineAccessByField(Class<?> clazz)
285     {
286         Class<?> checkClass = clazz;
287         while (checkClass != null && !checkClass.equals(Object.class))
288         {
289             Method[] methods = checkClass.getDeclaredMethods();
290             for (Method method : methods)
291             {
292                 if (method.isSynthetic())
293                 {
294                     continue;
295                 }
296 
297                 if (method.isAnnotationPresent(Id.class) || method.isAnnotationPresent(EmbeddedId.class))
298                 {
299                     return Boolean.FALSE;
300                 }
301             }
302 
303             Field[] fields = checkClass.getDeclaredFields();
304             for (Field field : fields)
305             {
306                 if (field.isSynthetic())
307                 {
308                     continue;
309                 }
310 
311                 if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class))
312                 {
313                     return Boolean.TRUE;
314                 }
315             }
316 
317             checkClass = checkClass.getSuperclass();
318         }
319 
320         return null;
321     }
322 
323     protected Method[] getMethods(Class<?> entityClass)
324     {
325         return ClassHelperFactory.get().getMethods(entityClass);
326     }
327 
328     protected Field[] getFields(Class<?> entityClass)
329     {
330         return ClassHelperFactory.get().getFields(entityClass);
331     }
332 
333     /**
334      * ejb3 access through fields
335      */
336     protected void initFromFields(Context context, MetaDataWritable metaData, Field[] fields)
337     {
338         for (Field field : fields)
339         {
340             if (!validModifier(field.getModifiers(), false)
341                 || field.isSynthetic()
342                 || hasAnnotationTransient(field))
343             {
344                 // Field is static, or is marked with java keyword transient, or
345                 // is marked with JPA annotation @Transient. Ignore it.
346                 continue;
347             }
348             String name = field.getName();
349             Class<?> type = field.getType();
350 
351             if (metaData.isWantedField(createFullName(context, name)))
352             {
353                 processField(context, metaData, field, name, type, true, true);
354             }
355         }
356     }
357 
358     /**
359      * process the given field - or ist superclass if it is embedded
360      */
361     protected void processField(Context context, MetaDataWritable metaData, AccessibleObject accessibleObject, 
362             String name, Class<?> type, Boolean canRead, Boolean canWrite)
363     {
364         if (accessibleObject.isAnnotationPresent(IgnoreProperty.class))
365         {
366             // skip this field
367             return;
368         }
369 
370         // embeddable if its annotation with @embedded - also check of @id, it might be a composite-key
371         if (processEmbedded(context, metaData, accessibleObject, name, type))
372         {
373             return;
374         }
375 
376         if (metaData.isParentOfWantedField(name))
377         {
378             // we processed this field due to the fact that it was the parent of a requestedField
379             embeddEntity(context, metaData, name, type);
380             // now exit
381             return;
382         }
383 
384         MetaFieldWritable mdField = metaData.getOrCreateField(createFullName(context, name));
385         mdField.setType(type);
386         if (canRead != null)
387         {
388             mdField.setCanRead(canRead.booleanValue());
389         }
390         if (canWrite != null)
391         {
392             mdField.setCanWrite(canWrite.booleanValue());
393         }
394         mdField.setEmbedded(context.isEmbedded());
395         initFromType(context, mdField, type);
396         initFromAnnotations(context, mdField, accessibleObject);
397     }
398 
399     protected boolean processEmbedded(Context context, MetaDataWritable metaData, AccessibleObject accessibleObject,
400             String name, Class<?> type)
401     {
402         if (accessibleObject.isAnnotationPresent(Embedded.class)
403                 || accessibleObject.isAnnotationPresent(Id.class)
404                 || accessibleObject.isAnnotationPresent(EmbeddedId.class))
405         {
406             if (type.isAnnotationPresent(Embeddable.class) || type.isAnnotationPresent(MappedSuperclass.class))
407             {
408                 // process embedded type
409                 try
410                 {
411                     boolean isId = context.isId()
412                         || accessibleObject.isAnnotationPresent(Id.class)
413                         || accessibleObject.isAnnotationPresent(EmbeddedId.class);
414 
415                     context.startEmbedded(name, true, isId);
416 
417                     create(context, metaData, type);
418                 }
419                 finally
420                 {
421                     context.endEmbedded();
422                 }
423                 return true;
424             }
425         }
426 
427         return false;
428     }
429 
430     /**
431      * check if we should embedd this entity
432      */
433     protected boolean checkEmbeddEntity(Context context, MetaDataWritable metaData, String name)
434     {
435         String checkName = createFullName(context, name) + ".";
436 
437         for (String fieldName : metaData.getRequestedFields())
438         {
439             if (fieldName.startsWith(checkName))
440             {
441                 return true;
442             }
443         }
444 
445         return false;
446     }
447 
448     protected String createFullName(Context context, String name)
449     {
450         if (context.getContextName() != null)
451         {
452             return context.getContextName() + "." + name;
453         }
454         else
455         {
456             return name;
457         }
458     }
459 
460     /**
461      * embedd this entity
462      */
463     protected void embeddEntity(Context context, MetaDataWritable metaData, String name, Class<?> entityType)
464     {
465         // process embedded type
466         boolean previousLock = false;
467         try
468         {
469             boolean processAll = metaData.getRequestedFields().contains(createFullName(context, name + ".*"));
470             if (!processAll)
471             {
472                 previousLock = metaData.setLockFields(true);
473             }
474             context.startEmbedded(name, false, context.isId());
475             create(context, metaData, entityType);
476         }
477         finally
478         {
479             context.endEmbedded();
480             metaData.setLockFields(previousLock);
481         }
482     }
483 
484     /**
485      * init metadata from annotations
486      */
487     protected void initFromAnnotations(Context context, MetaFieldWritable mdField, AccessibleObject accessibleObject)
488     {
489         if (accessibleObject.isAnnotationPresent(DisplayOnly.class))
490         {
491             // display only
492             mdField.setDisplayOnly(true);
493         }
494         if (accessibleObject.isAnnotationPresent(ReadOnly.class))
495         {
496             ReadOnly readOnly = accessibleObject.getAnnotation(ReadOnly.class);
497 
498             // read-only only
499             mdField.setCanWrite(false);
500             if (readOnly.disabled())
501             {
502                 mdField.setDisabled(true);
503             }
504         }
505 
506         if (accessibleObject.isAnnotationPresent(UIComponent.class))
507         {
508             UIComponent component = accessibleObject.getAnnotation(UIComponent.class);
509             mdField.setWantedComponentType(component.type());
510         }
511 
512         if (accessibleObject.isAnnotationPresent(ComponentHandler.class))
513         {
514             ComponentHandler componentHandler = accessibleObject.getAnnotation(ComponentHandler.class);
515             String componentHandlerBeanName = componentHandler.value();
516             mdField.setComponentHandler(componentHandlerBeanName);
517         }
518 
519         if (accessibleObject.isAnnotationPresent(Column.class))
520         {
521             // is required
522             Column column = accessibleObject.getAnnotation(Column.class);
523             mdField.setRequired(!column.nullable());
524         }
525         if (accessibleObject.isAnnotationPresent(Basic.class))
526         {
527             // is required
528             Basic basic = accessibleObject.getAnnotation(Basic.class);
529             // do not clear flag if its already required from the @column annotation
530             if (!basic.optional())
531             {
532                 mdField.setRequired(!basic.optional());
533             }
534         }
535 
536         if (accessibleObject.isAnnotationPresent(Id.class)
537             || accessibleObject.isAnnotationPresent(EmbeddedId.class)
538             || context.isId())
539         {
540             mdField.setId(true);
541         }
542 
543         if (accessibleObject.isAnnotationPresent(Id.class))
544         {
545             // id column cant be written if its a generated value
546             if (accessibleObject.isAnnotationPresent(GeneratedValue.class))
547             {
548                 setSpecialFieldDisabled(mdField);
549             }
550         }
551 
552         if (accessibleObject.isAnnotationPresent(Version.class))
553         {
554             setSpecialFieldDisabled(mdField);
555         }
556 
557         if (accessibleObject.isAnnotationPresent(OneToOne.class))
558         {
559             mdField.setRelationType(RelationType.ONE_TO_ONE);
560             OneToOne relation = accessibleObject.getAnnotation(OneToOne.class);
561             if (!relation.optional())
562             {
563                 mdField.setRequired(relation.optional());
564             }
565         }
566         if (accessibleObject.isAnnotationPresent(OneToMany.class))
567         {
568             mdField.setRelationType(RelationType.ONE_TO_MANY);
569         }
570         if (accessibleObject.isAnnotationPresent(ManyToOne.class))
571         {
572             mdField.setRelationType(RelationType.MANY_TO_ONE);
573 
574             ManyToOne relation = accessibleObject.getAnnotation(ManyToOne.class);
575             if (!relation.optional())
576             {
577                 mdField.setRequired(relation.optional());
578             }
579         }
580         if (accessibleObject.isAnnotationPresent(ManyToMany.class))
581         {
582             mdField.setRelationType(RelationType.MANY_TO_MANY);
583         }
584         if (accessibleObject.isAnnotationPresent(DataProvider.class))
585         {
586             DataProvider dataProvider = accessibleObject.getAnnotation(DataProvider.class);
587             mdField.setDataSource(dataProvider.value());
588             mdField.setDataSourceDescription(dataProvider.description());
589 
590             String comparator = dataProvider.comparator();
591             if (comparator != null && comparator.length() > 0)
592             {
593                 mdField.setDataComparator(dataProvider.comparator());
594             }
595             if (!StringUtils.isEmpty(dataProvider.converterBean()))
596             {
597                 mdField.setConverterBean(dataProvider.converterBean());
598             }
599         }
600 
601         // get Temporal from model ...
602         if (accessibleObject.isAnnotationPresent(Temporal.class))
603         {
604             Temporal temporal = accessibleObject.getAnnotation(Temporal.class);
605             mdField.setTemporalType(temporal.value());
606         }
607         // ... but override with our own Temporal if required
608         if (accessibleObject.isAnnotationPresent(org.apache.myfaces.orchestra.dynaForm.annot.ui.Temporal.class))
609         {
610             org.apache.myfaces.orchestra.dynaForm.annot.ui.Temporal temporal =
611                 accessibleObject.getAnnotation(org.apache.myfaces.orchestra.dynaForm.annot.ui.Temporal.class);
612             mdField.setTemporalType(temporal.value());
613         }
614 
615         Class<?> type = mdField.getType();
616         if (type.isAnnotationPresent(Entity.class))
617         {
618             mdField.setEntityType(true);
619         }
620 
621         if (accessibleObject.isAnnotationPresent(Min.class))
622         {
623             Min annot = accessibleObject.getAnnotation(Min.class);
624             mdField.setMinValue((double) annot.value());
625         }
626         if (accessibleObject.isAnnotationPresent(Max.class))
627         {
628             Max annot = accessibleObject.getAnnotation(Max.class);
629             mdField.setMaxValue((double) annot.value());
630         }
631         if (accessibleObject.isAnnotationPresent(Length.class))
632         {
633             Length annot = accessibleObject.getAnnotation(Length.class);
634             mdField.setMinSize(annot.min());
635             mdField.setMaxSize(annot.max());
636         }
637         if (accessibleObject.isAnnotationPresent(NotNull.class))
638         {
639             mdField.setRequired(true);
640         }
641         if (accessibleObject.isAnnotationPresent(Range.class))
642         {
643             Range annot = accessibleObject.getAnnotation(Range.class);
644             mdField.setMinValue((double) annot.min());
645             mdField.setMaxValue((double) annot.max());
646         }
647 
648         if (accessibleObject.isAnnotationPresent(DisplaySize.class))
649         {
650             DisplaySize displaySize = accessibleObject.getAnnotation(DisplaySize.class);
651             mdField.setDisplaySize(displaySize.value());
652         }
653 
654         // Handle arbitrary metadata attributes
655         if (accessibleObject.isAnnotationPresent(MetaAttribute.class))
656         {
657             MetaAttribute attr = accessibleObject.getAnnotation(MetaAttribute.class);
658             String name = attr.name();
659             String value = attr.value();
660             mdField.setAttribute(name, value);
661         }
662     }
663 
664     /**
665      * configure a special fields as disabled. e.g. used for Id, Version, ....
666      */
667     protected void setSpecialFieldDisabled(MetaFieldWritable mdField)
668     {
669         mdField.setCanWrite(false);
670         mdField.setDisabled(true);
671     }
672 
673     /**
674      * ejb3 access through methods (properties)
675      */
676     protected void initFromMethods(Context context, MetaDataWritable metaData, Method[] methods)
677     {
678         for (Method method : methods)
679         {
680             if (!validModifier(method.getModifiers(), true)
681                 || method.isSynthetic()
682                 || hasAnnotationTransient(method)
683                 || SYSTEM_METHODS.contains(method.getName()))
684             {
685                 continue;
686             }
687             
688             // Given a method of name "getFoo", look for a member field of name "foo".
689             String methodName = method.getName();
690             String propertyName = convertMethodName(methodName);
691 
692             if (!metaData.isWantedField(createFullName(context, propertyName)))
693             {
694                 continue;
695             }
696 
697             if (methodName.startsWith("get") || methodName.startsWith("is"))
698             {
699                 Class<?>[] parameters = method.getParameterTypes();
700                 if (parameters != null && parameters.length > 0)
701                 {
702                     // not a bean getter
703                     continue;
704                 }
705 
706                 processField(context, metaData, method, propertyName, method.getReturnType(), true, null);
707             }
708             else if (methodName.startsWith("set"))
709             {
710                 if (!void.class.equals(method.getReturnType()) && !Void.class.equals(method.getReturnType()))
711                 {
712                     // not a bean setter
713                     continue;
714                 }
715 
716                 Class<?>[] parameters = method.getParameterTypes();
717                 if (parameters != null && parameters.length != 1)
718                 {
719                     // not a bean setter
720                     continue;
721                 }
722 
723                 if (metaData.isWantedField(createFullName(context, propertyName)))
724                 {
725                     processField(context, metaData, method, propertyName, method.getParameterTypes()[0], null, true);
726                 }
727             }
728         }
729     }
730 
731     @SuppressWarnings("unchecked")
732     protected void initFromType(Context context, MetaFieldWritable mdField, Class type)
733     {
734         if (type.isEnum())
735         {
736             EnumSet es = EnumSet.allOf(type);
737             Object[] enums = es.toArray(new Object[]{es.size()});
738 
739             Selection[] selections = new Selection[enums.length];
740             for (int i = 0; i < enums.length; i++)
741             {
742                 Enum e = (Enum) enums[i];
743                 selections[i] = new Selection(e.name(), e);
744             }
745 
746             mdField.setAllowedSelections(selections);
747         }
748         /*
749         else if (Number.class.isAssignableFrom(type))
750         {
751         }        1
752         */
753     }
754 
755     /**
756      * get rid of get/set/is in method names
757      */
758     protected String convertMethodName(String name)
759     {
760         if (name.startsWith("get"))
761         {
762             name = name.substring("get".length());
763         }
764         else if (name.startsWith("set"))
765         {
766             name = name.substring("set".length());
767         }
768         else if (name.startsWith("is"))
769         {
770             name = name.substring("is".length());
771         }
772         return Character.toLowerCase(name.charAt(0)) + name.substring(1);
773     }
774 
775     /**
776      * skip method/fields annotated with transient
777      */
778     protected boolean hasAnnotationTransient(AccessibleObject accessibleObject)
779     {
780         return accessibleObject.isAnnotationPresent(Transient.class);
781     }
782 
783     /**
784      * skip method/fields marked as static/transient
785      */
786     protected boolean validModifier(int modifier, boolean isMethod)
787     {
788         if (isMethod && !Modifier.isPublic(modifier))
789         {
790             return false;
791         }
792         if (Modifier.isStatic(modifier))
793         {
794             return false;
795         }
796         if (Modifier.isTransient(modifier))
797         {
798             return false;
799         }
800 
801         return true;
802     }
803 }