Griffon at Devoxx 2008

Last week, the folks at Devoxx let me know that my BOF submission entitled Griffon: Re-imaging Desktop Java Technology had been accepted. If you're coming to Devoxx and want to learn about producing cool Swing apps and have fun doing it, plan on stopping by.

The Making of PleaseBrowseMe

In the last post, I released PleaseBrowseMe, a browser for pleasedress.me made with Griffon. In this post, I'm going to explain a bit more of the technical details of creating it. If you're looking for the app, go here.

withWorker

This is one area that would have been more difficult using Java. SwingWorker, the Java concept that withWorker is based on, is designed to be executed once. That is not a restriction in Groovy and was very helpful in loading a batch of images in a EDT-safe way.

def doImageLoad = { evt = null ->   
    withWorker(start:true) {
        onInit {
            doLater {
                model.running = true
                app.appFrames[0].getGlassPane().setVisible(true)
                app.appFrames[0].getGlassPane().setBusy(true)
            }
        }
        
        work {
            for(int i = model.startCount; i < model.endCount; i++) {
                def url = model.searchResults[i].imageUrl
                if (model.searchResults[i].image == null || 
model.searchResults[i].image == model.loadingImage) {
                    def image = ImageIO.read(new URL(url))
                    if (image.width > 180 || image.height > 180) {
                        image = GraphicsUtilities.createThumbnailFast(image,180)
                    }
                    publish([id:i, image:image])
                } else {
                    publish([id:i])
                }		
            }
        }
        
        onUpdate { chunks ->
                   chunks.each { imageMap ->
                   if (imageMap.image != null)                       		
                       model.searchResults[imageMap.id].put('image', imageMap.image)
                   view.shirtPanel.add(drawShirtPanel(imageMap.id))
            } 
        }
        
        onDone {
            model.running = false
            if (model.searchResults.size() == 0)
                view.shirtPanel.add(new JLabel("No results"))
            view.shirtPanel.revalidate() 
            app.appFrames[0].getGlassPane().setVisible(false)
        }
    }
}

search

The only thing special that's going on with search is that I used SwingX-WS calls for http requests/responses to avoid using the more verbose Apache varieties. The JXSearchField component from xswingx helps out too.

 

public search(text) {
    def session = new Session()
	def request = new Request(url:"http://pleasedress.me/api/1.0/shirts/search.xml?q="+text)
    def response = session.execute(request)
    def shirts = []
    if (response.getStatusCode() == StatusCode.OK) {
       def result = model.slurper.parseText(response.getBody())
       result.shirt.each {
            shirts+= [title:it.@title.toString(), url:it.@url.toString(), 
               image:model.loadingImage, imageUrl:it.@image.toString(), price:it.@price.toString()]
       }
       model.searchResults = shirts
       model.startCount = 0
       model.endCount = model.searchResults?.size() > 21 ? 21 : model.searchResults?.size()
       clearShirtPanel()
       doImageLoad()
    } else {
       	JOptionPane.showMessageDialog(app.appFrames[0], "Error", "Something went wrong.", JOptionPane.ERROR_MESSAGE)
    }
}

GlassPane

To indicate when the application was retrieving information from pleasedress.me, I used a JXBusyLabel as my GlassPane. The GlassPane is an invisible layer that exists on top of every frame. To not obscure the underlying view as much, I changed the point shape to circles(Ellipse). I also tweaked the colors to match the animation more apparent. Because it is specific to J(X)Frame, when referring to the glasspane, one must call app.appFrames[number] directly. view.getGlassPane() will not work.

 

import java.awt.Dimension
import java.awt.geom.*
import org.jdesktop.swingx.JXBusyLabel
import org.jdesktop.swingx.painter.BusyPainter

def busyLabel = new JXBusyLabel(new Dimension(700,700))
busyLabel.setOpaque(false)
busyLabel.setBusy(true)
busyLabel.getBusyPainter().setHighlightColor(java.awt.Color.DARK_GRAY)
busyLabel.getBusyPainter().setPointShape(new Ellipse2D.Float(1,1,10,10))

app.appFrames[0].setGlassPane(busyLabel)

PleaseBrowseMe

As its name somewhat indicates, PleaseBrowseMe is a desktop browser for the t-shirt search engine PleaseDressMe. Mostly it's a tech demo for Griffon, an app framework for Java/Groovy I helped create.

It allows you to:

  • Search pleasedress.me in real time
  • Launch a browser window/tab going directly to the retailer
  • Identify shirts you like and save them
  • Create a widget featuring the shirts you like


It also automatically saves all searches. No affliate links were altered in the creation of this app.

As always Java 6 is pretty much REQUIRED. If you like PleaseBrowseMe and are a Twitter user, check out FriendBackup as well.

 

