UiBinder tweaks for GWT Designer

52 views
Skip to first unread message

Konstantin.Scheglov

unread,
Jun 23, 2010, 6:40:01 AM6/23/10
to Google Web Toolkit Contributors

To support UiBinder in GWT Designer we need to have several changes
in UiBinder generators, writer and parsers.
I will describe below these changes as they are done now.
In real patch I will group as much implementation details as
possible into single class like DesignTimeUtils or GWTDesignerSupport.
It will check for GWT Designer presence and do something only in this
case.

So, main question is following. Will GWT team accept patch with such
changes?
This would allow us avoid creating internal patches for 2.0 and each
future version of GWT.


1. Declare in binder implementation interface and field with it.
Something like this:
public static interface WBPObjectHandler {void handle(String
path, Object object);}
public WBPObjectHandler wbpObjectHandler;
In method createAndBindUi() directly after creating each Widget
instance (but before applying setX() methods) "wbpObjectHandler" is
used to notify GWT Designer about new widget and its path in XML. GWT
Designer bind Widget instance to model (using path) and also asks
default values for all properties using getX() methods.

Path in XML is "/" separated string of indexes.
For example in
<ui:UiBinder>
<ui:style/>
<g:FlowPanel styleName="{style.panel}">
<g:Button text="New Button"/>
</g:FlowPanel>
</ui:UiBinder>

"0/1" is FlowPanel
"0/1/0" is Button

2. Declare in binder implementation Map with values of attributes.
public final java.util.Map wbpAttributes = new
java.util.HashMap();
and fill it, here is example if code generated for ui.xml above

if (wbpObjectHandler != null) wbpObjectHandler.handle("0/1/0",
f_Button2);
f_Button2.setText("New Button");
wbpAttributes.put("0/1/0 text", "New Button");
f_FlowPanel1.add(f_Button2);
if (wbpObjectHandler != null) wbpObjectHandler.handle("0/1",
f_FlowPanel1);
f_FlowPanel1.setStyleName("" + style.panel() + "");

GWT Designer needs to know attribute values to show them to user in
properties table. Not all properties have getter, so we can not get
these values for existing Widget object.


3. In special parsers for panels, remember also values of attributes
for artificial elements. For example "Cell" in CellPanelParser (and
"Dock" in DockPanel).

// Parse horizontal and vertical alignment attributes.
if (cellElem.hasAttribute(HALIGN_ATTR)) {
String value = cellElem.consumeAttribute(HALIGN_ATTR,
hAlignConstantType);
writer.addStatement("%1$s.setCellHorizontalAlignment(%2$s,
%3$s);",
fieldName, childFieldName, value);
//XXX <Instantiations
writer.addStatement("wbpAttributes.put(\"%s\", %s);",
widgetElem.getPath() + " Cell." + HALIGN_ATTR, value);
//XXX >Instantiations
}



4. To allow quick updates of design canvas as user changes properties,
without reloading GWT context each time, we should:
4.1. Generate Binder implementation class with new name each time, so
be able to define each time new class in same ClassLoader. Right now
we just add current time to the name of class.
//XXX <Instantiations
// generate class with new name each time, to allow refresh in
same ClassLoader
implName += "_wbp" + System.currentTimeMillis();
//XXX >Instantiations
4.2. To parse/render UI.XML file content without saving, i.e. from
memory/editor, generate should try to read document from memory.
Something like this:
private Document getW3cDoc(MortalLogger logger, String templatePath)
throws UnableToCompleteException {
//XXX <Instantiations
{
String content = System.getProperty("wbp.gwt.UiBinder " +
templatePath);
if (content != null) {
Document doc = null;
try {
doc = new
W3cDomHelper(logger.getTreeLogger()).documentFor(content);
} catch (SAXParseException e) {
logger.die("Error parsing XML (line " + e.getLineNumber() +
"): "
+ e.getMessage() + " " + content, e);
} catch (Throwable e) {
logger.die("Error parsing XML " + content, e);
}
return doc;
}
}
//XXX >Instantiations

URL url =
UiBinderGenerator.class.getClassLoader().getResource(templatePath); //
this is default implementation

Joel Webber

unread,
Jun 23, 2010, 9:31:17 AM6/23/10
to google-web-tool...@googlegroups.com, Ray Ryan
Konstantin,

I've not gone over these proposals in great detail, but it does seem like a reasonable idea to build "design time" hooks into UiBinder-generated code. One very important caveat would be that it must be possible for the compiler to strip them out completely in production mode (this seems likely, but we'd have to be very careful to make sure it happens in practice).

