mkarneim / pojobuilder

A Java Code Generator for Pojo Builders
Other
334 stars 44 forks source link

Special support for Optional Properties #134

Closed Adrodoc closed 6 years ago

Adrodoc commented 7 years ago

Optional Properties should be treated in a special way to allow the distinction between

Currently in both cases the constructor/static factory method is called with null. When using static factory methods to calculate dependened properties as described in #112 it is important to be able to differentiate the above cases. This distinction is probably also relevant for #101. This issue is also related to #92.

The example below shows what should be generated by PB. This is not backwards compatible in all regards, because the following two methods were removed due to type erasure:

public DummyBuilder withName(Builder<? extends Optional<String>> builder) {. .. }
public DummyBuilder withCountry(Builder<? extends Optional<String>> builder) {. .. }

This feature is backwards compatible to #66 meaning that any call to a with method using Optional.absent() is effectively ignored. The semantics of the following optional call are unchanged.

some(new MyPojoBuilder().withSomeProperty(Optional.fromNullable(value)));

However now it is also supported to supply null in an optional with method:

some(new MyPojoBuilder().withSomeProperty((Optional<String>) null));

In this case the constructor/setter/factory method will be called with null, regardless of whether the property is of type String or Optional<String>. In fact properties of type String and Optional<String> are treated almost identically. Missing user specification is represented by Optional.absent() for optional properties and the type dependened default value otherwise (0 for numbers, false for booleans, null for objects, etc.).

import com.google.common.base.Optional;
import net.karneim.pojobuilder.GeneratePojoBuilder;

public class Dummy {
  private String name;
  private String lastname;
  private String country;
  private String city;

  @GeneratePojoBuilder(withBuilderInterface = Builder.class, withBuilderProperties = true, withOptionalProperties = Optional.class)
  public Dummy(Optional<String> name, String lastname) {
    this.name = name.or("default");
    this.lastname = lastname;
  }

  public void setCountry(Optional<String> country) {
    this.country = country.or("Germany");
  }

  public void setCity(String city) {
    this.city = city;
  }
}

Shoud generate:

import javax.annotation.Generated;
import com.google.common.base.Optional;

@Generated("PojoBuilder")
public class DummyBuilder implements Builder<Dummy>, Cloneable {
  protected DummyBuilder self;
  protected Optional<String> value$name$java$lang$String = Optional.absent();
  protected Builder<? extends String> builder$name$java$lang$String;
  protected Optional<? extends String> value$lastname$java$lang$String = Optional.absent();
  protected Builder<? extends String> builder$lastname$java$lang$String;
  protected Optional<String> value$country$java$lang$String = Optional.absent();
  protected Builder<? extends String> builder$country$java$lang$String;
  protected Optional<? extends String> value$city$java$lang$String = Optional.absent();
  protected Builder<? extends String> builder$city$java$lang$String;

  /**
   * Creates a new {@link DummyBuilder}.
   */
  public DummyBuilder() {
    self = (DummyBuilder) this;
  }

  /**
   * Sets the default value for the {@link Dummy#name} property.
   *
   * @param value the default value
   * @return this builder
   */
  public DummyBuilder withName(String value) {
    if (value == null) {
      this.value$name$java$lang$String = null;
    } else {
      this.value$name$java$lang$String = Optional.of(value);
    }
    return self;
  }

  /**
   * Optionally sets the default value for the {@link Dummy#name} property.
   *
   * @param optionalValue the optional default value
   * @return this builder
   */
  public DummyBuilder withName(Optional<String> optionalValue) {
    if (optionalValue == null || optionalValue.isPresent()) {
      this.value$name$java$lang$String = optionalValue;
    }
    return self;
  }

  /**
   * Sets the default builder for the {@link Dummy#name} property.
   *
   * @param builder the default builder
   * @return this builder
   */
  public DummyBuilder withName(Builder<? extends String> builder) {
    this.builder$name$java$lang$String = builder;
    this.value$name$java$lang$String = Optional.absent();
    return self;
  }

  /**
   * Sets the default value for the {@link Dummy#lastname} property.
   *
   * @param value the default value
   * @return this builder
   */
  public DummyBuilder withLastname(String value) {
    if (value == null) {
      this.value$lastname$java$lang$String = null;
    } else {
      this.value$lastname$java$lang$String = Optional.of(value);
    }
    return self;
  }

  /**
   * Optionally sets the default value for the {@link Dummy#lastname} property.
   *
   * @param optionalValue the optional default value
   * @return this builder
   */
  public DummyBuilder withLastname(Optional<? extends String> optionalValue) {
    if (optionalValue == null || optionalValue.isPresent()) {
      this.value$lastname$java$lang$String = optionalValue;
    }
    return self;
  }