Launch button

 

PleaseBrowseMe screenshot

PleaseBrowseMe screenshot

Carbonado and Griffon

As it stands now,  Griffon doesn't yet support GORM. In pre-Griffon apps, I got around this by using Groovy's SQL datasets and some light raw SQL. Since Griffon is all about simplification, I decided to see if any object-relational mappers work with little modification. Carbonado is one that does.

Carbonado is a Java ORM, initially developed by Amazon that is now an Apache project. It has much of the stardard ORM fare but also supports Berkeley DB. One of the reasons I chose it for this small scall demo was the fact that it doesn't require configuration files. Domain objects are annotated interfaces whose concrete implementations are generated at run-time. Very Groovy-like.

Here's my StoredMessage domain object:

import com.amazon.carbonado.PrimaryKey;
import com.amazon.carbonado.Storable;
@PrimaryKey("ID")
public interface StoredMessage extends Storable {
    long getID();
    void setID(long id);
    String getMessage();
    void setMessage(String message);
}

Coming back to the Griffon structure for a moment, StoredMessage.groovy found its home in /src/main/groovy.

The model for this example is very short with only a String property:

import groovy.beans.Bindable

class CarbonadoDemoModel {
	@Bindable message
}

The view is equally brief, changing the label to be bound the model:

application(title:'CarbonadoDemo',  size:[150,75], 
   location:[50,50], locationByPlatform:true) {
    label(text:bind(source:model, sourceProperty:'message')) 
}

Startup.groovy is where the real heavy lifting is done(ie loading the repository, writing/reading from it, and binding to the model).

import java.io.File;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storage;
import org.apache.derby.jdbc.EmbeddedDataSource

import com.amazon.carbonado.repo.sleepycat.BDBRepositoryBuilder

def rootModel = app.models.root  

