11package com .introproventures .graphql .jpa .query .schema .impl ;
22
3- import com .introproventures .graphql .jpa .query .annotation .GraphQLIgnore ;
4-
53import java .beans .BeanInfo ;
64import java .beans .IntrospectionException ;
75import java .beans .Introspector ;
86import java .beans .PropertyDescriptor ;
97import java .lang .annotation .Annotation ;
8+ import java .lang .reflect .Field ;
9+ import java .lang .reflect .Modifier ;
10+ import java .util .Arrays ;
1011import java .util .Collection ;
12+ import java .util .Iterator ;
1113import java .util .LinkedHashMap ;
1214import java .util .Map ;
15+ import java .util .Objects ;
1316import java .util .Optional ;
17+ import java .util .Spliterator ;
18+ import java .util .Spliterators ;
19+ import java .util .function .Function ;
1420import java .util .stream .Collectors ;
1521import java .util .stream .Stream ;
22+ import java .util .stream .StreamSupport ;
1623
1724import javax .persistence .Transient ;
1825
26+ import com .introproventures .graphql .jpa .query .annotation .GraphQLIgnore ;
27+
1928public class IntrospectionUtils {
2029 private static final Map <Class <?>, CachedIntrospectionResult > map = new LinkedHashMap <>();
2130
@@ -24,25 +33,38 @@ public static CachedIntrospectionResult introspect(Class<?> entity) {
2433 }
2534
2635 public static boolean isTransient (Class <?> entity , String propertyName ) {
27- return isAnnotationPresent (entity , propertyName , Transient .class );
36+ if (!introspect (entity ).hasPropertyDescriptor (propertyName )) {
37+ throw new RuntimeException (new NoSuchFieldException (propertyName ));
38+ }
39+
40+ return Stream .of (isAnnotationPresent (entity , propertyName , Transient .class ),
41+ isModifierPresent (entity , propertyName , Modifier ::isTransient ))
42+ .anyMatch (it -> it .isPresent () && it .get () == true );
2843 }
29-
44+
3045 public static boolean isIgnored (Class <?> entity , String propertyName ) {
31- return isAnnotationPresent (entity , propertyName , GraphQLIgnore .class );
46+ return isAnnotationPresent (entity , propertyName , GraphQLIgnore .class )
47+ .orElseThrow (() -> new RuntimeException (new NoSuchFieldException (propertyName )));
3248 }
3349
34- private static boolean isAnnotationPresent (Class <?> entity , String propertyName , Class <? extends Annotation > annotation ){
50+ private static Optional < Boolean > isAnnotationPresent (Class <?> entity , String propertyName , Class <? extends Annotation > annotation ){
3551 return introspect (entity ).getPropertyDescriptor (propertyName )
36- .map (it -> it .isAnnotationPresent (annotation ))
37- .orElse (false );
52+ .map (it -> it .isAnnotationPresent (annotation ));
3853 }
3954
55+ private static Optional <Boolean > isModifierPresent (Class <?> entity , String propertyName , Function <Integer , Boolean > function ){
56+ return introspect (entity ).getField (propertyName )
57+ .map (it -> function .apply (it .getModifiers ()));
58+ }
59+
4060 public static class CachedIntrospectionResult {
4161
4262 private final Map <String , CachedPropertyDescriptor > map ;
4363 private final Class <?> entity ;
4464 private final BeanInfo beanInfo ;
45-
65+ private final Map <String , Field > fields ;
66+
67+ @ SuppressWarnings ("rawtypes" )
4668 public CachedIntrospectionResult (Class <?> entity ) {
4769 try {
4870 this .beanInfo = Introspector .getBeanInfo (entity );
@@ -54,6 +76,11 @@ public CachedIntrospectionResult(Class<?> entity) {
5476 this .map = Stream .of (beanInfo .getPropertyDescriptors ())
5577 .map (CachedPropertyDescriptor ::new )
5678 .collect (Collectors .toMap (CachedPropertyDescriptor ::getName , it -> it ));
79+
80+ this .fields = iterate ((Class ) entity , k -> Optional .ofNullable (k .getSuperclass ()))
81+ .flatMap (k -> Arrays .stream (k .getDeclaredFields ()))
82+ .filter (f -> map .containsKey (f .getName ()))
83+ .collect (Collectors .toMap (Field ::getName , it -> it ));
5784 }
5885
5986 public Collection <CachedPropertyDescriptor > getPropertyDescriptors () {
@@ -64,6 +91,14 @@ public Optional<CachedPropertyDescriptor> getPropertyDescriptor(String fieldName
6491 return Optional .ofNullable (map .getOrDefault (fieldName , null ));
6592 }
6693
94+ public boolean hasPropertyDescriptor (String fieldName ) {
95+ return map .containsKey (fieldName );
96+ }
97+
98+ public Optional <Field > getField (String fieldName ) {
99+ return Optional .ofNullable (fields .get (fieldName ));
100+ }
101+
67102 public Class <?> getEntity () {
68103 return entity ;
69104 }
@@ -83,6 +118,10 @@ public PropertyDescriptor getDelegate() {
83118 return delegate ;
84119 }
85120
121+ public Class <?> getPropertyType () {
122+ return delegate .getPropertyType ();
123+ }
124+
86125 public String getName () {
87126 return delegate .getName ();
88127 }
@@ -92,11 +131,9 @@ public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
92131 }
93132
94133 private boolean isAnnotationPresentOnField (Class <? extends Annotation > annotation ) {
95- try {
96- return entity .getDeclaredField (delegate .getName ()).isAnnotationPresent (annotation );
97- } catch (NoSuchFieldException e ) {
98- return false ;
99- }
134+ return Optional .ofNullable (fields .get (delegate .getName ()))
135+ .map (f -> f .isAnnotationPresent (annotation ))
136+ .orElse (false );
100137 }
101138
102139 private boolean isAnnotationPresentOnReadMethod (Class <? extends Annotation > annotation ) {
@@ -105,4 +142,38 @@ private boolean isAnnotationPresentOnReadMethod(Class<? extends Annotation> anno
105142
106143 }
107144 }
145+
146+ /**
147+ * The following method is borrowed from Streams.iterate,
148+ * however Streams.iterate is designed to create infinite streams.
149+ *
150+ * This version has been modified to end when Optional.empty()
151+ * is returned from the fetchNextFunction.
152+ */
153+ protected static <T > Stream <T > iterate ( T seed , Function <T , Optional <T >> fetchNextFunction ) {
154+ Objects .requireNonNull (fetchNextFunction );
155+
156+ Iterator <T > iterator = new Iterator <T >() {
157+ private Optional <T > t = Optional .ofNullable (seed );
158+
159+ @ Override
160+ public boolean hasNext () {
161+ return t .isPresent ();
162+ }
163+
164+ @ Override
165+ public T next () {
166+ T v = t .get ();
167+
168+ t = fetchNextFunction .apply (v );
169+
170+ return v ;
171+ }
172+ };
173+
174+ return StreamSupport .stream (
175+ Spliterators .spliteratorUnknownSize ( iterator , Spliterator .ORDERED | Spliterator .IMMUTABLE ),
176+ false
177+ );
178+ }
108179}
0 commit comments