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}