1919import java .io .IOException ;
2020import java .io .Reader ;
2121import java .io .StringReader ;
22+ import java .io .Writer ;
2223import java .util .ArrayList ;
24+ import java .util .Collections ;
25+ import java .util .Comparator ;
2326import java .util .HashMap ;
27+ import java .util .Iterator ;
2428import java .util .List ;
2529import java .util .Map ;
2630import java .util .Set ;
31+ import okio .ByteString ;
2732import org .apache .commons .lang3 .tuple .MutablePair ;
2833import org .apache .commons .lang3 .tuple .Pair ;
2934import org .slf4j .Logger ;
3035import org .slf4j .LoggerFactory ;
36+ import org .yaml .snakeyaml .DumperOptions ;
3137import org .yaml .snakeyaml .constructor .Constructor ;
38+ import org .yaml .snakeyaml .introspector .Property ;
39+ import org .yaml .snakeyaml .nodes .MappingNode ;
3240import org .yaml .snakeyaml .nodes .Node ;
41+ import org .yaml .snakeyaml .nodes .NodeTuple ;
3342import 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 ;
3446
3547public class Yaml {
3648 private static Map <String , Class <?>> classes = new HashMap <>();
@@ -262,13 +274,56 @@ public static List<Object> loadAll(Reader reader) throws IOException {
262274 return list ;
263275 }
264276
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+
265317 /** Defines constructor logic for custom types in this library. */
266318 public static class CustomConstructor extends Constructor {
267319 @ Override
268320 protected Object constructObject (Node node ) {
269321 if (node .getType () == IntOrString .class ) {
270322 return constructIntOrString ((ScalarNode ) node );
271323 }
324+ if (node .getType () == byte [].class ) {
325+ return constructByteArray ((ScalarNode ) node );
326+ }
272327 return super .constructObject (node );
273328 }
274329
@@ -279,11 +334,99 @@ private IntOrString constructIntOrString(ScalarNode node) {
279334 return new IntOrString (node .getValue ());
280335 }
281336 }
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+ }
282425 }
283426
284427 /** @return An instantiated SnakeYaml Object. */
285428 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 () );
287430 }
288431
289432 /**
0 commit comments