001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017 MicroBean.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.helm.chart;
018
019import java.beans.IntrospectionException;
020import java.beans.PropertyDescriptor;
021
022import java.io.Closeable;
023import java.io.IOException;
024
025import java.lang.reflect.Array;
026
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.Map;
030import java.util.Objects;
031import java.util.TreeSet;
032import java.util.Set;
033
034import com.google.protobuf.AnyOrBuilder;
035
036import hapi.chart.ChartOuterClass.ChartOrBuilder;
037import hapi.chart.ConfigOuterClass.ConfigOrBuilder;
038import hapi.chart.MetadataOuterClass.MaintainerOrBuilder;
039import hapi.chart.MetadataOuterClass.MetadataOrBuilder;
040import hapi.chart.TemplateOuterClass.TemplateOrBuilder;
041
042import org.yaml.snakeyaml.DumperOptions;
043import org.yaml.snakeyaml.Yaml;
044
045import org.yaml.snakeyaml.constructor.Constructor;
046
047import org.yaml.snakeyaml.introspector.BeanAccess;
048import org.yaml.snakeyaml.introspector.MethodProperty;
049import org.yaml.snakeyaml.introspector.Property;
050import org.yaml.snakeyaml.introspector.PropertyUtils;
051
052import org.yaml.snakeyaml.nodes.MappingNode;
053import org.yaml.snakeyaml.nodes.NodeTuple;
054import org.yaml.snakeyaml.nodes.Tag;
055
056import org.yaml.snakeyaml.representer.Representer;
057
058/**
059 * An object capable of writing or serializing or otherwise
060 * representing a {@link ChartOrBuilder}.
061 *
062 * @author <a href="https://about.me/lairdnelson"
063 * target="_parent">Laird Nelson</a>
064 *
065 * @see #write(ChartOuterClass.ChartOrBuilder)
066 */
067public abstract class AbstractChartWriter implements Closeable {
068
069
070  /*
071   * Constructors.
072   */
073
074
075  /**
076   * Creates a new {@link AbstractChartWriter}.
077   */
078  protected AbstractChartWriter() {
079    super();
080  }
081
082
083  /*
084   * Instance methods.
085   */
086
087  
088  /**
089   * Writes or serializes or otherwise represents the supplied {@link
090   * ChartOrBuilder}.
091   *
092   * @param chartBuilder the {@link ChartOrBuilder} to write; must not
093   * be {@code null}
094   *
095   * @exception IOException if a write error occurs
096   *
097   * @exception NullPointerException if {@code chartBuilder} is {@code
098   * null}
099   *
100   * @exception IllegalArgumentException if the {@link
101   * ChartOrBuilder#getMetadata()} method returns {@code null}, or if
102   * the {@link MetadataOrBuilder#getName()} method returns {@code
103   * null}, or if the {@link MetadataOrBuilder#getVersion()} method
104   * returns {@code null}
105   *
106   * @exception IllegalStateException if a subclass has overridden the
107   * {@link #createYaml()} method to return {@code null} and calls it
108   *
109   * @see #write(Context, ChartOuterClass.ChartOrBuilder,
110   * ChartOuterClass.ChartOrBuilder)
111   */
112  public final void write(final ChartOrBuilder chartBuilder) throws IOException {
113    this.write(null, null, Objects.requireNonNull(chartBuilder));
114  }
115
116  /**
117   * Writes or serializes or otherwise represents the supplied {@code
118   * chartBuilder} as a subchart of the supplied {@code parent} (which
119   * may be, and often is, {@code null}).
120   *
121   * @param context the {@link Context} representing the write
122   * operation; may be {@code null}
123   *
124   * @param parent the {@link ChartOrBuilder} functioning as the
125   * parent chart; may be, and often is, {@code null}; must not be
126   * identical to the {@code chartBuilder} parameter value
127   *
128   * @param chartBuilder the {@link ChartOrBuilder} to actually write;
129   * must not be {@code null}; must not be identical to the {@code
130   * parent} parameter value
131   *
132   * @exception IOException if a write error occurs
133   *
134   * @exception NullPointerException if {@code chartBuilder} is {@code null}
135   *
136   * @exception IllegalArgumentException if {@code parent} is
137   * identical to {@code chartBuilder}, or if the {@link
138   * ChartOrBuilder#getMetadata()} method returns {@code null}, or if
139   * the {@link MetadataOrBuilder#getName()} method returns {@code
140   * null}, or if the {@link MetadataOrBuilder#getVersion()} method
141   * returns {@code null}
142   *
143   * @exception IllegalStateException if a subclass has overridden the
144   * {@link #createYaml()} method to return {@code null} and calls it
145   *
146   * @see #beginWrite(Context, ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)
147   *
148   * @see #writeMetadata(Context, MetadataOuterClass.MetadataOrBuilder)
149   *
150   * @see #writeConfig(Context, ConfigOuterClass.ConfigOrBuilder)
151   *
152   * @see #writeTemplate(Context, TemplateOuterClass.TemplateOrBuilder)
153   *
154   * @see #writeFile(Context, AnyOrBuilder)
155   *
156   * @see #writeSubchart(Context, ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)
157   *
158   * @see #endWrite(Context, ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)
159   */
160  protected void write(Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
161    Objects.requireNonNull(chartBuilder);
162    if (parent == chartBuilder) {
163      throw new IllegalArgumentException("parent == chartBuilder");
164    }
165    final MetadataOrBuilder metadata = chartBuilder.getMetadataOrBuilder();
166    if (metadata == null) {
167      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata() == null"));
168    } else if (metadata.getName() == null) {
169      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata().getName() == null"));
170    } else if (metadata.getVersion() == null) {
171      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata().getVersion() == null"));
172    }
173
174    if (context == null) {
175      final Map<Object, Object> map = new HashMap<>(13);
176      context = new Context() {
177          @Override
178          public final <T> T get(final Object key, final Class<T> type) {
179            Objects.requireNonNull(key);
180            Objects.requireNonNull(type);
181            return type.cast(map.get(key));
182          }
183          
184          @Override
185          public final void put(final Object key, final Object value) {
186            Objects.requireNonNull(key);
187            Objects.requireNonNull(value);
188            map.put(key, value);
189          }
190          
191          @Override
192          public final boolean containsKey(final Object key) {
193            return map.containsKey(key);
194          }
195
196          @Override
197          public final void remove(final Object key) {
198            map.remove(key);
199          }
200        };
201    }
202
203    this.beginWrite(context, parent, chartBuilder);
204    
205    this.writeMetadata(context, metadata);
206
207    this.writeConfig(context, chartBuilder.getValuesOrBuilder());
208
209    final Collection<? extends TemplateOrBuilder> templates = chartBuilder.getTemplatesOrBuilderList();
210    if (templates != null && !templates.isEmpty()) {
211      for (final TemplateOrBuilder template : templates) {
212        this.writeTemplate(context, template);
213      }
214    }
215
216    final Collection<? extends AnyOrBuilder> files = chartBuilder.getFilesOrBuilderList();
217    if (files != null && !files.isEmpty()) {
218      for (final AnyOrBuilder file : files) {
219        this.writeFile(context, file);
220      }
221    }
222
223    final Collection<? extends ChartOrBuilder> subcharts = chartBuilder.getDependenciesOrBuilderList();
224    if (subcharts != null && !subcharts.isEmpty()) {
225      for (final ChartOrBuilder subchart : subcharts) {
226        if (subchart != null) {
227          this.writeSubchart(context, chartBuilder, subchart);
228        }
229      }
230    }
231
232    this.endWrite(context, parent, chartBuilder);
233    
234  }
235
236  /**
237   * Creates and returns a new {@link Yaml} instance for (optional)
238   * use in writing {@link ConfigOrBuilder} and {@link
239   * MetadataOrBuilder} objects.
240   *
241   * <p>This method never returns {@code null}.</p>
242   *
243   * <p>Overrides of this method must not return {@code null}.</p>
244   *
245   * <p>Behavior is undefined if overrides of this method interact
246   * with other methods defined by this class.</p>
247   *
248   * @return a non-{@code null} {@link Yaml} instance
249   */
250  protected Yaml createYaml() {
251    final Representer representer = new TerseRepresenter();
252    representer.setPropertyUtils(new CustomPropertyUtils());
253    final DumperOptions options = new DumperOptions();
254    options.setAllowReadOnlyProperties(true);
255    return new Yaml(new Constructor(), representer, options);
256  }
257
258  /**
259   * Marshals the supplied {@link Object} to YAML in the context of
260   * the supplied {@link Context} and returns the result.
261   *
262   * <p>This method never returns {@code null}.</p>
263   *
264   * <p>This method may call the {@link #createYaml()} method.</p>
265   */
266  protected final String toYAML(final Context context, final Object data) throws IOException {
267    Objects.requireNonNull(context);
268    Yaml yaml = context.get(Yaml.class.getName(), Yaml.class);
269    if (yaml == null) {
270      yaml = this.createYaml();
271      if (yaml == null) {
272        throw new IllegalStateException("createYaml() == null");
273      }
274      context.put(Yaml.class.getName(), yaml);
275    }
276    return yaml.dumpAsMap(data);
277  }
278
279  /**
280   * A callback method invoked when the {@link #write(Context,
281   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)}
282   * method has been invoked.
283   *
284   * <p>The default implementation of this method does nothing.</p>
285   *
286   * @param context the {@link Context} representing the write
287   * operation; must not be {@code null}
288   *
289   * @param parent the {@link ChartOrBuilder} functioning as the
290   * parent chart; may be, and often is, {@code null}; must not be
291   * identical to the {@code chartBuilder} parameter value
292   *
293   * @param chartBuilder the {@link ChartOrBuilder} to actually write;
294   * must not be {@code null}; must not be identical to the {@code
295   * parent} parameter value
296   *
297   * @exception IOException if a write error occurs
298   *
299   * @exception NullPointerException if either {@code context} or {@code chartBuilder} is {@code null}
300   *
301   * @exception IllegalArgumentException if {@code parent} is
302   * identical to {@code chartBuilder}
303   *
304   * @exception IllegalStateException if a subclass has overridden the
305   * {@link #createYaml()} method to return {@code null} and calls it
306   * from this method for some reason
307   *
308   */
309  protected void beginWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
310    Objects.requireNonNull(context);
311    Objects.requireNonNull(chartBuilder);
312    if (parent == chartBuilder) {
313      throw new IllegalArgumentException("parent == chartBuilder");
314    }
315  }
316
317  /**
318   * A callback method invoked when the {@link #write(Context,
319   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
320   * is time to write a relevant {@link MetadataOrBuilder} object.
321   *
322   * @param context the {@link Context} representing the write
323   * operation; must not be {@code null}
324   *
325   * @param metadata the {@link MetadataOrBuilder} to write; must not
326   * be {@code null}
327   *
328   * @exception IOException if a write error occurs
329   *
330   * @exception NullPointerException if either {@code context} or
331   * {@code metadata} is {@code null}
332   *
333   * @exception IllegalStateException if a subclass has overridden the
334   * {@link #createYaml()} method to return {@code null} and calls it
335   * from this method
336   */
337  protected abstract void writeMetadata(final Context context, final MetadataOrBuilder metadata) throws IOException;
338
339  /**
340   * A callback method invoked when the {@link #write(Context,
341   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
342   * is time to write a relevant {@link ConfigOrBuilder} object.
343   *
344   * @param context the {@link Context} representing the write
345   * operation; must not be {@code null}
346   *
347   * @param config the {@link ConfigOrBuilder} to write; must not
348   * be {@code null}
349   *
350   * @exception IOException if a write error occurs
351   *
352   * @exception NullPointerException if either {@code context} or
353   * {@code config} is {@code null}
354   *
355   * @exception IllegalStateException if a subclass has overridden the
356   * {@link #createYaml()} method to return {@code null} and calls it
357   * from this method
358   */
359  protected abstract void writeConfig(final Context context, final ConfigOrBuilder config) throws IOException;
360
361  /**
362   * A callback method invoked when the {@link #write(Context,
363   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
364   * is time to write a relevant {@link TemplateOrBuilder} object.
365   *
366   * @param context the {@link Context} representing the write
367   * operation; must not be {@code null}
368   *
369   * @param template the {@link TemplateOrBuilder} to write; must not
370   * be {@code null}
371   *
372   * @exception IOException if a write error occurs
373   *
374   * @exception NullPointerException if either {@code context} or
375   * {@code template} is {@code null}
376   *
377   * @exception IllegalStateException if a subclass has overridden the
378   * {@link #createYaml()} method to return {@code null} and calls it
379   * from this method for some reason
380   */
381  protected abstract void writeTemplate(final Context context, final TemplateOrBuilder template) throws IOException;
382
383  /**
384   * A callback method invoked when the {@link #write(Context,
385   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
386   * is time to write a relevant {@link AnyOrBuilder} object
387   * (representing an otherwise undifferentiated Helm chart file).
388   *
389   * @param context the {@link Context} representing the write
390   * operation; must not be {@code null}
391   *
392   * @param file the {@link AnyOrBuilder} to write; must not be {@code
393   * null}
394   *
395   * @exception IOException if a write error occurs
396   *
397   * @exception NullPointerException if either {@code context} or
398   * {@code file} is {@code null}
399   *
400   * @exception IllegalStateException if a subclass has overridden the
401   * {@link #createYaml()} method to return {@code null} and calls it
402   * from this method for some reason
403   */
404  protected abstract void writeFile(final Context context, final AnyOrBuilder file) throws IOException;
405
406  /**
407   * A callback method invoked when the {@link #write(Context,
408   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
409   * is time to write a relevant {@link ChartOrBuilder} object
410   * (representing a subchart within an encompassing parent Helm
411   * chart).
412   *
413   * <p>The default implementation of this method calls the {@link
414   * #write(Context, ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method.</p>
415   *
416   * @param context the {@link Context} representing the write
417   * operation; must not be {@code null}
418   *
419   * @param parent the {@link ChartOrBuilder} representing the Helm
420   * chart that parents the {@code subchart} parameter value; must not
421   * be {@code null}
422   *
423   * @param subchart the {@link ChartOrBuilder} representing the
424   * subchart to write; must not be {@code null}
425   *
426   * @exception IOException if a write error occurs
427   *
428   * @exception NullPointerException if either {@code context} or
429   * {@code parent} or {@code subchart} is {@code null}
430   *
431   * @exception IllegalArgumentException if {@code parent} is
432   * identical to {@code subchart}, or if the {@link
433   * ChartOrBuilder#getMetadata()} method returns {@code null} when
434   * invoked on either non-{@code null} {@link ChartOrBuilder}, or if
435   * the {@link MetadataOrBuilder#getName()} method returns {@code
436   * null}, or if the {@link MetadataOrBuilder#getVersion()} method
437   * returns {@code null}
438   *
439   * @exception IllegalStateException if a subclass has overridden the
440   * {@link #createYaml()} method to return {@code null} and calls it
441   * from this method for some reason
442   *
443   * @see #write(Context, ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)
444   */
445  protected void writeSubchart(final Context context, final ChartOrBuilder parent, final ChartOrBuilder subchart) throws IOException {
446    this.write(Objects.requireNonNull(context), Objects.requireNonNull(parent), Objects.requireNonNull(subchart));
447  }
448
449  /**
450   * A callback method invoked when the {@link #write(Context,
451   * ChartOuterClass.ChartOrBuilder, ChartOuterClass.ChartOrBuilder)} method has been invoked and it
452   * is time to end the write operation.
453   *
454   * <p>The default implementation of this method does nothing.</p>
455   *
456   * @param context the {@link Context} representing the write
457   * operation; must not be {@code null}
458   *
459   * @param parent the {@link ChartOrBuilder} representing the Helm
460   * chart that parents the {@code chartBuilder} parameter value; may be,
461   * and often is, {@code null}
462   *
463   * @param chartBuilder the {@link ChartOrBuilder} representing the
464   * chart currently involved in the write operation; must not be
465   * {@code null}
466   *
467   * @exception IOException if a write error occurs
468   *
469   * @exception NullPointerException if either {@code context} or
470   * {@code chartBuilder} is {@code null}
471   *
472   * @exception IllegalArgumentException if {@code parent} is
473   * identical to {@code chartBuilder}
474   *
475   * @exception IllegalStateException if a subclass has overridden the
476   * {@link #createYaml()} method to return {@code null} and calls it
477   * from this method for some reason
478   */
479  protected void endWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
480    Objects.requireNonNull(context);
481    Objects.requireNonNull(chartBuilder);
482    if (parent == chartBuilder) {
483      throw new IllegalArgumentException("parent == chartBuilder");
484    }
485  }
486
487
488  /*
489   * Inner and nested classes.
490   */
491
492
493  /**
494   * A class representing the state of a write operation.
495   *
496   * @author <a href="https://about.me/lairdnelson"
497   * target="_parent">Laird Nelson</a>
498   */
499  protected static abstract class Context {
500
501
502    /*
503     * Constructors.
504     */
505
506
507    /**
508     * Creates a new {@link Context}.
509     */
510    private Context() {
511      super();
512    }
513
514
515    /*
516     * Instance methods.
517     */
518
519    /**
520     * Returns the object indexed under the supplied {@code key}, if
521     * any, {@linkplain Class#cast(Object) cast to the proper
522     * <code>Class</code>}.
523     *
524     * <p>Implementations of this method may return {@code null}.</p>
525     *
526     * @param <T> the type of object expected
527     *
528     * @param key the key under which something is hopefully stored;
529     * may be {@code null}
530     *
531     * @param type the {@link Class} to cast the result to; must not
532     * be {@code null}
533     *
534     * @return the object in question, or {@code null}
535     *
536     * @exception NullPointerException if {@code type} is {@code null}
537     *
538     * @see #put(Object, Object)
539     */
540    public abstract <T> T get(final Object key, final Class<T> type);
541
542    /**
543     * Stores the supplied {@code value} under the supplied {@code key}.
544     *
545     * @param key the key under which the supplied {@code value} will
546     * be stored; may be {@code null}
547     *
548     * @param value the object to store; may be {@code null}
549     *
550     * @see #get(Object, Class)
551     */
552    public abstract void put(final Object key, final Object value);
553
554    /**
555     * Returns {@code true} if this {@link Context} implementation
556     * contains an object indexed under an {@link Object} {@linkplain
557     * Object#equals(Object) equal to} the supplied {@code key}.
558     *
559     * @param key the key in question; may be {@code null}
560     *
561     * @return {@code true} if this {@link Context} implementation
562     * contains an object indexed under an {@link Object} {@linkplain
563     * Object#equals(Object) equal to} the supplied {@code key};
564     * {@code false} otherwise
565     */
566    public abstract boolean containsKey(final Object key);
567
568    /**
569     * Removes any object indexed under an {@link Object} {@linkplain
570     * Object#equals(Object) equal to} the supplied {@code key}.
571     *
572     * @param key the key in question; may be {@code null}
573     */
574    public abstract void remove(final Object key);
575    
576  }
577
578  /**
579   * A {@link Representer} that attempts not to output default values
580   * or YAML tags.
581   *
582   * @author <a href="https://about.me/lairdnelson"
583   * target="_parent">Laird Nelson</a>
584   */
585  private static final class TerseRepresenter extends Representer {
586
587
588    /*
589     * Constructors.
590     */
591
592
593    /**
594     * Creates a new {@link TerseRepresenter}.
595     */
596    private TerseRepresenter() {
597      super();
598    }
599
600
601    /*
602     * Instance methods.
603     */
604
605
606    /**
607     * Represents a Java bean normally, but without any YAML tag
608     * information.
609     *
610     * @param properties a {@link Set} of {@link Property} instances
611     * indicating what facets of the supplied {@code bean} should be
612     * represented; ignored by this implementation
613     *
614     * @param bean the {@link Object} to represent; may be {@code null}
615     *
616     * @return the result of invoking {@link
617     * Representer#representJavaBean(Set, Object)}, but after adding
618     * {@link Tag#MAP} as a {@linkplain Representer#addClassTag(Class,
619     * Tag) class tag} for the supplied {@code bean}'s class
620     */
621    @Override
622    protected final MappingNode representJavaBean(final Set<Property> properties, final Object bean) {
623      if (bean != null) {
624        final Class<?> beanClass = bean.getClass();
625        if (this.getTag(beanClass, null) == null) {
626          this.addClassTag(beanClass, Tag.MAP);
627        }
628      }
629      return super.representJavaBean(properties, bean);      
630    }
631
632    /**
633     * Overrides the {@link
634     * Representer#representJavaBeanProperty(Object, Property, Object,
635     * Tag)} method to return {@code null} when the given property
636     * value can be omitted from its YAML representation without loss
637     * of information.
638     *
639     * @param bean the Java bean whose property value is being
640     * represented; may be {@code null}
641     *
642     * @param property the {@link Property} whose value is being
643     * represented; may be {@code null}
644     *
645     * @param value the value being represented; may be {@code null}
646     *
647     * @param tag the {@link Tag} in effect; may be {@code null}
648     *
649     * @return {@code null} or the result of invoking the {@link
650     * Representer#representJavaBeanProperty(Object, Property, Object,
651     * Tag)} method with the supplied values
652     */
653    @Override
654    protected final NodeTuple representJavaBeanProperty(final Object bean, final Property property, final Object value, final Tag tag) {
655      final NodeTuple returnValue;
656      if (value == null || value.equals(Boolean.FALSE)) {
657        returnValue = null;
658      } else if (value instanceof CharSequence) {
659        if (((CharSequence)value).length() <= 0) {
660          returnValue = null;
661        } else {
662          returnValue = super.representJavaBeanProperty(bean, property, value, tag);
663        }
664      } else if (value instanceof Collection) {
665        if (((Collection<?>)value).isEmpty()) {
666          returnValue = null;
667        } else {
668          returnValue = super.representJavaBeanProperty(bean, property, value, tag);
669        }
670      } else if (value instanceof Map) {
671        if (((Map<?, ?>)value).isEmpty()) {
672          returnValue = null;
673        } else {
674          returnValue = super.representJavaBeanProperty(bean, property, value, tag);
675        }
676      } else if (value.getClass().isArray()) {
677        if (Array.getLength(value) <= 0) {
678          returnValue = null;
679        } else {
680          returnValue = super.representJavaBeanProperty(bean, property, value, tag);
681        }
682      } else {
683        returnValue = super.representJavaBeanProperty(bean, property, value, tag);
684      }
685      return returnValue;
686    }
687    
688  }
689
690  /**
691   * A {@link PropertyUtils} that knows how to represent certain
692   * properties of certain Helm-related objects for the purposes of
693   * serialization to YAML.
694   *
695   * @author <a href="https://about.me/lairdnelson"
696   * target="_parent">Laird Nelson</a>
697   */
698  private static final class CustomPropertyUtils extends PropertyUtils {
699
700
701    /*
702     * Constructors.
703     */
704
705
706    /**
707     * Creates a new {@link CustomPropertyUtils}.
708     */
709    private CustomPropertyUtils() {
710      super();
711    }
712
713
714    /*
715     * Instance methods.
716     */
717    
718
719    /**
720     * Returns a {@link Set} of {@link Property} instances that will
721     * represent Java objects of the supplied {@code type} during YAML
722     * serialization.
723     *
724     * <p>This implementation overrides the {@link
725     * PropertyUtils#createPropertySet(Class, BeanAccess)} method to
726     * build explicit representations for {@link MetadataOrBuilder},
727     * {@link MaintainerOrBuilder} and {@link ConfigOrBuilder}
728     * interfaces.</p>
729     *
730     * @param type a {@link Class} for which a {@link Set} of
731     * representational {@link Property} instances should be returned;
732     * may be {@code null}
733     *
734     * @param beanAccess ignored by this implementation
735     *
736     * @return a {@link Set} of {@link Property} instances; never
737     * {@code null}
738     */
739    @Override
740    protected final Set<Property> createPropertySet(final Class<?> type, final BeanAccess beanAccess) {
741      final Set<Property> returnValue;
742      if (MetadataOrBuilder.class.isAssignableFrom(type)) {
743        returnValue = new TreeSet<>();
744        try {
745          returnValue.add(new MethodProperty(new PropertyDescriptor("apiVersion", type, "getApiVersion", null)));
746          returnValue.add(new MethodProperty(new PropertyDescriptor("appVersion", type, "getAppVersion", null)));
747          returnValue.add(new MethodProperty(new PropertyDescriptor("condition", type, "getCondition", null)));
748          returnValue.add(new MethodProperty(new PropertyDescriptor("deprecated", type, "getDeprecated", null)));
749          returnValue.add(new MethodProperty(new PropertyDescriptor("description", type, "getDescription", null)));
750          returnValue.add(new MethodProperty(new PropertyDescriptor("engine", type, "getEngine", null)));
751          returnValue.add(new MethodProperty(new PropertyDescriptor("home", type, "getHome", null)));
752          returnValue.add(new MethodProperty(new PropertyDescriptor("icon", type, "getIcon", null)));
753          returnValue.add(new MethodProperty(new PropertyDescriptor("keywords", type, "getKeywordsList", null)));
754          returnValue.add(new MethodProperty(new PropertyDescriptor("maintainers", type, "getMaintainersOrBuilderList", null)));
755          returnValue.add(new MethodProperty(new PropertyDescriptor("name", type, "getName", null)));
756          returnValue.add(new MethodProperty(new PropertyDescriptor("sources", type, "getSourcesList", null)));
757          returnValue.add(new MethodProperty(new PropertyDescriptor("tags", type, "getTags", null)));
758          returnValue.add(new MethodProperty(new PropertyDescriptor("tillerVersion", type, "getTillerVersion", null)));
759          returnValue.add(new MethodProperty(new PropertyDescriptor("version", type, "getVersion", null)));
760        } catch (final IntrospectionException introspectionException) {
761          throw new IllegalStateException(introspectionException.getMessage(), introspectionException);
762        }
763      } else if (MaintainerOrBuilder.class.isAssignableFrom(type)) {
764        returnValue = new TreeSet<>();
765        try {
766          returnValue.add(new MethodProperty(new PropertyDescriptor("name", type, "getName", null)));
767          returnValue.add(new MethodProperty(new PropertyDescriptor("email", type, "getEmail", null)));
768        } catch (final IntrospectionException introspectionException) {
769          throw new IllegalStateException(introspectionException.getMessage(), introspectionException);
770        }
771      } else if (ConfigOrBuilder.class.isAssignableFrom(type)) {
772        returnValue = new TreeSet<>();
773        try {
774          returnValue.add(new MethodProperty(new PropertyDescriptor("raw", type, "getRaw", null)));
775        } catch (final IntrospectionException introspectionException) {
776          throw new IllegalStateException(introspectionException.getMessage(), introspectionException);
777        }
778      } else {
779        returnValue = super.createPropertySet(type, beanAccess);
780      }
781      return returnValue;
782    }
783    
784  }
785  
786}