19
19
import java .io .IOException ;
20
20
import java .io .Reader ;
21
21
import java .io .StringReader ;
22
+ import java .io .Writer ;
22
23
import java .util .ArrayList ;
24
+ import java .util .Collections ;
25
+ import java .util .Comparator ;
23
26
import java .util .HashMap ;
27
+ import java .util .Iterator ;
24
28
import java .util .List ;
25
29
import java .util .Map ;
26
30
import java .util .Set ;
31
+ import okio .ByteString ;
27
32
import org .apache .commons .lang3 .tuple .MutablePair ;
28
33
import org .apache .commons .lang3 .tuple .Pair ;
29
34
import org .slf4j .Logger ;
30
35
import org .slf4j .LoggerFactory ;
36
+ import org .yaml .snakeyaml .DumperOptions ;
31
37
import org .yaml .snakeyaml .constructor .Constructor ;
38
+ import org .yaml .snakeyaml .introspector .Property ;
39
+ import org .yaml .snakeyaml .nodes .MappingNode ;
32
40
import org .yaml .snakeyaml .nodes .Node ;
41
+ import org .yaml .snakeyaml .nodes .NodeTuple ;
33
42
import org .yaml .snakeyaml .nodes .ScalarNode ;
43
+ import org .yaml .snakeyaml .nodes .Tag ;
44
+ import org .yaml .snakeyaml .representer .Represent ;
45
+ import org .yaml .snakeyaml .representer .Representer ;
34
46
35
47
public class Yaml {
36
48
private static Map <String , Class <?>> classes = new HashMap <>();
@@ -262,13 +274,56 @@ public static List<Object> loadAll(Reader reader) throws IOException {
262
274
return list ;
263
275
}
264
276
277
+ /**
278
+ * Takes an API object and returns a YAML String representing that object.
279
+ *
280
+ * @param object The API object to dump.
281
+ * @return A YAML String representing the API object.
282
+ */
283
+ public static String dump (Object object ) {
284
+ return getSnakeYaml ().dump (object );
285
+ }
286
+
287
+ /**
288
+ * Takes an API object and writes a YAML string representing that object to the writer.
289
+ *
290
+ * @param object The API object to dump
291
+ * @param writer The writer to write the YAML to.
292
+ */
293
+ public static void dump (Object object , Writer writer ) {
294
+ getSnakeYaml ().dump (object , writer );
295
+ }
296
+
297
+ /**
298
+ * Takes an Iterator of YAML API objects and returns a YAML string representing all of them
299
+ *
300
+ * @param data The list of YAML API objects
301
+ * @return A String representing the list of YAML API objects.
302
+ */
303
+ public static String dumpAll (Iterator <? extends Object > data ) {
304
+ return getSnakeYaml ().dumpAll (data );
305
+ }
306
+
307
+ /**
308
+ * Takes an Iterator of YAML API objects and writes a YAML String representing all of them.
309
+ *
310
+ * @param data The list of YAML API objects.
311
+ * @param output The writer to output the YAML String to.
312
+ */
313
+ public static void dumpAll (Iterator <? extends Object > data , Writer output ) {
314
+ getSnakeYaml ().dumpAll (data , output );
315
+ }
316
+
265
317
/** Defines constructor logic for custom types in this library. */
266
318
public static class CustomConstructor extends Constructor {
267
319
@ Override
268
320
protected Object constructObject (Node node ) {
269
321
if (node .getType () == IntOrString .class ) {
270
322
return constructIntOrString ((ScalarNode ) node );
271
323
}
324
+ if (node .getType () == byte [].class ) {
325
+ return constructByteArray ((ScalarNode ) node );
326
+ }
272
327
return super .constructObject (node );
273
328
}
274
329
@@ -279,11 +334,99 @@ private IntOrString constructIntOrString(ScalarNode node) {
279
334
return new IntOrString (node .getValue ());
280
335
}
281
336
}
337
+
338
+ private byte [] constructByteArray (ScalarNode node ) {
339
+ return ByteString .decodeBase64 (node .getValue ()).toByteArray ();
340
+ }
341
+ }
342
+
343
+ public static class CustomRepresenter extends Representer {
344
+ public CustomRepresenter () {
345
+ this .setDefaultFlowStyle (DumperOptions .FlowStyle .BLOCK );
346
+ this .representers .put (IntOrString .class , new RepresentIntOrString ());
347
+ this .representers .put (byte [].class , new RepresentByteArray ());
348
+ }
349
+
350
+ private class RepresentIntOrString implements Represent {
351
+ @ Override
352
+ public Node representData (Object data ) {
353
+ IntOrString intOrString = (IntOrString ) data ;
354
+ if (intOrString .isInteger ()) {
355
+ return CustomRepresenter .this .representData (intOrString .getIntValue ());
356
+ } else {
357
+ return CustomRepresenter .this .representData (intOrString .getStrValue ());
358
+ }
359
+ }
360
+ }
361
+
362
+ private class RepresentByteArray implements Represent {
363
+ @ Override
364
+ public Node representData (Object data ) {
365
+ String value = ByteString .of ((byte []) data ).base64 ();
366
+ return representScalar (Tag .STR , value );
367
+ }
368
+ }
369
+
370
+ /**
371
+ * This returns the ordering of properties that by convention should appear at the beginning of
372
+ * a Yaml object in Kubernetes.
373
+ */
374
+ private int getPropertyPosition (String property ) {
375
+ switch (property ) {
376
+ case "apiVersion" :
377
+ return 0 ;
378
+ case "kind" :
379
+ return 1 ;
380
+ case "metadata" :
381
+ return 2 ;
382
+ case "spec" :
383
+ return 3 ;
384
+ case "type" :
385
+ return 4 ;
386
+ default :
387
+ return Integer .MAX_VALUE ;
388
+ }
389
+ }
390
+
391
+ @ Override
392
+ protected MappingNode representJavaBean (Set <Property > properties , Object javaBean ) {
393
+ MappingNode node = super .representJavaBean (properties , javaBean );
394
+ // Always set the tag to MAP so that SnakeYaml doesn't print out the class name as a tag.
395
+ node .setTag (Tag .MAP );
396
+ // Sort the output of our map so that we put certain keys, such as apiVersion, first.
397
+ Collections .sort (
398
+ node .getValue (),
399
+ new Comparator <NodeTuple >() {
400
+ @ Override
401
+ public int compare (NodeTuple a , NodeTuple b ) {
402
+ String nameA = ((ScalarNode ) a .getKeyNode ()).getValue ();
403
+ String nameB = ((ScalarNode ) b .getKeyNode ()).getValue ();
404
+ int intCompare =
405
+ Integer .compare (getPropertyPosition (nameA ), getPropertyPosition (nameB ));
406
+ if (intCompare != 0 ) {
407
+ return intCompare ;
408
+ } else {
409
+ return nameA .compareTo (nameB );
410
+ }
411
+ }
412
+ });
413
+ return node ;
414
+ }
415
+
416
+ @ Override
417
+ protected NodeTuple representJavaBeanProperty (
418
+ Object javaBean , Property property , Object propertyValue , Tag customTag ) {
419
+ // returning null for a null property value means we won't output it in the Yaml
420
+ if (propertyValue == null ) {
421
+ return null ;
422
+ }
423
+ return super .representJavaBeanProperty (javaBean , property , propertyValue , customTag );
424
+ }
282
425
}
283
426
284
427
/** @return An instantiated SnakeYaml Object. */
285
428
public static org .yaml .snakeyaml .Yaml getSnakeYaml () {
286
- return new org .yaml .snakeyaml .Yaml (new CustomConstructor ());
429
+ return new org .yaml .snakeyaml .Yaml (new CustomConstructor (), new CustomRepresenter () );
287
430
}
288
431
289
432
/**
0 commit comments