Tuesday, August 19, 2008

On JavaFX

JavaFX has the potential to be a really cool technology, but it has a little ways to go yet.

At work we are trying to figure out what we want to do in the Rich Internet Application space and I was asked to review JavaFX to ensure that it could perform one really critical niche function - that it can be used with existing libraries to display chemical structures. The simple answer is that it can. I had no trouble at all writing very thin plain Java wrapper classes around ChimePro, Marvin Beans, and JMol so that I could use them in a JavaFX script.

The more complicated answer, though, is that there are still lots of gotchas.
  • The API has been in a state of flux, so code examples that you can google up tend to be unusable (ignore anything dated 2007 or that has an import for javafx.ui.*).
  • The API is still fluxing, so you can expect lots of changes soon. One critical bit that relates to my Swing component test is that the base display (container) classes are changing. To get the cool "drag from the browser" behavior you need to start with the Application class (or maybe Applet) and add Nodes to the Scene, but to use Swing components you have to use the older containers (that work fine for a JNLP application). And you can't easily subclass Node to fix this yourself because it is an abstract class that has an abstract method that returns an object in the com.sun.* hierarchy - something you probably shouldn't mess with.
  • There isn't a lot of good information out there. There is one book. It is a decent book, but it tried to hit a moving target so it isn't perfect. Combine the book with the author's blog and you have one good source of information. The API documentation and related Sun documents is the other good source of information, but it is still far less complete than, say, the Java documentation.
  • Netbeans integration is not complete. It is really well done, it just isn't quite finished. The visual constructor/previewer is brilliant, but there isn't support yet for fixing problems (e.g. fixing imports) - you just get a red (!) and you have to figure out why (and the compiler messages aren't really all that informative ("I got confused by...")).
The language itself, though, is kind of neat once you get past the declarative style. It makes it easier than plain Java2D to create spiffy new graphical content. But you can't do anything in JavaFX that you can't do in Java2D if you know what you are about.

One of the central powers of JavaFX is that it can use any Java library, but that might also be its downfall, especially if it gets widely adopted by mediocre Java programmers. One of the reasons that Applets have gone the way of the Dodo bird is that people had a tendency to bloat the jar downloads to the point that the Applets were slow and finicky and painful to use. The same is certainly possible with JavaFX. Don't do this. Keep the presentation layer as thin and clean as you possibly can.

The allure of a declarative scripting language for rich application development that harnesses all of the power of the Java platform is undeniable and I will continue to play with the technology as it matures. But it isn't quite ready for prime time yet.

David

correction: Where I said Scene I should have said Stage. My memory tricked me. I am referring to javafx.application.Stage.



In answer to a question in the comments, here is an example of how to turn JMol into a JComponent. Note that although you don't see long lines in the blog, when you copy/paste the text you seem to get it all. I'm not sure why and I am open to suggestions for better ways to include code.



package fxstructurerendertest;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import org.jmol.api.*;
import org.jmol.adapter.smarter.*;


public class JMolDisplay extends JComponent implements ActionListener {

private static final long serialVersionUID = -5404266974773735194L;
private JmolViewer viewer;
private JmolAdapter modelAdapter;
private JPopupMenu popup = new JPopupMenu();
private JCheckBoxMenuItem dotsItem = new JCheckBoxMenuItem("Dots");//dots on/dots off
private JCheckBoxMenuItem spaceItem = new JCheckBoxMenuItem("Spacefill");//spacefill on/spacefill off
private JCheckBoxMenuItem ribbonItem = new JCheckBoxMenuItem("Ribbon"); //ribbon on/ribbon off
private JCheckBoxMenuItem colorAminoItem = new JCheckBoxMenuItem("Color Amino");//color amino/color NONE
private JCheckBoxMenuItem colorChainItem = new JCheckBoxMenuItem("Color Chain");//color chain/color NONE
private JCheckBoxMenuItem bondItem = new JCheckBoxMenuItem("Bonds");//wireframe 25/wireframe off - SELECTED
private JCheckBoxMenuItem atomItem = new JCheckBoxMenuItem("Atoms");//cpk 25/cpk 0 - SELECTED
private JMenuItem resetItem = new JMenuItem("Reset");//reset

public JMolDisplay() {
modelAdapter = new SmarterJmolAdapter(null);
this.setPreferredSize(new Dimension(100, 100));
viewer = JmolViewer.allocateViewer(this, modelAdapter);
viewer.setJmolDefaults();
viewer.setColorBackground("BLACK");
this.addMouseListener(new PopupListener());
bondItem.setSelected(true);
popup.add(dotsItem);
popup.add(spaceItem);
popup.add(ribbonItem);
popup.add(colorAminoItem);
popup.add(colorChainItem);
popup.add(bondItem);
popup.add(atomItem);
popup.add(resetItem);
dotsItem.addActionListener(this);
spaceItem.addActionListener(this);
ribbonItem.addActionListener(this);
colorAminoItem.addActionListener(this);
colorChainItem.addActionListener(this);
bondItem.addActionListener(this);
atomItem.addActionListener(this);
resetItem.addActionListener(this);
}

@Override
public void actionPerformed(ActionEvent e) {
Object c = e.getSource();
if (c == dotsItem) dots();
else if (c == spaceItem) spacefill();
else if (c == ribbonItem) ribbon();
else if (c == colorAminoItem) amino();
else if (c == colorChainItem) chain();
else if (c == bondItem) bond();
else if (c == atomItem) atoms();
else if (c == resetItem) {
if ((e.getModifiers() & ActionEvent.ALT_MASK) != 0) {
ScriptDialog d = new ScriptDialog();
d.pack();
d.setVisible(true);
} else reset();
}
}
private void reset() {
runScript("reset");
}
private void atoms() {
//log.debug("cpk toggle");
if (atomItem.isSelected()) runScript("select all;cpk 100");
else runScript("select all;cpk 0");
}
private void bond() {
//log.debug("bond toggle");
if (bondItem.isSelected()) runScript("select all;wireframe 25");
else runScript("select all;wireframe off");
}
private void chain() {
//log.debug("chain color toggle");
if (colorChainItem.isSelected()) runScript("select all;color chain");
else runScript("select all;color NONE");
}
private void amino() {
//log.debug("amino color toggle");
if (colorAminoItem.isSelected()) runScript("select all;color amino");
else runScript("select all;color NONE");
}
private void ribbon() {
//log.debug("ribbon toggle");
if (ribbonItem.isSelected()) runScript("select all;cartoon on\ncolor cartoons structure");
else runScript("select all;cartoon off");
}

private void spacefill() {
//log.debug("spacefill toggle");
if (spaceItem.isSelected()) runScript("select all;spacefill on");
else runScript("select all;spacefill off");
}

private void dots() {
//log.debug("dots toggle");
if (dotsItem.isSelected()) runScript("select all;dots on");
else runScript("select all;dots off");
}

public JmolViewer getViewer() {
return viewer;
}

class PopupListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
showPopupIfNeeded(e);
}

@Override
public void mouseReleased(MouseEvent e) {
showPopupIfNeeded(e);
}

private void showPopupIfNeeded(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(),
e.getX(), e.getY());
}
}
}