I'm going to have to defer to Ray on the architectural details (I haven't touched this generator in a while), and I suspect he'd want to make sure the proposed mechanisms would be generally useful for other kinds of design tools. He's on vacation this week, so I doubt he'll be able to look into it until next week at the earliest.

@rjrjr: Please do have a look at this when you have a moment, and if you'd like me to look at anything in particular, I'd be happy to.

Cheers,
joel.

Ray Ryan

unread,
Jul 1, 2010, 10:22:06 AM7/1/10
to google-web-tool...@googlegroups.com, Joel Webber, Konstanti...@gmail.com, Scott Blum
What does WBP stand for?

This doesn't sound like a tooling hook, it sounds like a data binding framework, and a very run time one — not really how we like to do things, and not how we're gearing up to write our own data binding support this month. 

What is the model object we're talking about? Does GWT designer impose a particular architecture on its users, or is it an implementation detail of designer itself?

I'd be a lot more comfortable with hooks that happen at code generation time, e.g. to allow you to navigate the model binder builds before things get written to disk.


2. Declare in binder implementation Map with values of attributes.
      public final java.util.Map wbpAttributes = new
java.util.HashMap();
 and fill it, here is example if code generated for ui.xml above

   if (wbpObjectHandler != null) wbpObjectHandler.handle("0/1/0",
f_Button2);
   f_Button2.setText("New Button");
   wbpAttributes.put("0/1/0 text", "New Button");
   f_FlowPanel1.add(f_Button2);
   if (wbpObjectHandler != null) wbpObjectHandler.handle("0/1",
f_FlowPanel1);
   f_FlowPanel1.setStyleName("" + style.panel() + "");

 GWT Designer needs to know attribute values to show them to user in
properties table. Not all properties have getter, so we can not get
these values for existing Widget object.

Again, this smells of reflection and runtime data binding. I need more context.  


3. In special parsers for panels, remember also values of attributes
for artificial elements. For example "Cell" in CellPanelParser (and
"Dock" in DockPanel).

   // Parse horizontal and vertical alignment attributes.
   if (cellElem.hasAttribute(HALIGN_ATTR)) {
     String value = cellElem.consumeAttribute(HALIGN_ATTR,
hAlignConstantType);
     writer.addStatement("%1$s.setCellHorizontalAlignment(%2$s,
%3$s);",
         fieldName, childFieldName, value);
     //XXX <Instantiations
     writer.addStatement("wbpAttributes.put(\"%s\", %s);",
widgetElem.getPath() + " Cell." + HALIGN_ATTR, value);
     //XXX >Instantiations
   }



4. To allow quick updates of design canvas as user changes properties,
without reloading GWT context each time, we should:
4.1. Generate Binder implementation class with new name each time, so
be able to define each time new class in same ClassLoader. Right now
we just add current time to the name of class.
   //XXX <Instantiations
   // generate class with new name each time, to allow refresh in
same ClassLoader
   implName += "_wbp" + System.currentTimeMillis();
   //XXX >Instantiations

We could not do this with a timestamp, as it's important that the same code always produce the same binary. Would appending an md5 sum to the class name do the trick?
 