try {
	// Need to build access to a Repository instance. In this
	// case, we're building access to a Repository that stores
	// data in Berkeley DB.
	BDBRepositoryBuilder builder = new BDBRepositoryBuilder();
	// All Repositories require a name to be configured.
	builder.setProduct("JE");
	builder.setName("test");
	builder.setEnvironmentHome("testRepo");
	builder.setTransactionWriteNoSync(true);

	// Now build the Repository instance.
	Repository repo = builder.build();
	// Access Storage for a specific type of Storable.
	Storage<storedmessage> storage = repo.storageFor(StoredMessage.class);
	/*
//Insert message. StoredMessage message = storage.prepare(); message.setID(1); message.setMessage("Hello Carbonado!") message.insert() */ // Get message from repository StoredMessage message = storage.prepare(); message.setID(1); message.load() rootModel.message = message.getMessage() } catch (Exception e) { e.printStackTrace() }

Until GORM is there, Carbonado seems like a good solution for small to medium scale applications that are too big to do raw SQL and too small to deal with the multiple config files Hibernate needs.

Download the source here.

Porting a Groovy app to Griffon

Just before launch, I started porting the Groovy port of jFlubber to Griffon. I would have included some screenshots but it looks the same. I’ll be delving into the code a bit so take and minute to download the old and new versions. On the changes:

Resources
The original app lived in one mess of a directory along with the images. Images now live in the resources directory and are properly separated from the code.

Helper classes
Also in the main src directory were two helper classes to encapsulate some Java Timer capabilities(GroovyTimerTask and UpdateTimeTask). Though Griffon will be able to locate them in the view directory, they are not in and of themselves views. It’s more appropriate for them to be outside the Griffon MVC architecture in src/main/groovy.

MVC
By far, this was the area that benefited the most from the port. What began as a big clusterf—k all in one file has evolved to a nice separation of MVC. Thanks to @Bindable and changing from a concrete class as a view to a view clousure, I was able to eliminate some useless instance variable move what needed to referenced from the view to the model.

import groovy.beans.Bindable 
import org.jdesktop.swingx.* 
import org.jdesktop.swingx.painter.* 

class GriffonFlubberModel { 
    @Bindable String timeText = ’0:00’ 
    @Bindable String flubText = ’’
    @Bindable TextPainter timeLabel 
    @Bindable Timer timer 
    @Bindable long startTime = 0L 
    @Bindable int labelNo = 0 
}


Likewise most of the helper functions for the view moved to the controller.

Awakening the Griffon

Griffon, inspired by the mythical creature, is a Swing application framework for Groovy.

Griffon is the fruit of almost a year’s long effort by the Groovy Swing team and an attempt to take the best of rapid application development, as indicated by its Grails-like structure, the über-sexiness of Groovy, and the pleothra of components for Swing.

Griffon aims to reduce the typical confusion that occurs with traditional Swing development. Due to the MVC structure of Griffon, you never have to go searching for files or be confused on how to start that new project. Everything begins with:

 

griffon create-app <APP_NAME>

 

The builder infrastructure enables seamless integration of different widget libraries. Want to mix traditional Swing, JIDE, and SwingX? No problem.

Griffon’s built-in scripts include targets for desktop, webstart, and applets. To not leave our Mac friends in the cold, the baseline requirement is Java 5 or higher. We thought it was very important for all platforms to have equal access on release day.

In this release, we have included three sample applications:

  • Greet, a Groovy Twitter client featured in the JavaOne Script Bowl,
  • GrailsSnoop, a Grails documentation browser, and
  • WidgetsKitchenSink, a WIP showing components in the included widget sets.

Griffon is Swing simplified.

Download Griffon here and run through the Quickstart.

For the folks on Debian, I’ll be rolling a deb soon. If there are any Windows or Mac folks who want to package the framework on those platforms, email the griffon-user list or tweet Danno, Andres, or myself so we can coordinate efforts.

Much thanks to those that have been poking and prodding the framework, kicking its tires and such. Continue to do so.

Crowdsourcing Google Maps

According to a recent post on Techcrunch, Google is opening up access to Map Maker, a tool allowing modification of map data, adding or editing roads, points of interest and other Google Map features where Google has limited map data, to India.  It had been open only to smaller island nations previously.

For those not blessed by Google/Yahoo/Mapquest, OpenStreetMap was previously the only option to allow citizen-cartographers to add and edit map features. Because it is open to everyone, the information can be old, missing and/or incorrect. For most points in the United States, the level of detail is pretty good but some other countries I checked were pretty blank besides main roads. The data is  Creative Commons licensed and free for use without any restrictions so if your mashup is localized to areas with good detail, OSM might be a good alternative to Google Maps. Its site is more barebones and focused on interacting with the map and not all the secondary services Google Maps supplies.

With many areas of rapid development and re-development in the United States that could benefit from Map Maker, I think it’s a matter of when it will get opened up the the US and not if. I just hope that OpenStreetMap’s community doesn’t lose contributers and that they do it for the love of cartography and don’t just target the bigger name.

Why Openmoko will continue to not matter

After hearing the recent news that Openmoko, an open source mobile phone platform,  can run an ARM port of Debian Linux, I was happy for about a split second. The lofty expectations of my momentary euphoria, namely that there would be a viable, currently availible, alternative to Android that could run some sort of Java and possibly Groovy, were quickly dashed by reality. This realization was brought on by the fact that Openmoko is plagued by the Linux trap1. There are currently four distributions2. That’s three too many.

Love it or hate it, part of the reason the iPhone has done so well is that it is a single platform to target. Windows was fine too, until the 25 versions of Vista. The term "year of Desktop Linux" stopped becoming a farcical statement went Linux became concrete for the neophyte started to mean Ubuntu Linux and not the ethereal consortium of distros each tailored to someone’s whim.  In an already crowded mobile landscape, who in their right mind is going to develop for a platform with four different variants, each with their own possible quirks? What people need is the fallacy of choice, they would prefer that more than one option but not more than a handful. We already have the handful with iPhone, Android, Sybian, Windows Mobile and Palm, the market can't support another that has already split its internal market into 4 pieces.


1The number of choices causes confusion and limited adoption.

2Only two of them are official distributions.

FriendBackup update

In the weeks since I released FriendBackup, there have been at least two other efforts(web sites) to accomplish the same goal. Since they were offering more than just backing up friends, it was about time I updated FriendBackup.

You can now backup followers, DMs, favorities and tweets in addition to your friends. Use care when downloading everything or you might max out. Because multiple streams are being backed up, I changed the file format to Excel (97/2000) to better group the data. Tweets, for instance, are on a different sheet from favorities and everything else. Here’s the obligatory screen shot:

FriendBackup screenshot

The next set of updates will probably store a lightweight file with the vitals so that if you run it today and again two weeks from now, it’ll only download what is needed.

Unlike the backup websites, I don’t have to promise you I won’t keep your data because it never comes to me in the first place. All communication is between your machine and Twitter.

Launch button

Groovier PasswordKeeper

Password Keeper is a Groovy port of an AIR application of the same name featured on MakeUseOf.com. The Substance Look and Feel closely simulates the look of the source app. While I could have tweaked the fonts a bit but figured it wasn’t worth it for a quick port. On the backend, I’m using an instance of Derby(JavaDB) to store the passwords.

The original:

 

My version:

You can test run the app by visiting the link below: As usual, Java 6 is recommended.
Launch

You don’t always need AIR (or JFX), Java-based technologies can look pretty.