In preparation for submitting a patch with Java8 Streams to GWT, I've been using streams in more situations, and have found a case with AutoBeans where they don't behave correctly. Specifically, AutoBeanFactoryGenerator builds a "shim" type for each object that it will interact with, starting with the bean types declared on the factory, and recursively handling each type it encounters as return types from declared methods.
ABFGenerator doesn't have any special wiring for List/Map/Set, which surprised me, since there is a specific class to wrap Splittable instances rather than getting an autobean wrapper/shim type - the SplittableList, SplittableSet and SplittableComplex/SimpleMap types serve this purpose - at least I thought it did. Upon reading more deeply, it seems that the generated type emul.java.util.ListAutoBean is used if 1. a wrapped (not-splittable) autobean 2. has a method that returns List, and 3. the returned value from that method/getter is not itself an autobean. In that case, the 'normal' list type gets wrapped, and the shim delegates all of its calls to the non-autobean list. Not what I had thought happened, but hey, great to learn. I had assumed that the generics issue would get in the way of this - no autobean type can actually be generic, since when we turn the JS into actual Java types, we can't just cast at will - the interfaces are not backed by JSOs after all (until you look at Splittable, but that's a different story). The generator will give you nice errors if you attempt to register a generic type *other* than by wrapping an existing type. ---- The first problem I've run into is in adding a method like Java8's Collection.stream() or Collection.spliterator(). For Spliterator, for example, it attempts to generate a type emul.java.util.SpliteratorAutoBean, which means the method getComparator(): public class SpliteratorAutoBean extends com.google.web.bindery.autobean. shared.impl.AbstractAutoBean<java.util.Spliterator> { private final java.util.Spliterator shim = new java.util.Spliterator() { public java.util.Comparator getComparator() { java.util.Comparator toReturn = SpliteratorAutoBean.this.getWrapped(). getComparator(); if (toReturn != null) { if (SpliteratorAutoBean.this.isWrapped(toReturn)) { toReturn = SpliteratorAutoBean.this.getFromWrapper(toReturn); } else { toReturn = new emul.java.util.ComparatorAutoBean(getFactory(), toReturn).as(); } } return toReturn; } So then a ComparatorAutoBean is generated, and in turn, it must have all of its method emulated. Unfortunately, it gets a few of these wrong (ABFGenerator doesn't think to avoid wrapping static methods): public java.util.Comparator naturalOrder() { java.util.Comparator toReturn = ComparatorAutoBean.this.getWrapped(). naturalOrder(); if (toReturn != null) { if (ComparatorAutoBean.this.isWrapped(toReturn)) { toReturn = ComparatorAutoBean.this.getFromWrapper(toReturn); } else { toReturn = new emul.java.util.ComparatorAutoBean(getFactory(), toReturn).as(); } } ComparatorAutoBean.this.call("naturalOrder", toReturn ); return toReturn; } This is solved easily enough by checking to see if a method is static as we iterate through and build up shims. However, this isn't sufficient to make the SpliteratorAutoBean sane. ---- Quick reminder: methods in autobeans are in four varieties - GET, SET, SET_BUILDER, and CALL. SET_BUILDER is a SET that returns 'this' so you can have a nice builder api, and anything that doesn't look like a getter or a setter is made into a CALL. Here's a funny thing - Splitterator.getComparator() looks like a GET, not a CALL. Spliterator.getExactSizeIfKnown() also is a GET, but on the other hand, Spliterator.hasCharacteristics(int) is a CALL, since it has a parameter. This means that we a) descend and generate a ComparatorAutoBean type as above, but also that the AutoBeanVisitor wiring must be able to also descend into this. This introduces our second problem (in bold): @Override protected void traverseProperties(com.google.web.bindery. autobean.shared.AutoBeanVisitor visitor, com.google.web.bindery.autobean. shared.impl.AbstractAutoBean.OneShotContext ctx) { com.google.web.bindery.autobean.shared.impl.AbstractAutoBean bean; Object value; com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext propertyContext; java.util.Spliterator as = as(); bean = (com.google.web.bindery.autobean.shared.impl.AbstractAutoBean) com.google.web.bindery.autobean.shared.AutoBeanUtils.getAutoBean(as. getComparator()); propertyContext = new com.google.web.bindery.autobean.gwt.client.impl. ClientPropertyContext( as, com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext. Setter.beanSetter(SpliteratorAutoBean.this, "comparator"), new Class<?>[] {java.util.Comparator.class, *T **extends java.lang.Object.class*}, new int[] {1, 0} ); if (visitor.visitReferenceProperty("comparator", bean, propertyContext)) { if (bean != null) { bean.traverse(visitor, ctx); } } visitor.endVisitReferenceProperty("comparator", bean, propertyContext); value = as.getExactSizeIfKnown(); propertyContext = new com.google.web.bindery.autobean.gwt.client.impl. ClientPropertyContext( as, com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext. Setter.beanSetter(SpliteratorAutoBean.this, "exactSizeIfKnown"), long.class ); if (visitor.visitValueProperty("exactSizeIfKnown", value, propertyContext)) { } visitor.endVisitValueProperty("exactSizeIfKnown", value, propertyContext ); } This gets us back to the generics problem - our SpliteratorAutoBean gives up the Spliterator<T> generic T when it implements it, but that T still matters for the return type of Spliterator.getComparator(). This doesn't bite us for Collection's spliterator() or stream() methods since they are CALLs, but GETs do require that they behave, and Java8 didn't check to see if AutoBeans would get along with them before they added these new methods. So here's why I have this as a long email, instead of simply as a submitted patch, landed before https://gwt-review.googlesource.com/#/c/13730/ and the streams patch - how do we resolve this: - Do we work harder to clean up the ClientPropertyContext type data and just toss back Object.class? (my preferred solution, seems cleanest) - Or do we go deeper, since this isn't actually a property at all, and somehow blacklist these GET AutoBeanMethods from being actually marked as GET, so that we skip this issue? (unsure how to go about this in a sane way, but very open to suggestions, since this gets worse in our third issue) For the first option, ModelUtils.ensureBaseType() is the culprit - we are calling ensureBaseType(JWildCardType[class ? super T]). @SuppressWarnings("unchecked") public static <T extends JType> T ensureBaseType(T maybeParameterized) { if (maybeParameterized.isArray() != null) { JArrayType array = maybeParameterized.isArray(); return (T) array.getOracle().getArrayType( ensureBaseType(array.getComponentType())); } if (maybeParameterized.isTypeParameter() != null) { return (T) maybeParameterized.isTypeParameter().getBaseType(); } if (maybeParameterized.isParameterized() != null) { return (T) maybeParameterized.isParameterized().getBaseType(); } if (maybeParameterized.isRawType() != null) { return (T) maybeParameterized.isRawType().getBaseType(); } if (maybeParameterized.isWildcard() != null) { return (T) maybeParameterized.isWildcard().getBaseType(); } return maybeParameterized; } We correctly identify this as a wildcard type, and attempt to return the base type, which is not enough - it is a JTypeParameter[class T extends java.lang.Object]. The base type of *that* then is our goal, a JRealClassType[java.lang.Object]. As such, calling ensureBaseType() on getBaseType() from *at least* the return value from isWildcard will fix this, though I'd like someone who knows more about these types to confirm how nested and ugly types can get. ---- Finally, a third issue, where our raw types bites us harder: public class StreamAutoBean extends com.google.web.bindery.autobean.shared. impl.AbstractAutoBean<*java.util.stream.Stream*> { private final java.util.stream.Stream shim = new *java* *.util.stream.Stream*() { ... public java.util.stream.Stream onClose(java.lang.Runnable closeHandler) { *java**.util.stream.Stream* toReturn = StreamAutoBean.this.getWrapped ().*onClose*(closeHandler); if (toReturn != null) { if (StreamAutoBean.this.isWrapped(toReturn)) { toReturn = StreamAutoBean.this.getFromWrapper(toReturn); } else { toReturn = new emul.java.util.stream.StreamAutoBean(getFactory(), toReturn).as(); } } StreamAutoBean.this.call("onClose", toReturn, closeHandler); return toReturn; } (For convenience, getWrapped in the superclass AbstractAutoBean: public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable { ... protected T getWrapped() { checkWrapped(); return wrapped; } ) The compiler error (occurs both in JavaC as well as GWT, so its not GWT specific): [ERROR] Line 290: Type mismatch: cannot convert from BaseStream to Stream or Error:(290, 88) java: incompatible types: java.util.stream.BaseStream cannot be converted to java.util.stream.Stream A little more context (from the stream patch, based entirely on types declared in Javadocs): public interface Stream<T> extends BaseStream<T, *Stream<T**>*> public interface BaseStream<T,S extends BaseStream<T,*S*>> { ... *S* onClose(Runnable closeHandler); The problem is that since our AbstractAutoBean is only generic on Stream, Java apparently throws away other type details. Since we don't specify the T for Stream, we don't get to say what S will be in BaseStream<T,S>, and so onClose can only return the raw type, BaseStream. Fixing this is either introducing generics correctly all over all generated autobeans, or casting the crap out of values that come out of methods that should be generic. Casting seems to be the expedient solution for every toReturn value, but its also clearly not a very nice one, and not necessary anywhere except for cases where generics are allowed. And, as mentioned, autobeans really don't play nicely with generics anyway, so perhaps blacklisting should be back on the table. With the cast-all-the-things approach, this final case compiles too. ---- Proposed patch at https://gwt-review.googlesource.com/14410. -- You received this message because you are subscribed to the Google Groups "GWT Contributors" group. To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/78588fa5-c831-4b32-8df4-57b1525ff86c%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.