Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

2010-03-11

Compiling Java applications to native Windows executables

Being a Java developer really sucks when it comes to making end-user desktop applications. You want these applications to be light, fast and easily redistributable. Packaging a bunch of jars along with startup script is the worst thing you can do. It may work quite well in various Linux distributions, where you can make a .deb or .rpm which automatically installs JRE, puts startup script to /usr/local/bin and creates a nice launcher with icon in the applications menu. In Mac OS you can use that outdated JRE that comes by default and then easily bundle all scripts and jars into an .app. But in Windows it does not work that way. You can use JSmooth to make .exe out of .jar and use NSIS to create a nice installer, but there still is a problem. User may not have a compatible JRE, or he/she may choose not to have one just because Java sucks for running on it's bloated memory hogging virtual machine. I have to agree here. As a user, I have no faith in desktop applications that are written in Java.

So what is the solution? Compile to native executable and forget JRE. It is possible, but it will not be a walk in the park. First, you will have to get familiar with GNU Compiler Collection and particularly with GCJ.

Let's compile a Hello World application like this one:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello native world!");
    }
}
  1. Get the patched GCJ here: http://www.thisiscool.com/gcc_mingw.htm (120 MB)
  2. Extract it somewhere and add thisiscool-gcc/gcc-ejc/bin to your PATH
  3. Compile Hello.java as Hello.exe: gcj --main=Hello -o Hello Hello.java
  4. Enjoy your statically linked Hello.exe which prints Hello native world! and runs without JRE.
Wait. Hello.exe is 37MB? Holy crap! Well, it's a little overhead you have to pay for loosing the JRE, this .exe contains the whole garbage collection mechanism, etc. Also, you will be able to compile SWT GUI applications! It is possible to get smaller executables with MinGW tooling. Hello.java compiled with MinGW is about 3MB, but it has other issues, I couldn't manage to get the system output working. You may be more lucky though.

Anyway, when you compile native binaries, your Java code cannot be decompiled, so you have better protection than by using obfuscators. And the best thing is that end users won't complain that "it's crap because it's Java". They just wouldn't know.

I wish Sun (I just can't say Oracle when referring to Java, it makes me sick, sorry) could create an official AOT Java compiler so developers would not have to go through hellfire to make native executables.

2009-09-29

XML processing in Java

One of the things that most Java developers tackle on daily basis is dealing with XML. Despite the fact that XML is taking lots of criticism and new formats like YAML are emerging and becoming more popular, you cannot avoid XML it's too widespread and used everywhere. It's the main format for interchanging data across systems and even people. There is a great deal of fat books that show how to use various XML APIs and libraries to handle the beast with all it's standards and extensions. There are many solid tools that had been continuously developed for years by large communities (Xalan, Xerces, JDOM, DOM4J).

And still XML processing in Java is still a major pain in the ass.
I see two reasons for that: 
  1. XML is too bloated as a format. See the picture below (click to enlarge):
  2. Java libraries that deal with XML are bloated. It's natural because they simply try to implement the specifications

Let's say you have a Java application which receives some data in form of simple XML:

<?xml version="1.0" encoding="UTF-8"?>
<data>
  <entry id="1">entry number one</entry>
<entry id="2">entry number two</entry>
</data>
Your application has this class:

public class Entry {
private int id;
private String content;
//the usual setters and getters here
}
If you would want to parse this XML with Java, into Entry objects you would usually do something like this:

try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("data.xml"));
NodeList nl = doc.getElementsByTagName("entry");
for (int i = 0; i < nl.getLength(); i++) {
Entry entry = new Entry();
entry.setId(Integer.parseInt(nl.item(i).getAttributes()
.getNamedItem("id").getNodeValue()));
entry.setContent(nl.item(i).getTextContent());
System.out.println(entry);
//do real stuff
}
}
catch (final Exception e) {
System.out.println("Failed parsing: " + e);
//do real handling
}
Expected output:

Entry:{id: 1; content: entry number one}
Entry:{id: 2; content: entry number two}
In Java 6 DocumentBuilderFactory.newInstance() will usually return an instance of this implementation: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl 
This is Xerces embedded into the JRE. What's wrong with that? First, it's a huge library with big memory footprint. It will be outdated in comparison with what you can get at the official homepage, so if you want to go for the latest version with all the bug fixes, you will have to add another megabyte of jars to your project, set a system property (javax.xml.parsers.DocumentBuilderFactory) to change the default implementation and hope your code works. Then you have to know DOM. You have to use an ugly for loop to iterate the results instead of doing it right (for (Node n : doc.getElementsByTagName("entry") { ... }).
Even though Java aims to be loosely coupled, you can use the API and switch implementations, you should keep in mind that API changes over time, and implementations work differently. I have seen legacy code where you can find sick things like DocumentBuilderFactoryImpl = (DocumentBuilderFactoryImpl) DocumentBuilderFactory.newInstance();, I have seen Axis failing to parse complex SOAP messages after switching to different, newer JDK, I have seen third party software vendors who start cursing when you change your web service implementation and your WSDL is generated with minor cosmetic differences (i.e.: xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" instead of previous version: xmlns:s="http://schemas.xmlsoap.org/wsdl/soap/"). In all these cases APIs and implementations failed to do what they were meant for. Of course, everything can be fixed, but it takes time and nerves, and these things are precious.
XML processing in Java is terrible, and the worst part is when you have to go through all this just to parse a simple piece of data. Why couldn't it be as simple as that:

for (XmlSlicer piece : XmlSlicer.cut(data).getTags("entry")) {
//each piece is: <entry id="...">...</entry>
Entry entry = new Entry();
entry.setId(Integer.parseInt(piece.getTagAttribute("entry", "id")));
entry.setContent(piece.get("entry").toString());
System.out.println(entry);   
//do real stuff
}
After being fed up with Java's great XML APIs and libraries I made a small tool for simple daily work with XML files.
The code above would work in XML Zen - a small and lightweight XML processing library that supports ~1% of what other XML processing libraries can do, however this 1% of functionality is what you use 90% of the time. There are no big APIs, just simple logic driven object oriented processing of XML strings. And it's just a little over 10Kb.
You can add XML Zen dependency with Maven, just set the dev.java.net repo first:

<repositories>
<repository>
  <id>maven2-repository.dev.java.net</id>
  <name>Java.net Repository for Maven</name>
  <url>http://download.java.net/maven/2</url>
  </repository>
  <!-- other repositories -->
</repositories>
Then the dependency:

<dependency>
    <groupId>com.googlecode.xmlzen<groupId>
    <artifactId>xmlzen</artifactId>
    <version>0.1.1</version>
</dependency>
That's it, you are ready to go. And when it comes to building XML and XML Zen is not enough for your needs, check out this great project: http://java.ociweb.com/mark/programming/WAX.html.

2009-03-21

Gmail4J - Gmail API for Java



Seems that Google has no Gmail API available, so I made a small Java library called Gmail4J. The library is designed to be extensible, allowing various implementations. Currently the only available implementation allows getting new unread messages from their RSS feed. That's not much, but it's a start.

Here is the example code (updated to conform with Gmail4J 0.2):

GmailClient client = new RssGmailClient();
GmailConnection connection = new HttpGmailConnection("user", "pass".toCharArray());
client.setConnection(connection);
final List<GmailMessage> messages = client.getUnreadMessages();
for (GmailMessage message : messages) {
System.out.println(message);
}

Next implementation will probably be based on JavaMail IMAP functionality. It should be able to do more than getting unread messages.

2009-03-15

JAD Java Decompiler download mirror

As http://www.kpdus.com is no longer accessible, JAD Java Decompiler download is extremely hard to find. I've put up a mirror where you can get jad executable for Windows, Linux and Mac OS X: http://www.varaneckas.com/jad

Hope this helps those who are having a hard time finding a working JAD download.

2009-02-13

Screw all GUI builders

Are you making your GUI with a builder? Do you like the generated code you get? I hate it. Even though I like the idea of building GUI with visual means (WYSIWYG), I can't stand the mess that code generators produce. In addition to that, there are more serious downsides:

  • You don't know how exactly the generated code works. You don't need to. You start not to care and GUI application development becomes a process of drawing and adding simple event handlers here and there.

  • Most GUI builders force you to use single class for single window, so generated classes tend to have thousands of lines of code.

  • Most GUI builders don't want you to modify the generated code. And if you do, they either break or rewrite your code.

  • GUI builders force you to use an IDE, mostly one you started coding with. So if you start with NetBeans, you most likely be forced to stay with it for the whole project.

  • The generated code is far from being optimal. It's not resize-friendly, not dynamic enough, it has many hard-coded values, refactoring is most likely impossible, because builder would not allow that.

So, why are you using GUI builders? Is it because you're doing GUI apps during your day job, you need fast results and you don't want to learn more than you have to? Or you just have no choice? That's reasonable, but when you have a choice, consider learning how Swing or SWT works, spend some time reading the API docs and examining the code - it's amazing how fast and dynamic your GUI building process can get when you finally get a clear understanding HOW to use all the widgets and layouts. Let me show you. Here's a window from Hawkscope app that I'm making in my spare time. It was generated with Jigloo GUI builder in Eclipse. First let's see how it looks:



The code (all comments removed):
package com.varaneckas.hawkscope.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.cloudgarden.resource.SWTResourceManager;
import com.varaneckas.hawkscope.Version;
import com.varaneckas.hawkscope.cfg.ConfigurationFactory;
import com.varaneckas.hawkscope.util.IOUtils;
import com.varaneckas.hawkscope.util.IconFactory;
import com.varaneckas.hawkscope.util.OSUtils;

public class AboutWindow extends org.eclipse.swt.widgets.Dialog {

private Shell dialogShell;
private Canvas logoCanvas;
private Label appNameLabel;
private Label appSloganLabel;
private Label appVersion;
private Label appHomepageValue;
private Button copyReportButton;
private Button closeButton;
private Label environmentLabel;
private Text environmentTextArea;
private Label appHomepageLabel;
private Label appReleasedValue;
private Label appReleasedLabel;
private Label appVersionValue;

public AboutWindow(final Shell parent, final int style) {
super(parent, style);
}

public synchronized void open() {
if (dialogShell != null && !dialogShell.isDisposed()) {
dialogShell.setVisible(true);
dialogShell.forceFocus();
return;
}
final Shell parent = getParent();
dialogShell = new Shell(parent, SWT.DIALOG_TRIM
| SWT.APPLICATION_MODAL);
{
SWTResourceManager.registerResourceUser(dialogShell);
}
dialogShell.setImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope16.png"));
dialogShell.setText("About");

dialogShell.setLayout(new FormLayout());
dialogShell.layout();
dialogShell.pack();
dialogShell.setSize(516, 322);
{
copyReportButton = new Button(dialogShell, SWT.PUSH | SWT.CENTER);
FormData copyReportButtonLData = new FormData();
copyReportButtonLData.width = 125;
copyReportButtonLData.height = 29;
copyReportButtonLData.left = new FormAttachment(0, 1000, 314);
copyReportButtonLData.top = new FormAttachment(0, 1000, 252);
copyReportButton.setLayoutData(copyReportButtonLData);
copyReportButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
IOUtils.copyToClipboard(Version.getEnvironmentReport());
}
});
copyReportButton.setText("Co&py to Clipboard");
OSUtils.adjustButton(copyReportButton);
}
{
closeButton = new Button(dialogShell, SWT.PUSH | SWT.CENTER);
FormData closeButtonLData = new FormData();
closeButtonLData.width = 47;
closeButtonLData.height = 29;
closeButtonLData.left = new FormAttachment(0, 1000, 451);
closeButtonLData.top = new FormAttachment(0, 1000, 252);
closeButton.setLayoutData(closeButtonLData);
closeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
dialogShell.dispose();
}
});
closeButton.setText("&Close");
OSUtils.adjustButton(closeButton);
}
{
environmentLabel = new Label(dialogShell, SWT.NONE);
FormData environmentLabelLData = new FormData();
environmentLabelLData.width = 486;
environmentLabelLData.height = 17;
environmentLabelLData.left = new FormAttachment(0, 1000, 12);
environmentLabelLData.top = new FormAttachment(0, 1000, 127);
environmentLabel.setLayoutData(environmentLabelLData);
environmentLabel.setText("Environment");
environmentLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
environmentTextArea = new Text(dialogShell, SWT.MULTI | SWT.WRAP
| SWT.V_SCROLL | SWT.BORDER);
FormData environmentTextAreaLData = new FormData();
environmentTextAreaLData.width = 468;
environmentTextAreaLData.height = 90;
environmentTextAreaLData.left = new FormAttachment(0, 1000, 12);
environmentTextAreaLData.top = new FormAttachment(0, 1000, 150);
environmentTextArea.setLayoutData(environmentTextAreaLData);
environmentTextArea.setText(Version.getSystemProperties());
environmentTextArea.setEditable(false);
}
{
appHomepageValue = new Label(dialogShell, SWT.NONE);
appHomepageValue.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
appHomepageValue.setCursor(new Cursor(dialogShell.getDisplay(),
SWT.CURSOR_HAND));
appHomepageValue.setForeground(
new Color(dialogShell.getDisplay(), 0, 0, 255));
FormData appHomepageValueLData = new FormData();
appHomepageValueLData.width = 242;
appHomepageValueLData.height = 17;
appHomepageValueLData.left = new FormAttachment(0, 1000, 256);
appHomepageValueLData.top = new FormAttachment(0, 1000, 104);
appHomepageValue.setLayoutData(appHomepageValueLData);
appHomepageValue.setToolTipText("Click to open in browser");
appHomepageValue.setText(Version.HOMEPAGE);
}
{
appHomepageLabel = new Label(dialogShell, SWT.NONE);
FormData appHomepageLabelLData = new FormData();
appHomepageLabelLData.width = 94;
appHomepageLabelLData.height = 17;
appHomepageLabelLData.left = new FormAttachment(0, 1000, 156);
appHomepageLabelLData.top = new FormAttachment(0, 1000, 104);
appHomepageLabel.setLayoutData(appHomepageLabelLData);
appHomepageLabel.setText("Homepage:");
appHomepageLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appReleasedValue = new Label(dialogShell, SWT.NONE);
FormData appReleasedValueLData = new FormData();
appReleasedValueLData.width = 242;
appReleasedValueLData.height = 17;
appReleasedValueLData.left = new FormAttachment(0, 1000, 256);
appReleasedValueLData.top = new FormAttachment(0, 1000, 81);
appReleasedValue.setLayoutData(appReleasedValueLData);
appReleasedValue.setText(Version.VERSION_DATE);
}
{
appReleasedLabel = new Label(dialogShell, SWT.NONE);
FormData appReleasedLabelLData = new FormData();
appReleasedLabelLData.width = 77;
appReleasedLabelLData.height = 17;
appReleasedLabelLData.left = new FormAttachment(0, 1000, 156);
appReleasedLabelLData.top = new FormAttachment(0, 1000, 81);
appReleasedLabel.setLayoutData(appReleasedLabelLData);
appReleasedLabel.setText("Released:");
appReleasedLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appVersionValue = new Label(dialogShell, SWT.NONE);
FormData appVersionValueLData = new FormData();
appVersionValueLData.width = 242;
appVersionValueLData.height = 17;
appVersionValueLData.left = new FormAttachment(0, 1000, 256);
appVersionValueLData.top = new FormAttachment(0, 1000, 58);
appVersionValue.setLayoutData(appVersionValueLData);
if (Version.isUpdateAvailable() == null) {
appVersionValue.setText(Version.VERSION_NUMBER);
if (ConfigurationFactory.getConfigurationFactory()
.getConfiguration().checkForUpdates()) {
appVersionValue.setToolTipText("Could not get version information.");
}
} else {
if (Version.isUpdateAvailable()) {
appVersionValue.setForeground(new Color(dialogShell
.getDisplay(), 255, 0, 0));
appVersionValue.setText(Version.VERSION_NUMBER
+ " (Update Available!)");
appVersionValue.setToolTipText("Click to go to update " +
"download page");
appVersionValue.setCursor(new Cursor(dialogShell
.getDisplay(), SWT.CURSOR_HAND));
appVersionValue.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.DOWNLOAD_URL);
dialogShell.dispose();
}
});
} else {
appVersionValue.setText(Version.VERSION_NUMBER);
appVersionValue.setToolTipText("Latest available version!");
appVersionValue.setForeground(new Color(dialogShell
.getDisplay(), 0, 128, 0));
}
}
}
{
appVersion = new Label(dialogShell, SWT.NONE);
FormData appVersionLData = new FormData();
appVersionLData.width = 77;
appVersionLData.height = 17;
appVersionLData.left = new FormAttachment(0, 1000, 156);
appVersionLData.top = new FormAttachment(0, 1000, 58);
appVersion.setLayoutData(appVersionLData);
appVersion.setText("Version:");
appVersion.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appSloganLabel = new Label(dialogShell, SWT.WRAP);
FormData appSloganLabelLData = new FormData();
appSloganLabelLData.width = 342;
appSloganLabelLData.height = 17;
appSloganLabelLData.left = new FormAttachment(0, 1000, 156);
appSloganLabelLData.top = new FormAttachment(0, 1000, 35);
appSloganLabel.setLayoutData(appSloganLabelLData);
appSloganLabel.setText(Version.APP_SLOGAN);
}
{
appNameLabel = new Label(dialogShell, SWT.NONE);
FormData appNameLabelLData = new FormData();
appNameLabelLData.width = 342;
appNameLabelLData.height = 17;
appNameLabelLData.left = new FormAttachment(0, 1000, 156);
appNameLabelLData.top = new FormAttachment(0, 1000, 12);
appNameLabel.setLayoutData(appNameLabelLData);
appNameLabel.setText("Hawkscope");
appNameLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
final FormData logoCanvasLData = new FormData();
logoCanvasLData.width = 114;
logoCanvasLData.height = 109;
logoCanvasLData.left = new FormAttachment(0, 1000, 12);
logoCanvasLData.top = new FormAttachment(0, 1000, 12);
logoCanvas = new Canvas(dialogShell, SWT.RESIZE);
logoCanvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.drawImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope128.png"), 0, 0, 128,
128, 0, 0, 114, 109);
}
});
logoCanvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
logoCanvas.setCursor(new Cursor(dialogShell.getDisplay(),
SWT.CURSOR_HAND));
logoCanvas.setToolTipText("Click to visit Homepage");
logoCanvas.setLayoutData(logoCanvasLData);
}
dialogShell.setLocation(getParent().toDisplay(100, 100));
dialogShell.open();
Display display = dialogShell.getDisplay();
while (!dialogShell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}

}


