Skip to main content

Mmmm... Java

Posted by evanx on October 17, 2006 at 11:07 AM PDT



<cs_comment title-->


What We Might Get

I understand a bit more now about closures thanks to great comments
in a previous blog entry.

For instance, return, break and continue inside the closure, might be used
to "transfer control outside the body of the closure" (as said here)
ie. in back in the enclosing method. Since such functionality could be mimicked
by throwing exceptions, what about using the throw keyword as follows.

   void refreshCalendarItems() {
      try {
         while (retry-- > 0) {
            List calendarItems = BlockingWorker() {
               try {
                  calendarItems = connection.getCalendarItems(today,
                      ProgressDisplay(int percent) {
                         publishProgress(percent);
                      });
                  return calendarItems;
                      // from closure, not from enclosing refreshCalendarItems()
               } catch (CancellationException e) {
                  publishMessage("OK, so you wanna cancel, fine!");
                  throw return; // from refreshCalendarItems()
               } catch (IOException e) {
                  publishMessage("Comms error occurred");
                  throw continue; // retry
               } catch (RuntimeException e) {
                  publishMessage("Unrecoverable fatal exception");
                  throw e; // propogate out of closure
               } finally {
                  ...
               }
            }
         }
         ... // update GUI with calendarItems
      } finally {
         ...
      }
   }

where our BlockingWorker is self-starting, and would pop up a modal progress
dialog to block while the worker is running. I'll try write such a worker
in a later blog article, which will probably use a message bus.

What C# Has Got That We Want

C# has delegates. I like those, eg. for event handler registration.

My crystal ball tells me that Java will get popular features of other popular languages.
This is a bug and a feature of software, that competing products spur each other on to more
"convenience" aka complexity. And languages, toolkits and tools are very competitive,
eg. Java vs .NET, RoR, et al. Each release will try to provide more feature candy
for developers eg. inspired by popular features in competing languages.
"We got closures and delegates too, haa-Haa!"

What I Really Want

What i keep banging on about is toolable references to properties and methods.

What about a convention like MyClass#updateGui
and object#updateGui, representing a class artifact by name,
ie. a Field, Method or PropertyDescriptor. Actually first and foremost properties,
then methods eg. event handlers, then maybe fields, or maybe not even fields.

The important thing is that MyClass#updateGui is understood by IDEs,
and so auto-completable, verifiable, refactorable, and all that.

The effect could be that the compiler sees MyClass#updateGui as
just the String "updateGui", or maybe better, as
new ClassArtifact(MyClass.class, "updateGui").

The result is that we can do cool stuff like refactorable,
toolable, reflection-safe beans binding, method delegation
and what-not as follows.

public class MoviePresentationModel {
    String title;
    Integer year;
    ...
    public String getTitle() {
       return title;
    }

    @StringValidator(empty = false)
    public void setTitle(String title) {
       this.title = title;
    }
    ...
}

public class MoviePresentation {
   GuiHelper helper = new GuiHelper(this);
   ...
   JTextField movieTitle = new JTextField();
   ...
   MoviePresentationModel presentationModel = new MoviePresentationModel()

