@@ -15,11 +15,21 @@ public class DataSchemaGenerator {
1515 private static final int MAX_PROPS = 100 ;
1616
1717 public static DataSchemaItem getDataSchema (Object data ) {
18- return new DataSchemaGenerator ().getDataSchema (data , 0 );
18+ // We use an IdentityHashMap to skip the hashCode() and equals() checks
19+ // these checks can take longer than a simple identity check, and in rare cases
20+ // be themselves recursive.
21+ Set <Object > scanned = Collections .newSetFromMap (new IdentityHashMap <>());
22+ return new DataSchemaGenerator ().getDataSchema (data , 0 , scanned );
1923 }
2024
21- private DataSchemaItem getDataSchema (Object data , int depth ) {
22- if (data == null ) {
25+ private DataSchemaItem getDataSchema (Object data , int depth , Set <Object > scanned ) {
26+ if (depth > MAX_TRAVERSAL_DEPTH ) {
27+ // avoid expensive recursion loops
28+ return new DataSchemaItem (DataSchemaType .EMPTY );
29+ }
30+ depth += 1 ;
31+
32+ if (data == null || scanned .contains (data )) {
2333 // Handle null as a special case
2434 return new DataSchemaItem (DataSchemaType .EMPTY );
2535 }
@@ -29,15 +39,18 @@ private DataSchemaItem getDataSchema(Object data, int depth) {
2939 return new DataSchemaItem (primitiveToType (data ));
3040 }
3141
42+ // Don't add primitive types to the scanned list (which avoids slow & heavy recursions)
43+ scanned .add (data );
44+
3245 // Collection is a catch-all for lists, sets, ...
3346 if (data instanceof Collection <?> dataList ) {
34- return getDataSchema (dataList .toArray (), depth );
47+ return getDataSchema (dataList .toArray (), depth , scanned );
3548 }
3649 // Arrays are still another thing :
3750 if (data .getClass ().isArray ()) {
3851 DataSchemaItem items = null ;
3952 for (int i = 0 ; i < Math .min (MAX_ARRAY_DEPTH , Array .getLength (data )); i ++) {
40- DataSchemaItem childDataSchemaItem = getDataSchema (Array .get (data , i ), depth );
53+ DataSchemaItem childDataSchemaItem = getDataSchema (Array .get (data , i ), depth , scanned );
4154 if (items == null ) {
4255 items = childDataSchemaItem ;
4356 } else {
@@ -53,32 +66,30 @@ private DataSchemaItem getDataSchema(Object data, int depth) {
5366
5467 // If the depth is less than the maximum depth, get the schema for each property
5568 Map <String , DataSchemaItem > props = new HashMap <>();
56- if (depth <= MAX_TRAVERSAL_DEPTH ) {
57- if (data instanceof Map <?, ?> map ) {
58- for (Object key : map .keySet ()) {
69+ if (data instanceof Map <?, ?> map ) {
70+ for (Object key : map .keySet ()) {
71+ if (props .size () >= MAX_PROPS ) {
72+ // We cannot allow more properties than MAX_PROPS, breaking for loop.
73+ break ;
74+ }
75+ props .put ((String ) key , getDataSchema (map .get (key ), depth , scanned ));
76+ }
77+ } else if (data .getClass ().toString ().startsWith ("class org.codehaus.groovy" )) {
78+ // pass through, we do not want to check org.codehaus.groovy
79+ } else {
80+ Field [] fields = data .getClass ().getDeclaredFields ();
81+ for (Field field : fields ) {
82+ try {
83+ if (Modifier .isTransient (field .getModifiers ())) {
84+ continue ; // Do not scan transient fields.
85+ }
86+ field .setAccessible (true ); // Allow access to private fields
5987 if (props .size () >= MAX_PROPS ) {
6088 // We cannot allow more properties than MAX_PROPS, breaking for loop.
6189 break ;
6290 }
63- props .put ((String ) key , getDataSchema (map .get (key ), depth + 1 ));
64- }
65- } else if (data .getClass ().toString ().startsWith ("class org.codehaus.groovy" )) {
66- // pass through, we do not want to check org.codehaus.groovy
67- } else {
68- Field [] fields = data .getClass ().getDeclaredFields ();
69- for (Field field : fields ) {
70- try {
71- if (Modifier .isTransient (field .getModifiers ())) {
72- continue ; // Do not scan transient fields.
73- }
74- field .setAccessible (true ); // Allow access to private fields
75- if (props .size () >= MAX_PROPS ) {
76- // We cannot allow more properties than MAX_PROPS, breaking for loop.
77- break ;
78- }
79- props .put (field .getName (), getDataSchema (field .get (data ), depth + 1 ));
80- } catch (IllegalAccessException | RuntimeException ignored ) {
81- }
91+ props .put (field .getName (), getDataSchema (field .get (data ), depth , scanned ));
92+ } catch (IllegalAccessException | RuntimeException ignored ) {
8293 }
8394 }
8495 }
0 commit comments