4.2. To parse/render UI.XML file content without saving, i.e. from
memory/editor, generate should try to read document from memory.
Something like this:
 private Document getW3cDoc(MortalLogger logger, String templatePath)
     throws UnableToCompleteException {
   //XXX <Instantiations
   {
     String content = System.getProperty("wbp.gwt.UiBinder " +
templatePath);
     if (content != null) {
       Document doc = null;
       try {
         doc = new
W3cDomHelper(logger.getTreeLogger()).documentFor(content);
       } catch (SAXParseException e) {
         logger.die("Error parsing XML (line " + e.getLineNumber() +
"): "
             + e.getMessage() + " " + content, e);
       } catch (Throwable e) {
         logger.die("Error parsing XML  " + content, e);
       }
       return doc;
     }
   }
   //XXX >Instantiations

   URL url =
UiBinderGenerator.class.getClassLoader().getResource(templatePath); //
this is default implementation

Something like this seems reasonable. @scott, since we're talking about a development tool hook here, would there be any need to go through the resource oracle? 

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Konstantin Scheglov

unread,
Jul 1, 2010, 3:55:32 PM7/1/10
to Ray Ryan, google-web-tool...@googlegroups.com, Joel Webber, Scott Blum
What does WBP stand for?

  WBP is acronym for "WindowBuilder Pro" - set of plugins for Eclipse for WYSIWYG development of GUI for Swing, SWT, RCP, XWT and GWT.

 

 Path in XML is "/" separated string of indexes.
 For example in
<ui:UiBinder>
       <ui:style/>
       <g:FlowPanel styleName="{style.panel}">
               <g:Button text="New Button"/>
       </g:FlowPanel>
</ui:UiBinder>

 "0/1" is FlowPanel
 "0/1/0" is Button

This doesn't sound like a tooling hook, it sounds like a data binding framework, and a very run time one — not really how we like to do things, and not how we're gearing up to write our own data binding support this month. 

What is the model object we're talking about? Does GWT designer impose a particular architecture on its users, or is it an implementation detail of designer itself?

  GWT Designer is tool for WYSIWYG building of GWT UI, which development I lead at Instantiations.
  Its latest release works only with Java source for GWT, but now I work on UiBinder support too and many things already work.
 
  This is absolutely not related to databinding, we just want to produce GUI builder for UiBinder.
  I think that tooling was specified as one of the reasons to use XML for GWT UI. So, this is on what I work now. :-)

 

I'd be a lot more comfortable with hooks that happen at code generation time, e.g. to allow you to navigate the model binder builds before things get written to disk.

  Note, that nothing is going to be written on disk, because we speak only about design time.
  For design time we use tweaked "dev" code, what we call "hosted mode support", which we use to create CompilingClassLoader. And for design time we don't write any output of generators on disk. Users don't need this at design time.
  And any such "wbp" related code (note, this is just current name, we could use for example "designTime" prefix) will be generated only if Beans.isDesignTime() is set to true. Nothing will be generated for "dev mode" or "deploy mode".

 

4. To allow quick updates of design canvas as user changes properties,
without reloading GWT context each time, we should:
4.1. Generate Binder implementation class with new name each time, so
be able to define each time new class in same ClassLoader. Right now
we just add current time to the name of class.
   //XXX <Instantiations
   // generate class with new name each time, to allow refresh in
same ClassLoader
   implName += "_wbp" + System.currentTimeMillis();
   //XXX >Instantiations

We could not do this with a timestamp, as it's important that the same code always produce the same binary. Would appending an md5 sum to the class name do the trick?

  Well, I hope that now it is clear that nothing is going to be "produced" with such "design time" tweaks.
  At these modes generated code will be exactly same as it is now.


--
Konstantin Scheglov,
Instantiations, Inc.

Ray Ryan

unread,
Jul 2, 2010, 10:20:38 AM7/2/10
to Konstantin Scheglov, google-web-tool...@googlegroups.com, Joel Webber, Scott Blum
On Thu, Jul 1, 2010 at 3:55 PM, Konstantin Scheglov <konstanti...@gmail.com> wrote:


What does WBP stand for?

  WBP is acronym for "WindowBuilder Pro" - set of plugins for Eclipse for WYSIWYG development of GUI for Swing, SWT, RCP, XWT and GWT.

 

 Path in XML is "/" separated string of indexes.
 For example in
<ui:UiBinder>
       <ui:style/>
       <g:FlowPanel styleName="{style.panel}">
               <g:Button text="New Button"/>
       </g:FlowPanel>
