Saturday, August 18, 2012

vCards

When you meet someone for the first time, whether it be a friend or a business partner, how do you exchange contact information? Maybe you send an email to each other to share your email address. Maybe you text or call each other on your cell phones to share your cell phone number. Maybe you write your number on a piece of paper. There are many ways to do this, but each of these ways is error-prone. What if you forget to include a vital piece of information, like the spelling of your last name or the URL of your website?

The idea behind the vCard standard is to provide an easy and hassle-free way for people to share their contact information electronically. A vCard is basically an electronic business card--it contains information like your name, mailing address, email address, telephone number, website, and a picture of yourself. So, when you meet someone new, instead sending them an email with your phone number, address, website, and "darn it what else do they need to know", you can just email them your vCard.

Pretty much all email clients including GMail, Outlook, and Mail can import vCards into their address books. And since many email clients also allow you to export your contacts as a vCard, the vCard standard can act as a sort of data transmission format if you want to switch email clients. You can export all your contacts as a vCard, and then import the vCard into the other email client.

Developer's Overview

From a software engineering perspective, a vCard is just a plain text file (there is also a less popular XML format). It consists of a number of "properties", each of which has zero or more "parameters" and exactly one "value". Each property goes on its own line. Long lines are usually "folded", which means that they are split up into multiple lines. The folded lines all start with a whitespace character to show that they're part of the same line. Binary data, like photos, are encoded in base64.

Here's what my vCard looks like.

BEGIN:VCARD
VERSION:4.0
KIND:individual
SOURCE:http://mikeangstadt.name/mike-angstadt.vcf
FN:Michael Angstadt
N:Angstadt;Michael;;Mr;
NICKNAME:Mike
PHOTO;VALUE=uri:data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4Q
 ZgAASUkqAAgAAAAEABoBBQABAAAAPgAAABsBBQABAAAARgAAACgBAwABAAAAAgAAADEB
 AATgAAAAAAAABgAAAAAQAAAGAAAAABAAAAUGFpbnQuTkVUIHYzLjUuMTAA/9sAQwACAQ
 [more base64 data]
REV;VALUE=timestamp:20120818T155230Z
EMAIL;TYPE=home:mike.angstadt@gmail.com
TEL;TYPE=cell;VALUE=uri:tel:+1 555-555-1234
TEL;TYPE=home;VALUE=uri:tel:+1 555-555-9876
URL;TYPE=home:http://mikeangstadt.name
URL;TYPE=work:http://code.google.com/p/ez-vcard
TZ;VALUE=text:America/New_York
GEO;VALUE=uri:geo:39.95,75.1667
CATEGORIES:Java software engineer,vCard expert,Nice guy
UID:urn:uuid:dd418720-c754-4631-a869-db89d02b831b
LANG:en-US
X-GENERATOR:EZ vCard v0.2.1-SNAPSHOT http://code.google.com/p/ez-vcard
END:VCARD

As you can see, it contains my name, email address, website, and other data. The telephone information is fake, but I wanted to include it just to show what telephone data looks like. The PHOTO property contains a profile picture of myself. Because this property value is so large, it has been folded into multiple lines. The PHOTO property also contains a parameter, "VALUE", which states that the property value is a URI. All vCards must start with "BEGIN:VCARD" and end with "END:VCARD". vCards must use the \r\n newline character sequence.

There are three different vCard versions: 2.1, 3.0, 4.0. Versions 3.0 and 4.0 are RFC standards, defined in RFC 2426 and RFC 6350 respectively. Versions 2.1 and 3.0 are very similar, but version 4.0 is significantly different from the previous versions. It adds many new properties and parameters, and redefines how the values of many existing properties should be encoded.

EZ-vCard

EZ-vCard is an open source Java library that I wrote that reads and creates vCards. It supports all versions of the vCard standard. My goal was to design an API that was as easy to use as possible. For example, here's how to create a vCard file with some basic information:

VCard vcard = new VCard();

vcard.setFormattedName(new FormattedNameType("Barak Obama"));

EmailType email = new EmailType("barak.obama@whitehouse.gov");
email.addType(EmailTypeParameter.WORK);
vcard.addEmail(email);

email = new EmailType("superdude22@hotmail.com");
email.addType(EmailTypeParameter.HOME);
vcard.addEmail(email);

TelephoneType tel = new TelephoneType("(555) 123-5672");
tel.addType(TelephoneTypeParameter.CELL);
vcard.addTelephoneNumber(tel);

File file = new File("obama.vcf");
vcard.write(file);

It's also very easy to read a vCard file using EZ-vCard:

File file = new File("obama.vcf");
VCard vcard = VCard.parse(file);

Nothing says "I'm professional" like a vCard! Create your own vCard today!

Sunday, August 12, 2012

JUnit and Temporary Files

Oftentimes, an application interacts with the file system by reading from or creating files and directories. This functionality should, of course, be unit tested to ensure that it works as expected. Also, the unit tests should be self-contained, meaning that any files it reads from or creates should be located within the project itself and not at some location like "C:\unit-test-files". In addition, these temporary files and directories should be cleaned up when the test is done running because, well, they're temporary. And they definitely should not be commited to version control.

You could just throw the files in a location that you know is temporary, like the "target" directory if you use Maven or your operating system's temporary file directory. The problem with this is that if the files are not deleted between test runs, then it could skew your test results. No matter where you put them, they have to be cleaned up when the test is finished running.

You could write the cleanup code yourself OR you could use JUnit's TemporaryFolder class. This class takes care of cleaning up these files and directories after each test finishes running. It will always clean up the files, whether the test passes, fails, or throws an exception. It creates the temp folder when a test starts and deletes the temp folder when the test finishes. It does this for every test method in the class.

Under the covers, TemporaryFolder uses the File.createTempFile() method to create the directory, so it's storing the directory in your operating system's temp directory. It also assigns a unique name to the directory, so if the JVM crashes and TemporaryFolder does NOT clean up your files, the results of your next test run will not be skewed by the files from the previous run.

Here's a code sample demonstrating how the TemporaryFolder class works.

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

class FileTest {
  @Rule
  public TemporaryFolder temp = new TemporaryFolder();

  @Test
  public void basicTest() throws IOException {
    //the temporary folder is created before this test method runs

    File fileWithoutName = temp.newFile();
    File fileWithName = temp.newFile("myfile.txt");

    File dirWithoutName = temp.newFolder();
    File dirWithName = temp.newFolder("myfolder");

    File fileInsideCreatedDir = new File(dirWithName, "myfile2.txt");

    //the temporary folder is deleted when this test method finishes
  }
}

A class-level instance of TemporaryFolder is created and tagged with the @Rule annotation. This annotation instructs the class to create the temporary folder before a test runs and delete the temporary folder after the test finishes. This field MUST be "public".

The newFile() method creates a new file within the temporary directory. If a file name is not passed into the method, then it will generate a random file name.

The newFolder() method creates a new directory within the temporary directory. As with newFile(), if a name is not passed into the method, then it will generate a random name for the directory.

Note: The zero-argument versions of newFile() and newFolder() were added fairly recently to the API. If you get compilation errors trying to use these, update your JUnit library to the latest version (4.10 at the time of this writing).

You can, of course, create files and directories within a directory that is created with newFolder(). Just pass the File object that was returned by newFolder() into the first argument of the File constructor (as demonstrated with the fileInsideCreatedDir object).

This is a wonderful little gem that takes the pain out of unit testing file system code. I wish I had known about it sooner.