  /**
   * Sets the default builder for the {@link Dummy#lastname} property.
   *
   * @param builder the default builder
   * @return this builder
   */
  public DummyBuilder withLastname(Builder<? extends String> builder) {
    this.builder$lastname$java$lang$String = builder;
    this.value$lastname$java$lang$String = Optional.absent();
    return self;
  }

  /**
   * Sets the default value for the {@link Dummy#country} property.
   *
   * @param value the default value
   * @return this builder
   */
  public DummyBuilder withCountry(String value) {
    if (value == null) {
      this.value$country$java$lang$String = null;
    } else {
      this.value$country$java$lang$String = Optional.of(value);
    }
    return self;
  }

  /**
   * Optionally sets the default value for the {@link Dummy#country} property.
   *
   * @param optionalValue the optional default value
   * @return this builder
   */
  public DummyBuilder withCountry(Optional<String> optionalValue) {
    if (optionalValue == null || optionalValue.isPresent()) {
      this.value$country$java$lang$String = optionalValue;
    }
    return self;
  }

  /**
   * Sets the default builder for the {@link Dummy#country} property.
   *
   * @param builder the default builder
   * @return this builder
   */
  public DummyBuilder withCountry(Builder<? extends String> builder) {
    this.builder$country$java$lang$String = builder;
    this.value$country$java$lang$String = Optional.absent();
    return self;
  }

  /**
   * Sets the default value for the {@link Dummy#city} property.
   *
   * @param value the default value
   * @return this builder
   */
  public DummyBuilder withCity(String value) {
    if (value == null) {
      this.value$city$java$lang$String = null;
    } else {
      this.value$city$java$lang$String = Optional.of(value);
    }
    return self;
  }

  /**
   * Optionally sets the default value for the {@link Dummy#city} property.
   *
   * @param optionalValue the optional default value
   * @return this builder
   */
  public DummyBuilder withCity(Optional<? extends String> optionalValue) {
    if (optionalValue == null || optionalValue.isPresent()) {
      this.value$city$java$lang$String = optionalValue;
    }
    return self;
  }

  /**
   * Sets the default builder for the {@link Dummy#city} property.
   *
   * @param builder the default builder
   * @return this builder
   */
  public DummyBuilder withCity(Builder<? extends String> builder) {
    this.builder$city$java$lang$String = builder;
    this.value$city$java$lang$String = Optional.absent();
    return self;
  }

  /**
   * Returns a clone of this builder.
   *
   * @return the clone
   */
  @Override
  public Object clone() {
    try {
      DummyBuilder result = (DummyBuilder) super.clone();
      result.self = result;
      return result;
    } catch (CloneNotSupportedException e) {
      throw new InternalError(e.getMessage());
    }
  }

  /**
   * Returns a clone of this builder.
   *
   * @return the clone
   */
  public DummyBuilder but() {
    return (DummyBuilder) clone();
  }

  /**
   * Creates a new {@link Dummy} based on this builder's settings.
   *
   * @return the created Dummy
   */
  @Override
  public Dummy build() {
    try {
      Optional<String> _name = null;
      if (value$name$java$lang$String == null || value$name$java$lang$String.isPresent()) {
        _name = value$name$java$lang$String;
      } else if (builder$name$java$lang$String != null) {
        String builtValue = builder$name$java$lang$String.build();
        if (builtValue == null) {
          _name = null;
        } else {
          _name = Optional.<String>of(builtValue);
        }
      }
      String _lastname = null;
      if (value$lastname$java$lang$String != null) {
        if (value$lastname$java$lang$String.isPresent()) {
          _lastname = value$lastname$java$lang$String.get();
        } else if (builder$lastname$java$lang$String != null) {
          _lastname = builder$lastname$java$lang$String.build();
        }
      }
      Dummy result = new Dummy(_name, _lastname);
      if (value$country$java$lang$String == null || value$country$java$lang$String.isPresent()) {
        result.setCountry(value$country$java$lang$String);
      } else if (builder$country$java$lang$String != null) {
        String builtValue = builder$country$java$lang$String.build();
        if (builtValue == null) {
          result.setCountry((Optional<String>) null);
        } else {
          result.setCountry(Optional.<String>of(builtValue));
        }
      }
      if (value$city$java$lang$String == null) {
        result.setCity((String) null);
      } else {
        if (value$city$java$lang$String.isPresent()) {
          result.setCity(value$city$java$lang$String.get());
        } else if (builder$city$java$lang$String != null) {
          result.setCity(builder$city$java$lang$String.build());
        }
      }
      return result;
    } catch (RuntimeException ex) {
      throw ex;
    } catch (Exception ex) {
      throw new java.lang.reflect.UndeclaredThrowableException(ex);
    }
  }
}