The Source for Java Technology Collaboration
User: Password:



Tim Boudreau's Blog

Programming Archives


What if we built Java code with...Java?

Posted by timboudreau on January 30, 2008 at 01:36 PM | Permalink | Comments (23)

My friend Jon had an interesting insight: Both Ant and Maven rely on lots of XML. XML is good for describing data and terrible for describing behavior. A build is mostly behavior. What if, instead of tormenting Ant into iterating on a bunch of subprojects of subprojects, if we just used an actual programming language to write build scripts. Like, oh, say...Java, for instance?

So he created the Gosling project. It's pretty embryonic - and I think some of the file and resource classes could be replaced by straight usage of things like javax.tools.FileObject, but it has a nice simplicity. Here's the constructor for Gosling's own build to build itself. It has a similar feel to what Jon did in designing Wicket's APIs:

    public GoslingProject() {
        final Folder root = new Folder("/Projects/gosling/workspace/gosling");
        final Folder source = root.folder("src");
        final Folder lib = root.folder("lib");
        final Folder target = root.folder("target");
        add(new JavaApplicationBuilder() {
            @Override
            protected Set getDependencies() {
                final ResourceDescriptorSet dependencies = new ResourceDescriptorSet();
                dependencies.add(Apache.apache.wicket.core.development.resources());
                dependencies.add(Apache.apache.wicket.extensions.development.resources());
                return dependencies;
            }

            @Override
            protected Set getJars() {
                return lib.nestedJars();
            }

            @Override
            protected Folder getSourceFolder() {
                return source;
            }

            @Override
            protected Folder getTargetFolder() {
                return target;
            }
        });
}
Brazilian Salt Shakers I spent last night wrestling with writing an Ant script to build, test and build Javadoc for an ad-hoc collection of projects, where a bunch of custom information needs to be gathered from the projects and embedded elsewhere, and I just found myself thinking this is so not the way to build software!.