   public MoviePresentation() {
      binder.bind(movieTitle, presentationModel, presentationModel#title);
      refreshButton.addActionListener(helper.createActionListener(this#refresh));
   }

   @Action(block = Action.WINDOW, worker = BlockingWorker.class)
   protected void refresh() {
      ...
      helper.invokeAndWait(this#updateGui);
   }

   @InEdt()
   protected void updateGui() {
      ...
   }
}

I like this because i have a little problem with strings as references. Because they aint
toolable, verifiable, factorable, refactorable, and worst of all, you gotta
type them out letter by letter!

Enum references with no Strings attached

I guess we can implement this ourselves as follows.

@PropertyInfo(MoviePresentationModel.class)
public enum MoviePresentationModelProperty {
   title,
   year,
   ...
}

@MethodInfo(MoviePresentation.class)
public enum MoviePresentationMethod {
   refresh,
   updateGui,
   ...
}

We just gotta make sure that we include these in unit tests,
so that when a refactoring operation breaks something,
we find out sooner rather than later.

Our application might then be factored as follows.

public class MoviePresentation {
   GuiHelper helper = new GuiHelper(this);
   ...
   JTextField movieTitle = new JTextField();
   ...
   protected MoviePresentation() {
      helper.bind(movieTitle, presentationModel, MoviePresentationModelProperty.title);
      refreshButton.addActionListener(helper.createActionListener(MoviePresentationMethod.refresh));
   }

   @Action(block = Action.WINDOW, worker = BlockingWorker.class)
   protected void refresh() {
      ...
      helper.invokeAndWait(MoviePresentationMethod.updateGui);
   }
   ...
}

where our helper can use name() to get the name of the enum object,
and use that to get the Method or PropertyDescriptor being referenced,
since it was already passed a reference to this
in its constructor.

The problem is that MoviePresentationMethod.updateGui is not navigable,
eg. using Alt-G in Netbeans, and that's a showstopper for me, for methods anyway.

Testing Enum Property References

Let's say we gonna use enums for property references, rather than strings.
To make it safe for refactoring, we need to test, which we can do as follows.

    @Test()
    public void test()  {
        try {
            testProperty(MoviePresentationModelProperty.class);
            ... // all our other such classes
        } catch (Exception e) {           
            assertTrue(e.getMessage(), false);
        }
    }
   
     public void testProperty(Class propertyEnumType) throws Exception {
        PropertyInfo propertyInfo =
                (PropertyInfo) propertyEnumType.getAnnotation(PropertyInfo.class);
        if (propertyInfo == null) {
            throw new Exception(propertyEnumType.getSimpleName());
        }
        test(propertyInfo.value(), propertyEnumType);
    }
   
    public void test(Class beanClass, Class propertyEnumType) throws Exception {
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        List propertyNameList = new ArrayList();
        for (PropertyDescriptor descriptor: beanInfo.getPropertyDescriptors()) {
            propertyNameList.add(descriptor.getName());
        }
        for (Enum propertyReference: (Enum[]) propertyEnumType.getEnumConstants()) {
            String propertyName = propertyReference.name();
            if (!propertyNameList.contains(propertyName)) {
                throw new Exception(propertyName);
            }
        }
    }

Mmmm... refactoring.

Postscript

I wrote this blog using very minimal thingymajig which i wrote just yesterday,
which i call quitehyper. I'll write a blog on it a week or so, once i've
written a WebStart'able GUI tool to go with it. But here's a sneak preview of its code :)

Here's a snippet of the source text (for this article).

What Might We Get //section  

Now that i understand a bit more about
:{closures}{http://weblogs.java.net/blog/evanx/archive/2006/10/better_than_goo.html},
i'm a happier and more fulfilled person.
No really. Hopefully no one everytakes anything i say seriously!?

For instance, #return, #break and #continue inside the closure, can refer
to the enclosing method. Since such functionality can only otherwise be mimicked
by throwing exceptions, i suggest that we use the #throw keyword? Unless
someone else already did?



   void refreshCalendarItems() {
      try {
         while (retry-- > 0) {
            List calendarItems = BlockingWorker() {
               try {
                  calendarItems = connection.getCalendarItems(today,
                      ProgressDisplay(int percent) {
                         publishProgress(percent);
                      });
                  return calendarItems; // from closure, not from refresh()
...

We automatically do some syntax highlighting (and HTML escaping) of the
Java code in pre blocks, as below, as below. (That is not a typo.)

public class NHyperJavaProcessor {    
    static final String keywordStyle = "color: #000099; font-weight: bold";
    static final String stringLiteralStyle = "color: #99006b";
    static final String numericLiteralStyle = "color: #780000";
    static final String methodStyle = "font-weight: bold";
   
    static final String[] delimiters = new String[] {
        "\n", "... //", "//", "<", ">",
        " ", ".", "(", ")", "{", "}", "[", "]", "\\"", """,
        "+", "-", "*", "/", "@", "%%", "%", "#",
        "package", "import",
        "static", "final", "abstract", "synchronized", "transient",
        "default",
        "class", "interface", "@interface", "enum",
        "extends", "implements",
        "public", "private", "protected",
        "true", "false", "null",
        "instanceof",
        "void", "boolean", "int", "char", "long",
        "throws", "throw", "try", "catch", "finally",
        "new", "this", "super",
        "if", "else", "for", "while",
        "continue", "break",
        "return"
    };
   
    enum NState {
        quoteState,
        spaceState,
        dotState,
        wordState,
        noState;
    };
   
    enum NType {
        wordType,
        leftRoundBracketType,
        punctuationType,
        keywordType,
        delimiterType,
        dotType,
        quoteType,
        spaceType,
        leftAngleBracketType,
        rightAngleBracketType,
        ampersandType,
        percentType,
        noType;
    };

    String sourceText;
    StringBuilder stringBuilder = new StringBuilder();
    StringBuilder subBuilder = new StringBuilder();
    NState state = NState.noState;
   
    public NHyperJavaProcessor(String sourceText) {
        this.sourceText = sourceText;
    }
   
    public static String processText(String sourceText) {
        return new NHyperJavaProcessor(sourceText).process();
    }
   
    protected String process() {
        for (String token : NTokeniser.tokeniseText(sourceText, delimiters)) {
            processToken(token);
        }
        stringBuilder.append(subBuilder);
        return stringBuilder.toString();
    }
   
    protected void processToken(String token) {
        NType type = getTokenType(token);
        token = escape(token, type);
        if (state == NState.wordState) {
            if (type == NType.leftRoundBracketType) {
                stringBuilder.append(spanStyle(methodStyle, subBuilder.toString()));
                stringBuilder.append(token);
                subBuilder.setLength(0);
                state = NState.noState;
                return;
            }
            state = NState.noState;
        } else if (state == NState.quoteState) {
            if (type == NType.quoteType) {
                subBuilder.append(token);
                stringBuilder.append(spanStyle(stringLiteralStyle, subBuilder.toString()));
                subBuilder.setLength(0);
                state = NState.noState;
                return;
            }
            subBuilder.append(token);
            return;
        } else if (state == NState.spaceState || state == NState.dotState) {
            if (type == NType.wordType) {
                stringBuilder.append(subBuilder);
                subBuilder.setLength(0);
                subBuilder.append(token);
                state = NState.wordState;
                return;
            }
            state = NState.noState;
        }
        state = NState.noState;
        if (type == NType.quoteType) {
            stringBuilder.append(subBuilder);
            subBuilder.setLength(0);
            subBuilder.append(token);
            state = NState.quoteState;
            return;
        } else if (type == NType.spaceType) {
            state = NState.spaceState;
        } else if (type == NType.dotType) {
            state = NState.dotState;
        } else if (type == NType.keywordType) {
            token = spanStyle(keywordStyle, token);
        }
        stringBuilder.append(subBuilder);
        stringBuilder.append(token);
        subBuilder.setLength(0);
    }
   
    protected NType getTokenType(String token) {
        if (token.equals(""")) return NType.quoteType;
        if (token.equals("(")) return NType.leftRoundBracketType;
        if (token.equals(" ")) return NType.spaceType;
        if (token.equals("\t")) return NType.spaceType;
        if (token.equals("\n")) return NType.spaceType;
        if (token.equals(".")) return NType.dotType;
        if (token.equals("<")) return NType.leftAngleBracketType;
        if (token.equals(">")) return NType.rightAngleBracketType;
        if (token.equals("&")) return NType.ampersandType;
        if (token.equals("%")) return NType.percentType;
        if (stringHelper.startsWithLetter(token)) {
            if (stringHelper.equals(token, delimiters)) {
                return NType.keywordType;
            } else {
                return NType.wordType;
            }
        }
        return NType.noType;
    }
   
    protected String spanStyle(String style, String string) {
        return formatter.format("%s", style, string);
    }
   
    protected String escape(String token, NType type) {
        if (type == NType.leftAngleBracketType) return "<";
        if (type == NType.rightAngleBracketType) return ">";
        if (type == NType.ampersandType) return "&";
        return token;
    }
}   

which is like a state machine or something?

Related Topics >>