Wednesday, August 3, 2011

Tips for using EGit and GitHub

EGit is an Eclipse plugin for working with Git repositories.  Here are a few tips based on my recent experience.

  • Install Egit from the Indigo update site (http://download.eclipse.org/releases/indigo), it's under the Collaboration subgroup.
  • Create the repository in GitHub first.  Then use the EGit "Git Repositories" view in Eclipse to create a local clone (there's a button to do this in the upper right-hand corner of the view).  Creating the repository in GitHub first helps ensure that pushing and pulling between your local clone and the main GitHub repository is straight-forward.
  • When cloning the repository, use the HTTP repository link provided in your GitHub dashboard.  If you stick with HTTP, it seems you can avoid the laborious process of installing the Git binaries on your local machine, setting up SSH keys, etc.
  • You can then add an existing Eclipse project to the repository through Team > Share Project > Git.

Thursday, July 21, 2011

JFace Wizard Tips and Tricks

The JFace wizard classes offer a decent, basic framework for creating user interface wizards.  However, I found it hard to figure out how to accomplish a few basic things, such as getting notifications on page changes, disabling the Back button, and changing the text on a button.  These things are not that difficult to do, as it turns out, but the API documentation is very limited.  And while there are some good tutorials out there, they do not cover these tasks -- I had to dig in to the source code to find the solutions.

First, changing the text on a button.  You need to subclass WizardDialog and override createButtonsForButtonBar():

@Override 
protected void createButtonsForButtonBar(Composite parent) {
    super.createButtonsForButtonBar(parent);
    Button finishButton = getButton(IDialogConstants.FINISH_ID);
    finishButton.setText("Submit");
}

What about getting notified when the page changes (i.e. the user clicked the Next or Back buttons)?  In your Wizard's addPages() method, you can add the necessary listeners:

public class MyWizard extends Wizard implements IPageChangingListener, IPageChangedListener {
    @Override
    public void addPages() {
        // add pages here ...
        this.addPage(new MyPage());
        
        WizardDialog dialog = (WizardDialog)getContainer();
        dialog.addPageChangingListener(this);
        dialog.addPageChangedListener(this);
    }

    @Override
    public void pageChanged(PageChangedEvent event) {
        // ...
    }

    @Override
    public void handlePageChanging(PageChangingEvent event) {
        // ...
    }
}

Disabling the back button is harder than it should be, due to non-intuitive behavior in the default implementation of WizardPage.getPreviousPage().  You can call setPreviousPage( null ), and getPreviousPage() still returns the previous page.  You need to override the implementation of getPreviousPage() in order to disable the back button:

public abstract class MyWizardPage extends WizardPage {
    private boolean backButtonEnabled = true;

    public void setBackButtonEnabled(boolean enabled) {
        backButtonEnabled = enabled;
        getContainer().updateButtons();
    }
    
    @Override
    public IWizardPage getPreviousPage() {
        if (!backButtonEnabled) {
            return null;
        }
        return super.getPreviousPage();
    }
}

Finally, if you want to choose when to enable the Finish button, you will probably be overriding the canFinish() method on your Wizard subclass.  I did this, but made the mistake of creating my own canFinish boolean field.  This shadowed a field of the same name in the parent class -- as a result, I could never get my Finish button to become enabled!  Changing the field name to myCanFinish fixed the issue.

Wednesday, July 13, 2011

Subclipse on Windows

I ran in to a little glitch with Subclipse (the Eclipse plugin for Subversion access) on Windows.  In general, this plugin works well, but I was using "Team > Add to svn:ignore" to exclude files from version control.  For the first file (a directory, actually), this worked fine.  For the second file, I kept getting an error from the Subversion server (Google project hosting, in my case) when I tried to commit.

I finally suspected that this might be a Windows-specific issue, since multiple entries in svn:ignore are separated with carriage return and/or linefeed. I switched the Subclipse SVN client from the pure Java SVNKit to JavaHL (under Window > Preferences > Team > SVN).  This (mostly) resolves the issue.  You still sometimes get a conflict on the project when you try to commit a change to the svn:ignore property.  This conflict can be fixed by first selecting to update the project file from the Subversion server, then committing your changes to it (that seems strange, I know, but it seems to work and I have not lost any changes yet).

Note that if you are using 64-bit Windows, switching from SVNKit to JavaHL is a little trickier.  If you are using a 64-bit JVM, you will need to separately install a 64-bit version of the JavaHL library.  More details here.

Tuesday, July 12, 2011

Really simple functional extensions for Java

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Util {
    public interface Predicate<T> {
        boolean check(T instance);
    }
   
    public interface Attr<T, A> {
        A get(T instance);
    }

    public static <T> List<T> truncate(List<T> list, int maxLength) {
        if (list.size() > maxLength) {
            return new ArrayList<T>(list.subList(0, maxLength));
        }
        return list;
    }

    public static <T> List<T> filter(List<T> list, Predicate<T> checker) {
        List<T> filtered = new ArrayList<T>();
        for (T item: list) {
            if (checker.check(item)) {
                filtered.add(item);
            }
        }
        return filtered;
    }
   
    public static <T> int count(List<T> list, Predicate<T> checker) {
        int count = 0;
        for (T item: list) {
            if (checker.check(item)) {
                count++;
            }
        }
        return count;
    }
   
    public static <T, A> Map<A, List<T>> rollup(List<T> list, Attr<T, A> attr) {
        Map<A, List<T>> result = new HashMap<A, List<T>>();
        for (T item: list) {
            A key = attr.get(item);
            List<T> itemList = result.get(key);
            if (itemList == null) {
                itemList = new ArrayList<T>();
                itemList.add(item);
                result.put(key, itemList);
            } else {
                itemList.add(item);
            }
        }
        return result;
    }
}

Monday, July 11, 2011

Eclipse and Maven

Tips for those getting started with Maven and Eclipse. If you're like me, you want to try using Maven for dependency management, but you prefer to leave the building of your project to Eclipse. To start with, install the m2eclipse plugin from this location.

To convert an existing Java project to Maven:
  • Right-click project > Configure > Convert to Maven Project
After doing this, you may notice two issues:
  • Project source folders have been converted to regular folders. Right-click the folder and select Build Path > Add to Build Path to fix this.
  • The JRE system library version has changed. Go to Project Properties > Java Build Path > Libraries to make sure your JRE system library is correct.
Adding new dependencies using the plugin's cool lookup tool is straight-forward. Right-click the generated pom.xml and select Maven > Add Dependency.