Now, a hand-rewritten version with no GUI builder:



The code:
package com.varaneckas.hawkscope.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.varaneckas.hawkscope.Version;
import com.varaneckas.hawkscope.cfg.ConfigurationFactory;
import com.varaneckas.hawkscope.tray.TrayManager;
import com.varaneckas.hawkscope.util.IOUtils;
import com.varaneckas.hawkscope.util.IconFactory;

public class AboutShell {

private Shell shell;
private FormData layout;
private Font bold;
private Color red;
private Color green;
private Color blue;
private Cursor hand;
private Canvas logo;
private Label labelAppName;
private Label labelAppSlogan;
private Label labelVersion;
private Label labelReleased;
private Label labelHomePage;
private Label labelAppVersion;
private Label labelAppReleased;
private Label labelAppHomePage;
private Label labelEnvironment;
private Text textEnvironment;
private Button buttonCopyToClipboard;
private Button buttonClose;

public void open() {
if (shell != null && !shell.isDisposed()) {
shell.setVisible(true);
shell.forceFocus();
return;
}
createShell();
createResources();
createLogo();
createLabelAppName();
createLabelAppSlogan();
createLabelVersion();
createLabelReleased();
createLabelHomePage();
createLabelAppVersion();
createLabelAppReleased();
createLabelAppHomePage();
createLabelEnvironment();
createButtonClose();
createButtonCopyToClipboard();
createTextEnvironment();
shell.pack();
shell.open();
}

private void createResources() {
final FontData data = new FontData();
data.setHeight(10);
data.setStyle(SWT.BOLD);
bold = new Font(shell.getDisplay(), data);
red = new Color(shell.getDisplay(), 255, 0, 0);
green = new Color(shell.getDisplay(), 0, 128, 0);
blue = new Color(shell.getDisplay(), 0, 0, 255);
hand = new Cursor(shell.getDisplay(), SWT.CURSOR_HAND);
}

private void createShell() {
shell = new Shell(TrayManager.getInstance().getShell(), SWT.SHELL_TRIM);
final FormLayout layout = new FormLayout();
layout.spacing = 6;
layout.marginHeight = 12;
layout.marginWidth = 12;
shell.setLocation(shell.getParent().toDisplay(100, 100));
shell.setImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope16.png"));
shell.setText("About");
shell.setLayout(layout);
shell.layout();
}

private FormData relativeTo(final Control top, final Control left) {
layout = new FormData();
layout.top = new FormAttachment(top);
layout.left = new FormAttachment(left);
return layout;
}

private FormData relativeToBottomRight(final Control right) {
layout = new FormData();
layout.bottom = new FormAttachment(100, 0);
if (right == null) {
layout.right = new FormAttachment(100, 0);
} else {
layout.right = new FormAttachment(right);
}
return layout;
}

private void createLogo() {
logo = new Canvas(shell, SWT.NONE);
logo.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.drawImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope128.png"), 0, 0);
}
});
logo.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
logo.setCursor(hand);
logo.setToolTipText("Click to visit Homepage");
layout = relativeTo(null, null);
layout.width = 128;
layout.height = 128;
logo.setLayoutData(layout);
}

private void createLabelAppName() {
labelAppName = new Label(shell, SWT.NONE);
labelAppName.setText(Version.APP_NAME);
labelAppName.setLayoutData(relativeTo(null, logo));
labelAppName.setFont(bold);
}

private void createLabelAppSlogan() {
labelAppSlogan = new Label(shell, SWT.NONE);
labelAppSlogan.setLayoutData(relativeTo(labelAppName, logo));
labelAppSlogan.setText(Version.APP_SLOGAN);
}

private void createLabelVersion() {
labelVersion = new Label(shell, SWT.NONE);
labelVersion.setText("Version:");
labelVersion.setFont(bold);
labelVersion.setLayoutData(relativeTo(labelAppSlogan, logo));
}

private void createLabelAppVersion() {
labelAppVersion = new Label(shell, SWT.NONE);
labelAppVersion.setText(Version.VERSION_NUMBER);
labelAppVersion.setLayoutData(relativeTo(labelAppSlogan, labelHomePage));
updateLabelAppVersion();
}

private void updateLabelAppVersion() {
if (Version.isUpdateAvailable() == null) {
if (ConfigurationFactory.getConfigurationFactory()
.getConfiguration().checkForUpdates()) {
labelAppVersion.setToolTipText("Could not get version information.");
}
} else {
if (Version.isUpdateAvailable()) {
labelAppVersion.setForeground(red);
labelAppVersion.setText(Version.VERSION_NUMBER
+ " (Update Available!)");
labelAppVersion.setToolTipText("Click to go to update " +
"download page");
labelAppVersion.setCursor(hand);
labelAppVersion.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(final MouseEvent event) {
Program.launch(Version.DOWNLOAD_URL);
shell.dispose();
}
});
} else {
labelAppVersion.setText(Version.VERSION_NUMBER);
labelAppVersion.setToolTipText("Latest available version!");
labelAppVersion.setForeground(green);
}
}
}

private void createLabelReleased() {
labelReleased = new Label(shell, SWT.NONE);
labelReleased.setText("Released:");
labelReleased.setFont(bold);
labelReleased.setLayoutData(relativeTo(labelVersion, logo));
}

private void createLabelAppReleased() {
labelAppReleased = new Label(shell, SWT.NONE);
labelAppReleased.setText(Version.VERSION_DATE);
labelAppReleased.setLayoutData(relativeTo(labelVersion, labelHomePage));
}

private void createLabelHomePage() {
labelHomePage = new Label(shell, SWT.NONE);
labelHomePage.setText("Homepage:");
labelHomePage.setFont(bold);
labelHomePage.setLayoutData(relativeTo(labelReleased, logo));
}

private void createLabelAppHomePage() {
labelAppHomePage = new Label(shell, SWT.NONE);
labelAppHomePage.setText(Version.HOMEPAGE);
labelAppHomePage.setLayoutData(relativeTo(labelReleased, labelHomePage));
labelAppHomePage.setCursor(hand);
labelAppHomePage.setForeground(blue);
labelAppHomePage.setToolTipText("Click to open in browser");
labelAppHomePage.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(final MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
}

private void createLabelEnvironment() {
labelEnvironment = new Label(shell, SWT.NONE);
labelEnvironment.setText("Environment");
labelEnvironment.setFont(bold);
labelEnvironment.setLayoutData(relativeTo(logo, null));
}

private void createButtonClose() {
buttonClose = new Button(shell, SWT.PUSH);
buttonClose.setText("&Close");
buttonClose.setLayoutData(relativeToBottomRight(null));
buttonClose.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
shell.dispose();
}
});
}

private void createButtonCopyToClipboard() {
buttonCopyToClipboard = new Button(shell, SWT.PUSH);
buttonCopyToClipboard.setText("C&opy to Clipboard");
buttonCopyToClipboard.setLayoutData(relativeToBottomRight(buttonClose));
buttonCopyToClipboard.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
IOUtils.copyToClipboard(Version.getEnvironmentReport());
}
});
}

private void createTextEnvironment() {
textEnvironment = new Text(shell, SWT.MULTI | SWT.WRAP
| SWT.V_SCROLL | SWT.BORDER);
textEnvironment.setText(Version.getEnvironmentReport());
textEnvironment.setEditable(false);
layout = relativeTo(labelEnvironment, null);
layout.right = new FormAttachment(100, 0);
layout.bottom = new FormAttachment(buttonClose);
layout.width = 500;
layout.height = 150;
textEnvironment.setLayoutData(layout);
}

}


If you compare the two versions, handcoded one is superior in most aspects. The code is smaller, more readable and text editor friendly. The window can be resized, it is better looking - in generated code the logo image was scaled due to dragging inacuracy. And, believe it or not, I've spent less time creating the handcoded GUI version than "drawing" the automated one and then hacking it's generated code. Of course, if you know your tools well, you can be much more productive with a GUI builder, but I'll rather learn the low-level GUI API than some commercial third party product that treats you like parents treat their kids with LEGO.

No more GUI builders for me.

2009-01-22

Hawkscope: Getting Better



Finally, I've managed to finish the SWT implementation of Hawkscope GUI. Along with some other minor fixes and improvements it ended up as a new Hawkscope version: 0.3.0.

A list of key points for this release:

  • Tray icon should work with Mac OS X (at least with 64-bit Leopard /w Java 6)

  • Hawkscope menu is more responsive and usable

  • Keyboard navigation through the menu is finally available

  • Each OS/Architecture has it's own build

  • Added -delay <milliseconds> startup parameter as a workaround for Java bug #6438179 that prevented Hawkscope from auto-starting in operating systems like Ubuntu. Read more in the User Guide

By the way, if you're on 64-bit Vista or 64-bit Leopard with Java 6 and you've got Maven in your hands, you could be helpful by providing the build for your OS. Thank You!

If Java is your cocaine, you are welcome to join the development - just contact me.

Download links for the new Hawkscope 0.3.0:

hawkscope-0.3.0-linux-gtk-32.jar - Linux GTK 32-bit Executable JAR
hawkscope-0.3.0-linux-gtk-64.jar - Linux GTK 64-bit Executable JAR
hawkscope-0.3.0-win-32.exe - Windows XP/Vista 32-bit Executable
hawkscope-0.3.0-win-32.jar - Windows XP/Vista 32-bit Executable JAR

2009-01-20

Dive into Java

Java Jump-start for experienced software developers. Another presentation I gave at work.

2009-01-06

Using Unicode in Java .property files

Using multi-byte characters in Java .property files? Forget the Sun's native2ascii application. Rather than doing the irritating conversions, create UTF-8 encoded .property files, edit them directly and use Utf8ResourceBundle to access them. The code:

package com.varaneckas.utils;

import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

/**
* UTF-8 friendly ResourceBundle support
*
* Utility that allows having multi-byte characters inside java .property files.
* It removes the need for Sun's native2ascii application, you can simply have
* UTF-8 encoded editable .property files.
*
* Use:
* ResourceBundle bundle = Utf8ResourceBundle.getBundle("bundle_name");
*
* @author Tomas Varaneckas <tomas.varaneckas@gmail.com>
*/
public abstract class Utf8ResourceBundle {

/**
* Gets the unicode friendly resource bundle
*
* @param baseName
* @see ResourceBundle#getBundle(String)
* @return Unicode friendly resource bundle
*/
public static final ResourceBundle getBundle(final String baseName) {
return createUtf8PropertyResourceBundle(
ResourceBundle.getBundle(baseName));
}

/**
* Creates unicode friendly {@link PropertyResourceBundle} if possible.
*
* @param bundle
* @return Unicode friendly property resource bundle
*/
private static ResourceBundle createUtf8PropertyResourceBundle(
final ResourceBundle bundle) {
if (!(bundle instanceof PropertyResourceBundle)) {
return bundle;
}
return new Utf8PropertyResourceBundle((PropertyResourceBundle) bundle);
}

/**
* Resource Bundle that does the hard work
*/
private static class Utf8PropertyResourceBundle extends ResourceBundle {

/**
* Bundle with unicode data
*/
private final PropertyResourceBundle bundle;

/**
* Initializing constructor
*
* @param bundle
*/
private Utf8PropertyResourceBundle(final PropertyResourceBundle bundle) {
this.bundle = bundle;
}

@Override
@SuppressWarnings("unchecked")
public Enumeration getKeys() {
return bundle.getKeys();
}

@Override
protected Object handleGetObject(final String key) {
final String value = bundle.getString(key);
if (value == null)
return null;
try {
return new String(value.getBytes("ISO-8859-1"), "UTF-8");
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException("Encoding not supported", e);
}
}
}
}

2008-11-11

InputStreamChain

If you have several Java InputStreams that you want to queue up into a single InputStream object, you can use this:

package com.varaneckas;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;

/**
* {@link InputStream} implementation that allows chaining of various
* streams for seamless sequential reading
*
* @author Tomas Varaneckas <tomas.varaneckas@gmail.com>
*/
public class InputStreamChain extends InputStream {

/**
* Input stream chain
*/
private final LinkedList<InputStream> streams = new LinkedList<InputStream>();

/**
* Currently active stream
*/
private InputStream current;

/**
* Default constructor
*/
public InputStreamChain() {
//nothing to do
}

/**
* Constructor with an initial stream
*
* @param first Initial InputStream
*/
public InputStreamChain(final InputStream first) {
addInputStream(first);
}

/**
* Constructor with an array of initial streams
*
* @param streams Array of initial InputStreams
*/
public InputStreamChain(final InputStream[] streams) {
for (InputStream stream : streams) {
addInputStream(stream);
}
}

/**
* Vararg constructor
*
* @param streams initial input streams
*/
public InputStreamChain(final InputStream ... streams) {
for (InputStream stream : streams) {
addInputStream(stream);
}
}

/**
* Adds input stream to the end of chain
*
* @param stream InputStream to add to chain
* @return instance of self (for fluent calls)
*/
public InputStreamChain addInputStream(final InputStream stream) {
streams.addLast(stream);
if (current == null) {
current = streams.removeFirst();
}
return this;
}

@Override
public int read() throws IOException {
int bit = current.read();
if (bit == -1 && streams.size() > 0) {
try {
current.close();
} catch (final IOException e) {
//replace this with a call to logging facility
e.printStackTrace();
}
current = streams.removeFirst();
bit = read();
}
return bit;
}

@Override
public int available() throws IOException {
int available = current.available();
for (InputStream stream : streams) {
available += stream.available();
}
return available;
}

@Override
public void close() throws IOException {
current.close();
}

@Override
public boolean markSupported() {
return current.markSupported();
}

@Override
public synchronized void mark(int i) {
current.mark(i);
}

@Override
public synchronized void reset() throws IOException {
current.reset();
}

@Override
public long skip(long l) throws IOException {
return current.skip(l);
}

}


Example code:
InputStream chuck = new ByteArrayInputStream("Chuck ".getBytes());
InputStream norris = new ByteArrayInputStream("Norris".getBytes());
InputStream chuckNorris = new InputStreamChain()
.addInputStream(chuck)
.addInputStream(norris);
//will print "Chuck Norris"
System.out.println(new BufferedReader(
new InputStreamReader(chuckNorris)).readLine());


Have fun!

2008-10-07

Hawkscope 0.2.0

This little peace of software has lately become my passion that reminds me of those days when I was programming 16 hours a day 7 days a week. Anyway, here are the highlights for the new Hawkscope 0.2.0:

  • Size dropped down from 740Kb to 122Kb.

  • Fixed bugs that prevented writing the configuration file in Windows and reading it in all OS.

  • Added Quick Access List menu that allows custom folders to be listed on top of all partitions. User home is there by default. This list can contain dynamic variables like Java properties (${user.home}/Desktop) or environmental variables (${$JAVA_HOME}). Read the User Guide to find out how to configure your own Quick Access List.

  • Floppy drives are ignored by default to avoid annoying device buzz whenever mouse travels over floppy disk entry. This can be turned back on by changing display.floppy property to "1". By the way, same thing can be done with hidden files. They are hidden by default, but there is display.hidden property in [user_home]/.hawkscope.properties.

  • Improvements in About and Error dialogs. You can now copy nicely formatted bug reports to Clipboard for easy submission. If you use Hawkscope and find anything suspicious, please, copy the report and add new issue, it takes just 10-20 seconds.

  • You can find more changes in Hawkscope Changelog.

    Here's how the new version looks on Windows Vista:



    Stay tuned for future improvements that include a full blown plugin system, GUI driven configuration and more.

    2008-09-28

    Hawkscope: System Tray File Browser

    I'm happy to announce the first usable release of my new weekend project. It's Hawkscope - a simple productivity tool that allows you to find and open any file or folder in seconds by single-clicking a tray icon and navigating through dynamically generated menus that reflect the contents of your available file systems.

    Hawkscope is open source, it's built with Java 1.6, therefore it doesn't run on Mac OS X (for now...), even with SoyLatte JDK. I tested it on Windows (XP and Vista) and Linux (Ubuntu + Gnome). Should work perfectly where Java 6 System Tray and Desktop API are supported.

    Here's how it looks on Windows Vista (running inside VirtualBox):


    And on my Ubuntu:


    You can always download latest release here. Enjoy!

    2008-09-26

    Java 6 on 32-bit Intel Mac

    Dying to have Java 6 on an older, non Core 2 Duo Intel Mac? You should get SoyLatte.

    SoyLatte is a functional, X11-based port of the FreeBSD Java 1.6 patchset to Mac OS X Intel machines. SoyLatte is initially focused on supporting Java 6 development; however, the long-term view far more captivating: open development of Java 7 for Mac OS X, with a release available in concert with the official Sun release, supported on all recent versions of Mac OS X.

    It lacks some features like system tray support, but overall these folks are doing a way better job than the official Apple Java team.

    2008-09-18

    Eclipse Template: Singleton Pattern

    Are you writing your Java classes as Singletons quite often? Use this Eclipse template and make any class a singleton in two seconds:

    Setup

    Go to Window -> Preferences -> Java -> Editor -> Templates. Create New:



    Code for copy paste:
    private static final ${enclosing_type} instance = new ${enclosing_type}();
    private ${enclosing_type}() {}
    public static ${enclosing_type} getInstance() {
        return instance;
    }


    Action

    Type "single" or "singleton", hit Content Assist shortcut key (ctrl+space by default), then enter:



    And there you go - a singleton in two seconds (actual time may vary on your typing and CPU speed).



    You may want to use a different Singleton implementation. Check out Java Singleton: The Proper Way for a good example.

    2008-09-01

    Add loggers to your Java code in seconds using Eclipse Templates

    Custom Eclipse Templates can greatly increase your productivity by automating daily development. For instance, instead of creating Apache Commons Logging loggers by hand, you can do this:

    Setup

    Go to Window -> Preferences -> Java -> Editor -> Templates. Create New:



    Action

    Type "log", hit Content Assist shortcut key (ctrl+space by default), then enter:



    Hit Ctrl+Shift+O to import Log and LogFactory:



    Saves hell of a time in the long run.

    2008-07-25

    Slap your Java code hard with Maven and PMD

    How good you think your code is? How can you be sure it's optimal, bug and bullet proof? Unit Tests? In case the coverage is good, they will tell if your code works, but will it tell if and where your code sucks in general? Let's get down to business.

    As Maven is used de facto for Java builds, I assume you're using it. You may have heard of PMD, but have you tried it? If you have, did you know that it has a nice Maven Plugin? A quick way to integrate it:

    Add the following to your pom.xml. Sadly, default check rulesets are too friendly, so you may want to try my configuration (even the "too hardcore" block):







    org.apache.maven.plugins
    maven-jxr-plugin


    org.apache.maven.plugins
    maven-pmd-plugin


    rulesets/basic.xml
    rulesets/braces.xml
    rulesets/clone.xml
    rulesets/codesize.xml
    rulesets/coupling.xml
    rulesets/favorites.xml
    rulesets/finalizers.xml
    rulesets/imports.xml
    rulesets/junit.xml
    rulesets/migrating_to_15.xml
    rulesets/optimizations.xml
    rulesets/typeresolution.xml
    rulesets/unusedcode.xml
    rulesets/strings.xml


    true
    true
    utf-8
    5
    20
    1.5








    To generate a report, simply run:

    mvn pmd:pmd

    You may want to build Java cross reference for links to source code to work:

    mvn jxr:jxr

    Finally, open the report and see how naughty the code is:

    target/site/pmd.html

    Watch out for Cyclomatic Complexity!

    2008-05-30

    XSL Engine

    Amazingly, some bright minds of the company I work at supported the idea to release one of our products as an open source software. It's nice to see a huge "Nothing is Free" business opening up a little. I'm proud to present the release of the XSL Engine:

    XSLE at Google Code

    XSL Engine is an XSL transformation server and client. It provides united processing of XSL transformations, independent of any programming environment. This can remove load from other applications. It features high throughput, with the possibility to increase the throughput of the XSL transformations by setting up new servers. It operates in an Apache Tomcat Web container. XSL documents are loaded into cache. XSL Includes are supported. PDF can be generated (using XSL-FO). XSL cache can be automatically replicated among remote servers.

    2008-05-10

    Getting Unicode output in Eclipse Console

    Tired of seeing garbled Eclipse Console output? Here is a quick and dirty tutorial for getting Unicode output in Eclipse Console.

    Fact: You will not get Unicode output in Eclipse Console while using System.out directly in Windows. See Eclipse BUG #13865.

    1. add -Dfile.encoding=UTF-8 to your eclipse.ini

    2. make sure your Eclipse Console font supports Unicode. You can try it out by typing unicode characters directly to console with keyboard. Console Font is set in Window -> Preferences -> General -> Appearance -> Colors and Fonts -> Debug -> Console Font

    3. if you are NOT using Windows, set your system encoding to UTF-8. You should now see Unicode characters in Console after restarting Eclipse.

    4. if you are using Windows or do not want to change your OS encoding, you will have to avoid using System.out stream directly. Instead, wrap it up with java.io.PrintStream:
    PrintStream sysout = new PrintStream(System.out, true, "UTF-8");
    sysout.println("\u2297\u0035\u039e\u322F\u5193");


    5. if you are using Log4J with ConsoleAppender, make sure to set the encoding property to UTF-8. Example:
    #TRACE appender
    log4j.appender.stdout.trace=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.trace.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.trace.encoding=UTF-8
    log4j.appender.stdout.trace.layout.ConversionPattern=%p [%c] - %m%n
    log4j.appender.stdout.trace.Threshold=TRACE


    Happy development!

    2008-04-21

    Make your Eclipse rock

    Eclipse is one of the greatest IDEs available out there. In my opinion it's the best, because in comparison with other choices such as Intellij IDEA, Eclipse is free and open. People say NetBeans are getting good, however I am too skeptic to believe. As it's the matter of preference, I will stick with Eclipse.

    Yet, after a fresh and clean install Eclipse (v3.3 as of this moment) is not yet kicking. A few crucial things are missing. I'll give out my recipe to making Eclipse rock. So, let's get on with that:

    1. Fresh install from www.eclipse.org. As I'm NOT doing J2EE/Web/XML stuff at home, Eclipse IDE for Java Developers is the best choice for me.
    2. Give it more RAM! Default values are pathetic, so you should edit your eclipse.ini to look like this:
      -showsplash
      org.eclipse.platform
      --launcher.XXMaxPermSize
      256M
      -vmargs
      -Dosgi.requiredJavaVersion=1.5
      -Xms256m
      -Xmx1024m
      -XX:PermSize=256m
      Say bye to those possible out of memory and permgen space errors.
    3. Fine-tune the Preferences. Go straight to workspace and open Window -> Preferences.
      General:
      Check "Show heap status" - it's nice to see how much heap you've got.
      General -> Appearance:
      Uncheck "Show text on the perspective bar". That gives more space for perspective icons.
      Check "Show traditional style tabs". Performance.
      Uncheck "Enable animations". Performance.
      General -> Editors -> Text Editors:
      Check "Insert spaces for tabs". You may not want that if you're a tab fan.
      Check "Show print margin". 80 is good.
      Check "Show line numbers". Who would not want to see line numbers by default?
      General -> Editors -> Text Editors -> Spelling:
      Uncheck "Enable spell checking". Performance. And hell, that's no Microsoft Word.
      General -> Startup and Shutdown:
      Uncheck "Mylin Tasks UI" from "Plug-ins activated on startup". Unless you REALLY want to use Mylin. I think it sucks and terribly slows Eclipse down.
      General -> Workspace:
      Choose "UTF-8" as your "Text file encoding". Unicode is the way to go.
      Choose "Unix" as your "New text file line delimiter". This is also is the way to go.
      Java -> Code Style -> Formatter:
      Click "Edit..." on Eclipse [built-in] profile. Set new profile name. In "Identation" tab set "Tab policy" to "Spaces only". You may skip this if you're a tab dude.
      Java -> Compiler -> Errors/Warnings:
      You may want to harden your compiler warnings for more beautiful and strict development. I tend to override these:
      In "Code style": "Undocumented empty block" -> "Warning".
      In "Unnecessary code": "Unnecessary 'else' statement" -> "Warning".
      In "Unnecessary code": "Unnecessary cast or 'instanceof' operation" -> "Warning".
      In "Unnecessary code": "Unnecessary declaration of thrown checked exception" -> "Warning".
      Java -> Compiler -> Javadoc:
      Set "Malformed Javadoc comments:" -> "Warning".
      Set "Only consider members as visible as: " -> "Private".
      You want your Javadoc clean, don't you.
      Java -> Editor -> Content Assist -> Advanced:
      In content assist proposal list uncheck entries marked with "(Mylin)" and check the alternatives: "Other Java Proposals", "Template Proposals", "Type Proposals".
      In cycling list check all same entries: "Other Java Proposals", "Template Proposals", "Type Proposals".
      Web and XML -> XML Files -> Source:
      In "Formatting" section check "Indent using spaces" and set Indentation size to "2". Otherwise you will end up with tabs in your XML.
    4. Subversive - the best Eclipse plug-in for SVN support:
      Help -> Software Updates -> Find and Install. Search for new features to install. Add two New Remote Sites - "Subversive SVN Connectors" with URL: http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/ and "Subversive plug-in" with URL: http://download.eclipse.org/technology/subversive/0.7/update-site/
      Check http://www.eclipse.org/subversive/downloads.php for latest URLs.
      Install new components. Skip Mylin integration and sources.
    5. Maven plug-in. You MUST know what Maven is, otherwise don't do Java. Seriously. You may choose from Tycho and Q: http://maven.apache.org/eclipse-plugin.html
      I was a long-time user of Tycho, however Q looks really promising. I'm trying it right now for the first time. Yeah, definitely, go for Q. Installation is easy, just add New Remote Site:
      Q4E: http://q4e.googlecode.com/svn/trunk/updatesite/
    6. XML Buddy. Your light weight swiss army knife in Eclipse XML editing. Download plug-in manually from http://xmlbuddy.com and drop it into your Eclipse plugins folder, then restart.
    7. JADClipse. Your daily Java decompiler. Best for those times when you need to go under the hood. Make this a habit. You will need JAD in your PATH: http://www.kpdus.com/jad.html
      Then download JADClipse plug-in from http://jadclipse.sourceforge.net/, drop it to Eclipse plugins folder and restart.
    Enjoy your tools!