public void runScript(String script) {
viewer.evalString(script);
}

class ScriptDialog extends JDialog implements ActionListener {
JTextArea scriptArea;
ScriptDialog() {
scriptArea = new JTextArea();
scriptArea.setPreferredSize(new Dimension(200,200));
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(scriptArea, BorderLayout.CENTER);
JButton runButton = new JButton("Run");
this.getContentPane().add(runButton, BorderLayout.SOUTH);
runButton.addActionListener(this);
}

@Override
public void actionPerformed(ActionEvent e) {
runScript(scriptArea.getText());
}
}

/**
*
* @param structure
*/
public void setStructure(String structure) {
viewer.openStringInline(structure);
}

/**
*
*/
@Override
public void paint(Graphics g) {
Rectangle rectClip = new Rectangle();
g.getClipBounds(rectClip);
viewer.renderScreenImage(g, this.getSize(), rectClip);
}


}


Monday, August 4, 2008

Sourdough Recipe Conversion

In principle it seems like converting a yeast recipe to sourdough should be very straightforward. But given my limited success with the attempt to convert the basic Artisan Bread in Five Minutes a Day recipe to sourdough I felt I needed to validate the principle with a more normal recipe.

Let's start with how I think it should work. I think you should be able to convert any recipe by simply calculating the water and flour contribution of the starter and subtract those amounts from your recipe. In my case 238g of the starter always equals 125g of flour and 0.5 cups of water (because that's how I feed it). And of course you need to add time for the slower growing culture to do its thing.