I mean, the appeal of things like Ant is that many things are built in a pretty similar way; Maven is even more "my way or the highway" in that regard. I use (and sometimes like) both of them. And in theory an Ant XML script (xml script - now there's an oxymoron) is human readable - although I challenge anyone to make heads or tails of this in ten minutes. It certainly beats Make and tab vs. space madness. There's an argument that it's toolable - that a tool can analyze an Ant script. This seems to me to be a red-herring if you do a design like Wicket or Gosling use - where you know that the entire state you need to analyze is going to be set up in the constructor of a known class of a known type. The Javac Tree API may not be for the faint of heart, but analyzing the closure of a constructor is perfectly doable.

My point is that Ant doesn't really deliver the clarity it promised except in the most trivial of cases. You could have at least as much clarity with plain-old Java code - you just need to start from a good design so the code can speak for itself. And a design where all targets will be added to the build in the constructor is pretty darned clear. Isn't one of the things the agile crowd talks about a lot letting code speak for itself?

Not to mention that running javac in-process ought to be blazingly fast. And that such a project could import and call existing Ant tasks to do things with a thin adapter layer - so anybody's custom tasks or missing functionality could be handled leveraging stuff that's already out there.

It seems like a pretty nice idea to me.

The Joy of Generics

Posted by timboudreau on July 22, 2006 at 01:02 AM | Permalink | Comments (12)

Okay, Generics are old news. But since NetBeans was still targetting JDK 1.4 until a few months ago, and most of my coding is NetBeans plug-ins, I'm just starting to really appreciate them. In particular, I'm finding they have a very nice effect on the things I choose to write and how I write them.

Of course the basics are quite convenient - just being able to eliminate casts and get type checking on uses of collections is handy. But what I find I am doing that's very useful is making different choices about what to write and how to write it.

As a case in point, this week I wrote a little standalone Bézier Spline editor - a project I sort of distracted myself into - I needed to design a very precise GeneralPath as part of some tweaks to NetBeans popup windows' appearance on Mac OS. So rather than sit around with graph paper, I wrote a little app that would let me draw what I needed around a screen shot, and spit out the source code for a corresponding GeneralPath object.

Anyway, since I still have some plans for the Imagine project, I was thinking about how best to design an image editor, and wanted to do some experimenting that might be useful later. What I ended up with is a concept where there are EditingModes, and an EditingMode makes available a set of tools that should be shown in a toolbar and a menu. The user can select editing modes, and the user can select tools (and also EditingModes have lists of actions of their own).

So I've got three things that need basically the same thing - each has a list of objects that share a type. For each list of objects, I need a corresponding list of toggle buttons and menu items. But the list contents are all different types.

This is where generics come to the rescue - in the past, I probably wouldn't have thought to take this approach, simply because all of the casting required would be ugly, and it wouldn't be terribly type-safe. What I did is create a single class with a generic type; its constructor takes an argument of a 3 method interface which must have the same generic type. So all of the logic about manufacturing menu items and such is shared; I just implement the Logic interface for each one and pass it to the constructor and I'm done. The Logic interface handles fetching the display name of an object from the list, fetching the icon for the object, and performing whatever action should be performed when the menu item or button is invoked.

The point here isn't that the code is wildly beautiful - but that it's really enjoyable to find a technique that changes the way you think about what you can do. In the past, what I would have written to do this would be either uglier, or there simply would have been three cut-and-paste implementations, depending on how expedient I was being. So I'm finding that generics are adding a bit of extra fun into programming - I can have more logic be, uh, generic, and write less boilerplate as a result. To illustrate, here's what I mean:

package pathdesign.app;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JToggleButton;

public class ActionMapper <T extends Object> {
    private List <T> objects;
    private final Logic logic;
    private T selection;
    
    interface Logic <T> {
        void invoke (T t);
        String getDisplayName (T t);
        Icon getIcon (T t);
    }
    
    public ActionMapper(List <T> objects, Logic<T> invoker, T initialSelection) {
        this.objects = objects == null ? new ArrayList <T> () : 
            new ArrayList<T>(objects);
        this.selection = initialSelection == null ? objects.size() > 0 ?
            objects.get(0) : null : initialSelection;
        
        if (initialSelection == null) {
            throw new NullPointerException ("Can't determine initial selection");
        }
        selection = initialSelection;        
        this.logic = invoker;
        if (invoker == null) {
            throw new NullPointerException ("Invoker null"); //NOI18N
        }
    }
    
    public interface MapperAction <T> extends Action {
        public T getItem();
    }
    
    public boolean setObjects (List <T> objects, T selection) {
        if (!this.objects.equals(objects)) {
            this.objects = new ArrayList<T>(objects);
            setSelection (selection);
            buttons = null;
            actions = null;
            menuItems = null;
            return true;
        } else {
            return setSelection (selection);
        }
    }
    
    public List <T> getObjects() {
        return new ArrayList <T> (objects);
    }
    
    private boolean inSetSelection = false;
    public boolean setSelection (T selection) {
        if (inSetSelection) return false;
        inSetSelection = true;
        try {
            if (!objects.contains(selection)) {
                throw new IllegalArgumentException ("I don't know about "  //NOI18N
                        + selection);
            }
            boolean result = !selection.equals (this.selection);
            if (result) {
                this.selection = selection;
                updateButton(selection);
            }
            return result;
        } finally {
            inSetSelection = false;
        }
    }
    
    public T getSelection() {
        return selection;
    }
    
    private List <MapperAction<T>> actions;
    public List <MapperAction<T>> getActions() {
        if (actions == null) {
            actions = createActions();
        }
        return actions;
    }
    
    private List <JToggleButton> buttons;
    public List <JToggleButton> getButtons() {
        if (buttons == null) {
            buttons = createButtons();
        }
        return buttons;
    }
    
    public List <JMenuItem> menuItems;
    public List <JMenuItem> getMenuItems() {
        if (menuItems == null) {
            menuItems = createMenuItems();
        }
        return menuItems;
    }
    
    private List <MapperAction <T>> createActions() {
        List <MapperAction<T>> result = new ArrayList <MapperAction<T>> (objects.size());
        for (T item : objects) {
            MapperAction <T> action = new MapperActionImpl <T>(item);
            action.putValue(Action.NAME, logic.getDisplayName (item));
            result.add (action);
        }
        return result;
    }
    
    private List <JToggleButton> createButtons() {
        List <JToggleButton> result = new ArrayList <JToggleButton> (objects.size());
        ButtonGroup grp = new ButtonGroup();
        for (MapperAction<T> action : getActions()) {
            JToggleButton button = new JToggleButton();
            button.setAction (action);
            button.setIcon (logic.getIcon (action.getItem()));
            result.add (button);
            if (getSelection() == action.getItem()) {
                button.setSelected(true);
            }
            grp.add (button);
        }
        return result;
    }
    
    private List <JMenuItem> createMenuItems() {
        List <JMenuItem> result = new ArrayList <JMenuItem> (objects.size());
        ButtonGroup grp = new ButtonGroup();
        for (MapperAction<T> action : getActions()) {
            JMenuItem button = new JMenuItem();
            button.setAction (action);
            button.setIcon (logic.getIcon (action.getItem()));
            result.add (button);
            if (getSelection() == action.getItem()) {
                button.setSelected(true);
            }
            grp.add (button);
        }
        return result;
    }
    
    private void updateButton (T t) {
        JToggleButton b = buttonFor (t);
        if (b != null) {
            b.setSelected (true);
        }
    }
    
    private JToggleButton buttonFor (T t) {
        if (buttons != null) {
            for (JToggleButton button : buttons) {
                MapperAction<T> action = (MapperAction<T>) button.getAction();
                if (action.getItem().equals (t))  {
                    return button;
                }
            }
        }
        return null;
    }
    
    private final class MapperActionImpl <T> extends AbstractAction implements MapperAction <T> {
        private final T item;
        public MapperActionImpl (T item) {
            this.item = item;
        }
        
        public void actionPerformed(ActionEvent e) {
            logic.invoke(getItem());
        }

        public T getItem() {
            return item;
        }
    }
}
That being said, there are things that I find ugly about generics - Map m = new HashMap() is less work to read then Map <String, Foo> m = new HashMap <String, Foo> (), and it can get really ugly, e.g. Map <String, Reference<Bar<Baz>>> m = new HashMap <String, Reference<Bar<Baz>>> () - but for cases like the above code, they're darned useful - and moreover, fun.

NetBeans now has Mac-friendly key bindings

Posted by timboudreau on September 08, 2004 at 01:37 PM | Permalink | Comments (7)

I just committed some changes to NetBeans key bindings handling, so that mac users will get key bindings that are much more like other mac apps (no change for Windows or Linux users).

Specifically what's different:

  • Ctrl -> Command - everything that normally maps to Ctrl will use the Command key instead, except where that's impossible (Command-H always hides the app, Command-Q always quits, Command-~ always changes windows, that sort of thing)
  • Command-G maps to Find-Next
  • Command-R maps to Replace (Ctrl-H, the windows key binding, works as well)
  • Cursor control keys in the editor are the default mac ones - Option-Left-arrow to skip to next word, etc. (Option==Alt)

Also, dialog buttons will now appear in the correct order on Macs, with OK on the right.

Alas, these changes will not be in NetBeans 4.0 Beta 2 - I missed the cut-off, but it's better to review things properly than to rush. They will be in Beta 3.

Please give it a try, and let me know if any of your habitual keystrokes are doing the wrong thing. You can download tonight's build here. Be sure to select "Daily" from the "Build Type" drop-down.

The build with these changes will become available just after 4PM PST, or 1AM GMT.



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds