1. #11
    Sencha User
    Join Date
    Aug 2012
    Posts
    42
    Vote Rating
    0
    ramgovind is on a distinguished road

      0  

    Default


    class ClientWithNullableParentKeyValueProvider
    implements ValueProvider<PersonProxy, Long>
    {
    PersonProxy object;
    ClientWithNullableParentKeyValueProvider(PersonProxy object){
    this.object=object;
    }

    @Override
    public Long getValue(PersonProxy object) {
    return object.getParentKey() == null ? null : object.getParent().getParentKey();
    }


    @Override
    public void setValue(PersonProxy object, Long value) {
    if (object.getParent() == null) {
    object.setParent(cproxy);
    }
    object.getParent().setParentKey(value);
    }


    @Override
    public String getPath() {
    return "parent.parentKey";
    }
    }Its working with Proxy.

    Real credit goes to Colin!

  2. #12
    Sencha User
    Join Date
    Apr 2010
    Location
    Bogotá Colombia
    Posts
    5
    Vote Rating
    0
    gersonjohan is on a distinguished road

      0  

    Default


    Hi Colin,

    I think that at least for the case of reading (get), the code generator must validate is parent objects are null.

    I apreciate if you include this in a future release.

  3. #13
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,717
    Answers
    109
    Vote Rating
    88
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Short answer: null checks seem like an obvious win, but aren't. They make for ugly generated code, inconsistent results, and are a bit nasty to futher customize.

    Long answer, brace yourself:

    We avoided this in the PropertyAccess implementation for several reasons. First, this starts down the road of customization that we didn't want to entertain - if you want to build a feature like this, you can either modify/extend our generator classes, or write ValueProviders by hand (or generate them in some other way). We've also recieved requests for some default value instead of null - say, if parent is null, then parent.name should return "no parent on file" - this is more customization that doesn't belong in code like this. Second, this will certainly cause the compiler to emit more complex code - instead of just reading out the property value, now we need logic just to get values like this. In many cases, these ValueProviders compile down to just direct field access:
    Code:
    (obj.parent.name)
    //instead of
    (obj.getParent().getName())
    so adding null checks at each step end up as
    Code:
    (obj == null ? null : (obj.parent == null ? null : obj.parent.name))
    Clearly this would be almost twice as complex for each new possibly-null step in the @Path chain. At present, we still consider the costs here to outweigh the benefits.

    Now, lets look at the other half of ValueProvider: setValue. What should setValue do in your case if parent is null? Throw away the value? Continue to throw an exception (i.e. back to non-'null safe' behavior)? Automatically create a new Person object and invoke the parent setter? The first sounds a lot like 'fail silently', and the last ends up turning
    Code:
    obj.parent.name = val
    into
    Code:
    (obj.parent == null && obj.parent = new Person()), obj.parent.name = val
    This then assumes that all classes that might be used in such a way a) have default/public constructors, and b) make sense to create like this.

    If you are interested in customizing this behavior, ValueProvider generation is covered by the ValueProviderCreator class. This is invoked by PropertyAccessGenerator, as well as the XTemplateGenerator when either of those need paths to be traversed into values. There is also a LabelProviderCreator class and a ModelKeyProviderCreator class that deal with their own tasks - though a null key is never safe to use within the GXT classes that use ModelKeyProvider.

    ValueProviderCreator.getGetterExpression(String) is responsible for generating a Java expression that can read out the value from within the given object expression. If you want to use statements instead of expressions, consider taking over at a higher level, at appendGetterBody. Once you've created your own subclass, you can copy the PropertyAccessGenerator, and reference your creator instead of ValueProviderCreator. Finally, instruct GWT to use your custom generator when building PropertyAccess types with a generate-with rule, something like:
    Code:
    <generate-with class="com.project.rebind.NullPropertyAccessGenerator">
        <when-type-assignable class="com.sencha.gxt.data.shared.PropertyAccess" />
    </generate-with>
    Just make sure this rule is after any inherits statement that pulls in GXT, and it will take over for property access generation.

    if you aren't interested in modifying or forking the PropertyAccess generation (perhaps with a new interface like NullSafePropertyAccess?), here's another idea - a NullSafeValueProvider wrapper around one to many ValueProviders. Something like this - start with a PropertyAccess that emits ValueProviders without "." in them, and compose them like this:

    Code:
    ValueProvider<Company, String> ownerFirstName = NullSafeValueProvider.buildWith(companyProps.owner()).and(personProps.name()).and(nameProps.firstName()).done();
    Where companyProps is a PropertyAccess<Company>, personProps is a PropertyAccess<Person>, and nameProps is a PropertyAccess<Name>, each with the given getters/setters. Here is the quick NullSafeValueProvider class and builder code I just threw together - the builder looks extra verbose, but going with this approach should ensure type safety - that each chained ValueProvider matches the one before it:
    Code:
      public class NullSafeValueProvider<T,V> implements ValueProvider<T, V> {
        public static class IntermediateBuilder<T,V> {
          private List<ValueProvider<?, ?>> providers;
          private IntermediateBuilder(List<ValueProvider<?,?>> providers) {
            this.providers = providers;
          }
          public <U> IntermediateBuilder<T,U> and(ValueProvider<V,U> next) {
            providers.add(next);
            return new IntermediateBuilder<T, U>(providers);
          }
          public ValueProvider<T,V> done() {
            return new NullSafeValueProvider<T, V>(providers);
          }
        }
        public static <T,V> IntermediateBuilder<T,V> buildFrom(ValueProvider<T,V> initial) {
          List<ValueProvider<?,?>> rawProviders = new ArrayList<ValueProvider<?,?>>();
          rawProviders.add(initial);
          return new IntermediateBuilder<T,V>(rawProviders);
        }
        private final List<ValueProvider<?,?>> providers;
        private NullSafeValueProvider(List<ValueProvider<?, ?>> providers) {
          this.providers = providers;
        }
        @Override
        public String getPath() {
          StringBuilder sb = new StringBuilder();
          for (ValueProvider<?, ?> vp : providers) {
            if (sb.length() != 0) {
              sb.append(".");
            }
            sb.append(vp.getPath());
          }
          return sb.toString();
        }
        @SuppressWarnings({"unchecked", "rawtypes"})
        public V getValue(T object) {
          Object result = object;
          for (ValueProvider vp : providers) {
            if (result == null) {
              return null;
            }
            result = vp.getValue(result);
          }
          return (V) result;
        }
        @SuppressWarnings({"rawtypes", "unchecked"})
        public void setValue(T object, V value) {
          //This is currently written to fail if you hit a null before the final object, but
          //could also be changed to emit a warning, or fail silently
          Object actual = object;
          for (ValueProvider vp : providers.subList(0, providers.size() - 1)) {
            if (actual == null) {
              throw new NullPointerException(getPath());
            }
            actual = vp.getValue(actual);
          }
          ((ValueProvider)providers.get(providers.size() - 1)).setValue(actual, value);
        }
      }

    Untested (and unsupported), but it should allow for a clean API when chaining together existing valueproviders, checking for nulls at the end of each. At present, we don't intend to add this to the library