Monday, May 31, 2010

Flair Additions

So I made some improvements to my developer forum flair!

Firstly, I added a Sun Forums flair, which displays your username, number of posts, and number of Dukes.


The background image to the left changes depending on how many "Dukes" you have, which are like these special points you can get for answering questions. It will also put a star next to your name if you are a moderator.

This one was a little more complicated because I couldn't pass the HTML directly into a DOMDocument object like with JavaRanch. I had to use tidy to clean up the HTML before passing it to DOMDocument:
//tidy the page
$tidy = new tidy("http://forums.sun.com/profile.jspa?userID=$id");
$tidy->cleanRepair();
$tidy = preg_replace('/<\/?nobr>/', '', $tidy); //<nobr> tags must be removed for DOMDocument

//load the page
$html = new DOMDocument();
$html->loadHTML($tidy);
I also had to pretty much do all development on the mangst.com server because the PHP installation on my iMac doesn't have tidy installed and it doesn't support all of the GD graphics functions.

Secondly, I added the ability to view the flair as a dynamically created image (nerdgasm!!!). Just change the "type" parameter to "png" or "jpeg":
<img src="http://www.mangst.com/flair/sun?id=1071155&type=png" />


I was actually surprised how easy this was. I had this fear that generating an image programmatically would be hugely complicated, but it wasn't really. The hardest part was keeping track of the pixels to make sure everything lined up right. I tried to make it look as close as possible to the Javascript and HTML versions, but for some reason the bold version of Verdana comes out looking a lot less bold in the image.

Tuesday, May 25, 2010

JavaRanch Flair

Check out my JavaRanch flair!



I was so inspired by the flair over at Stackoverflow that I thought it would be fun to create my own! It basically works just like Stackoverflow's does. You have three options for how to include it in your webpage:

  • Javascript - You can include it using a <script> tag, which injects the flair into the DOM via Javascript.
    <script
      src="http://www.mangst.com/flair/javaranch?id=209694&type=js"
      type="text/javascript">
    </script>
    
  • HTML - You can include it using an <iframe>, which loads the flair as HTML into the frame.
    <iframe
      src="http://www.mangst.com/flair/javaranch?id=209694&type=html"
      marginwidth="0"
      marginheight="0" 
      frameborder="0"
      scrolling="no"
      width="220"
      height="100">
    </iframe>
    
  • JSON - You can get the raw data with JSON and handle the data however you wish with Javascript.
    JSON URL: http://www.mangst.com/flair/javaranch?id=209694&type=json
On the backend, what it does is it screen scrapes your JavaRanch profile page, plucking out the information that it needs. It uses a PHP DOMDocument object to load the HTML, then uses XPath to get the data fields.
$html = new DOMDocument();
$html->loadHtmlFile("http://www.coderanch.com/forums/user/profile/$id");
$xpath = new DOMXPath($html);
$username = $xpath->query("//span[@id='profileUserName']")->item(0)->textContent;
//...

I think it's pretty elegant, though it will break if JavaRanch decides to do any site redesigns. I was afraid that it wouldn't be able to load the HTML into a DOM, since webpage HTML tends not to be well formed XML, which would have prevented me from using XPath. The SimpleXMLElement class wouldn't accept the HTML, but the DOMDocument class did.

Friday, May 7, 2010

SimpleDateFormat and Thread Safety

SimpleDateFormat
The Java class SimpleDateFormat converts Dates to Strings and vice versa. For example, the following code converts the String "04/15/2010" to a Date object and back again:
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
Date d = df.parse("04/15/2010");
String s = df.format(d);
"04/15/2010".equals(s); //true
This class is documented as not being thread-safe, but I decided to see for myself if this was true.

Thread-safety proof
I created a program that proves that SimpleDateFormat is not thread-safe. It creates X threads which run concurrently. Each thread generates Y random Dates, then formats each Date in two ways: using a local instance of SimpleDateFormat that no other thread has access to, and using a static instance of SimpleDateFormat which all threads use. The Strings created by the local instance are added to one List and the Strings created by the static instance are added to another List.

If everything is synchronized properly, then these two lists should be identical. But because SimpleDateFormat is not thread safe and all threads use a shared static instance, the lists do not always come out identical (I've found that around ten threads and ten dates-per-thread consistently produce different lists).

If the call to "staticDf.format()" is wrapped in a synchronized block, then the lists always come out identical, which shows that SimpleDateFormat needs to be manually synchronized and is therefore not thread-safe.

Usage
The following command will create ten threads, each of which will generate twenty dates:
java SimpleDateFormatThreadSafe 10 20

Source code
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Proves that SimpleDateFormat is indeed not thread safe (as documented in the
 * javadocs).
 * @author mangstadt
 */
public class SimpleDateFormatThreadSafe {
  private static final String format = "MM/dd/yy";
  private static final DateFormat staticDf = new SimpleDateFormat(format);
  private static int numThreads = 10;
  private static int numLoopsPerThread = 10;

  public static void main(String args[]) throws Exception {
    //get the arguments
    if (args.length > 0){
      numThreads = Integer.parseInt(args[0]);
      if (args.length > 1){
        numLoopsPerThread = Integer.parseInt(args[1]);
      }
    }

    //create the threads
    MyThread threads[] = new MyThread[numThreads];
    for (int i = 0; i < threads.length; ++i) {
      threads[i] = new MyThread();
    }

    //start the threads
    for (MyThread t : threads) {
      t.start();
    }

    //check the results
    boolean allIdentical = true;
    for (MyThread t : threads) {
      t.join();
      if (!t.localList.equals(t.staticList)){
        System.out.println(t.getName() + " lists are different:");
        System.out.println("local:  " + t.localList);
        System.out.println("static: " + t.staticList);
        System.out.println();
        allIdentical = false;
      }
    }
    if (allIdentical){
      System.out.println("All lists are identical.");
    }
  }

  private static class MyThread extends Thread {
    public final List localList = new ArrayList();
    public final List staticList = new ArrayList();

    @Override
    public void run() {
      DateFormat localDf = new SimpleDateFormat(format);
      for (int i = 0; i < numLoopsPerThread; ++i) {
        //create a random Date
        Calendar c = Calendar.getInstance();
        c.set(Calendar.MONTH, randInt(0, 12));
        c.set(Calendar.DATE, randInt(1, 21));
        c.set(Calendar.YEAR, randInt(1990, 2011));
        Date d = c.getTime();

        //add formatted dates to lists
        localList.add(localDf.format(d));
        //synchronized (staticDf){
        staticList.add(staticDf.format(d));
        //}
      }
    }
  }

  private static int randInt(int min, int max) {
    return (int) (Math.random() * (max - min) + min);
  }
}