I have been baking a lot of typical sourdough bread and I wanted to try this technique with something very different. On the bread spectrum, not much could be more different from a crusty free form hearth baked bread than an American sandwich bread baked in a bread pan. My favorite sandwich bread recipe is the American Sandwich Bread from The New Best Recipe cookbook, so that is my starting point.

I decided to start with 238g of starter, because this is the amount I normally feed. This is more or less equivalent to a cup of starter. The original recipe calls for 532g of flour: 532-125=407g flour. It calls for 1/3C warm water that I normally dissolve the yeast in, this gets removed. It normally calls for a cup of milk, but we need to decrease the liquid a bit more, call it 7oz of milk.
  • 238g fed starter (fed overnight at room temperature just before using)
  • 407g King Arthur all-purpose flour
  • 7oz 2% milk, warmed to tepid
  • 3T butter, melted and cooled a bit
  • 63g honey
  • 1T kosher salt
Put everything in the KitchenAid bowl. Mix with the dough hook until combined, then turn up to medium speed and knead for about 10 minutes until satiny, scraping the dough off the hook a couple of times. Allow to double. Punch down, pat into a rectangle, fold into thirds to make a cylinder and pinch the seam tightly. Place seam down in a greased loaf pan and press the loaf flat and into the corners. Allow to double. Bake with steam at 350 degrees F for 35-45 minutes until the internal temperature is 195 degrees F.

I ended up needing to add a couple tablespoons of flour during kneading to get the right consistency, but this is within normal variability.

One thing that surprised me is how long the rises took. My normal sourdough recipe takes about 2 hours, this took 4 hours. That is for each of two rises, so 8 hours of total rising time. I might try this again with double the starter, but if I do I will probably need to use nonfat dried milk to get any of the milk character into the dough (because that will double the amount of water also and force me to reduce the milk further).

The bread turned out very well. The loaf is just slightly shorter and the crumb slightly denser than the standard recipe, but the difference isn't huge. It doesn't taste like sourdough at all, it just tastes like very nice normal sandwich bread. That was a bit surprising as I would expect 8 hours of rising to generate lots of sour flavor - maybe the honey counters it.

Was it worth it? No, probably not. Eight hours for a result you can get in two is kind of silly. But it validates the conversion principle and I expect I'll try this with other recipes to see what kind of effect I get.

Get out your calculator and try your favorite recipe with sourdough starter. You might like it. But leave yourself plenty of time.

David

Sunday, August 3, 2008

Yes, I Think Your Project Sucks

I am a software/systems architect in a very large corporation for which software and IT are not core business. In this role and previous roles I have frequently been called upon to participate in project code reviews.

When I perform these reviews I usually generate pages of notes and constructive criticisms that I share with the project team. I don't pull punches in these reviews. If I spot some code that is smelly I will highlight it and explain why it is wrong. I will call out shoddy code organization and poorly thought out build scripts and stupid package naming and bad error handling and crappy unit tests and useless comments and other details ad nauseum.

But all of these details are the trees. What makes me like or dislike a project is the overview of the forest. To be sure, one element is the sheer number of details that I have to call out. An overgrown forest stands no chance. Here are some other higher level things that I look for:
  • Evidence of a professional developer attitude
  • Evidence that the end goals are understood
  • Evidence of higher order design/architecture