</ui:UiBinder>

 "0/1" is FlowPanel
 "0/1/0" is Button

This doesn't sound like a tooling hook, it sounds like a data binding framework, and a very run time one — not really how we like to do things, and not how we're gearing up to write our own data binding support this month. 

What is the model object we're talking about? Does GWT designer impose a particular architecture on its users, or is it an implementation detail of designer itself?

  GWT Designer is tool for WYSIWYG building of GWT UI, which development I lead at Instantiations.
  Its latest release works only with Java source for GWT, but now I work on UiBinder support too and many things already work.
 
  This is absolutely not related to databinding, we just want to produce GUI builder for UiBinder.
  I think that tooling was specified as one of the reasons to use XML for GWT UI. So, this is on what I work now. :-)

Well that makes a lot more sense. :-)

It occurs to me that this could be a lot easier when we revive r7816, which I had to roll back (r7858) due to its naive handling of tables. If we make the paths the binder builds part of its public api, you could have your map and we wouldn't have to think as hard about hiding design time hooks from developers, or at least not all of them. Certainly, you shouldn't make your changes before that code comes back (and it really, really needs to come back).

In your original note you suggested:

   In method createAndBindUi() directly after creating each Widget instance (but before applying setX() methods) "wbpObjectHandler" is used to notify GWT Designer about new widget and its path in XML. GWT Designer bind Widget instance to model (using path) and also asks default values for all properties using getX() methods.

Why do you need this notice before the set calls? 
 

 

I'd be a lot more comfortable with hooks that happen at code generation time, e.g. to allow you to navigate the model binder builds before things get written to disk.

  Note, that nothing is going to be written on disk, because we speak only about design time.
  For design time we use tweaked "dev" code, what we call "hosted mode support", which we use to create CompilingClassLoader. And for design time we don't write any output of generators on disk. Users don't need this at design time.
  And any such "wbp" related code (note, this is just current name, we could use for example "designTime" prefix) will be generated only if Beans.isDesignTime() is set to true. Nothing will be generated for "dev mode" or "deploy mode".

 

4. To allow quick updates of design canvas as user changes properties,
without reloading GWT context each time, we should:
4.1. Generate Binder implementation class with new name each time, so
be able to define each time new class in same ClassLoader. Right now
we just add current time to the name of class.
   //XXX <Instantiations
   // generate class with new name each time, to allow refresh in
same ClassLoader
   implName += "_wbp" + System.currentTimeMillis();
   //XXX >Instantiations

We could not do this with a timestamp, as it's important that the same code always produce the same binary. Would appending an md5 sum to the class name do the trick?

  Well, I hope that now it is clear that nothing is going to be "produced" with such "design time" tweaks.
  At these modes generated code will be exactly same as it is now.

But if we can avoid having alternative behavior at design time life is simpler. Would check sums on the generated class names meet that need? 

Konstantin Scheglov

unread,
Jul 2, 2010, 3:39:35 PM7/2/10
to google-web-tool...@googlegroups.com, Ray Ryan, Joel Webber, Scott Blum
On Fri, Jul 2, 2010 at 6:20 PM, Ray Ryan <rj...@google.com> wrote:

 
  This is absolutely not related to databinding, we just want to produce GUI builder for UiBinder.
  I think that tooling was specified as one of the reasons to use XML for GWT UI. So, this is on what I work now. :-)

Well that makes a lot more sense. :-)

It occurs to me that this could be a lot easier when we revive r7816, which I had to roll back (r7858) due to its naive handling of tables. If we make the paths the binder builds part of its public api, you could have your map and we wouldn't have to think as hard about hiding design time hooks from developers, or at least not all of them. Certainly, you shouldn't make your changes before that code comes back (and it really, really needs to come back).

  Sorry, but I don't understand on what to look in r7816.
  Can you give hint how this can help in accessing widget objects and attribute values?



  If you speak about paths like "0/1/0", then I already have implementation for preparing/remembering them in XMLElement.

------------
public class XMLElement {
    //XXX <Instantiations
    private static final WeakHashMap<Element, String> wbpElementPaths = new WeakHashMap<Element, String>();
    public static void rememberPathForElements(Document doc) {
        rememberPathForElements(doc.getDocumentElement(), "0");
    }
    private static void rememberPathForElements(Element element, String path) {
        wbpElementPaths.put(element, path);
        NodeList childNodes = element.getChildNodes();
        int elementIndex = 0;
        for (int i = 0; i < childNodes.getLength(); ++i) {
            Node childNode = childNodes.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                Element childElement = (Element) childNode;
                rememberPathForElements(childElement, path + "/" + elementIndex++);
            }
        }
    }   
    public String getPath() {
        return wbpElementPaths.get(elem);
    }
    //XXX >Instantiations
------------

  This method is called from UiBinderGenerator.generateOnce()
-------
    Document doc = getW3cDoc(logger, templatePath);

    //XXX <Instantiations
    XMLElement.rememberPathForElements(doc);
    //XXX >Instantiations

    uiBinderWriter.parseDocument(doc, binderPrintWriter);
-------

 

In your original note you suggested:

   In method createAndBindUi() directly after creating each Widget instance (but before applying setX() methods) "wbpObjectHandler" is used to notify GWT Designer about new widget and its path in XML. GWT Designer bind Widget instance to model (using path) and also asks default values for all properties using getX() methods.

Why do you need this notice before the set calls? 

  We need default values for properties. This allows us to remove attribute (or method invocation in case of Java version of GWTD) when user tries to set for property same value as default. Easiest example - flipping "enabled" between true/false. Default value is "true", so only enabled="false" is written into XML.

  But if enabled="false" already existed in XML, and we ask isEnabled() after set calls, then it is "false" and we can not know that default value is "true". So, we need ask isEnabled() before setEnabled(false), i.e. directly after widget creation.

  Of course situation is trivial for "enabled" and standard GWT widgets and we could hardcode its default value. But we can not expect anything about custom widgets - some of them may be will use enabled "false" by default, or introduce new properties for which we also need to know value.


  At same time we need not only "default" values, but also "actual" values for properties/attributes. Not all setX() properties have corresponding getX() method. So, only reliable way for GWT Designer to know value is patch UiBinder generator to remember these values into map.
 

 
 

4. To allow quick updates of design canvas as user changes properties,
without reloading GWT context each time, we should:
4.1. Generate Binder implementation class with new name each time, so
be able to define each time new class in same ClassLoader. Right now
we just add current time to the name of class.
   //XXX <Instantiations
   // generate class with new name each time, to allow refresh in
same ClassLoader
   implName += "_wbp" + System.currentTimeMillis();
   //XXX >Instantiations

We could not do this with a timestamp, as it's important that the same code always produce the same binary. Would appending an md5 sum to the class name do the trick?

  Well, I hope that now it is clear that nothing is going to be "produced" with such "design time" tweaks.
  At these modes generated code will be exactly same as it is now.

But if we can avoid having alternative behavior at design time life is simpler. Would check sums on the generated class names meet that need? 

  Probably yes.
  But I think that we should weight what is better/worse - quick check for design time or more-less time consuming calculation of check sum, plus having not-so-friendly class names in hosted mode.


Konstantin.Scheglov

unread,
Jul 10, 2010, 4:13:03 PM7/10/10
to Google Web Toolkit Contributors, Joel Webber, Ray Ryan

So... I don't understand what to do now.
Should I prepare patch?
Would be sad to miss GWT 2.1 release.
I believe that UiBinder support in GWT Designer will be ready at the
time when GWT 2.1 will be actual version, so would be good to allow
them play nicely with each other.

Ray Ryan

unread,
Jul 12, 2010, 1:52:29 PM7/12/10
to Konstantin Scheglov, google-web-tool...@googlegroups.com, Joel Webber, Scott Blum
[Sorry for the spotty response time, it's vacation season.]

Okay, that's pretty cute. And completely isolated from the patch I pointed you at, so don't worry about it.

I'm open to this. My only reservation at this point is the question of how we'll ensure that it's only available at "tool time."  
Reply all
Reply to author
Forward
0 new messages