11package com .sap .cloud .sdk .datamodel .openapi .generator ;
22
33import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .FIX_REDUNDANT_IS_BOOLEAN_PREFIX ;
4+ import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .FIX_REMOVE_UNUSED_COMPONENTS ;
5+ import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .USE_EXCLUDE_PATHS ;
6+ import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .USE_EXCLUDE_PROPERTIES ;
47import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .USE_FLOAT_ARRAYS ;
58import static com .sap .cloud .sdk .datamodel .openapi .generator .GeneratorCustomProperties .USE_ONE_OF_CREATORS ;
69
10+ import java .util .Arrays ;
711import java .util .HashSet ;
12+ import java .util .LinkedHashSet ;
13+ import java .util .LinkedList ;
814import java .util .List ;
915import java .util .Map ;
1016import java .util .Set ;
2329
2430import com .sap .cloud .sdk .datamodel .openapi .generator .model .GenerationConfiguration ;
2531
32+ import io .swagger .v3 .oas .models .OpenAPI ;
2633import io .swagger .v3 .oas .models .media .Schema ;
2734import lombok .extern .slf4j .Slf4j ;
2835
36+ @ SuppressWarnings ( "PMD.TooManyStaticImports" )
2937@ Slf4j
3038class CustomJavaClientCodegen extends JavaClientCodegen
3139{
@@ -38,6 +46,33 @@ public CustomJavaClientCodegen( @Nonnull final GenerationConfiguration config )
3846 this .config = config ;
3947 }
4048
49+ @ Override
50+ public void preprocessOpenAPI ( @ Nonnull final OpenAPI openAPI )
51+ {
52+ if ( USE_EXCLUDE_PROPERTIES .isEnabled (config ) ) {
53+ final String [] exclusions = USE_EXCLUDE_PROPERTIES .getValue (config ).trim ().split ("[,\\ s]+" );
54+ for ( final String exclusion : exclusions ) {
55+ final String [] split = exclusion .split ("\\ ." , 2 );
56+ preprocessRemoveProperty (openAPI , split [0 ], split [1 ]);
57+ }
58+ }
59+
60+ if ( USE_EXCLUDE_PATHS .isEnabled (config ) ) {
61+ final String [] exclusions = USE_EXCLUDE_PATHS .getValue (config ).trim ().split ("[,\\ s]+" );
62+ for ( final String exclusion : exclusions ) {
63+ if ( !openAPI .getPaths ().keySet ().remove (exclusion ) ) {
64+ log .error ("Could not remove path {}" , exclusion );
65+ }
66+ }
67+ }
68+
69+ super .preprocessOpenAPI (openAPI );
70+
71+ if ( FIX_REMOVE_UNUSED_COMPONENTS .isEnabled (config ) ) {
72+ preprocessRemoveRedundancies (openAPI );
73+ }
74+ }
75+
4176 @ Override
4277 protected
4378 void
@@ -104,6 +139,124 @@ protected void updateModelForComposedSchema(
104139 }
105140 }
106141
142+ /**
143+ * Remove property from specification.
144+ *
145+ * @param openAPI
146+ * The OpenAPI specification to update.
147+ * @param schemaName
148+ * The name of the schema to update.
149+ * @param propertyName
150+ * The name of the property to remove.
151+ */
152+ @ SuppressWarnings ( { "rawtypes" , "unchecked" , "ReplaceInefficientStreamCount" } )
153+ private void preprocessRemoveProperty (
154+ @ Nonnull final OpenAPI openAPI ,
155+ @ Nonnull final String schemaName ,
156+ @ Nonnull final String propertyName )
157+ {
158+ final var schema = openAPI .getComponents ().getSchemas ().get (schemaName );
159+ if ( schema == null ) {
160+ log .error ("Could not find schema {} to remove property {} from." , schemaName , propertyName );
161+ return ;
162+ }
163+ boolean removed = false ;
164+
165+ final Predicate <Schema > remove =
166+ s -> s != null && s .getProperties () != null && s .getProperties ().remove (propertyName ) != null ;
167+ final var schemasQueued = new LinkedList <Schema >();
168+ final var schemasDone = new HashSet <Schema >();
169+ schemasQueued .add (schema );
170+
171+ while ( !schemasQueued .isEmpty () ) {
172+ final var s = schemasQueued .remove ();
173+ if ( s == null || !schemasDone .add (s ) ) {
174+ continue ;
175+ }
176+ // check removal of direct schema property
177+ removed |= remove .test (s );
178+
179+ // check for allOf, anyOf, oneOf
180+ for ( final List <Schema > list : Arrays .asList (s .getAllOf (), s .getAnyOf (), s .getOneOf ()) ) {
181+ if ( list != null ) {
182+ schemasQueued .addAll (list );
183+ }
184+ }
185+ }
186+ if ( !removed ) {
187+ log .error ("Could not remove property {} from schema {}." , propertyName , schemaName );
188+ }
189+ }
190+
191+ /**
192+ * Remove unused schema components.
193+ *
194+ * @param openAPI
195+ * The OpenAPI specification to update.
196+ */
197+ @ SuppressWarnings ( { "rawtypes" , "unchecked" } )
198+ private void preprocessRemoveRedundancies ( @ Nonnull final OpenAPI openAPI )
199+ {
200+ final var queue = new LinkedList <Schema >();
201+ final var done = new HashSet <Schema >();
202+ final var refs = new LinkedHashSet <String >();
203+ final var pattern = Pattern .compile ("\\ $ref: #/components/schemas/(\\ w+)" );
204+
205+ // find and queue schemas nested in paths
206+ for ( final var path : openAPI .getPaths ().values () ) {
207+ final var m = pattern .matcher (path .toString ());
208+ while ( m .find () ) {
209+ final var name = m .group (1 );
210+ final var schema = openAPI .getComponents ().getSchemas ().get (name );
211+ queue .add (schema );
212+ refs .add (m .group (0 ).split (" " )[1 ]);
213+ }
214+ }
215+
216+ while ( !queue .isEmpty () ) {
217+ final var s = queue .remove ();
218+ if ( s == null || !done .add (s ) ) {
219+ continue ;
220+ }
221+
222+ // check for $ref attribute
223+ final var ref = s .get$ref ();
224+ if ( ref != null ) {
225+ refs .add (ref );
226+ final var refName = ref .substring (ref .lastIndexOf ('/' ) + 1 );
227+ queue .add (openAPI .getComponents ().getSchemas ().get (refName ));
228+ }
229+
230+ // check for direct properties
231+ if ( s .getProperties () != null ) {
232+ for ( final var s1 : s .getProperties ().values () ) {
233+ queue .add ((Schema ) s1 );
234+ }
235+ }
236+
237+ // check for array items
238+ if ( s .getItems () != null ) {
239+ queue .add (s .getItems ());
240+ }
241+
242+ // check for allOf, anyOf, oneOf
243+ for ( final List <Schema > list : Arrays .asList (s .getAllOf (), s .getAnyOf (), s .getOneOf ()) ) {
244+ if ( list != null ) {
245+ queue .addAll (list );
246+ }
247+ }
248+ }
249+
250+ // remove all schemas that have not been marked "used"
251+ openAPI .getComponents ().getSchemas ().keySet ().removeIf (schema -> {
252+ if ( !refs .contains ("#/components/schemas/" + schema ) ) {
253+ log .info ("Removing unused schema {}" , schema );
254+ return true ;
255+ }
256+ return false ;
257+ });
258+ }
259+
107260 /**
108261 * Use JsonCreator for interface sub-types in case there are any primitives.
109262 *
0 commit comments