Evidence of a professional developer attitude

Show me that you have a process. That you care about the quality of the project and the code. That you care what other developers think. That you are trying to set a good example. Some indicators:
  • Clean, well organized repository
  • Repository is up to date and has regular check-ins (with comments!)
  • Clear build scripts that serve as documentation for the build process and dependencies
  • Unit tests and TDD thinking (even if you don't do TDD)
  • Continuous build (or at least nightly)
  • A single central build environment (NOT your IDE, doofus!)

Evidence that the end goals are understood


Show me that you understand the user. That you aren't going to show the user error messages like, "Database Error: Code 5387". That the user interface doesn't freeze or time out. That you have processes in place so that you can quickly debug problems in production. If this is an API, show me that it is clear and understandable and that the contract utterly explicit. Some indicators:
  • A system for handling errors/exceptions that is logical, that logs details, that presents the user with a message that is clear and simple.
  • Asynchronous UI processes
  • Sensible timeout logic where appropriate
  • Integration/unit tests that monitor task time and alert when changes to the code may have slowed something down
  • For APIs do the error messages contain the extra details that a developer would need?
  • Do you eat your own dog food?
  • Are you logging in such a way and at an appropriate level of verbosity such that you can troubleshoot unexpected problems?
  • Documentation for any APIs. Yes, documentation. If you can't clearly explain in a document how to use your API, then what good are you (or it)?
Evidence of higher order design/architecture

Show me that you have thought about reusability. That you understand the value of layered abstractions. That you understand what an object is and what a process is. Every time you look for a library to do X for you and don't find it, do you then write a decent library to do X? Or do you just hack some code to do X into the middle of some poor unsuspecting class? Some indicators:
  • If you are writing a service that does Y, do you have a well orchestrated class/library that does Y, upon which you have layered a service? (as opposed to building the functionality directly into the service classes/methods)
  • Are complex tasks broken down into small logical slices? (as opposed to long confusing methods)
  • Are things that could/should be reusable actually reusable? (as opposed to having unnecessary interdependencies with the rest of the project)
  • Are your objects named like (and function like) nouns?
  • Are your method and object names indicative of actual behavior?
  • Is the project an assembly of small pieces that fit neatly into a bigger picture? (as opposed to a monolithic beast)
It isn't shocking to me that I have never seen a project that scores perfect on all of these indicators. Perfection should be rare. But it is shocking to me how many projects I have seen that score zero.

David

Saturday, August 2, 2008

Back Up Your Online Content!!

It is kind of ironic that I am writing this post now as I have just been doing research for myself on how best to do the opposite (put my back-ups online on e.g. S3).

It recently came to my attention (via a poker blogger who was irate that Tao of Poker got locked) that some blogs on Blogger got improperly locked by a spam detection bot. I did a little investigation to see how big the problem was and it is big. Really big. Here is a small sampling of what I found:
a b c d e f g h i j k l m n o p q r

Google has admitted that this is a mistake here and here. Fair enough. Mistakes happen. But what if it hadn't been a mistake? What if it happened and you were on a three week vacation and were offline? What if you lost all of your writing?

This got me thinking about how much I have been trusting online companies with my content. Probably too much given that they have absolutely no obligation to me at all. The only thing keeping me "safe" is the character of the folks actually working at Google et al. and the corporate fear of bad publicity. If my gmail account contents were lost I would be devastated. Possibly crippled.

So I did some long overdue back-ups. And you should too.

The best way to back up gmail is to turn on POP3 support with the "Enable POP for all mail" flag set. Then fire up a POP client and sync. It actually takes multiple syncs apparently because Google throttles the number of messages you can get in one go, so make sure that you have it all.

The best way to back up your Blogger content is to formulate a link like this:
  • http://twentyonetimestwo.blogspot.com/search?max-results=10000
Where you replace "twentyonetimestwo" with your blog name and "10000" with a number that is more than the total number of posts you have written (if necessary). Then do a "Save Page As" from Firefox. If you choose "web page, complete" as the type you will get your images too.

Do it now.

David