<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5682413770770674096</id><updated>2012-02-01T23:24:28.542-05:00</updated><category term='web-services'/><category term='soap'/><category term='javascript'/><category term='java'/><category term='photography'/><category term='php'/><category term='web'/><category term='java php'/><category term='programming'/><category term='graphics'/><category term='music'/><category term='wsdl'/><category term='web-programming'/><category term='drm. mp3'/><category term='book'/><category term='php java'/><category term='mvc'/><category term='managment'/><category term='cms'/><category term='amazon'/><category term='software'/><category term='play'/><category term='javaee'/><category term='drupal'/><category term='gimp'/><category term='pear'/><category term='phar'/><category term='eclipse'/><category term='framework'/><category term='image'/><category term='review'/><category term='itunes'/><title type='text'>Mike's Software Development Blog</title><subtitle type='html'>A computer blog with a focus on everything software development related.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>72</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3491475432077592766</id><published>2012-01-28T14:58:00.000-05:00</published><updated>2012-01-28T14:58:12.491-05:00</updated><title type='text'>The Java Collections Framework - Queues</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-WFrUp_wcNFo/TyRPRw2-h0I/AAAAAAAAAdo/2sAiSJg3lFY/s1600/java.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="166" width="125" src="http://4.bp.blogspot.com/-WFrUp_wcNFo/TyRPRw2-h0I/AAAAAAAAAdo/2sAiSJg3lFY/s200/java.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://docs.oracle.com/javase/tutorial/collections/"&gt;Collections Framework&lt;/a&gt; is a group of classes that allow you to group objects together in various ways and perform operations against those groupings. It is contained within the core Java API, so you don't have to install anything extra to use it. Its features are so basic and powerful that pretty much every piece of software written in Java makes use of this framework in one way or another. The framework has been around since Java 1.2 (1998) and has been improved upon with every subsequent Java release. There are four major collection types: &lt;strong&gt;Lists, Sets, Queues, and Maps&lt;/strong&gt;.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Nzyw-TCI9XI/TyRPgeSa-FI/AAAAAAAAAd0/41ppmz6QKJA/s1600/diagram.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="66" width="200" src="http://2.bp.blogspot.com/-Nzyw-TCI9XI/TyRPgeSa-FI/AAAAAAAAAd0/41ppmz6QKJA/s200/diagram.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The four basic collection types (List, Set, and Queue all inherit from the Collection interface).&lt;/div&gt;&lt;hr /&gt;&lt;p&gt;A &lt;strong&gt;queue&lt;/strong&gt; is an ordered collection of elements in which only the "top" element can be accessed.  When the top element is removed, or &lt;strong&gt;popped&lt;/strong&gt;, the next element is revealed.  The order in which the queue's elements are arranged depends upon the type of queue.  Also, note that the term &lt;strong&gt;push&lt;/strong&gt; is used to refer to an element being added to a queue.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A &lt;strong&gt;FIFO&lt;/strong&gt; (first in, first out) queue orders its elements according to how recently they were added to the queue--elements that were pushed first will be popped first.&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;LIFO&lt;/strong&gt; (last in, first out) queue, also known as a &lt;strong&gt;stack&lt;/strong&gt;, is the opposite of a FIFO queue--elements that were pushed first are popped &lt;em&gt;last&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;There's also a &lt;strong&gt;priority queue&lt;/strong&gt;, in which each element has a priority associated with it.  The elements with the highest priorities are popped first.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A &lt;strong&gt;dequeue&lt;/strong&gt; (double-ended queue) allows the programmer to push and pop elements from the bottom of the queue as well as from the top.&lt;/p&gt;&lt;h1&gt;"Exception vs. special return value" convension&lt;/h1&gt;&lt;p&gt;The Java Collections Framework has a number of implementations for different types of queues (all inheriting from the &lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/Queue.html"&gt;&lt;code&gt;Queue&lt;/code&gt;&lt;/a&gt; interface).  They all follow a special convension: &lt;strong&gt;for each operation that you can perform on the queue, there are two methods&lt;/strong&gt;.  Each method does the same thing, but they differ in the way in which they handle edge cases.  One method will throw an exception, while the other will return a special value.  Here are some examples of these kinds of methods from the &lt;code&gt;Queue&lt;/code&gt; interface.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;add()&lt;/code&gt; - pushes an element on the queue, throwing an &lt;code&gt;IllegalStateException&lt;/code&gt; if the queue is full.&lt;/li&gt;&lt;li&gt;&lt;code&gt;offer()&lt;/code&gt; - pushes an element on the queue, returning &lt;code&gt;false&lt;/code&gt; if the queue is full.&lt;/li&gt;&lt;li&gt;&lt;code&gt;remove()&lt;/code&gt; - pops the next element off the queue, throwing a &lt;code&gt;NoSuchElementException&lt;/code&gt; if the queue is empty.&lt;/li&gt;&lt;li&gt;&lt;code&gt;poll()&lt;/code&gt; - pops the next element off the queue, returning &lt;code&gt;null&lt;/code&gt; if the queue is empty.&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Example&lt;/h1&gt;&lt;p&gt;Here's an example of how to program a queue in Java.  This program simulates a drive-thru window at a fast-food restaurant.  Customers place their orders at the drive-thru window and then the kitchen makes their food.  The kitchen makes the meals in the same order in which they were received, so it is a FIFO queue (if you are the first car in line, you will be the first to receive your order).&lt;/p&gt;&lt;p&gt;I use a &lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/BlockingQueue.html"&gt;&lt;code&gt;BlockingQueue&lt;/code&gt;&lt;/a&gt;, which extends the &lt;code&gt;Queue&lt;/code&gt; interface.  It provides the ability to block the method call if you try to pop from an empty queue or push to a full queue.  I use the &lt;code&gt;take()&lt;/code&gt; method to pop the next element off the queue.  If the queue is empty, then it waits until an element is pushed onto the queue before executing the next line of code.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;BlockingQueue&lt;/code&gt; imlpementation I use is the &lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/ArrayBlockingQueue.html"&gt;&lt;code&gt;ArrayBlockingQueue&lt;/code&gt;&lt;/a&gt;, which requires that a max queue size be defined.  In the case of my drive-thru simulator, this is the maximum number of cars that can fit into the driveway.  I use the &lt;code&gt;offer()&lt;/code&gt; method to push orders onto the queue.  If this method returns &lt;code&gt;false&lt;/code&gt; then it means that the driveway is full of cars (the queue is full).  The would-be customer then has to drive away and find someplace else to get their food (nothing is pushed to the queue).&lt;/p&gt;&lt;pre class="brush: java"&gt;import java.text.MessageFormat;&lt;br /&gt;import java.util.*&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Drive-thru simulator for a fast-food restaurant.&lt;br /&gt; * @author Mike Angstadt - http://www.mangst.com&lt;br /&gt; */&lt;br /&gt;public class DriveThru {&lt;br /&gt;  private static Random rand = new Random();&lt;br /&gt;&lt;br /&gt;  public static void main(String args[]) throws InterruptedException {&lt;br /&gt;    //the number of cars that can fit in the driveway&lt;br /&gt;    int drivewaySize = 5;&lt;br /&gt;&lt;br /&gt;    //the food orders are placed in a FIFO queue with a max capacity&lt;br /&gt;    BlockingQueue&amp;lt;String&amp;gt; orders = new ArrayBlockingQueue&amp;lt;String&amp;gt;(drivewaySize);&lt;br /&gt;&lt;br /&gt;    //the drive-thru window takes orders from the customers and pushes those orders onto the queue&lt;br /&gt;    DriveThruWindow driveThruWindow = new DriveThruWindow(orders);&lt;br /&gt;    driveThruWindow.start();&lt;br /&gt;&lt;br /&gt;    //the kitchen pops the orders off the queue&lt;br /&gt;    Kitchen kitchen = new Kitchen(orders);&lt;br /&gt;    kitchen.start();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static class Kitchen extends Thread {&lt;br /&gt;    private BlockingQueue&amp;lt;String&amp;gt; orders;&lt;br /&gt;&lt;br /&gt;    public Kitchen(BlockingQueue&amp;lt;String&amp;gt; orders) {&lt;br /&gt;      this.orders = orders;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void run() {&lt;br /&gt;      int orderNum = 0;&lt;br /&gt;      while (true) {&lt;br /&gt;        try {&lt;br /&gt;          //get the next order&lt;br /&gt;          //if the queue is empty, then wait until an element is pushed onto it&lt;br /&gt;          String order = orders.take();&lt;br /&gt;&lt;br /&gt;          //make the food&lt;br /&gt;          System.out.println("Order " + orderNum + ": Making order \"" + order + "\"...");&lt;br /&gt;          Thread.sleep(rand.nextInt(9000) + 1000); //sleep for 1-10 seconds&lt;br /&gt;          System.out.println("Order " + orderNum + ": Order \"" + order + "\" finished.");&lt;br /&gt;          orderNum++;&lt;br /&gt;        } catch (InterruptedException e) {&lt;br /&gt;          break;&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static class DriveThruWindow extends Thread {&lt;br /&gt;    private List&amp;lt;MenuItem&amp;gt; menu = new ArrayList&amp;lt;MenuItem&amp;gt;();&lt;br /&gt;    private int orderNum = 0;&lt;br /&gt;    private BlockingQueue&amp;lt;String&amp;gt; orders;&lt;br /&gt;&lt;br /&gt;    public DriveThruWindow(BlockingQueue&amp;lt;String&amp;gt; orders) {&lt;br /&gt;      this.orders = orders;&lt;br /&gt;&lt;br /&gt;      //create the menu&lt;br /&gt;      MenuItem menuItem = new MenuItem("Quarter-pounder", new String[] { "cheese", "tomatoes", "onions", "pickles" }, "{0} with {1}");&lt;br /&gt;      menu.add(menuItem);&lt;br /&gt;      menuItem = new MenuItem("coke", new String[] { "Small", "Medium", "Large" }, "{1} {0}");&lt;br /&gt;      menu.add(menuItem);&lt;br /&gt;      menuItem = new MenuItem("chicken nuggets", new String[] { "6", "10", "20" }, "{1}-piece {0}");&lt;br /&gt;      menu.add(menuItem);&lt;br /&gt;      menuItem = new MenuItem("Apple pie");&lt;br /&gt;      menu.add(menuItem);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void run() {&lt;br /&gt;      while (true) {&lt;br /&gt;        //wait for the next car&lt;br /&gt;        try {&lt;br /&gt;          Thread.sleep(rand.nextInt(6000) + 1000); //sleep for 1-7 seconds&lt;br /&gt;        } catch (InterruptedException e) {&lt;br /&gt;          break;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        //get the customer's order&lt;br /&gt;        int index = rand.nextInt(menu.size());&lt;br /&gt;        String order = menu.get(index).generate();&lt;br /&gt;&lt;br /&gt;        //push the order onto the queue&lt;br /&gt;        boolean inserted = orders.offer(order);&lt;br /&gt;        if (inserted) {&lt;br /&gt;          System.out.println("Order " + orderNum + ": Car placed order for \"" + order + "\".");&lt;br /&gt;          orderNum++;&lt;br /&gt;        } else {&lt;br /&gt;          System.out.println("Driveway full...customer lost!");&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static class MenuItem {&lt;br /&gt;    private final String name;&lt;br /&gt;    private final String[] modifiers;&lt;br /&gt;    private final String format;&lt;br /&gt;&lt;br /&gt;    public MenuItem(String name) {&lt;br /&gt;      this(name, null, null);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public MenuItem(String name, String[] modifiers, String format) {&lt;br /&gt;      this.name = name;&lt;br /&gt;      this.modifiers = modifiers;&lt;br /&gt;      this.format = format;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String generate() {&lt;br /&gt;      if (format == null) {&lt;br /&gt;        return name;&lt;br /&gt;      } else {&lt;br /&gt;        int modifier = rand.nextInt(modifiers.length);&lt;br /&gt;        return MessageFormat.format(format, name, modifiers[modifier]);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3491475432077592766?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3491475432077592766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3491475432077592766' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3491475432077592766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3491475432077592766'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/java-collections-framework-queues.html' title='The Java Collections Framework - Queues'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-WFrUp_wcNFo/TyRPRw2-h0I/AAAAAAAAAdo/2sAiSJg3lFY/s72-c/java.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8121530713682596737</id><published>2012-01-26T23:14:00.000-05:00</published><updated>2012-01-26T23:14:30.821-05:00</updated><title type='text'>The Java Collections Framework - Sets</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-JM2b7YPNrY8/TyIhOM4AHWI/AAAAAAAAAdQ/u1w6ExBKirc/s1600/java.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="166" width="125" src="http://1.bp.blogspot.com/-JM2b7YPNrY8/TyIhOM4AHWI/AAAAAAAAAdQ/u1w6ExBKirc/s200/java.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://docs.oracle.com/javase/tutorial/collections/"&gt;Collections Framework&lt;/a&gt; is a group of classes that allow you to group objects together in various ways and perform operations against those groupings. It is contained within the core Java API, so you don't have to install anything extra to use it. Its features are so basic and powerful that pretty much every piece of software written in Java makes use of this framework in one way or another. The framework has been around since Java 1.2 (1998) and has been improved upon with every subsequent Java release. There are four major collection types: &lt;strong&gt;Lists, Sets, Queues, and Maps&lt;/strong&gt;.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-7Ev2Rvv2WSQ/TyIkhzSEo2I/AAAAAAAAAdc/l61_PHEaFuI/s1600/diagram.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="66" width="200" src="http://4.bp.blogspot.com/-7Ev2Rvv2WSQ/TyIkhzSEo2I/AAAAAAAAAdc/l61_PHEaFuI/s200/diagram.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The four basic collection types (List, Set, and Queue all inherit from the Collection interface).&lt;/div&gt;&lt;hr /&gt;&lt;p&gt;Another type of collection in the Java Collections Framework is the &lt;strong&gt;set&lt;/strong&gt;.  &lt;strong&gt;A set is a collection of elements where duplicates are &lt;em&gt;not&lt;/em&gt; allowed&lt;/strong&gt;.  If the programmer tries to add a duplicate element, the set will see that the element already exists and nothing will be added.  &lt;strong&gt;Sets also do not allow random access&lt;/strong&gt; like lists do.  The only way to retrieve elements from a set is to iterate over all the elements using an &lt;code&gt;Iterator&lt;/code&gt; or a foreach loop (so, for example, you cannot directly get the "fourth" element of a set).&lt;/p&gt;&lt;h1&gt;Buckets&lt;/h1&gt;&lt;p&gt;Sets are really fast when it comes to determining whether an element exists in the set or not.  The way sets do this is by making heavy use of the &lt;code&gt;Object.equals()&lt;/code&gt; and &lt;code&gt;Object.hashCode()&lt;/code&gt; methods.  When an object is added to the set, the set uses the object's hash to find a &lt;strong&gt;bucket&lt;/strong&gt; to store the object in.  Later, when the set is asked if it contains a given object, the set uses the hash of the given object to find a bucket, and then iterates over each element in that bucket.  It calls the &lt;code&gt;equals&lt;/code&gt; method on each element in the bucket to determine if the given object is the same as any of the elements in the bucket.  &lt;strong&gt;This is a lot faster than a list because a list has to iterate over every single element in order to determine if it contains an object&lt;/strong&gt;.  Sets just have to iterate over the elements in a single bucket, which contains only a fraction of the total number of elements in the set.&lt;/p&gt;&lt;h1&gt;Importance of hashCode()&lt;/h1&gt;&lt;p&gt;However, the efficiency of the set's search operation relies heavily on the quality of the object's &lt;code&gt;hashCode()&lt;/code&gt; implementation.  If the implementation is good, then the set will spread all of its elements evenly amongst all buckets.  However, if the implementation is &lt;em&gt;not&lt;/em&gt; good, then the elements will &lt;em&gt;not&lt;/em&gt; be spread evenly amongst all buckets.  This will cause some buckets to be larger than others, which means they will take longer to search over and decrease the overall performance of the set.&lt;/p&gt;&lt;p&gt;Probably the worst possible &lt;code&gt;hashCode()&lt;/code&gt; implementation you could create is one that returns a hard-coded value.  It's a completely valid implementation, but it will cause all elements to be stored in a single bucket, making the set's search performance no better than that of a list.&lt;/p&gt;&lt;pre class="brush: java"&gt;public class BlogPost {&lt;br /&gt;  private String content;&lt;br /&gt;  private Date created;&lt;br /&gt;  private List&amp;lt;String&amp;gt; comments;&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  public int hashCode(){&lt;br /&gt;    //the worst-possible implementation&lt;br /&gt;    //DO NOT USE!!&lt;br /&gt;    return 1;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;What you want to do is create a &lt;code&gt;hashCode()&lt;/code&gt; implementation that will generate a wide range of values.  Luckily, if you use Eclipse (or probably any other IDE), you can have one generated for you.  In Eclipse, right-click on the code and go to "Source &gt; Generate hashCode() and equals()".  It will prompt you for what class fields you want to include in the hash calculation and then generate the proper code automatically.&lt;/p&gt;&lt;pre class="brush: java"&gt;public class BlogPost {&lt;br /&gt;  private String content;&lt;br /&gt;  private Date created;&lt;br /&gt;  private List&amp;lt;String&amp;gt; comments;&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  public int hashCode() {&lt;br /&gt;    //the implementation generated by Eclipse (much better)&lt;br /&gt;    final int prime = 31;&lt;br /&gt;    int result = 1;&lt;br /&gt;    result = prime * result + ((comments == null) ? 0 : comments.hashCode());&lt;br /&gt;    result = prime * result + ((content == null) ? 0 : content.hashCode());&lt;br /&gt;    result = prime * result + ((created == null) ? 0 : created.hashCode());&lt;br /&gt;    return result;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h1&gt;Relationship between hashCode() and equals()&lt;/h1&gt;&lt;p&gt;Note that Java expects the &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; methods to follow certain rules (read this three times to let it sink in):&lt;/p&gt;&lt;ol&gt;&lt;li&gt;If two objects are &lt;code&gt;equal&lt;/code&gt;, then they must have the same hash.&lt;/li&gt;&lt;li&gt;But if two objects have the same hash, &lt;em&gt;they are not necessarily equal&lt;/em&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;It is your responsibility as a programmer to follow these rules.  If you do not, then sets (as well as other collection types from the Collections Framework) will not function correctly.  The code that Eclipse generates complies with these rules.&lt;/p&gt;&lt;h1&gt;Example&lt;/h1&gt;&lt;p&gt;Here's a short code example of how to use a set in Java.&lt;/p&gt;&lt;pre class="brush: java"&gt;Set&amp;lt;String&amp;gt; users = new HashSet&amp;lt;String&amp;gt;();&lt;br /&gt;users.add("Mark");&lt;br /&gt;users.add("Steve");&lt;br /&gt;users.add("Mark");&lt;br /&gt;users.add("Kelly");&lt;br /&gt;System.out.println(users.contains("mark"));&lt;br /&gt;System.out.println(users.contains("Mark"));&lt;br /&gt;for (String user : users){&lt;br /&gt;  System.out.println(user);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;On line 1, I create the &lt;code&gt;Set&lt;/code&gt; object.  I'm assigning the object to a variable of type &lt;code&gt;Set&lt;/code&gt; instead of type &lt;code&gt;HashSet&lt;/code&gt; so that I can more easily change implementations if I ever need to in the future.&lt;/p&gt;&lt;p&gt;On lines 2-5, I add elements to the set.  On line 4, I try adding a duplicate element, but nothing will be added because sets do not allow duplicate elements.  No exception will be thrown or anything--the &lt;code&gt;add()&lt;/code&gt; method will just return "false" instead of "true".&lt;/p&gt;&lt;p&gt;Line 6 will print "false" because the String "mark" is not the same as the string "Mark".  Line 7 will print "true".&lt;/p&gt;&lt;p&gt;On lines 8-10, I iterate over the entire set, printing each user name.  This is the only way to retrieve elements from a set.&lt;/p&gt;&lt;h1&gt;Set Implementations&lt;/h1&gt;&lt;p&gt;There are three implementations of the &lt;code&gt;Set&lt;/code&gt; interface.  They differ in the way in which they iterate over their elements.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html"&gt;&lt;code&gt;HashSet&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; - The most basic set implementation, iterates over elements in no particular order.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashSet.html"&gt;&lt;code&gt;LinkedHashSet&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; - Iterates over elements in the order in which they were added to the set.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/TreeSet.html"&gt;&lt;code&gt;TreeSet&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; - Iterates over elements in their sorted order.  The way in which the elements are sorted can be defined by having the elements implement the &lt;code&gt;Comparable&lt;/code&gt; interface, or by passing in an implementation of the &lt;code&gt;Comparator&lt;/code&gt; interface to the &lt;code&gt;TreeSet&lt;/code&gt; constructor.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8121530713682596737?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8121530713682596737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8121530713682596737' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8121530713682596737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8121530713682596737'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/java-collections-framework-sets.html' title='The Java Collections Framework - Sets'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-JM2b7YPNrY8/TyIhOM4AHWI/AAAAAAAAAdQ/u1w6ExBKirc/s72-c/java.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-273413687058521656</id><published>2012-01-23T08:33:00.000-05:00</published><updated>2012-01-23T08:33:39.701-05:00</updated><title type='text'>The Java Collections Framework - Lists</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-TXj1gjG93_Q/Tx1hVDq_uPI/AAAAAAAAAdE/cb72Csd7HwU/s1600/java.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="166" width="125" src="http://3.bp.blogspot.com/-TXj1gjG93_Q/Tx1hVDq_uPI/AAAAAAAAAdE/cb72Csd7HwU/s200/java.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://docs.oracle.com/javase/tutorial/collections/"&gt;Collections Framework&lt;/a&gt; is a group of classes that allow you to group objects together in various ways and perform operations against those groupings.  It is contained within the core Java API, so you don't have to install anything extra to use it.  Its features are so basic and powerful that pretty much every piece of software written in Java makes use of this framework in one way or another.  The framework has been around since Java 1.2 (1998) and has been improved upon with every subsequent Java release.  There are four major collection "types": &lt;strong&gt;Lists, Sets, Queues, and Maps&lt;/strong&gt;.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-DmgjUxm8kt0/TxxXkIl_vtI/AAAAAAAAAcs/F-Z5tP3RDtU/s1600/diagram3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="72" width="200" src="http://2.bp.blogspot.com/-DmgjUxm8kt0/TxxXkIl_vtI/AAAAAAAAAcs/F-Z5tP3RDtU/s200/diagram3.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The four basic collection types (List, Set, and Queue all inherit from the Collection interface).&lt;/div&gt;&lt;hr /&gt;&lt;p&gt;In this blog post, I'll discuss the &lt;strong&gt;List&lt;/strong&gt; type.  &lt;strong&gt;Lists are defined as being an ordered collection of elements where duplicate elements are allowed&lt;/strong&gt;.  Probably the most used list implementation (and probably the most used class in the entire Collections Framework) is the &lt;a href="http://download.oracle.com/javase/7/docs/api/java/util/ArrayList.html"&gt;&lt;code&gt;ArrayList&lt;/code&gt;&lt;/a&gt; class.  This class is meant to behave just like an array, with the added ability of being able to add, insert, and remove elements (which cannot be done with normal arrays).&lt;/p&gt;&lt;pre class="brush: java"&gt;List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;names.add("Mark");&lt;br /&gt;names.add("Steve");&lt;br /&gt;names.add("Jill");&lt;br /&gt;names.remove(1);&lt;br /&gt;names.remove("Jill");&lt;br /&gt;names.add(0, "Zach");&lt;br /&gt;Collections.sort(names);&lt;br /&gt;System.out.println(names);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;On line 1, I create a new instance of an &lt;code&gt;ArrayList&lt;/code&gt;.  Using generics, I specify that the list will only contain &lt;code&gt;String&lt;/code&gt; objects (generics support was added to the Collections Framework in Java 1.5).  If I try to add anything other than a &lt;code&gt;String&lt;/code&gt; to my &lt;code&gt;ArrayList&lt;/code&gt;, a compilation error will be thrown.&lt;/p&gt;&lt;p&gt;Also notice how I'm assigning my &lt;code&gt;ArrayList&lt;/code&gt; object to a variable of type &lt;a href="http://download.oracle.com/javase/7/docs/api/java/util/List.html"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt; instead of type &lt;code&gt;ArrayList&lt;/code&gt;.  &lt;code&gt;List&lt;/code&gt; is an interface that &lt;code&gt;ArrayList&lt;/code&gt; implements.  The reason for doing this is that there are multiple implementations of the &lt;code&gt;List&lt;/code&gt; interface.  By assigning my &lt;code&gt;ArrayList&lt;/code&gt; to a variable of type &lt;code&gt;List&lt;/code&gt;, I'm making my code more flexible so that it's not tied to a specific &lt;code&gt;List&lt;/code&gt; implementation.  The only thing the code cares about is that the &lt;code&gt;List&lt;/code&gt; variable satisfies the definition of what a "list" is supposed to be within the Collections Framework (an ordered collection where duplicates are allowed).  It is good practice to apply this technique throughout the Collections Framework to maximize code flexibility.&lt;/p&gt;&lt;p&gt;On lines 2-4, I populate my list by adding some elements to it.  The &lt;code&gt;add()&lt;/code&gt; method appends the element to the end of the list.&lt;/p&gt;&lt;p&gt;On line 5, I remove an element by specifying the element index (&lt;code&gt;Lists&lt;/code&gt; are 0-based just like normal arrays, so "0" corresponds to the first element, "1" to the second, etc).  This will remove the "Steve" element.  Then, on line 6, I remove an element by passing in a &lt;code&gt;String&lt;/code&gt; object.  When an element is removed in this way, it iterates over each element in the list, using the object's &lt;code&gt;equals()&lt;/code&gt; method to determine which element to remove.  If it doesn't find any such element, nothing is removed.&lt;/p&gt;&lt;p&gt;On line 6 of the code sample, I insert an element into the &lt;code&gt;List&lt;/code&gt; at a specific index.  The index I specify is "0", which means it will be inserted at the beginning of the list.&lt;/p&gt;&lt;p&gt;Then, on line 7, I sort the list so that the names are ordered alphabetically.  This method requires that the class of the elements in the list implement the &lt;a href="http://download.oracle.com/javase/7/docs/api/java/util/Comparable.html"&gt;&lt;code&gt;Comparable&lt;/code&gt;&lt;/a&gt; interface, which defines how two objects of that class are compared against each other when they are sorted.  The &lt;code&gt;String&lt;/code&gt; class already implements this interface, defining that Strings be sorted alphabetically.  If you want to sort the list differently, you can pass your own implementation of the &lt;a href="http://download.oracle.com/javase/7/docs/api/java/util/Comparator.html"&gt;&lt;code&gt;Comparator&lt;/code&gt;&lt;/a&gt; interface as a second argument of the &lt;code&gt;sort()&lt;/code&gt; method.  For example, to sort the strings by length, you could write:&lt;/p&gt;&lt;pre class="brush: java"&gt;Collections.sort(names, new Comparator&amp;lt;String&amp;gt;(){&lt;br /&gt;  @Override&lt;br /&gt;  public int compareTo(String element1, String element2){&lt;br /&gt;    //sort Strings by length&lt;br /&gt;    if (element1.length() &amp;lt; element2.length(){&lt;br /&gt;      return -1; //"element1" should come before "element2"&lt;br /&gt;    } else if (element1.length() &amp;gt; element2.length()){&lt;br /&gt;      return 1; //"element1" should come after "element2"&lt;br /&gt;    } else {&lt;br /&gt;      return 0; //"element1" and "element2" are equal&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Finally, on line 8, I print the list out so I can see what it looks like.  Passing an object into the &lt;code&gt;System.out.println()&lt;/code&gt; method will print the return value of that object's &lt;code&gt;toString()&lt;/code&gt; method.  The list's &lt;code&gt;toString()&lt;/code&gt; method generates a string representation of the list by calling the &lt;code&gt;toString()&lt;/code&gt; method of every list element.  It surrounds the entire list in brackets and separates each element with a comma.&lt;/p&gt;&lt;pre&gt;&lt;br /&gt;[Mark, Zach]&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-273413687058521656?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/273413687058521656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=273413687058521656' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/273413687058521656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/273413687058521656'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/java-collections-framework-lists.html' title='The Java Collections Framework - Lists'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-TXj1gjG93_Q/Tx1hVDq_uPI/AAAAAAAAAdE/cb72Csd7HwU/s72-c/java.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2104031853685247621</id><published>2012-01-14T13:50:00.000-05:00</published><updated>2012-01-14T13:50:42.550-05:00</updated><title type='text'>Adding a StatCounter tracker to your website</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-b3Vwxomxuro/TxG7ABWjrnI/AAAAAAAAAao/9TBZ7z0JrEk/s1600/statcounter-logo.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="32" width="200" src="http://3.bp.blogspot.com/-b3Vwxomxuro/TxG7ABWjrnI/AAAAAAAAAao/9TBZ7z0JrEk/s200/statcounter-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.statcounter.com"&gt;StatCounter&lt;/a&gt; is a free service that tracks the number of hits your website receives.  It records a wealth of information about your visitors, including the type of browser they use, their screen resolution, and their geographic location.  It can also tell you whether the user came to your website from a search engine, showing you the exact search query that the user entered.&lt;/p&gt;&lt;h1&gt;Adding the tracking script to your site&lt;/h1&gt;&lt;p&gt;After creating a StatCounter account, click the "Add Project" link in the menubar at the top of the screen.  Fill in all the information and select "Invisible tracking" for the counter type on the right side of the page.  This option will make the counter invisible to visitors of your webpage.  I use this option because it makes my website look more professional.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-cxtqHVf4P94/TxG56lVF3yI/AAAAAAAAAaE/P4LakonwYjM/s1600/2-invisible-tracking.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="125" width="200" src="http://3.bp.blogspot.com/-cxtqHVf4P94/TxG56lVF3yI/AAAAAAAAAaE/P4LakonwYjM/s200/2-invisible-tracking.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The "Invisible tracking" option will make your counter invisible.&lt;/div&gt;&lt;p&gt;It then brings you to a page that contains a long list of different installation instructions for a variety of platforms, frameworks, blogs, and hosting services.  Choose the one that best fits your particular setup.  If you have a plain, "vanilla" website that doesn't really use anything extra, then click the "Default Installation Guide" link on the right.  That's what I'll be doing.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-gvN4XKrtxPg/TxG6NbAyyGI/AAAAAAAAAaQ/5wUg6yII1pI/s1600/3-default-guide.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="125" width="200" src="http://3.bp.blogspot.com/-gvN4XKrtxPg/TxG6NbAyyGI/AAAAAAAAAaQ/5wUg6yII1pI/s200/3-default-guide.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;Use the default installation guide if none of the other guides apply to your setup.&lt;/div&gt;&lt;p&gt;The next page displays the code that you'll need to add to your website.  The code inside the "Standard" tab is the best overall choice.  If you're a stickler for writing websites in well-formed XHTML, use the code in the "Standard (xhtml)" tab.  If you don't want to use Javascript on your website, use the code in the "Basic" tab.  And if you want your code to be both XHTML-compliant and Javascript-free, use the code in the "Basic (xhtml)" tab.  Note that &lt;strong&gt;the "Basic" versions do not record as much information about the visitor&lt;/strong&gt;.  Javascript has to be used in order to get certain information, like screen resolution and geographic location.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-CBc50R0E4XE/TxG6dqONaTI/AAAAAAAAAac/cjzdamuPjeQ/s1600/4-code.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="125" width="200" src="http://3.bp.blogspot.com/-CBc50R0E4XE/TxG6dqONaTI/AAAAAAAAAac/cjzdamuPjeQ/s200/4-code.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The "Standard" option is the recommended choice.&lt;/div&gt;&lt;p&gt;I'm going to use the "Standard" code.  Copy and paste the code right before the &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag on your website.  By placing the code here, the StatCounter script won't run until the entire page is loaded.  If it were to be placed "higher up" in the HTML, like in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag, then the rest of the page wouldn't load until the StatCounter script finishes downloading and executing.  Sometimes, this can take a few seconds, so it's best to put the script at the end of the page so it doesn't keep the rest of the page from loading.&lt;/p&gt;&lt;p&gt;Once you've added the code to your website, you can check to make sure it works by clicking the "Check Installation" button at the bottom of the installation guide page.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-AsS9OKjLPN8/TxHNQR7HbfI/AAAAAAAAAa0/1I9_9Bmbp7o/s1600/10-check.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="125" width="200" src="http://4.bp.blogspot.com/-AsS9OKjLPN8/TxHNQR7HbfI/AAAAAAAAAa0/1I9_9Bmbp7o/s200/10-check.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;StatCounter can check to make sure you've installed the code correctly.&lt;/div&gt;&lt;h1&gt;Creating a blocking cookie&lt;/h1&gt;&lt;p&gt;Lastly, you're going to want to add a &lt;strong&gt;blocking cookie&lt;/strong&gt; to your computer.  This cookie will prevent your own visits to your website from being tracked so that the project statistics aren't skewed by your own activity.  Go to the main "Projects" page and then click the "Blocking Cookie" link in the menu bar at the top of the screen.  Then, click the "Create Blocking Cookie" button.  This will create a blocking cookie that applies to all of your StatCounter projects.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-aaTDQ8Z5Yx8/TxHNdWVULzI/AAAAAAAAAbA/ewiJfI6DKYY/s1600/9-blocking.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="125" width="200" src="http://1.bp.blogspot.com/-aaTDQ8Z5Yx8/TxHNdWVULzI/AAAAAAAAAbA/ewiJfI6DKYY/s200/9-blocking.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;A blocking cookie will prevent your own visits from being recorded.&lt;/div&gt;&lt;p&gt;Remember that if you have multiple browsers on your computer, you'll have to do this for each browser, because each browser has its own set of cookies.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2104031853685247621?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2104031853685247621/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2104031853685247621' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2104031853685247621'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2104031853685247621'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/adding-statcounter-tracker-to-your.html' title='Adding a StatCounter tracker to your website'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-b3Vwxomxuro/TxG7ABWjrnI/AAAAAAAAAao/9TBZ7z0JrEk/s72-c/statcounter-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2424686137953908268</id><published>2012-01-08T14:18:00.000-05:00</published><updated>2012-01-12T19:03:36.280-05:00</updated><title type='text'>GWT Image Bundles</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/--wsZ1M-1uKU/TwnsQ-ANNEI/AAAAAAAAAZ4/1GMa3Jfhbuw/s1600/gwt-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="92" width="100" src="http://4.bp.blogspot.com/--wsZ1M-1uKU/TwnsQ-ANNEI/AAAAAAAAAZ4/1GMa3Jfhbuw/s200/gwt-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Often times, websites contain a number of small, icon-sized images.  These images don't take long to download individually, but collectively, they can slow down the load time of a webpage because one HTTP request has to be made for every image.  Plus, the HTTP protocol states that browsers can only make two simultaneous requests per domain at a time.  This means that no matter how fast your Internet connection is, only two images can be downloaded at a time.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;CSS image sprites&lt;/strong&gt; help to get around this bottleneck.  An image sprite is a single image that has multiple images inside of it.  CSS styling is used to "cut out" an individual image from the image sprite and display it on the page.  By consolidating all the images into a single image, the browser only has to make one HTTP request, as opposed to making multiple HTTP requests for each image.  This decreases the load time of the page.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-z883np4B3_M/TwnpAbpsW7I/AAAAAAAAAZU/7YeiBs00e5c/s1600/amazon-image-sprite.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="163" width="200" src="http://1.bp.blogspot.com/-z883np4B3_M/TwnpAbpsW7I/AAAAAAAAAZU/7YeiBs00e5c/s200/amazon-image-sprite.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;An image sprite from &lt;a href="http://www.amazon.com"&gt;Amazon&lt;/a&gt;.&lt;/div&gt;&lt;p&gt;GWT gives you the ability to combine your application's icons into an image sprite automatically.  In this tutorial, I'm going to show you how this is done by adding some images to my Twitter Search application.  I will be adding:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;An icon for the "Search" button.&lt;/li&gt;&lt;li&gt;An icon for the "Privacy Policy" button.&lt;/li&gt;&lt;li&gt;An icon to display along with error messages.&lt;/li&gt;&lt;/ol&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-kTcuFL9jICM/TwnpcQl7x4I/AAAAAAAAAZg/yaAXF0JWdLU/s1600/app.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="112" width="200" src="http://4.bp.blogspot.com/-kTcuFL9jICM/TwnpcQl7x4I/AAAAAAAAAZg/yaAXF0JWdLU/s200/app.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;The TwitterSearch application with the new images.&lt;/div&gt;&lt;h1&gt;Creating an image bundle interface&lt;/h1&gt;&lt;p&gt;First, create an interface that extends the &lt;code&gt;ClientBundle&lt;/code&gt; interface.  This is called an &lt;strong&gt;image bundle&lt;/strong&gt; and it's what we'll use to access the individual images from our image sprite.&lt;/p&gt;&lt;pre class="brush: java"&gt;package com.acme.twittersearch.client;&lt;br /&gt;&lt;br /&gt;import com.google.gwt.resources.client.ClientBundle;&lt;br /&gt;import com.google.gwt.resources.client.ImageResource;&lt;br /&gt;&lt;br /&gt;public interface Images extends ClientBundle {&lt;br /&gt;  @Source("search.png")&lt;br /&gt;  ImageResource search();&lt;br /&gt;&lt;br /&gt;  @Source("error.png")&lt;br /&gt;  ImageResource error();&lt;br /&gt;&lt;br /&gt;  @Source("document.png")&lt;br /&gt;  ImageResource privacyPolicy();&lt;br /&gt;&lt;br /&gt;  @Source("loading.gif")&lt;br /&gt;  ImageResource loading();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The image bundle contains one method per image.  Each method must return a &lt;code&gt;ImageResource&lt;/code&gt; object and can be given any name.  Each method is also annotated with the path to the image file.  This path is &lt;strong&gt;relative to the location of the image bundle interface&lt;/strong&gt;.  Since my image bundle is in the &lt;code&gt;com.acme.twittersearch.client&lt;/code&gt; package, this means that all my images are there too (the images cannot be in the &lt;code&gt;war&lt;/code&gt; directory, they have to be inside the classpath, alongside the source code).  The GWT Eclipse plugin will tell you if the path to an image is incorrect by throwing a Java compilation error.&lt;/p&gt;&lt;p&gt;I also add the loading animation image to my image bundle.  Normally, animated GIFs cannot be added to image sprites, but GWT still allows you to add them to image bundles.  They won't be added to the image sprite that GWT generates, but programmatically, they are treated as if they were part of the image sprite.&lt;/p&gt;&lt;h1&gt;Adding the images to the application&lt;/h1&gt;&lt;p&gt;Now that I've created my image bundle, I can add the images to my application.  First, I'll create an instance of the image bundle via deferred binding:&lt;/p&gt;&lt;pre class="brush: java"&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;  private final Images images = GWT.create(Images.class);&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Instances of &lt;code&gt;ImageResource&lt;/code&gt; can be passed into the constructor of the &lt;code&gt;Image&lt;/code&gt; class, so that's what I'll do for the error and loading images.&lt;/p&gt;&lt;pre class="brush: java"&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;  [...]&lt;br /&gt;  private Image errorImage;&lt;br /&gt;  private Image loadingImage;&lt;br /&gt;  [...]&lt;br /&gt;&lt;br /&gt;  private void createWidgets() {&lt;br /&gt;    [...]&lt;br /&gt;    errorImage = new Image(images.error());&lt;br /&gt;    loadingImage = new Image(images.loading());&lt;br /&gt;    [...]&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The "search" and "privacy policy" images are a little more complicated because of the way the &lt;code&gt;Button&lt;/code&gt; widget works.  After creating an &lt;code&gt;Image&lt;/code&gt; object for each icon, I generate raw HTML code, which will be passed into the &lt;code&gt;Button&lt;/code&gt; constructor (note: calling the &lt;code&gt;toString()&lt;/code&gt; method on any widget object will return its HTML code).&lt;/p&gt;&lt;pre class="brush: java"&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;  [...]&lt;br /&gt;  private Button privacyPolicyButton;&lt;br /&gt;  private Button searchButton;&lt;br /&gt;  [...]&lt;br /&gt;&lt;br /&gt;  private void createWidgets() {&lt;br /&gt;    [...]&lt;br /&gt;&lt;br /&gt;    Image icon = new Image(images.search());&lt;br /&gt;    SafeHtmlBuilder builder = new SafeHtmlBuilder();&lt;br /&gt;    builder.appendHtmlConstant(icon.toString());&lt;br /&gt;    builder.appendEscaped(" Search");&lt;br /&gt;    searchButton = new Button(builder.toSafeHtml());&lt;br /&gt;    searchButton.addClickHandler(new ClickHandler() {&lt;br /&gt;      [...]&lt;br /&gt;    });&lt;br /&gt;&lt;br /&gt;    icon = new Image(images.privacyPolicy());&lt;br /&gt;    builder = new SafeHtmlBuilder();&lt;br /&gt;    builder.appendHtmlConstant(icon.toString());&lt;br /&gt;    builder.appendEscaped(" Privacy Policy");&lt;br /&gt;    privacyPolicyButton = new Button(builder.toSafeHtml());&lt;br /&gt;    privacyPolicyButton.addClickHandler(new ClickHandler() {&lt;br /&gt;      [...]&lt;br /&gt;    });&lt;br /&gt;&lt;br /&gt;    [...]&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And that's really all there is to it!  GWT will create the actual image sprite for you.  To see what the image sprite looks like, compile the GWT application and then navigate to the &lt;code&gt;war/twittersearch&lt;/code&gt; folder.  The image sprite will be somewhere inside this folder (it will have a crazy name like "F0B5712E038983254B46645D77476DA7.cache.png").&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-jqF58K7_lIM/TwnqAo_t1gI/AAAAAAAAAZs/e5ydb4rpzuw/s1600/image-bundle.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="32" width="48" src="http://3.bp.blogspot.com/-jqF58K7_lIM/TwnqAo_t1gI/AAAAAAAAAZs/e5ydb4rpzuw/s200/image-bundle.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="caption" style="text-align:center; font-style:italic"&gt;GWT automatically creates an image sprite from the images in our image bundle.&lt;/div&gt;&lt;p&gt;More information on GWT image bundles can be found in the &lt;a href="http://code.google.com/webtoolkit/doc/latest/DevGuideUiImageBundles.html"&gt;GWT Developer Guide&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The complete source code from this tutorial can be downloaded &lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch-ImageBundles.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;b&gt;CORRECTION (1/12/2011)&lt;/b&gt;: Even though GWT generates an image sprite from an image bundle when compiled, it actually only uses the image sprite for IE 6 and possibly IE 7 (thanks &lt;a href="http://www.blogger.com/profile/12195257853404325396"&gt;Boris&lt;/a&gt;).  What GWT really does is encode the images as &lt;b&gt;data URIs&lt;/b&gt;.  A data URI puts the actual image data directly inside the HTML page, instead of referencing the image via a URL like normal (I wrote a brief &lt;a href="http://mangstacular.blogspot.com/2010/04/data-uri.html"&gt;blog post&lt;/a&gt; on data URIs a while back and also wrote a &lt;a href="http://www.mangst.com/projects/data-uri"&gt;data URI generation tool&lt;/a&gt;).  I'm not sure why GWT does this, but I'm sure it has its reasons.  You should avoid using data URIs for large images--since the image data is embedded directly inside the page, the browser can't cache it, so the image basically has to be downloaded every time the page is loaded.  &lt;i&gt;--Mike&lt;/i&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2424686137953908268?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2424686137953908268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2424686137953908268' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2424686137953908268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2424686137953908268'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/gwt-image-bundles.html' title='GWT Image Bundles'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/--wsZ1M-1uKU/TwnsQ-ANNEI/AAAAAAAAAZ4/1GMa3Jfhbuw/s72-c/gwt-logo.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7560815972710014326</id><published>2012-01-02T19:24:00.000-05:00</published><updated>2012-01-03T20:52:41.890-05:00</updated><title type='text'>Custom border images in the Highslide Editor</title><content type='html'>&lt;p&gt;&lt;a href="http://highslide.com/"&gt;Highslide&lt;/a&gt; is a Javascript library used for creating "lightboxes", or Javascript-based popups, for your website.  The Highslide website has an &lt;a href="http://highslide.com/editor/"&gt;editor&lt;/a&gt; that you can use to customize the look and feel of your lightbox.  It's actually pretty powerful, allowing you to customize everything from the popup animation to the font size of the image caption.  Once you've customized the lightbox to your liking, you can view the HTML, CSS, and Javascript code required to make it work on your own website.&lt;/p&gt;&lt;p&gt;However, if your lightbox has the "graphic outline" setting enabled (this setting can be accessed by clicking the "Border and outline" button on the  "Style" tab), then the &lt;code&gt;hs.outlineType&lt;/code&gt; configuration variable in the Javascript code will be set to &lt;code&gt;custom&lt;/code&gt;.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-h_mYV92c5D4/TwJF3t0oEXI/AAAAAAAAAYk/2mru2cwNJGo/s1600/js-window.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="135" width="200" src="http://3.bp.blogspot.com/-h_mYV92c5D4/TwJF3t0oEXI/AAAAAAAAAYk/2mru2cwNJGo/s200/js-window.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;i&gt;The "custom" outline type will not work with your own website.&lt;/i&gt;&lt;/center&gt;&lt;p&gt;This is not an outline type that comes with the Highslide installation, so if you try using this on your own website, it won't work.  What the Highslide Editor does behind the scenes, is make an AJAX call to a &lt;code&gt;custom.php&lt;/code&gt; script, which dynamically generates a border image based on the settings you've defined in the editor.  As far as I can tell, the editor doesn't provide a way for you to download this image, so you have to get the URL to the &lt;code&gt;custom.php&lt;/code&gt; script yourself, and download the image that way.  Here's how to do that using &lt;a href="https://www.google.com/chrome"&gt;Chrome&lt;/a&gt;:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Click the Wrench button, then go to &lt;strong&gt;"Tools &gt; Developer Tools"&lt;/strong&gt;.  This opens a window that shows additional information about the current browser tab.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-YbAGOVENbMo/TwJFnIQxF4I/AAAAAAAAAYM/8KuNLmkOarU/s1600/open-dev-tools.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="130" width="200" src="http://2.bp.blogspot.com/-YbAGOVENbMo/TwJFnIQxF4I/AAAAAAAAAYM/8KuNLmkOarU/s200/open-dev-tools.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;i&gt;Opening the "Developer Tools" window in Chrome&lt;/i&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;"Network"&lt;/strong&gt; button at the top of the screen.  This screen displays all HTTP requests that the browser tab makes, including the images that it downloads and the AJAX requests that it makes.  When you first open this screen, it will be blank.  But if you refresh the Highslide Editor webpage, the Network screen will flood with all the requests the browser has to make in order to load the page.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-itv3IY4T1zM/TwJGE1JPWfI/AAAAAAAAAYw/X1vYo2t3zlc/s1600/network.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="160" width="200" src="http://3.bp.blogspot.com/-itv3IY4T1zM/TwJGE1JPWfI/AAAAAAAAAYw/X1vYo2t3zlc/s200/network.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;i&gt;The "Network" screen shows all the HTTP requests that were made.&lt;/i&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Now, &lt;strong&gt;click the sample image in the Highslide Editor&lt;/strong&gt; so that it expands into a popup.  This causes the browser to make an AJAX request to the &lt;code&gt;custom.php&lt;/code&gt; script (for some reason, it makes the same request twice).&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-YMMUDa2wup0/TwJG14SjAFI/AAAAAAAAAY8/BLguwrszCpk/s1600/custom-request.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="94" width="200" src="http://3.bp.blogspot.com/-YMMUDa2wup0/TwJG14SjAFI/AAAAAAAAAY8/BLguwrszCpk/s200/custom-request.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;i&gt;An AJAX request to the &lt;code&gt;custom.php&lt;/code&gt; script is made when the sample image is clicked.&lt;/i&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Right-click on the &lt;code&gt;custom.php&lt;/code&gt; request in the Network screen and select "Open Link In New Tab".&lt;/strong&gt;  This will open the border image in a new browser tab, allowing you to save it to your local Highslide installation.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-CHwELO-d1v0/TwJHIwagb1I/AAAAAAAAAZI/gAd7ZMkdp8s/s1600/open-new-tab.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="161" width="200" src="http://4.bp.blogspot.com/-CHwELO-d1v0/TwJHIwagb1I/AAAAAAAAAZI/gAd7ZMkdp8s/s200/open-new-tab.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;i&gt;Load the image in a new tab.&lt;/i&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Finally, &lt;strong&gt;save the border image to the "graphics/outlines" folder&lt;/strong&gt; of your Highslide installation.  Whatever name you give the file (minus the file extension) will be what you'll use in the &lt;code&gt;hs.outlineType&lt;/code&gt; setting.  For example:&lt;/p&gt;&lt;pre class="brush: js"&gt;//uses the "graphics/outlines/rounded-corners.png" file&lt;br /&gt;hs.outlineType = 'rounded-corners';&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7560815972710014326?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7560815972710014326/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7560815972710014326' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7560815972710014326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7560815972710014326'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/custom-border-images-in-highslide.html' title='Custom border images in the Highslide Editor'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-h_mYV92c5D4/TwJF3t0oEXI/AAAAAAAAAYk/2mru2cwNJGo/s72-c/js-window.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6531158645950936199</id><published>2012-01-01T22:08:00.000-05:00</published><updated>2012-01-01T22:14:53.425-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gimp'/><category scheme='http://www.blogger.com/atom/ns#' term='image'/><category scheme='http://www.blogger.com/atom/ns#' term='graphics'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Making an image transparent with GIMP</title><content type='html'>&lt;p&gt;I'm not a graphic designer, so if I need an image for my website, like a loading animation or an "error" icon, I'll often just do a Google image search.  One quality that I look for in images is that they have transparent backgrounds.  Images with transparent backgrounds tend to look nicer because they allow the background color or background image of the website to show through the image.  Images without such backgrounds are surrounded by an ugly white square.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-eijIQrdKZCo/TwEZUIwNrZI/AAAAAAAAAWs/RuWiG_6T1RA/s1600/without-trans.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="174" width="200" src="http://1.bp.blogspot.com/-eijIQrdKZCo/TwEZUIwNrZI/AAAAAAAAAWs/RuWiG_6T1RA/s200/without-trans.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;An image without a transparent background.&lt;/em&gt;&lt;/center&gt;&lt;p&gt;However, it is possible to add transparency to a non-transparent image.  I'll show you how to do this using the free, open-source &lt;a href="http://www.gimp.org"&gt;GIMP&lt;/a&gt; image editor.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;After opening the image, the first thing to do is add an alpha channel.  This is a special layer in the image that defines exactly where the image is transparent.  &lt;strong&gt;Go to "Layer &gt; Transparency &gt; Add Alpha Channel"&lt;/strong&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Lho8Ku9Fvm0/TwEe1-1ZkuI/AAAAAAAAAX0/dP68qbv3ic4/s1600/add-alpha-channel.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="110" width="200" src="http://4.bp.blogspot.com/-Lho8Ku9Fvm0/TwEe1-1ZkuI/AAAAAAAAAX0/dP68qbv3ic4/s200/add-alpha-channel.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;Adding an alpha channel.&lt;/em&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Next, select the parts of the image that you want to make transparent.  This can be done using the &lt;strong&gt;Fuzzy Select Tool&lt;/strong&gt;.  This tool will select all parts of the image that are similar in color to the part of the image that you click on.  You can increase or decrease the sensitivity of this tool by adjusting the Threshold setting (this is helpful if the background is similar in color to the foreground).&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-a1G8Z3pOCrI/TwEbIw8ApjI/AAAAAAAAAXE/GAvfAJ8lwt0/s1600/fuzzy-select.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="174" width="200" src="http://3.bp.blogspot.com/-a1G8Z3pOCrI/TwEbIw8ApjI/AAAAAAAAAXE/GAvfAJ8lwt0/s200/fuzzy-select.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;The Fuzzy Select tool&lt;/em&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Then, go to &lt;strong&gt;"Edit &gt; Clear"&lt;/strong&gt;.  This will delete everything that you've selected with the Fuzzy Select Tool and expose the transparency layer.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-CL5vT8U0MO8/TwEhBkO4nZI/AAAAAAAAAYA/WaGL6KnuXAc/s1600/clear.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="200" width="169" src="http://2.bp.blogspot.com/-CL5vT8U0MO8/TwEhBkO4nZI/AAAAAAAAAYA/WaGL6KnuXAc/s200/clear.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;The image after the background was cleared.&lt;/em&gt;&lt;/center&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;You're now ready to save your changes&lt;/strong&gt;.  If the image is a JPEG, you'll have to save it in a different format because &lt;strong&gt;JPEGs do not support transparency&lt;/strong&gt;.  PNG supports all levels of transparency, so, for example, you can make one part of your image 100% transparent and another part 30% transparent.  The GIF format supports complete transparency, but not partial transparency like PNG does.  GIF images do not support many colors, however, so &lt;strong&gt;if you are making a JPEG image transparent, it's best to save it as a PNG&lt;/strong&gt; because JPEGs tend to have a lot of colors in them.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-ghf401sNl7c/TwEcWxEJA8I/AAAAAAAAAXc/zF1fqclYnig/s1600/with-trans.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="186" width="200" src="http://4.bp.blogspot.com/-ghf401sNl7c/TwEcWxEJA8I/AAAAAAAAAXc/zF1fqclYnig/s200/with-trans.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;The webpage background can be viewed through transparent images.&lt;/em&gt;&lt;/center&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6531158645950936199?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6531158645950936199/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6531158645950936199' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6531158645950936199'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6531158645950936199'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2012/01/making-image-transparent-with-gimp.html' title='Making an image transparent with GIMP'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-eijIQrdKZCo/TwEZUIwNrZI/AAAAAAAAAWs/RuWiG_6T1RA/s72-c/without-trans.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2489557499927389722</id><published>2011-12-19T21:30:00.000-05:00</published><updated>2011-12-20T19:27:46.194-05:00</updated><title type='text'>A Javascript script with query string parameters?</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-5NaIcZFxhTA/Tu_u5KSe2iI/AAAAAAAAAWI/mwP5lSfTtIY/s1600/topup-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="68" width="160" src="http://4.bp.blogspot.com/-5NaIcZFxhTA/Tu_u5KSe2iI/AAAAAAAAAWI/mwP5lSfTtIY/s200/topup-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="http://gettopup.com"&gt;TopUp&lt;/a&gt; is one of the more popular so-called "lightbox" libraries that allows you to create Web 2.0 popups for tasks such as viewing full resolution images and playing videos.  It works like this:  Instead of opening a new browser window, like traditional popups do, TopUp uses Javascript to modify the webpage's DOM and display a popup window inside the webpage.  This creates a more fluent user experience because it doesn't push the user away from the webpage they're currently viewing, like traditional popups tend to do.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-7EflqOzirFI/Tu_vBmcC1cI/AAAAAAAAAWU/Zwj1rGdegRE/s1600/topup-screenshot.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="174" width="200" src="http://2.bp.blogspot.com/-7EflqOzirFI/Tu_vBmcC1cI/AAAAAAAAAWU/Zwj1rGdegRE/s200/topup-screenshot.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;Lightboxes are popups that are part of the webpage itself.&lt;/em&gt;&lt;/center&gt;&lt;p&gt;I was reading the library's &lt;a href="http://gettopup.com/documentation"&gt;documentation&lt;/a&gt; and noticed that you can pass configuration settings via query string parameters in the URL to the Javascript file.  For example, to enable "fast mode" (a setting that improves support for IE 6 and 7), you would import the TopUp script into your page like so:&lt;/p&gt;&lt;pre class="brush: xml"&gt;&amp;lt;script&lt;br /&gt;  type="text/javascript"&lt;br /&gt;  src="path/to/top_up-min.js?fast_mode=1"&amp;gt;&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The question I asked myself was, "How are they passing query string parameters into a Javascript file?"  If &lt;code&gt;top_up-min.js&lt;/code&gt; was really a server-side script that returned Javascript code, then I could understand.  But it's an ordinary, plain-text &lt;code&gt;.js&lt;/code&gt; file!  The file can't be aware of its own URL, so it can't see the parameters!&lt;/p&gt;&lt;p&gt;I opened up the source code to have a look.  The author queries the DOM to get a reference to the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element that imported the file, and then parses the element's &lt;code&gt;src&lt;/code&gt; attribute to get the query string parameters.  Pretty clever!&lt;/p&gt;&lt;p&gt;But how does he get a reference to the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element?  There's nothing unique about this element that you can use to pull it out from the DOM.  No unique ID.  No specially-named class attribute.  How does he do it?  What he does is he adds a dummy element to the DOM as soon as the script loads.  This guarantees that the dummy element will be added immediately &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element, so he can just get the dummy element's previous sibling and wham...you've got your &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; node.&lt;/p&gt;&lt;pre class="brush: js"&gt;var scriptElement = (function deriveScriptElement() {&lt;br /&gt;  var id = "tu_dummy_script";&lt;br /&gt;  document.write('&amp;lt;script id="' + id + '"&amp;gt;&amp;lt;/script&amp;gt;');&lt;br /&gt;&lt;br /&gt;  var dummyScript = document.getElementById(id);&lt;br /&gt;  var element = dummyScript.previousSibling;&lt;br /&gt;&lt;br /&gt;  dummyScript.parentNode.removeChild(dummyScript);&lt;br /&gt;  return element;&lt;br /&gt;}());&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Some people say that programming is not a skill that requires creativity.  I would ask these people to look at this code sample and tell me that it's not creative!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2489557499927389722?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2489557499927389722/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2489557499927389722' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2489557499927389722'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2489557499927389722'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/javascript-script-with-query-string.html' title='A Javascript script with query string parameters?'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-5NaIcZFxhTA/Tu_u5KSe2iI/AAAAAAAAAWI/mwP5lSfTtIY/s72-c/topup-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3997914456363482410</id><published>2011-12-15T19:01:00.000-05:00</published><updated>2011-12-15T19:01:31.041-05:00</updated><title type='text'>GWT History Mechanism</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-NhyR9uNz2kw/TuqJEwy2KiI/AAAAAAAAAVY/yRJpAseWG-o/s1600/Abe-Lincoln-gwt.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="200" width="166" src="http://2.bp.blogspot.com/-NhyR9uNz2kw/TuqJEwy2KiI/AAAAAAAAAVY/yRJpAseWG-o/s200/Abe-Lincoln-gwt.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;GWT provides a way to let you interact with the history of the client's browser.  This helps to better integrate your GWT application with the browser and provide a more streamlined experience to the user.  It allows the application to respond to the user clicking the "back" and "forward" buttons.&lt;/p&gt;&lt;p&gt;In this tutorial, I'm going to add history support to my TwitterSearch application that I created in some &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-series-synchronous-and.html"&gt;previous&lt;/a&gt; &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-2-of-3-server-side-rpc.html"&gt;blog&lt;/a&gt; &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-3-of-3-take-ride-on-client.html"&gt;posts&lt;/a&gt;.  This is a simple app that allows the user to perform tweet searches, as well as view the Twitter privacy policy.  By adding history support, the user will be able to navigate back to searches that she made previously.  Feel free to download the &lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch-History.zip"&gt;complete source code&lt;/a&gt; so that you can better follow along with this blog post.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-BH6DRM-LTwk/TuqJMzGg0lI/AAAAAAAAAVk/Pxg19UTghjU/s1600/look.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="170" width="200" src="http://4.bp.blogspot.com/-BH6DRM-LTwk/TuqJMzGg0lI/AAAAAAAAAVk/Pxg19UTghjU/s200/look.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;What the TwitterSearch application looks like&lt;/em&gt;&lt;/center&gt;&lt;p&gt;Because GWT apps never navigate away from the main, HTML page, the way GWT implements history is by adding a &lt;strong&gt;fragment identifier&lt;/strong&gt; to the URL.  The fragment identifier starts with a hash (&lt;code&gt;#&lt;/code&gt;) and comes at the end of the URL.  You often see fragment identifiers being used on large webpages.  It allows you to jump around to different parts of the same page by clicking links that have fragment identifiers in their URLs.  GWT, however, uses them to identify the "state" that the application was in at a specific point in history.&lt;/p&gt;&lt;h1&gt;Enable history support&lt;/h1&gt;&lt;p&gt;The first thing to do is make sure your GWT application has history support enabled.  This is done by adding an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; element to the HTML page (if you've created your GWT application using Eclipse, then it will be enabled by default):&lt;/p&gt;&lt;pre class="brush: xml"&gt;&amp;lt;html&amp;gt;&lt;br /&gt;  [...]&lt;br /&gt;  &amp;lt;body&amp;gt;&lt;br /&gt;    &amp;lt;iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"&amp;gt;&amp;lt;/iframe&amp;gt;&lt;br /&gt;    [...]&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h1&gt;Adding history tokens&lt;/h1&gt;&lt;p&gt;Next, we must figure out when to add entries to the browser's history.  In our case, we want to add an entry when the user performs a search and when the user views the privacy policy.&lt;/p&gt;&lt;pre class="brush: java"&gt;searchButton = new Button("Search");&lt;br /&gt;searchButton.addClickHandler(new ClickHandler() {&lt;br /&gt;  @Override&lt;br /&gt;  public void onClick(ClickEvent event) {&lt;br /&gt;    String query = searchQueryTextBox.getText();&lt;br /&gt;    History.newItem(query);&lt;br /&gt;    doSearch(query);&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;privacyPolicyButton = new Button("Privacy Policy");&lt;br /&gt;privacyPolicyButton.addClickHandler(new ClickHandler() {&lt;br /&gt;  @Override&lt;br /&gt;  public void onClick(ClickEvent event) {&lt;br /&gt;    History.newItem("_privacyPolicy");&lt;br /&gt;    doGetPrivacyPolicy();&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Here, we're adding a new history token when the "Search" and "Privacy Policy" buttons are clicked by calling the static &lt;code&gt;History.newItem()&lt;/code&gt; method (new instances of the &lt;code&gt;History&lt;/code&gt; class are never created--only its static methods are used).  This method simply takes a String as an argument, which is the text that will appear in the fragment identifier.  We use the search query as this string for tweet searches, and we use the hard-coded string &lt;code&gt;_privacyPolicy&lt;/code&gt; for when the privacy policy is viewed.  The &lt;code&gt;doSearch()&lt;/code&gt; and &lt;code&gt;doGetPrivacyPolicy()&lt;/code&gt; methods call the Twitter API and then update the UI with the results (&lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch-History.zip"&gt;download&lt;/a&gt; the complete TwitterSearch project to see how this is done, or read my &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-series-synchronous-and.html"&gt;previous&lt;/a&gt; &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-2-of-3-server-side-rpc.html"&gt;blog&lt;/a&gt; &lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-3-of-3-take-ride-on-client.html"&gt;posts&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Try performing a search now--you'll see the search query appear in the fragment identifier of the URL.  But if you click "back", nothing will happen!  We must explicitly tell our application how to respond to these events.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-EsISk9JTmDU/TuqJfXvk7wI/AAAAAAAAAVw/5SOyJevARTc/s1600/fragment-identifier.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="50" width="200" src="http://2.bp.blogspot.com/-EsISk9JTmDU/TuqJfXvk7wI/AAAAAAAAAVw/5SOyJevARTc/s200/fragment-identifier.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;The history token is added to the fragment identifier in the URL&lt;/em&gt;&lt;/center&gt;&lt;h1&gt;Handling history change events&lt;/h1&gt;&lt;p&gt;Now, let's handle what happens when the user clicks the "back" or "forward" buttons.  We'll do this in our &lt;code&gt;onModuleLoad()&lt;/code&gt; method, so that the handler  we define is registered as soon as our app loads.&lt;/p&gt;&lt;pre class="brush: java"&gt;@Override&lt;br /&gt;public void onModuleLoad() {&lt;br /&gt;  History.addValueChangeHandler(new ValueChangeHandler&amp;lt;String&amp;gt;() {&lt;br /&gt;    @Override&lt;br /&gt;    public void onValueChange(ValueChangeEvent&amp;lt;String&amp;gt; event) {&lt;br /&gt;      String token = event.getValue();&lt;br /&gt;      if ("_privacyPolicy".equals(token)){&lt;br /&gt;        doGetPrivacyPolicy();&lt;br /&gt;      } else {&lt;br /&gt;        searchQueryTextBox.setText(token);&lt;br /&gt;        doSearch(token);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  });&lt;br /&gt;&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you can see, we call the &lt;code&gt;History.addValueChangeHandler()&lt;/code&gt; method and pass in an implementation of the &lt;code&gt;ValueChangeHandler&lt;/code&gt; interface.  This handler will be invoked every time the user goes back or forward in their browser history.  In the handler's only method, &lt;code&gt;onValueChange()&lt;/code&gt;, we get the history token (which is the fragment identifier in the URL), and then use it to update our UI accordingly.&lt;/p&gt;&lt;h1&gt;Handling history tokens on app startup&lt;/h1&gt;&lt;p&gt;Besides giving the user the ability to use "back" and "forward", another good thing about using history is that the user can save bookmarks of our app.  For example, a user might want to frequently check Twitter for news on her favorite celebrity, Brad Pitt.  She might want to bookmark her search, so that she can reopen the bookmark at a later time and immediately see the latest search results.&lt;/p&gt;&lt;p&gt;To do this, we need to check for an existing history token on startup.  We must check for it manually because the presence of a history token on startup will not fire a &lt;code&gt;ValueChangeEvent&lt;/code&gt; (this event is only fired when the user clicks "back" or "forward").&lt;/p&gt;&lt;pre class="brush: java"&gt;@Override&lt;br /&gt;public void onModuleLoad() {&lt;br /&gt;  [...]&lt;br /&gt;&lt;br /&gt;  String token = History.getToken();&lt;br /&gt;  if (!token.isEmpty()){&lt;br /&gt;    if ("_privacyPolicy".equals(token)){&lt;br /&gt;      doGetPrivacyPolicy();&lt;br /&gt;    } else {&lt;br /&gt;      searchQueryTextBox.setText(token);&lt;br /&gt;      doSearch(token);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;History.getToken()&lt;/code&gt; is called to get the current history token.  The method returns an empty string if there is no token, so we first check to see if the string empty.  If it's not, then we update the UI according to the token.  Note that this should happen at the very end of the &lt;code&gt;onModuleLoad()&lt;/code&gt; method because the UI widgets must be created first in order for the UI to be updated.&lt;/p&gt;&lt;p&gt;Does this code look familiar?  It should because much of it is identical to the code we wrote for the &lt;code&gt;History.addValueChangeHandler()&lt;/code&gt; method, so let's refactor it out into its own method:&lt;/p&gt;&lt;pre class="brush: java"&gt;@Override&lt;br /&gt;public void onModuleLoad() {&lt;br /&gt;  History.addValueChangeHandler(new ValueChangeHandler&amp;lt;String&amp;gt;() {&lt;br /&gt;    @Override&lt;br /&gt;    public void onValueChange(ValueChangeEvent&amp;lt;String&amp;gt; event) {&lt;br /&gt;      String token = event.getValue();&lt;br /&gt;      handleHistoryToken(token);&lt;br /&gt;    }&lt;br /&gt;  });&lt;br /&gt;&lt;br /&gt;  [...]&lt;br /&gt;&lt;br /&gt;  String token = History.getToken();&lt;br /&gt;  if (!token.isEmpty()){&lt;br /&gt;    handleHistoryToken(token);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private void handleHistoryToken(String token){&lt;br /&gt;  if ("_privacyPolicy".equals(token)){&lt;br /&gt;    doGetPrivacyPolicy();&lt;br /&gt;  } else {&lt;br /&gt;    searchQueryTextBox.setText(token);&lt;br /&gt;    doSearch(token);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h1&gt;Changing the window title&lt;/h1&gt;&lt;p&gt;It's also helpful to change the window title of the web page to reflect the history token.  That way, when the user views their entire history, they can have a better idea of what Twitter searches they performed at a glance.  To do this, we'll modify two sections of code.  First, we'll modify the place where we add new history tokens.&lt;/p&gt;&lt;pre class="brush: java"&gt;searchButton = new Button("Search");&lt;br /&gt;searchButton.addClickHandler(new ClickHandler() {&lt;br /&gt;  @Override&lt;br /&gt;  public void onClick(ClickEvent event) {&lt;br /&gt;    String query = searchQueryTextBox.getText();&lt;br /&gt;    History.newItem(query);&lt;br /&gt;    Window.setTitle("Twitter Search - " + query);&lt;br /&gt;    doSearch(query);&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;privacyPolicyButton = new Button("Privacy Policy");&lt;br /&gt;privacyPolicyButton.addClickHandler(new ClickHandler() {&lt;br /&gt;  @Override&lt;br /&gt;  public void onClick(ClickEvent event) {&lt;br /&gt;    History.newItem("_privacyPolicy");&lt;br /&gt;    Window.setTitle("Twitter Search - Privacy Policy");&lt;br /&gt;    doGetPrivacyPolicy();&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Here, we've added calls to &lt;code&gt;Window.setTitle()&lt;/code&gt; to set the title of the browser window.  It's very important that you call this method &lt;em&gt;after&lt;/em&gt; adding the history entry.  Otherwise, the titles will not be synced properly with the history entries.&lt;/p&gt;&lt;p&gt;Second, we'll modify our &lt;code&gt;handleHistoryToken()&lt;/code&gt; method to set the proper Window title when a history token is loaded.&lt;/p&gt;&lt;pre class="brush: java"&gt;private void handleHistoryToken(String token){&lt;br /&gt;  if ("_privacyPolicy".equals(token)){&lt;br /&gt;    Window.setTitle("Twitter Search - Privacy Policy");&lt;br /&gt;    doGetPrivacyPolicy();&lt;br /&gt;  } else {&lt;br /&gt;    searchQueryTextBox.setText(token);&lt;br /&gt;    Window.setTitle("Twitter Search - " + token);&lt;br /&gt;    doSearch(token);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now, when you view your history, you'll get a good glimpse of all your past searches.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-WDNgEJ3Dp2M/TuqJyiXf-4I/AAAAAAAAAV8/LfKDTuKoYK0/s1600/title-before-and-after.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="67" width="200" src="http://2.bp.blogspot.com/-WDNgEJ3Dp2M/TuqJyiXf-4I/AAAAAAAAAV8/LfKDTuKoYK0/s200/title-before-and-after.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;What the browser history looks like before and after setting window titles.&lt;/em&gt;&lt;/center&gt;&lt;hr /&gt;&lt;p&gt;I hope you've enjoyed my GWT History tutorial.  For more information, please see the &lt;a href="http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsHistory.html"&gt;GWT History page&lt;/a&gt; in the GWT Developer's Guide.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3997914456363482410?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3997914456363482410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3997914456363482410' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3997914456363482410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3997914456363482410'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/gwt-history-mechanism.html' title='GWT History Mechanism'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-NhyR9uNz2kw/TuqJEwy2KiI/AAAAAAAAAVY/yRJpAseWG-o/s72-c/Abe-Lincoln-gwt.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-1634330854796202492</id><published>2011-12-11T12:23:00.000-05:00</published><updated>2011-12-12T19:37:53.394-05:00</updated><title type='text'>GWT RPC (Part 3 of 3) - Take a ride on the client-side</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s1600/gwt-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="92" width="100" src="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s200/gwt-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;GWT is all about creating web applications.  And a web application wouldn't be a web application without client/server communication.  A mechanism called GWT RPC (remote procedure call) is used for this communication in GWT.  In this series of tutorials, I'm going to show you how GWT RPC works by building a simple GWT application that uses the Twitter API to perform tweet searches and retrieve the Twitter privacy policy.&lt;/p&gt;&lt;p&gt;The process involves three steps.  I'll be covering step 3 in this blog post.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-series-synchronous-and.html"&gt;Create the synchronous and asynchronous interfaces that define each RPC method&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-2-of-3-server-side-rpc.html"&gt;Create the server-side implementation of each RPC method&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Create the UI (user interface) that the client will use perform the searches&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can download the entire sample project used in this tutorial &lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;In this blog post, I'll show you how to design a basic UI that will call the RPC methods we created in the previous post.  This UI will give us the ability to search for tweets and to view the Twitter privacy policy.  Along with explaining how to call the GWT RPC methods from client code, I'll also give you a brief introduction into UI programming.&lt;/p&gt;&lt;h1&gt;EntryPoint&lt;/h1&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package com.acme.twittersearch.client;&lt;br /&gt;&lt;br /&gt;[imports...]&lt;br /&gt;&lt;br /&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;[...]&lt;br /&gt;  @Override&lt;br /&gt;  public void onModuleLoad() {&lt;br /&gt;    createWidgets();&lt;br /&gt;    layoutWidgets();&lt;br /&gt;  }&lt;br /&gt;[...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The first thing to notice is that the class implements the &lt;code&gt;EntryPoint&lt;/code&gt; interface.  Every GWT application must have one class that implements this.  It tells GWT where to start when a user loads the application in her web browser.  The interface consists of only one method, &lt;code&gt;onModuleLoad()&lt;/code&gt;, which can be thought of as the GWT-equivalent of the &lt;code&gt;public static void main(String args[])&lt;/code&gt; method you see in command-line Java programs.&lt;/p&gt;&lt;p&gt;In our class, &lt;code&gt;onModuleLoad()&lt;/code&gt; calls two private methods (that I've created), &lt;code&gt;createWidgets()&lt;/code&gt; and &lt;code&gt;layoutWidgets()&lt;/code&gt;.  The &lt;code&gt;createWidgets()&lt;/code&gt; method initializes our buttons, text boxes, etc.  The second method, &lt;code&gt;layoutWidgets()&lt;/code&gt;, adds those widgets to the page, positioning them in just the way that we want.  You are not required by GWT to organize your code in this way, but I find it helpful to do so because UI code is very verbose and can quickly grow in size as your application becomes more complex.&lt;/p&gt;&lt;p&gt;Also, the fully-qualified class name of our entry point class must be added to the GWT module definition file (TwitterSearch.gwt.xml), as shown below:&lt;/p&gt;&lt;pre class="brush: xml"&gt;&amp;lt;module rename-to='twittersearch'&amp;gt;&lt;br /&gt;  [...]&lt;br /&gt;  &amp;lt;entry-point class='com.acme.twittersearch.client.TwitterSearch'/&amp;gt;&lt;br /&gt;  [...]&lt;br /&gt;&amp;lt;/module&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h1&gt;Widgets&lt;/h1&gt;&lt;p&gt;I've defined my widget objects as class-level fields at the top of the class:&lt;/p&gt;&lt;pre class="brush: java"&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;  private Button privacyPolicyButton;&lt;br /&gt;  private Button searchButton;&lt;br /&gt;  private TextBox searchQueryTextBox;&lt;br /&gt;  private Panel resultsPanel;&lt;br /&gt;  private Label errorLabel;&lt;br /&gt;  private Image loadingImage;&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;privacyPolicyButton&lt;/code&gt;: This button will call the &lt;code&gt;getPrivacyPolicy()&lt;/code&gt; RPC method and display the returned privacy policy on the page.&lt;/li&gt;&lt;li&gt;&lt;code&gt;searchButton&lt;/code&gt;: This button will call the &lt;code&gt;searchTweets()&lt;/code&gt; RPC method and display the search results on the page.&lt;/li&gt;&lt;li&gt;&lt;code&gt;searchQueryTextBox&lt;/code&gt;: This is where the user will enter her Twitter search query.&lt;/li&gt;&lt;li&gt;&lt;code&gt;resultsPanel&lt;/code&gt;: We'll display the results from each RPC call here.&lt;/li&gt;&lt;li&gt;&lt;code&gt;errorLabel&lt;/code&gt;: If an error occurs while calling one of the RPC methods, we'll put the error message in here.&lt;/li&gt;&lt;li&gt;&lt;code&gt;loadingImage&lt;/code&gt;: We'll display an animated loading icon when the client sends an RPC request to let the user know that their request is being processed.  It's a good idea to always display some sort of loading image or message while an RPC request is being sent.  Even the simplest requests can take several seconds to complete, especially if the user is on a slow network or the server is experiencing heavy load.  In our case, we actually end up having to make &lt;em&gt;two&lt;/em&gt; network calls per request, (1) the call to the GWT server and (2) the call to the Twitter server.  So for us, having a loading image is essential (by the way, I got mine from &lt;a href="http://loadinfo.net/"&gt;loaderinfo.net&lt;/a&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;The HTML page&lt;/h1&gt;&lt;p&gt;Every GWT application has an HTML file that is loaded when the user navigates to the application in her browser.  Here, I'm creating the "Twitter Search App" header and adding an image of the Twitter bird.  I also have a CSS file which adds some extra formatting.  The Javascript file that is being included is what the browser will use when the GWT application is compiled and run in production.  The widgets that we load from our Java code will be added to the page dynamically at run time and be placed below the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; header (at the end of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag).&lt;/p&gt;&lt;pre class="brush: xml"&gt;&amp;lt;html&amp;gt;&lt;br /&gt;  &amp;lt;head&amp;gt;&lt;br /&gt;    &amp;lt;meta http-equiv="content-type" content="text/html; charset=UTF-8"&amp;gt;&lt;br /&gt;    &amp;lt;link type="text/css" rel="stylesheet" href="TwitterSearch.css"&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;Twitter Search App&amp;lt;/title&amp;gt;&lt;br /&gt;    &amp;lt;script type="text/javascript" language="javascript" src="twittersearch/twittersearch.nocache.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;  &amp;lt;/head&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;body&amp;gt;&lt;br /&gt;    &amp;lt;img src="twitter-bird.png" align="right"/&amp;gt;&lt;br /&gt;    &amp;lt;h1&amp;gt;Twitter Search App&amp;lt;/h1&amp;gt;&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-20ZAri8YMq0/TuFq7OJKR6I/AAAAAAAAAVA/jq8ofoNp8Ak/s1600/blank.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="96" width="200" src="http://2.bp.blogspot.com/-20ZAri8YMq0/TuFq7OJKR6I/AAAAAAAAAVA/jq8ofoNp8Ak/s200/blank.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;What the app will look like.&lt;/em&gt;&lt;/center&gt;&lt;h1&gt;Calling the RPC methods&lt;/h1&gt;&lt;p&gt;Calling the RPC methods involves us using the asynchronous interface we created back in step 1 of this blog series.  You might be thinking, "But &lt;code&gt;TwitterSearchServiceAsync&lt;/code&gt; is an interface and we never created an implementation class for it."  You are correct.  But this is actually not a problem--GWT uses a technique called "deferred binding", which means that GWT will create a implementation for us during runtime.  This is done by calling the &lt;code&gt;GWT.create()&lt;/code&gt; method:&lt;/p&gt;&lt;pre class="brush: java"&gt;public class TwitterSearch implements EntryPoint {&lt;br /&gt;  private final TwitterSearchServiceAsync service = GWT.create(TwitterSearchService.class);&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Even though we want an instance of the asynchronous interface, we pass it the class object of the &lt;em&gt;synchronous&lt;/em&gt; interface, &lt;code&gt;TwitterSearchService&lt;/code&gt;.  The method then returns an instance of the asynchronous interface, &lt;code&gt;TwitterSearchServiceAsync&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Remember how each method in the asynchronous interface took an extra &lt;code&gt;AsyncCallback&lt;/code&gt; parameter?  We will be using this now.  Let's write the code that will do the Twitter search:&lt;/p&gt;&lt;pre class="brush: java"&gt;searchButton = new Button("Search");&lt;br /&gt;searchButton.addClickHandler(new ClickHandler() {&lt;br /&gt;  @Override&lt;br /&gt;  public void onClick(ClickEvent event) {&lt;br /&gt;    setLoading(true);&lt;br /&gt;    String query = searchQueryTextBox.getText();&lt;br /&gt;    service.searchTweets(query, new AsyncCallback&amp;lt;List&amp;lt;Tweet&amp;gt;&amp;gt;() {&lt;br /&gt;      @Override&lt;br /&gt;      public void onFailure(Throwable caught) {&lt;br /&gt;        errorLabel.setText(caught.getMessage());&lt;br /&gt;        errorLabel.setVisible(true);&lt;br /&gt;        setLoading(false);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      @Override&lt;br /&gt;      public void onSuccess(List&amp;lt;Tweet&amp;gt; result) {&lt;br /&gt;        resultsPanel.clear();&lt;br /&gt;        for (Tweet tweet : result) {&lt;br /&gt;          SafeHtmlBuilder builder = new SafeHtmlBuilder();&lt;br /&gt;          builder.appendHtmlConstant("&amp;lt;b&amp;gt;User: &amp;lt;/b&amp;gt;");&lt;br /&gt;          builder.appendEscaped(tweet.getFrom_user());&lt;br /&gt;          builder.appendHtmlConstant("&amp;lt;br /&amp;gt;&amp;lt;b&amp;gt;Created: &amp;lt;/b&amp;gt;");&lt;br /&gt;          builder.appendEscaped(tweet.getCreated_at());&lt;br /&gt;          builder.appendHtmlConstant("&amp;lt;br /&amp;gt;&amp;lt;b&amp;gt;Tweet: &amp;lt;/b&amp;gt;");&lt;br /&gt;          builder.appendEscaped(tweet.getText());&lt;br /&gt;          builder.appendHtmlConstant("&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;");&lt;br /&gt;          resultsPanel.add(new HTML(builder.toSafeHtml()));&lt;br /&gt;        }&lt;br /&gt;        setLoading(false);&lt;br /&gt;      }&lt;br /&gt;    });&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you can see, we add a &lt;code&gt;ClickHandler&lt;/code&gt; to the search button to define what happens when the search button is clicked.  When the button is clicked, we first set the page to a "loading" state by calling &lt;code&gt;setLoading(true)&lt;/code&gt;.  This method disables the buttons so the user can't click them and displays the loading image.  We then grab the search query from the search text box and make a call to the &lt;code&gt;searchTweets&lt;/code&gt; RPC method using the asynchronous interface.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-DC-XLHWH59Y/TuFpRjm7z8I/AAAAAAAAAUc/zJe_qWm1sEw/s1600/search-loading.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="106" width="200" src="http://1.bp.blogspot.com/-DC-XLHWH59Y/TuFpRjm7z8I/AAAAAAAAAUc/zJe_qWm1sEw/s200/search-loading.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;A loading animation appears when a RPC request is made.&lt;/em&gt;&lt;/center&gt;&lt;p&gt;The &lt;code&gt;onFailure()&lt;/code&gt; method of the &lt;code&gt;AsyncCallback&lt;/code&gt; class is called when the RPC method throws an exception.  The thrown exception is passed in as an argument to the method.  If this happens, we put the exception message in our error label so the user can get an idea of what went wrong.  We then set the page back to its normal, "non-loading" state by calling &lt;code&gt;setLoading(false)&lt;/code&gt;, which will re-enable the buttons and hide the loading image.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-_IY0WDcLgqY/TuFpaE7cwkI/AAAAAAAAAUo/kgBQSpDrv-E/s1600/search-noquery.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="108" width="200" src="http://4.bp.blogspot.com/-_IY0WDcLgqY/TuFpaE7cwkI/AAAAAAAAAUo/kgBQSpDrv-E/s200/search-noquery.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;An error is thrown when no search query is specified.&lt;/em&gt;&lt;/center&gt;&lt;p&gt;The &lt;code&gt;onSuccess()&lt;/code&gt; method of the &lt;code&gt;AsyncCallback&lt;/code&gt; class is called when the RPC method completes without error.  Here, we clear the results panel of past searches, and then loop through the list of returned tweets, adding each one of them to the page.  After all tweets are displayed, we call &lt;code&gt;setLoading(false)&lt;/code&gt; to re-enable the buttons and hide the loading image.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-lgyZwdjDKA4/TuFpfaT_3YI/AAAAAAAAAU0/gMew5Ce9ZZI/s1600/search-results.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="176" width="200" src="http://4.bp.blogspot.com/-lgyZwdjDKA4/TuFpfaT_3YI/AAAAAAAAAU0/gMew5Ce9ZZI/s200/search-results.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;center&gt;&lt;em&gt;Search results for "#christmas".&lt;/em&gt;&lt;/center&gt;&lt;p&gt;I'm using the &lt;code&gt;SafeHtmlBuilder&lt;/code&gt; class here to generate the HTML that is used to display each tweet.  This class is like the &lt;code&gt;StringBuilder&lt;/code&gt; and &lt;code&gt;StringBuffer&lt;/code&gt; classes in that it allows you to efficiently create a large string in a peace-meal fashion.  However, &lt;code&gt;SafeHtmlBuilder&lt;/code&gt; specializes in making strings of HTML code.  The &lt;code&gt;appendEscaped()&lt;/code&gt; method escapes all HTML special characters in the string before appending it (for example, converting all &lt;code&gt;&amp;lt;&lt;/code&gt; characters to &lt;code&gt;&amp;amp;lt;&lt;/code&gt;).  The &lt;code&gt;appendHtmlConstant()&lt;/code&gt; method does not escape HTML special characters, so you can use this to add HTML tags to the string.&lt;/p&gt;&lt;p&gt;The RPC method call for the &lt;code&gt;getPrivacyPolicy()&lt;/code&gt; method follows the same pattern:&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;privacyPolicyButton = new Button("Privacy Policy");&lt;br /&gt;privacyPolicyButton.addClickHandler(new ClickHandler() {&lt;br /&gt; @Override&lt;br /&gt; public void onClick(ClickEvent event) {&lt;br /&gt;    setLoading(true);&lt;br /&gt;    service.getPrivacyPolicy(new AsyncCallback&amp;lt;String&amp;gt;() {&lt;br /&gt;      @Override&lt;br /&gt;      public void onFailure(Throwable caught) {&lt;br /&gt;        errorLabel.setText(caught.getMessage());&lt;br /&gt;        errorLabel.setVisible(true);&lt;br /&gt;        setLoading(false);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      @Override&lt;br /&gt;      public void onSuccess(String result) {&lt;br /&gt;        resultsPanel.clear();&lt;br /&gt;&lt;br /&gt;        // convert newlines to &amp;nbsp;&amp;lt;br /&amp;nbsp;&amp;gt;&lt;br /&gt;        SafeHtmlBuilder builder = new SafeHtmlBuilder();&lt;br /&gt;        builder.appendEscapedLines(result);&lt;br /&gt;&lt;br /&gt;        resultsPanel.add(new HTML(builder.toSafeHtml()));&lt;br /&gt;&lt;br /&gt;        setLoading(false);&lt;br /&gt;      }&lt;br /&gt;    });&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;p&gt;I hoped you've enjoyed my three part series on GWT RPC.  Creating and calling GWT RPC methods is very easy to do once you become familiarized with these three steps.  Simply create the synchronous/asynchronous interfaces, create your server-side RPC implementation, and then call the RPC methods from the client using the asynchronous interface.&lt;/p&gt;&lt;p&gt;For more information on GWT, check out the &lt;a href="http://code.google.com/webtoolkit/doc/latest/DevGuide.html"&gt;GWT Developer's Guide&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-1634330854796202492?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/1634330854796202492/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=1634330854796202492' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1634330854796202492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1634330854796202492'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-3-of-3-take-ride-on-client.html' title='GWT RPC (Part 3 of 3) - Take a ride on the client-side'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s72-c/gwt-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3949673617562487217</id><published>2011-12-09T17:39:00.000-05:00</published><updated>2011-12-12T19:36:11.220-05:00</updated><title type='text'>GWT RPC (Part 2 of 3) - The server-side RPC implementation</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s1600/gwt-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="92" width="100" src="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s200/gwt-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;GWT is all about creating web applications.  And a web application wouldn't be a web application without client/server communication.  A mechanism called GWT RPC (remote procedure call) is used for this communication in GWT.  In this series of tutorials, I'm going to show you how GWT RPC works by building a simple GWT application that uses the Twitter API to perform tweet searches and retrieve the Twitter privacy policy.&lt;/p&gt;&lt;p&gt;The process involves three steps.  I'll be covering step 2 in this blog post.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-series-synchronous-and.html"&gt;Create the synchronous and asynchronous interfaces that define each RPC method&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Create the server-side implementation of each RPC method&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-3-of-3-take-ride-on-client.html"&gt;Create the UI (user interface) that the client will use perform the searches&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can download the entire sample project used in this tutorial &lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;Since we've already defined our RPC methods in the &lt;code&gt;TwitterSearchService&lt;/code&gt; synchronous interface, we're now ready to write the implementations of those RPC methods.  We'll create a new class named &lt;code&gt;TwitterSearchServiceImpl&lt;/code&gt; (the naming convention is to use the synchronous interface's name with "Impl" appended to the end).  It will be located in the "server" package because it's meant to only run on the server.  It will extend the &lt;code&gt;RemoteServiceServlet&lt;/code&gt; class and implement our &lt;code&gt;TwitterSearchService&lt;/code&gt; synchronous interface (it should &lt;em&gt;not&lt;/em&gt; implement the asynchronous interface).&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package com.acme.twittersearch.server;&lt;br /&gt;&lt;br /&gt;[imports...]&lt;br /&gt;&lt;br /&gt;public class TwitterSearchServiceImpl extends RemoteServiceServlet implements TwitterSearchService {&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We do a little bit of error checking in the &lt;code&gt;searchTweets&lt;/code&gt; method.  If the search query is empty, then an &lt;code&gt;IllegalArgumentException&lt;/code&gt; is thrown.  With GWT RPC, exceptions are sent over the wire to the client.  So the exact same exceptions can actually be handled in the client code (as you'll see in my next blog post).  Exceptions can be used to return validation errors, as well as return unexpected server-side errors to the client.&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;@Override&lt;br /&gt;public List&amp;lt;Tweet&amp;gt; searchTweets(String query) throws IllegalArgumentException, IOException {&lt;br /&gt;  query = query.trim();&lt;br /&gt;  if (query.isEmpty()) {&lt;br /&gt;    throw new IllegalArgumentException("No search query specified.");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  // see: https://dev.twitter.com/docs/api/1/get/search&lt;br /&gt;  String q = URLEncoder.encode(query, "UTF-8");&lt;br /&gt;  URL url = new URL("http://search.twitter.com/search.json?q=" + q);&lt;br /&gt;  HttpURLConnection connection = (HttpURLConnection) url.openConnection();&lt;br /&gt;  InputStream response = null;&lt;br /&gt;  try {&lt;br /&gt;    response = connection.getInputStream();&lt;br /&gt;    return parseSearchResponse(response);&lt;br /&gt;  } finally {&lt;br /&gt;    if (response != null) {&lt;br /&gt;      response.close();&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Also in the &lt;code&gt;searchTweets&lt;/code&gt; method, notice how the search query is passed into the &lt;code&gt;URLEncoder.encode()&lt;/code&gt; method.  This &lt;code&gt;URLEncoder.encode()&lt;/code&gt; method encodes the search query so that it can be safely included in the query string of the Twitter API URL.  You should always encode query string parameters in this way (just like you should always encode data before passing it into a SQL query string to avoid SQL injection).  If a value happens to contain a character that has a special meaning in a URL (like an equals sign or ampersand), then you're likely to run into trouble.&lt;/p&gt;&lt;p&gt;We use the &lt;code&gt;HttpURLConnection&lt;/code&gt; class to make the calls to the Twitter API.  This class is not on the &lt;a href="http://code.google.com/webtoolkit/doc/latest/RefJreEmulation.html"&gt;JRE Emulation list&lt;/a&gt;, but that's OK because we're only using it on the server.  If we were to use it on the client, we'd get an error because GWT does not know how to convert this class to Javascript.&lt;/p&gt;&lt;p&gt;We also use the &lt;a href="http://code.google.com/p/google-gson/"&gt;google-gson&lt;/a&gt; library to parse the JSON-encoded responses that are returned from the Twitter API.  The way google-gson works is that you create a class that mirrors the JSON data you are unmarshalling.  Each field in the class is named after a field in the JSON data.  Google-gson looks at these field names to determine how to unmarshall the JSON message.  So, we've created a &lt;code&gt;SearchResponse&lt;/code&gt; class for our tweet search API call and a &lt;code&gt;PrivacyPolicyResponse&lt;/code&gt; class for our privacy policy API call to do just that.  However, for this demonstration, we don't need to unmarshal all the data in each response, so I've only included fields for the data that we need.  You can see examples of the full JSON responses for the &lt;a href="https://dev.twitter.com/docs/api/1/get/search"&gt;search&lt;/a&gt; and  &lt;a href="https://dev.twitter.com/docs/api/1/get/legal/privacy"&gt;legal/privacy&lt;/a&gt; methods on the &lt;a href="https://dev.twitter.com/docs/api"&gt;Twitter API documentation page&lt;/a&gt;.&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;private List&amp;lt;Tweet&amp;gt; parseSearchResponse(InputStream response) throws IOException {&lt;br /&gt;  Reader reader = new InputStreamReader(response);&lt;br /&gt;  SearchResponse searchResponse = new Gson().fromJson(reader, SearchResponse.class);&lt;br /&gt;  return searchResponse.results;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private class SearchResponse {&lt;br /&gt;  public List&amp;lt;Tweet&amp;gt; results;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Under the covers, &lt;code&gt;TwitterSearchServiceImpl&lt;/code&gt; is a plain old Java servlet, which means that only &lt;em&gt;one instance&lt;/em&gt; of this class is created to handle &lt;em&gt;all&lt;/em&gt; requests.  Therefore, be sure to keep &lt;strong&gt;thread-safety&lt;/strong&gt; in mind.&lt;/p&gt;&lt;h1&gt;web.xml&lt;/h1&gt;&lt;p&gt;Our &lt;code&gt;TwitterSearchServiceImpl&lt;/code&gt; class must also be added to the web application's deployment descriptor (web.xml).&lt;/p&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;web-app ...&amp;gt;&lt;br /&gt;  [...]&lt;br /&gt;&lt;br /&gt;  &amp;lt;servlet&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;twitterSearchServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;servlet-class&amp;gt;com.acme.twittersearch.server.TwitterSearchServiceImpl&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;  &amp;lt;/servlet&amp;gt;  &lt;br /&gt;&lt;br /&gt;  &amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;twitterSearchServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;url-pattern&amp;gt;/twittersearch/search&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;/servlet-mapping&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;&lt;br /&gt;  [...]&lt;br /&gt;&amp;lt;/web-app&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you can see, the url-pattern for the servlet is &lt;code&gt;/twittersearch/search&lt;/code&gt;.  The first part, &lt;code&gt;twittersearch&lt;/code&gt;, is the name of our GWT module, which is defined in the module definition file (TwitterSearch.gwt.xml).  The second part, &lt;code&gt;search&lt;/code&gt;, is the value of the &lt;code&gt;@RemoteServiceRelativePath&lt;/code&gt; annotation in the &lt;code&gt;TwitterSearchService&lt;/code&gt; interface that we created in the previous blog post.&lt;/p&gt;&lt;h1&gt;Tweet class&lt;/h1&gt;&lt;p&gt;Let's finish by creating the &lt;code&gt;Tweet&lt;/code&gt; class.  Because we're unmarshalling JSON data into this class using google-gson, the field names in this class must be named after the fields in the JSON message.  Also, GWT requires that all objects that are sent over the wire implement &lt;code&gt;Serializable&lt;/code&gt;, so let's do that too.  The class must also be put in the "client" package, because it will be used in client code.&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package com.acme.twittersearch.client;&lt;br /&gt;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;&lt;br /&gt;public class Tweet implements Serializable {&lt;br /&gt;  private String id;&lt;br /&gt;  private String from_user;&lt;br /&gt;  private String created_at;&lt;br /&gt;  private String text;&lt;br /&gt;&lt;br /&gt;  [getter/setter methods...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;p&gt;In the next and final blog post of my GWT RPC series, you'll learn how to create a basic UI and how to call these RPC methods from the client.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3949673617562487217?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3949673617562487217/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3949673617562487217' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3949673617562487217'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3949673617562487217'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-2-of-3-server-side-rpc.html' title='GWT RPC (Part 2 of 3) - The server-side RPC implementation'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s72-c/gwt-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-1847492140545934290</id><published>2011-12-08T20:34:00.001-05:00</published><updated>2011-12-12T19:36:53.000-05:00</updated><title type='text'>GWT RPC (Part 1 of 3) - Synchronous and asynchronous interfaces</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s1600/gwt-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="92" width="100" src="http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s200/gwt-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;GWT is all about creating web applications.  And a web application wouldn't be a web application without client/server communication.  A mechanism called GWT RPC (remote procedure call) is used for this communication in GWT.  In this series of tutorials, I'm going to show you how GWT RPC works by building a simple GWT application that uses the Twitter API to perform tweet searches and retrieve the Twitter privacy policy.&lt;/p&gt;&lt;p&gt;The process involves three steps.  I'll be covering step 1 in this blog post.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Create the synchronous and asynchronous interfaces that define each RPC method&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-2-of-3-server-side-rpc.html"&gt;Create the server-side implementation of each RPC method&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mangstacular.blogspot.com/2011/12/gwt-rpc-part-3-of-3-take-ride-on-client.html"&gt;Create the UI (user interface) that the client will use perform the searches&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can download the entire sample project used in this tutorial &lt;a href="http://dl.dropbox.com/u/5187024/TwitterSearch.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;h1&gt;Synchronous interface&lt;/h1&gt;&lt;p&gt;The first thing to do is figure out what kind of communication you need to do with the server.  What actions do you need to perform?  What data do you need to retrieve?  In this case, we need to do two things: perform tweet searches and get the Twitter privacy policy.&lt;/p&gt;&lt;p&gt;These actions are defined in the what's called the &lt;strong&gt;synchronous interface&lt;/strong&gt;.  I'll create two RPC methods in my synchronous interface (there's no limit to how many you can define).  The interface must extend &lt;code&gt;RemoteService&lt;/code&gt; and have a &lt;code&gt;@RemoteServiceRelativePath&lt;/code&gt; annotation (which we'll revisit later).  It must also be in the &lt;code&gt;client&lt;/code&gt; package.&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package com.acme.twittersearch.client;&lt;br /&gt;&lt;br /&gt;[imports...]&lt;br /&gt;&lt;br /&gt;@RemoteServiceRelativePath("search")&lt;br /&gt;public interface TwitterSearchService extends RemoteService {&lt;br /&gt;  List&amp;lt;Tweet&amp;gt; searchTweets(String query) throws IOException, IllegalArgumentException;&lt;br /&gt;  String getPrivacyPolicy() throws IOException;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The first method, &lt;code&gt;searchTweets&lt;/code&gt;, will take a search query as input and then return a list of tweets in the response.  Tweets contain a lot of information (such as an ID, author, and creation date, not to mention the actual tweet message), so I'm going to create a &lt;code&gt;Tweet&lt;/code&gt; class that encapsulates all of this information.  I'm not going to worry about creating this class right now, though.  For now, just assume that the &lt;code&gt;Tweet&lt;/code&gt; class holds all the information about a tweet that we need it to.&lt;/p&gt;&lt;p&gt;Also notice how &lt;code&gt;searchTweets&lt;/code&gt; throws two exceptions.  An &lt;code&gt;IOException&lt;/code&gt; will be thrown if there's a problem querying the Twitter API.  We'll also have it throw an &lt;code&gt;IllegalArgumentException&lt;/code&gt; if the search query is empty.  Even though &lt;code&gt;IllegalArgumentException&lt;/code&gt; is a runtime exception and doesn't have to be defined in the method's &lt;code&gt;throws&lt;/code&gt; clause in order for the Java compilation to pass, GWT requires it to be declared here.  Otherwise, it will send a generic "500 error" exception to the client instead of the real exception if that exception is thrown.&lt;/p&gt;&lt;p&gt;The second method, &lt;code&gt;getPrivacyPolicy&lt;/code&gt;, doesn't need any parameters.  It will return the Twitter privacy policy, which is a block of text that we will hold in a String object.  As with &lt;code&gt;searchTweets&lt;/code&gt;, it too will throw an &lt;code&gt;IOException&lt;/code&gt; if there's a problem calling the Twitter API.&lt;/p&gt;&lt;h1&gt;Asynchronous interface&lt;/h1&gt;&lt;p&gt;Now that we've defined our RPC methods in the synchronous interface, we're ready to create the &lt;strong&gt;asynchronous interface&lt;/strong&gt;.  This interface will contain the exact same methods that are in the synchronous interface, but in a slightly different form:&lt;/p&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package com.acme.twittersearch.client;&lt;br /&gt;&lt;br /&gt;[imports...]&lt;br /&gt;&lt;br /&gt;public interface TwitterSearchServiceAsync {&lt;br /&gt;  void searchTweets(String query, AsyncCallback&amp;lt;List&amp;lt;Tweet&amp;gt;&amp;gt; callback);&lt;br /&gt;  void getPrivacyPolicy(AsyncCallback&amp;lt;String&amp;gt; callback);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The interface can be named anything, but I've named it &lt;code&gt;TwitterSearchServiceAsync&lt;/code&gt; because the convention is to use the synchronous interface's name with "Async" appended to the end.&lt;/p&gt;&lt;p&gt;As you may have noticed, unlike their counterparts in the synchronous interface, the methods don't return anything.  They also take an extra parameter--an &lt;code&gt;AsyncCallback&lt;/code&gt; object.  This object will be used by the client to handle the response when the response is returned from the server.  Since this interface will be used by the client, it must be located in the &lt;code&gt;client&lt;/code&gt; package.  Also, note that, while the methods in the synchronous interface throw exceptions, the methods in the asynchronous interface do not.&lt;/p&gt;&lt;p&gt;Both of these interfaces must remain in-sync.  By that I mean, if one interface changes, the other must also change.  For example, if I change the return value of a method in the synchronous interfaces, I must make the same, corresponding change to the asynchronous interface (by changing the generics parameter of the &lt;code&gt;AsyncCallback&lt;/code&gt; parameter).  The Eclipse GWT plugin is helpful in this regard.  If it notices that the two interfaces are not in-sync, it will throw a Java compilation error.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;In my next blog post, I'll talk about creating the server-side implementation of the RPC methods.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-1847492140545934290?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/1847492140545934290/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=1847492140545934290' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1847492140545934290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1847492140545934290'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/gwt-rpc-series-synchronous-and.html' title='GWT RPC (Part 1 of 3) - Synchronous and asynchronous interfaces'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-MAbahhdlVmo/TuFmcnXf-qI/AAAAAAAAAUQ/BqKZ30E4isQ/s72-c/gwt-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6629597547515747569</id><published>2011-12-03T17:54:00.000-05:00</published><updated>2011-12-09T17:49:42.530-05:00</updated><title type='text'>GWT Introduction</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-jepZrOvULe4/TtqtCzs0OvI/AAAAAAAAAUE/H2idWnn3L3s/s1600/gwt-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="92" width="100" src="http://4.bp.blogspot.com/-jepZrOvULe4/TtqtCzs0OvI/AAAAAAAAAUE/H2idWnn3L3s/s200/gwt-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://code.google.com/webtoolkit/"&gt;Google Web Toolkit&lt;/a&gt; allows you to write Javascript-based web applications without having to write any Javascript yourself.  It translates Java code that you write into Javascript automatically.  In this informational overview, I'm going to walk you through creating a GWT application and then describe the basic components that make up a GWT application.&lt;/p&gt;&lt;h1&gt;Install the Eclipse plugin&lt;/h1&gt;&lt;p&gt;Google provides an Eclipse plugin you can use for developing GWT applications.  This is what I'll be using here, but the &lt;a href="http://code.google.com/webtoolkit/download.html"&gt;raw GWT SDK&lt;/a&gt; is available as well for command-line junkies.&lt;/p&gt;&lt;p&gt;The Eclipse update URL for the plugin is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;http://dl.google.com/eclipse/plugin/&amp;lt;version&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where &lt;code&gt;&amp;lt;version&amp;gt;&lt;/code&gt; is your Eclipse version (3.7, 3.6, etc).&lt;/p&gt;&lt;p&gt;You'll need to install these modules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Google Plugin for Eclipse&lt;/li&gt;&lt;li&gt;SDKs &gt; Google Web Toolkit SDK 2.4.0&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Create a new project&lt;/h1&gt;&lt;p&gt;Once the Eclipse plugin is installed, create a new "Web Application Project".&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-XXOX17_8eD0/TtqsILnbrcI/AAAAAAAAAS8/yFjZaBPaxgU/s1600/new-project-1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="163" width="200" src="http://4.bp.blogspot.com/-XXOX17_8eD0/TtqsILnbrcI/AAAAAAAAAS8/yFjZaBPaxgU/s200/new-project-1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Enter "SimpleApp" for the project name and "com.acme.simpleapp" for the package.  The package name will be the "base" package for all of the packages in your project as you'll see in a minute.  If you haven't installed the Google App Engine plugin, then uncheck the "Use Google App Engine" checkbox (I was on my netbook when I wrote this, so it's hard to see in the screenshot).  The Google App Engine is a cloud-based service that lets you upload your application to the web, but that's out of the scope for this article.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-NRtqVc-pVec/TtqsMVrF3DI/AAAAAAAAATI/Dm21dTXJIyA/s1600/new-project-2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="112" width="200" src="http://3.bp.blogspot.com/-NRtqVc-pVec/TtqsMVrF3DI/AAAAAAAAATI/Dm21dTXJIyA/s200/new-project-2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h1&gt;Running the project&lt;/h1&gt;&lt;p&gt;When you create a new GWT project in Eclipse, it creates a sample application, so let's run it.  Right click on the project folder and select "Run As &gt; Web Application".  A "Development Mode" view will appear, showing a URL.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-NfQiaEMDQLY/TtqsY6h32II/AAAAAAAAATU/srB_CD9dd8A/s1600/dev-mode-view.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="32" width="200" src="http://3.bp.blogspot.com/-NfQiaEMDQLY/TtqsY6h32II/AAAAAAAAATU/srB_CD9dd8A/s200/dev-mode-view.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Let's quickly take a look at the URL:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;http://127.0.0.1:8888/SimpleApp.html?gwt.codesvr=127.0.0.1:9997&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The web server (GWT uses &lt;a href="http://www.mortbay.org/"&gt;jetty&lt;/a&gt;, a light-weight web container) runs on port 8888 (remember that "127.0.0.1" is the same as "localhost").  The "gwt.codesvr" query string parameter is how GWT is able to run the application without compiling the project into Javascript.  This is called running it in "hosted mode" (which is what you'll always want to do while developing and debugging a project).  If this parameter is removed, it will instead use the Javascript code that was generated the last time the project was compiled (more on compilation later).  This is called running it in "web mode".  We haven't compiled the project yet, so running it in web mode will throw an error because there's no Javascript code to use.&lt;/p&gt;&lt;p&gt;Now, load the URL in your browser.  In order to run the application in hosted mode, you have to install a browser plugin, so install the plugin and then refresh the page.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-lt9whGGcjjg/Ttqse6Uru_I/AAAAAAAAATg/ZeFWDIBT7zg/s1600/browser-plugin.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="58" width="200" src="http://3.bp.blogspot.com/-lt9whGGcjjg/Ttqse6Uru_I/AAAAAAAAATg/ZeFWDIBT7zg/s200/browser-plugin.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;After a few seconds the web page will appear, asking you to enter your name.  Enter your name and click send.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-okfeuNt-DVU/Ttqsj6Ia8RI/AAAAAAAAATs/BSVRgrsUv8M/s1600/run-1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="108" width="200" src="http://2.bp.blogspot.com/-okfeuNt-DVU/Ttqsj6Ia8RI/AAAAAAAAATs/BSVRgrsUv8M/s200/run-1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;A popup message will appear with some information.  What it's doing here is sending a message to the server using GWT RPC, which is the main mechanism GWT uses to communicate between the client (a Javascript-powered web page) and the server (a Java web application).&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Xg61u7RM4lg/TtqsoIjnEMI/AAAAAAAAAT4/R1QndxBYWg0/s1600/run-2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="176" width="200" src="http://4.bp.blogspot.com/-Xg61u7RM4lg/TtqsoIjnEMI/AAAAAAAAAT4/R1QndxBYWg0/s200/run-2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h1&gt;Debugging the application&lt;/h1&gt;&lt;p&gt;You can set breakpoints and debug the GWT application just like any other Java application.  Just be sure to select "Debug As &gt; Web Application" instead of "Run As &gt; Web Application" when running the application.&lt;/p&gt;&lt;h1&gt;Translatable vs non-translatable code&lt;/h1&gt;&lt;p&gt;One thing to keep in mind when developing a GWT application is the idea of distinguishing between Java code that &lt;em&gt;should&lt;/em&gt; be converted to Javascript, from the Java code that &lt;em&gt;shouldn't&lt;/em&gt; be converted to Javascript.  This is defined in the "SimpleApp.gwt.xml" file, which is a configuration file for the application.&lt;/p&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;module rename-to='simpleapp'&amp;gt;&lt;br /&gt;  &amp;lt;!-- Inherit the core Web Toolkit stuff.                        --&amp;gt;&lt;br /&gt;  &amp;lt;inherits name='com.google.gwt.user.User'/&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Inherit the default GWT style sheet.  You can change       --&amp;gt;&lt;br /&gt;  &amp;lt;!-- the theme of your GWT application by uncommenting          --&amp;gt;&lt;br /&gt;  &amp;lt;!-- any one of the following lines.                            --&amp;gt;&lt;br /&gt;  &amp;lt;inherits name='com.google.gwt.user.theme.clean.Clean'/&amp;gt;&lt;br /&gt;  &amp;lt;!-- &amp;lt;inherits name='com.google.gwt.user.theme.standard.Standard'/&amp;gt; --&amp;gt;&lt;br /&gt;  &amp;lt;!-- &amp;lt;inherits name='com.google.gwt.user.theme.chrome.Chrome'/&amp;gt; --&amp;gt;&lt;br /&gt;  &amp;lt;!-- &amp;lt;inherits name='com.google.gwt.user.theme.dark.Dark'/&amp;gt;     --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Other module inherits                                      --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Specify the app entry point class.                         --&amp;gt;&lt;br /&gt;  &amp;lt;entry-point class='com.acme.simpleapp.client.SimpleApp'/&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Specify the paths for translatable code                    --&amp;gt;&lt;br /&gt;  &amp;lt;source path='client'/&amp;gt;&lt;br /&gt;  &amp;lt;source path='shared'/&amp;gt;&lt;br /&gt;&amp;lt;/module&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you can see, the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tags mark the "client" and "shared" packages as Javascript-compatible, which means they will be converted to Javascript code when the application is complied.&lt;/p&gt;&lt;p&gt;Keep in mind that, GWT can only convert certain Java classes to Javascript (for a complete list, see the &lt;a href="http://code.google.com/webtoolkit/doc/latest/RefJreEmulation.html"&gt;JRE Emulation Reference&lt;/a&gt;).  So, for example, even though creating a &lt;code&gt;FileOutputStream&lt;/code&gt; in the Java code won't throw any errors when the Java class is complied, an error &lt;em&gt;will&lt;/em&gt; be thrown when the project is compiled to Javascript or run in hosted mode.  To help you remember which Java classes are translatable, just remember that you're writing code for Javascript and there's certain things you can't do in this environment (like save files to disk, for example).  As long as you keep this in mind, you won't need to refer to the &lt;a href="http://code.google.com/webtoolkit/doc/latest/RefJreEmulation.html"&gt;JRE Emulation Reference&lt;/a&gt; very often.&lt;/p&gt;&lt;p&gt;Packages that are not defined as translatable code will run on the server as real Java code, so you can do whatever you want with them.&lt;/p&gt;&lt;h1&gt;Source code&lt;/h1&gt;&lt;p&gt;Let's just drill down each of the files in the "src" directory and describe what they do:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;SimpleApp.gwt.xml&lt;/strong&gt;: This file is called the "module definition".  It contains the project's configuration settings including which Java classes are translatable to Javascript, the "entry point" class (like the "main" method for the client), and the application's look and feel.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GreetingService.java&lt;/strong&gt;: An interface which defines the GWT RPC methods that are used by the client to communicate with the server.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GreetingServiceAsync.java&lt;/strong&gt;: An alternate version of the &lt;code&gt;GreetingService&lt;/code&gt; interface that the client uses to perform asynchronous communication with the server.  Note that these two interfaces must remain in-sync, so any time the &lt;code&gt;GreetingService&lt;/code&gt; interface is changed, the &lt;code&gt;GreetingServiceAsync&lt;/code&gt; interface must also be changed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;SimpleApp.java&lt;/strong&gt;: Contains all of the client-side GUI code.  It is also the entry point class.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GreetingsServiceImpl.java&lt;/strong&gt;: The server-side code that is called when the client sends requests to the server via GWT RPC.  It provides the implementation of all of GWT RPC methods defined in &lt;code&gt;GreetingService&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;FieldVerifier.java&lt;/strong&gt;: Contains validation code that is used on both the client and server.  This is one of the advantages to using GWT--because the same code can be used for both the client and server, only one version needs to be maintained.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Client-side code&lt;/h2&gt;&lt;p&gt;Let's quickly take a look at the client-side code.  In this sample application most of the code is in one class, &lt;code&gt;SimpleApp&lt;/code&gt;.&lt;/p&gt;&lt;pre class="brush: java"&gt;public class SimpleApp implements EntryPoint {&lt;br /&gt;&lt;br /&gt;  private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);&lt;br /&gt;&lt;br /&gt;  public void onModuleLoad() {&lt;br /&gt;    [...]&lt;br /&gt;  }&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;SimpleApp&lt;/code&gt; class implements the &lt;code&gt;EntryPoint&lt;/code&gt; interface and thus must implement the &lt;code&gt;onModuleLoad()&lt;/code&gt; method.  This method is where the application "starts" when the client loads it in her browser (like a "main" method).  To communicate with the server over GWT RPC, an instance of the &lt;code&gt;GreetingServiceAsync&lt;/code&gt; class is created.&lt;/p&gt;&lt;h2&gt;Server-side code&lt;/h2&gt;&lt;p&gt;When the client communicates with the server via GWT RPC using the &lt;code&gt;GreetingServiceAsync&lt;/code&gt; class, the code in &lt;code&gt;GreetingServiceImpl&lt;/code&gt; class is executed.&lt;/p&gt;&lt;pre class="brush: java"&gt;public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {&lt;br /&gt;&lt;br /&gt;  public String greetServer(String input) throws IllegalArgumentException {&lt;br /&gt;    [...]&lt;br /&gt;  }&lt;br /&gt;  [...]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Under the covers, this class is just a plain Java servlet, but with some GWT additions.  Notably, it does the work of converting all requests and responses to JSON when sent over the wire.&lt;/p&gt;&lt;p&gt;The only GWT RPC method here is &lt;code&gt;greetServer&lt;/code&gt;.  It accepts a String as the request and returns a String in the response.&lt;/p&gt;&lt;h1&gt;Compiling&lt;/h1&gt;&lt;p&gt;When you're ready to deploy your GWT application to production, you'll want to compile it.  This will translate all the client-side Java code into Javascript code, making it a fully-functional web application that you can run from a web container like Tomcat.&lt;/p&gt;&lt;p&gt;To do this, right click on the project folder and select "Google &gt; GWT Compile".  When it's done, just copy the "war" directory into the web container.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6629597547515747569?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6629597547515747569/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6629597547515747569' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6629597547515747569'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6629597547515747569'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/gwt-introduction.html' title='GWT Introduction'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-jepZrOvULe4/TtqtCzs0OvI/AAAAAAAAAUE/H2idWnn3L3s/s72-c/gwt-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-976954038554583574</id><published>2011-12-02T20:48:00.000-05:00</published><updated>2011-12-03T18:31:07.083-05:00</updated><title type='text'>Apache Commons IO Introduction</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-GExOF6-B7OA/Ttl-GfSYWbI/AAAAAAAAASk/xT1N0Tu0u9o/s1600/apache-feather.jpg" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="76" width="200" src="http://1.bp.blogspot.com/-GExOF6-B7OA/Ttl-GfSYWbI/AAAAAAAAASk/xT1N0Tu0u9o/s200/apache-feather.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://commons.apache.org/"&gt;Apache Commons&lt;/a&gt; project is a collection of over 40 individual Java libraries that provide convenience methods to eleviate the developer from having to write tedious, boiler-plate code.  The libraries try to stay as close to the core Java API as possible, so they have few or no dependencies on other libraries.&lt;/p&gt;&lt;p&gt;One of the libraries that I use a lot is the &lt;a href="http://commons.apache.org/io/"&gt;IO&lt;/a&gt; library.  It contains features that build upon the core Java IO API and also make it easier to interact with the filesystem.  I'm going to list three of the methods that I use a lot (and that you should use too).  Looking at the &lt;a href="http://commons.apache.org/io/api-release/index.html"&gt;API&lt;/a&gt; though, there's a ton more stuff this library can do, so don't let my tiny blog post keep you from exploring it.&lt;/p&gt;&lt;h1&gt;IOUtils.copy()&lt;/h1&gt;&lt;p&gt;The &lt;code&gt;IOUtils.copy()&lt;/code&gt; method is great for copying data from an &lt;code&gt;InputStream&lt;/code&gt; (or &lt;code&gt;Reader&lt;/code&gt;) to an &lt;code&gt;OutputStream&lt;/code&gt; (or &lt;code&gt;Writer&lt;/code&gt;).  No more creating arbitrarily-sized buffers and parenthesis-laden while loops!  What it does here isn't rocket-science--any programmer worth their salt could code this up themselves.  The point of using it is to make your code easier to read, less error-prone, and more consistent with the broader Java programming community.&lt;/p&gt;&lt;p&gt;These two code samples show just how much less code you have to write if you use this method:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Plain Java:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;InputStream in = ...&lt;br /&gt;OutputStream out = ...&lt;br /&gt;byte buffer[] = new byte[4096];&lt;br /&gt;int read;&lt;br /&gt;while ((read = in.read(buffer)) != -1){&lt;br /&gt;  out.write(buffer, 0, read);&lt;br /&gt;}&lt;br /&gt;in.close();&lt;br /&gt;out.close();&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Commons IO:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;InputStream in = ...&lt;br /&gt;OutputStream out = ...&lt;br /&gt;IOUtils.copy(in, out);&lt;br /&gt;in.close();&lt;br /&gt;out.close();&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;IOUtils.closeQuietly()&lt;/h1&gt;&lt;p&gt;The &lt;code&gt;IOUtils.closeQuietly()&lt;/code&gt; method eliminates the boiler-plate required to close a stream in Java.  It (1) checks to make sure the stream isn't null, (2) closes the stream, and (3) catches the &lt;code&gt;IOException&lt;/code&gt; that is thrown when the stream's &lt;code&gt;close()&lt;/code&gt; method is called.  Again, what it does here isn't rocket-science.  Its purpose is to reduce the amount of fluff in your code:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Plain Java:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;InputStream in = null;&lt;br /&gt;try{&lt;br /&gt;  in = ...&lt;br /&gt;  ...&lt;br /&gt;} catch (IOException e){&lt;br /&gt;  ...&lt;br /&gt;} finally{&lt;br /&gt;  try{&lt;br /&gt;    if (in != null){&lt;br /&gt;      in.close();&lt;br /&gt;    }&lt;br /&gt;  } catch (IOException e){}&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Commons IO:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;InputStream in = null;&lt;br /&gt;try{&lt;br /&gt;  in = ...&lt;br /&gt;  ...&lt;br /&gt;} catch (IOException e){&lt;br /&gt;  ...&lt;br /&gt;} finally{&lt;br /&gt;  IOUtils.closeQuietly(in);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;FileUtils.writeStringToFile()&lt;/h1&gt;&lt;p&gt;Whenever I switch from coding in PHP to coding in Java, my heart sinks a little bit when I have to write some text to a file.  In PHP, you can do this with a single function call.  But in Java, you need to write a dozen or so lines of code.  The &lt;code&gt;FileUtils.writeStringToFile()&lt;/code&gt; method, however, allows you to write text to a file in one fell swoop.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Plain Java:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;String text = "some text";&lt;br /&gt;File file = new File("myfile.txt");&lt;br /&gt;Writer writer = null;&lt;br /&gt;try{&lt;br /&gt;  writer = new PrintWriter(file);&lt;br /&gt;  writer.write(text);&lt;br /&gt;} catch (IOException e){&lt;br /&gt;  ...&lt;br /&gt;} finally {&lt;br /&gt;  if (writer != null){&lt;br /&gt;    try{&lt;br /&gt;      writer.close();&lt;br /&gt;    } catch (IOException e){}&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Commons IO:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;String text = "some text";&lt;br /&gt;File file = new File("myfile.txt");&lt;br /&gt;try{&lt;br /&gt;  FileUtils.writeStringToFile(file, text);&lt;br /&gt;} catch (IOException e){&lt;br /&gt;  ...&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I hope you've enjoyed my little introduction to &lt;a href="http://commons.apache.org/io/"&gt;Commons IO&lt;/a&gt;.  Using this library can greatly increase the readability and reliability of your code.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-976954038554583574?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/976954038554583574/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=976954038554583574' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/976954038554583574'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/976954038554583574'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/12/apache-commons-io-introduction.html' title='Apache Commons IO Introduction'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-GExOF6-B7OA/Ttl-GfSYWbI/AAAAAAAAASk/xT1N0Tu0u9o/s72-c/apache-feather.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8486423064150399146</id><published>2011-11-23T12:33:00.001-05:00</published><updated>2011-11-25T12:34:38.345-05:00</updated><title type='text'>Book review: Tomcat 6 Developer's Guide</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-bhSnTdVzS8Y/Ts0vv51R8-I/AAAAAAAAASY/XwwpZdwsy_c/s1600/cover.jpg" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="151" width="125" src="http://4.bp.blogspot.com/-bhSnTdVzS8Y/Ts0vv51R8-I/AAAAAAAAASY/XwwpZdwsy_c/s400/cover.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The book is very wholistic in its approach to educating the reader.  It provides a well-written overview of JavaEE and HTTP in Chapter 2.  And whenever it mentions a technology that Tomcat uses in some way, it immediately gives a brief overview of that technology.  I found this helpful because it gave me the knowledge I needed to continue reading without having to switch to a search engine.  For example, in Chapter 5, when discussing the JNDI functionality that Tomcat comes packaged with, the book pauses for a moment to explain the basic concepts of JNDI itself.  The book will also often describe the historical origins of the technology, like in Chapter 1, where it discusses the origins of Ant (spoiler alert: it was created as a custom build system for Tomcat).&lt;/p&gt;&lt;p&gt;The instructions for downloading the Tomcat source code and building it are very clear.  It provides a specific branch you can checkout from the Tomcat Subversion server, which is the exact version of Tomcat that was used when writing the book.  This allows you to follow along without worrying about dealing with differences between what you see in the code and what you see in the book.  And it also includes instructions for getting the project properly configured with Eclipse.&lt;/p&gt;&lt;p&gt;The later chapters of the book go into a lot of detail about the various classes that make up the Tomcat source code, so be prepared for some serious code spelunking.  Anyone who is interested in doing serious coding in the Tomcat codebase will gain a lot from these chapters.&lt;/p&gt;&lt;p&gt;I'd recommend this book to people who want to work on the Tomcat source code and need something to guide them through it.  I'd also recommend the book to JavaEE developers who use Tomcat on a regular basis and who want to expand their knowledge.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8486423064150399146?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8486423064150399146/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8486423064150399146' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8486423064150399146'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8486423064150399146'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/11/book-review-tomcat-6-developers-guide.html' title='Book review: Tomcat 6 Developer&apos;s Guide'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-bhSnTdVzS8Y/Ts0vv51R8-I/AAAAAAAAASY/XwwpZdwsy_c/s72-c/cover.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4458762185298701518</id><published>2011-10-15T13:19:00.000-04:00</published><updated>2011-10-15T13:26:11.099-04:00</updated><title type='text'>Microsoft Word: Disabling spellcheck for a block of text</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-u9YjN0aEe1Q/TpnCHFoipoI/AAAAAAAAARs/LQVOUP_ks98/s1600/clippy.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="120" width="120" src="http://2.bp.blogspot.com/-u9YjN0aEe1Q/TpnCHFoipoI/AAAAAAAAARs/LQVOUP_ks98/s400/clippy.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Often times, when I'm putting together a piece of documentation for work, it helps to include code samples and console commands in the document.  I like to use Word to write documentation because it allows you to stylize the text (such as increasing the font size of headers, making things bold, etc) which I think makes it easier to read (when used in moderation).  It's also nice for code samples because when you copy and paste code from Eclipse, it includes the syntax-highlighting of the code.  Creating plain text files is simpler, but you loose that readability aspect I think is important.&lt;/p&gt;&lt;p&gt;Anyway, one annoying thing is that Word spell checks and grammar checks these code samples and console commands, so you get all these red and green squiggly lines under the text.&lt;/p&gt;&lt;p&gt;However, it is possible to disable spell and grammar check for selected blocks of text!  First, select the text that you want to exclude from being checked.  Then, select the "Review" tab from the ribbon at the top of the screen and click on "Set Language".&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-myQgLc8_vbo/Tpm-ZNChZAI/AAAAAAAAARI/ZaOLadkuMtg/s1600/1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="106" width="400" src="http://1.bp.blogspot.com/-myQgLc8_vbo/Tpm-ZNChZAI/AAAAAAAAARI/ZaOLadkuMtg/s400/1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;In the dialog window that appears, click the "Do not check spelling and grammar" checkbox and click "OK".  If your document happens to have multiple written languages in it, you can also use this dialog to tell Word which blocks of text are written in which language.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-vfo_kAeUMd8/Tpm-uFHy9LI/AAAAAAAAARU/9rQP9mgGwIs/s1600/2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="309" width="270" src="http://2.bp.blogspot.com/-vfo_kAeUMd8/Tpm-uFHy9LI/AAAAAAAAARU/9rQP9mgGwIs/s400/2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Now, those squiggly lines should disappear!&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-k94M95bWZ1U/Tpm-zvKPGkI/AAAAAAAAARg/dLzAf5nXBM8/s1600/3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="105" width="400" src="http://1.bp.blogspot.com/-k94M95bWZ1U/Tpm-zvKPGkI/AAAAAAAAARg/dLzAf5nXBM8/s400/3.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4458762185298701518?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4458762185298701518/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4458762185298701518' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4458762185298701518'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4458762185298701518'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/10/microsoft-word-disabling-spellcheck-for.html' title='Microsoft Word: Disabling spellcheck for a block of text'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-u9YjN0aEe1Q/TpnCHFoipoI/AAAAAAAAARs/LQVOUP_ks98/s72-c/clippy.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-1115771120732284965</id><published>2011-10-08T14:10:00.000-04:00</published><updated>2011-10-08T14:10:26.072-04:00</updated><title type='text'>Book Review: Play Framework Cookbook</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-N0dmrDfjhsI/TpCQ8uyW2hI/AAAAAAAAARA/foVh4WEj9k4/s1600/cover.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="152" width="125" src="http://3.bp.blogspot.com/-N0dmrDfjhsI/TpCQ8uyW2hI/AAAAAAAAARA/foVh4WEj9k4/s400/cover.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.packtpub.com/play-framework-cookbook/book"&gt;Play Framework Cookbook&lt;/a&gt; is divided into seven chapters, each of which contains a dozen or so recipes.  The recipes range in skill level from beginner to advanced and are pretty much organized in this order, with the more basic recipes at the start of the book and the more advanced ones at the end.  Some examples of the more basic recipes include "Defining your own controllers" and "Basics of caching".  Advanced topics include "Understanding bytecode enhancement" and "Implementing your own persistence layer".  Nearly all of the recipes are self-contained so you don't have to read the book from start to finish--you can jump around at your leasure and read whatever looks interesting to you.&lt;/p&gt;&lt;p&gt;Each recipe is usually divided into four sections.  "Getting Ready" describes how to prepare your development environment for the recipe.  "How to do it..." covers the steps you have to take to complete the recipe, like what code you have to write.  A detailed explaination of those steps can be found in the next section, "How it works...".  The last section, "There's more..." (what's with all the ellipses?), contains supplemental information on how to improve upon the recipe and broaden its scope.  I like the way the author divided each recipe this way because it makes it easier to find what you're looking for.  For example, if I'm in the midst of coding something and need to refresh my memory about a particular method call or class name, I can jump right to the "How to do it..." section and not have to wade through a lot of text to find what I'm looking for.&lt;/p&gt;&lt;p&gt;I would say that the only prerequisite of the book is that you know how to program in Java, though having basic knowledge of Play beforehand will help.  The Forward contains an informative overview that describes where Play fits into wider the Java web application world and how it differentiates itself from other web frameworks.  And the recipes in the first chapter contain a lot of the basics of the framework, like how to define routes and create controllers.&lt;/p&gt;&lt;p&gt;But all the information in the first chapter can be found in the official Play documentation online.  Where the book really shines is in the subsequent chapters.  They describe how to perform specific tasks such as creating an RSS feed, creating PDFs, and generating JSON data.  They also describe how to integrate Play with a variety of systems like Apache, MongoDB, Spring, Google Charts, and Jenkins.  Getting an application to "play nice" with the other parts of a system, no matter what technology you are using, can be a hairy ordeal, so I think these recipes are especially helpful.&lt;/p&gt;&lt;p&gt;All of the code samples can be downloaded from the book's website.  Each sample corresponds to a single recipe and is a complete Play application, which is great because it means the samples are self-contained and can be run right off the bat.  However, I had a little trouble getting some of them to run.  I had to manually enable the in-memory database (the "db=mem" config property) because I was getting JPA errors on application startup.  But otherwise, the samples seemed to work fine.&lt;/p&gt;&lt;p&gt;All in all, I would recommend this book to anyone interested in learning more about Play.  It covers a wide variety of topics, so you're bound to find something useful no matter what kind of Play application you're developing (or want to develop) or what your skill level is.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-1115771120732284965?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/1115771120732284965/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=1115771120732284965' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1115771120732284965'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1115771120732284965'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/10/book-review-play-framework-cookbook.html' title='Book Review: Play Framework Cookbook'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-N0dmrDfjhsI/TpCQ8uyW2hI/AAAAAAAAARA/foVh4WEj9k4/s72-c/cover.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6277117776559929543</id><published>2011-09-19T18:48:00.001-04:00</published><updated>2011-09-19T18:48:46.116-04:00</updated><title type='text'>Upcoming book review: "Play Framework Cookbook"</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-wKrV5B5PGLI/TnfFZGgOB_I/AAAAAAAAAQ4/NFw50Yc7220/s1600/cover.jpg" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="152" width="125" src="http://4.bp.blogspot.com/-wKrV5B5PGLI/TnfFZGgOB_I/AAAAAAAAAQ4/NFw50Yc7220/s400/cover.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;I got an interesting email last Friday.  A publisher, &lt;a href="http://www.packtpub.com"&gt;Packt Publishing&lt;/a&gt;, noticed my recent blog posts about the &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; and asked if I would review the book, &lt;a href="http://www.packtpub.com/play-framework-cookbook/book"&gt;Play Framework Cookbook&lt;/a&gt; by Alexander Reelsen.  I've never been asked to write a book review before, so I'm really excited about it!  I'll be posting a review of the book within the next 2-3 weeks.&lt;/p&gt;&lt;p&gt;In the mean time, feel free to check out a &lt;a href="http://www.packtpub.com/sites/default/files/5528OS-Chapter-2-Using-Controllers.pdf?utm_source=packtpub&amp;amp;utm_medium=free&amp;amp;utm_campaign=pdf"&gt;sample chapter&lt;/a&gt; from the book entitled "Using Controllers" where the author covers topics such as how to generate PDFs and implement HTTP digest authentication.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6277117776559929543?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6277117776559929543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6277117776559929543' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6277117776559929543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6277117776559929543'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/upcoming-book-review-play-framework.html' title='Upcoming book review: &quot;Play Framework Cookbook&quot;'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-wKrV5B5PGLI/TnfFZGgOB_I/AAAAAAAAAQ4/NFw50Yc7220/s72-c/cover.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4667638093660480614</id><published>2011-09-17T11:37:00.000-04:00</published><updated>2011-09-17T11:37:05.129-04:00</updated><title type='text'>The Play Framework - Part 6</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-nH2yCYJSloE/TnS-RKQK1tI/AAAAAAAAAQw/rvWAC-SZDwk/s1600/play-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="56" width="152" src="http://1.bp.blogspot.com/-nH2yCYJSloE/TnS-RKQK1tI/AAAAAAAAAQw/rvWAC-SZDwk/s400/play-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;Here are a couple miscellaneous features of the Play Framework:&lt;/p&gt;&lt;p&gt;In the "conf/routes" file, a regular expression can be used to match a URL to a controller action.  The regular expression is enclosed within &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt; characters and comes before the variable name.  For example, the following route definition only matches if the last part of the URL is a number: &lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET  /posts/{&amp;lt;[0-9]+&amp;gt;id}  Application.show&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In a template, when looping through a list of elements, it is often helpful to know if the current element is the first or last element in the list.  To do this, append &lt;code&gt;_isFirst&lt;/code&gt; or &lt;code&gt;_isLast&lt;/code&gt; to the end of the variable name.  For example, the code below will output the contents of a list with each element separated by a comma.  A comma should always be printed after each element--except for the last element, which shouldn't print a comma:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#{list items:tags, as:tag}&lt;br /&gt;  ${tag}${tag_isLast ? '' : ', '}&lt;br /&gt;#{/list}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4667638093660480614?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4667638093660480614/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4667638093660480614' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4667638093660480614'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4667638093660480614'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework-part-6.html' title='The Play Framework - Part 6'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-nH2yCYJSloE/TnS-RKQK1tI/AAAAAAAAAQw/rvWAC-SZDwk/s72-c/play-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3221947982750389157</id><published>2011-09-15T21:16:00.000-04:00</published><updated>2011-09-15T21:16:36.083-04:00</updated><title type='text'>The Play Framework - Part 5 - CAPTCHAs</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-tmQwGYTI-sI/TnKhCQeXLDI/AAAAAAAAAQg/aCyGb7NgYZk/s1600/play-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="56" width="152" src="http://1.bp.blogspot.com/-tmQwGYTI-sI/TnKhCQeXLDI/AAAAAAAAAQg/aCyGb7NgYZk/s400/play-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;Play comes packaged with a CAPTCHA generator, which is good for ensuring that form submissions are sent by a human being and not by an automated bot.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;CAPTCHA&lt;/strong&gt; is an image that contains random letters and numbers that the user has to type in order to submit a form.  The letters and numbers are rendered in a non-uniform way in order to make it as difficult as possible for an automated image recognition algorithm to read (but still clear enough for a human to read).  For example, the characters may each be a different size and font.  It stands for (take a deep breath) "&lt;strong&gt;C&lt;/strong&gt;ompletely &lt;strong&gt;A&lt;/strong&gt;utomated &lt;strong&gt;P&lt;/strong&gt;ublic &lt;strong&gt;T&lt;/strong&gt;uring test to tell &lt;strong&gt;C&lt;/strong&gt;omputers and &lt;strong&gt;H&lt;/strong&gt;umans &lt;strong&gt;A&lt;/strong&gt;part".&lt;/p&gt;&lt;p&gt;To generate a CAPTCHA in Play, use the &lt;code&gt;Images.Captcha&lt;/code&gt; class:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;public class Application extends Controller{&lt;br /&gt;  public static void captcha(){&lt;br /&gt;    Images.Captcha captcha = Images.captcha();&lt;br /&gt;    String code = captcha.getText("#990000");&lt;br /&gt;    captcha.addNoise("#CCCCCC");&lt;br /&gt;    captcha.setBackground("#996633", "#FF9900");&lt;br /&gt;    renderBinary(captcha);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this code, I'm generating a CAPTCHA image and returning it in the response.  Calling the &lt;code&gt;getText()&lt;/code&gt; method sets the color of the text and returns the random string that the CAPTCHA is rendering.  As you can see, the notation used to define the color is conveniently the same notation that is used in HTML and CSS (however, you cannot use the three-character abbreviated form used by CSS).&lt;/p&gt;&lt;p&gt;I'm also adding noise to the image to make it even harder for an image recognition algorithm to read (it will add a gray squiggly line through the image).  And then, I'm setting a background gradient to have it fade from a dark to a light orange.&lt;/p&gt;&lt;p&gt;Because the &lt;code&gt;Captcha&lt;/code&gt; class implements &lt;code&gt;InputStream&lt;/code&gt;, it can be passed into the &lt;code&gt;renderBinary()&lt;/code&gt; method to send the image to the response.  It generates a PNG image.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-8Uf5IoCQbIQ/TnKhIZMh7-I/AAAAAAAAAQo/BxkxP3IcaAo/s1600/captcha2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="50" width="150" src="http://3.bp.blogspot.com/-8Uf5IoCQbIQ/TnKhIZMh7-I/AAAAAAAAAQo/BxkxP3IcaAo/s400/captcha2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3221947982750389157?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3221947982750389157/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3221947982750389157' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3221947982750389157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3221947982750389157'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework-part-5-captchas.html' title='The Play Framework - Part 5 - CAPTCHAs'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-tmQwGYTI-sI/TnKhCQeXLDI/AAAAAAAAAQg/aCyGb7NgYZk/s72-c/play-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8762545341730320539</id><published>2011-09-13T20:45:00.000-04:00</published><updated>2011-09-13T20:46:14.148-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='javaee'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>The Play Framework - Part 4</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-_Q8vcrMnYLU/Tm_41GUU4qI/AAAAAAAAAP4/kpDqOcgabzI/s1600/play-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="56" width="152" src="http://2.bp.blogspot.com/-_Q8vcrMnYLU/Tm_41GUU4qI/AAAAAAAAAP4/kpDqOcgabzI/s400/play-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;A special template syntax is used to link to other pages in the application (the actual URL of the page is not used).  This makes things more flexible, because you can change the page's URL in the "conf/routes" file without having to go through each template file and edit all the links that point to that page.  Take this example:&lt;/p&gt;&lt;p&gt;Template file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;a href="@{Application.index()}"&amp;gt;Home&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;"conf/routes" file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET  /home   Application.index&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Generated HTML:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;a href="/home"&amp;gt;Home&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Play sees that the &lt;code&gt;Application.index&lt;/code&gt; method is assigned to the URL "/home" and generates the appropriate HTML when a user requests the page.&lt;/p&gt;&lt;p&gt;To access a static resource (like images, Javascript files, and CSS files), put the path to that resource in single quotes.  With Play, all static resources go in the "public" directory.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;script src="@{'/public/javascripts/jquery-1.4.2.min.js'}"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As with seemingly every type of file in a Play application, any changes made to the "conf/routes" file while the application is running are applied immediately.  No restart is needed.&lt;/p&gt;&lt;p&gt;The "form" template tag can be used to create HTML forms:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#{form @Application.postComment(post.id)}&lt;br /&gt;  Name: &amp;lt;input type="text" name="author" value="${params.author}" /&amp;gt;&lt;br /&gt;  &amp;lt;input type="submit" value="Post" /&amp;gt;&lt;br /&gt;#{/form}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This ends up adding two more attributes to the generated &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag ("accept-charset" and "enctype") and also adds a hidden parameter called "authenticityToken" (probably used for security purposes).  You can also add attributes of your own, by supplying them as tag parameters after the action URL:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#{form @Application.postComment(post.id), onsubmit:'return validateForm()'}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8762545341730320539?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8762545341730320539/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8762545341730320539' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8762545341730320539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8762545341730320539'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework-part-4.html' title='The Play Framework - Part 4'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-_Q8vcrMnYLU/Tm_41GUU4qI/AAAAAAAAAP4/kpDqOcgabzI/s72-c/play-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6419448972500183137</id><published>2011-09-10T15:35:00.001-04:00</published><updated>2011-09-11T10:56:38.079-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='play'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='javaee'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>The Play Framework - Part 3</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-p0_i7ZiBRKc/Tmu7bRKOWuI/AAAAAAAAAPw/eP4nB_bUKeY/s1600/play-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="56" width="152" src="http://1.bp.blogspot.com/-p0_i7ZiBRKc/Tmu7bRKOWuI/AAAAAAAAAPw/eP4nB_bUKeY/s400/play-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;If you want to execute some code when the application first starts up, you can create a class that has the &lt;code&gt;@OnApplicationStart&lt;/code&gt; annotation and that extends the &lt;code&gt;Job&lt;/code&gt; class.  Put the code that you want to run on startup in a method called &lt;code&gt;doJob()&lt;/code&gt;.  This class can just go in the default package for simplicity, but it doesn't matter what package the class is in.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@OnApplicationStart&lt;br /&gt;public class Bootstrap extends Job {&lt;br /&gt;  @Override&lt;br /&gt;  public void doJob(){&lt;br /&gt;    if (User.count() == 0){&lt;br /&gt;      Fixtures.loadModels("initial-data.yml");&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In DEV mode, this will be run when the application gets its first request.  That way, if an error occurrs, it will appear in the browser.  But in PROD mode, the startup code will be run as soon as the &lt;code&gt;play run&lt;/code&gt; command is executed and if there are any errors, the server will fail to start up.&lt;/p&gt;&lt;p&gt;To pass variables to a template, simply pass them as arguments to the &lt;code&gt;render()&lt;/code&gt; method in the controller.  The variable will have the same name inside the template as it does inside the controller.&lt;/p&gt;&lt;p&gt;If you want to run some code every time a controller handles a request, use the &lt;code&gt;@Before&lt;/code&gt; annotation.  For example, the &lt;code&gt;addDefaults()&lt;/code&gt; method below will add two variables to the template every time a request comes in to the &lt;code&gt;Application&lt;/code&gt; controller (the &lt;code&gt;Play.configuration&lt;/code&gt; variable allows you to access values from the 'application.conf" file):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;public class Application extends Controller {&lt;br /&gt;  @Before&lt;br /&gt;  public static void addDefaults(){&lt;br /&gt;    //called before every request is handled&lt;br /&gt;    renderArgs.put(&lt;br /&gt;      "blogTitle",&lt;br /&gt;      Play.configuration.getProperty("blog.title")&lt;br /&gt;    );&lt;br /&gt;    renderArgs.put(&lt;br /&gt;      "blogBaseline",&lt;br /&gt;      Play.configuration.getProperty("blog.baseline")&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  //...&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Play adds some helpful utility methods to various datatypes inside template code.  Calling &lt;code&gt;pluralize()&lt;/code&gt; on a number will return the string "s" if the number is not 1.  Calling &lt;code&gt;format('MM/dd/yyyy')&lt;/code&gt; on a &lt;code&gt;Date&lt;/code&gt; object will format the date according to the given format string (you don't need to create a &lt;code&gt;SimpleDateFormat&lt;/code&gt; object).  Calling &lt;code&gt;nl2br()&lt;/code&gt; on a string will replace all newlines with &lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt; are snippets of template code that you can insert into other templates by calling them like functions.  The tag parameter names all begin with underscores in the tag file.  Tag files go in the "views/tags" directory.  The tag's name is the same as its file name (minus the file extension).&lt;/p&gt;&lt;p&gt;Calling a tag from a template:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#{hello person:'George', timeOfDay:'morning' /}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tag file (views/tags/hello.html):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Good ${_timeOfDay}, &amp;lt;b&amp;gt;${_person}&amp;lt;/b&amp;gt;!&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6419448972500183137?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6419448972500183137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6419448972500183137' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6419448972500183137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6419448972500183137'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework-part-3.html' title='The Play Framework - Part 3'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-p0_i7ZiBRKc/Tmu7bRKOWuI/AAAAAAAAAPw/eP4nB_bUKeY/s72-c/play-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8291190295566561452</id><published>2011-09-05T14:02:00.000-04:00</published><updated>2011-09-05T14:02:30.004-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='play'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='javaee'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>The Play Framework - Part 2 - Database</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-ZCKc6ajirss/TmUMSs0MH6I/AAAAAAAAAPo/qD8OjHj2fGE/s1600/play-logo.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="56" width="152" src="http://2.bp.blogspot.com/-ZCKc6ajirss/TmUMSs0MH6I/AAAAAAAAAPo/qD8OjHj2fGE/s400/play-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;To connect your application to a database, modify the "db.*" settings in the "conf/application.conf" file.  To get up and running quickly, uncomment the "db=mem" line to create an in-memory database (all changes will be lost, of course, when the application ends).&lt;/p&gt;&lt;p&gt;To save data to the database, start by creating entity classes inside the "model" package.  An entity class is a plain Java bean class that has JPA (Java Persistence Architecture) annotations.  Each entity class represents a table in the database, each instance of the class represents a row in the table, and each field in the instance represents a column in the row (getter and setter methods aren't needed for the fields--Play generates them automatically).  By default, the table name is the same as the class name.  To change the table name, use the &lt;code&gt;@Table&lt;/code&gt; annotation.&lt;/p&gt;&lt;p&gt;Each entity class can also optionally extend Play's &lt;code&gt;Model&lt;/code&gt; class, which gives it access to utility methods for saving and retrieving data.  For example, to insert a new row into the table, simply create a new instance of the class and call the &lt;code&gt;save()&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;User user = new User("Bob", "bob@gmail.com");&lt;br /&gt;user.save();&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To retrieve data, call the static &lt;code&gt;find()&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;List&amp;lt;User&amp;gt; users = User.find("byNameAndEmail", "Bob", "bob@gmail.com").fetch();&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You don't need to write any SQL code to interact with the database.  Play uses Hibernate for its persistence layer, so HQL (Hibernate Query Language) can be used for more complex queries.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;Model&lt;/code&gt; class also automatically generates an "id" primary key column for you (something that every table should have).  So basically, you should always extend this class.&lt;/p&gt;&lt;p&gt;Here's an example of an entity class.  It represents a blog post:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;package models;&lt;br /&gt;import java.util.*;&lt;br /&gt;import javax.persistence.*;&lt;br /&gt;import play.db.jpa.*;&lt;br /&gt;&lt;br /&gt;@Entity&lt;br /&gt;@Table(name="blog_posts")&lt;br /&gt;public class Post extends Model {&lt;br /&gt;  public String title;&lt;br /&gt;  public Date postedAt;&lt;br /&gt;&lt;br /&gt;  @Lob&lt;br /&gt;  //large amount of text&lt;br /&gt;  public String content;&lt;br /&gt;&lt;br /&gt;  @ManyToOne&lt;br /&gt;  //many Posts can belong to one User&lt;br /&gt;  public User author;&lt;br /&gt;&lt;br /&gt;  //a list of all comments that belong to this post&lt;br /&gt;  //(the comments are in a separate table)&lt;br /&gt;  //CascadeType.ALL = the post's comments will be automatically deleted when the post is deleted&lt;br /&gt;  @OneToMany(mappedBy="post", cascade=CascadeType.ALL)&lt;br /&gt;  public List&amp;lt;Comment&amp;gt; comments;&lt;br /&gt;&lt;br /&gt;  public Post(User author, String title, String content) {&lt;br /&gt;    comments = new ArrayList&amp;lt;Comment&amp;gt;();&lt;br /&gt;    this.author = author;&lt;br /&gt;    this.title = title;&lt;br /&gt;    this.content = content;&lt;br /&gt;    postedAt = new Date();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public Post addComment(String author, String content){&lt;br /&gt;    Comment newComment = new Comment(this, author, content).save();&lt;br /&gt;    comments.add(newComment);&lt;br /&gt;    save();&lt;br /&gt;    return this;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;Fixtures&lt;/code&gt; class provides more utility methods for interacting with the database.  For example, to delete the entire database, call the &lt;code&gt;deleteDatabase()&lt;/code&gt; method.  It can also execute SQL code contained in a file or persist the data in a specially-formatted YAML file (YAML is a new standard for representing structured data.  Its aim is to provide a better alternative to XML).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8291190295566561452?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8291190295566561452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8291190295566561452' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8291190295566561452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8291190295566561452'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework-part-2-database.html' title='The Play Framework - Part 2 - Database'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-ZCKc6ajirss/TmUMSs0MH6I/AAAAAAAAAPo/qD8OjHj2fGE/s72-c/play-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4981632706275613237</id><published>2011-09-04T12:55:00.000-04:00</published><updated>2011-09-04T13:06:43.482-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='play'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='javaee'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>The Play Framework</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-Q26fx8kO1Lc/TmOiLTkUQGI/AAAAAAAAAOg/kFcHfh2iMX4/s1600/logo.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="56" src="http://3.bp.blogspot.com/-Q26fx8kO1Lc/TmOiLTkUQGI/AAAAAAAAAOg/kFcHfh2iMX4/s200/logo.png" width="152" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="http://www.playframework.org/"&gt;Play Framework&lt;/a&gt; is a MVC web application framework for Java that focuses on being light-weight and easy to use--two qualities that you don't see very often in the Java web application world.  Writing a web application in Java has always been more complicated than, say, writing a web application in PHP.&lt;/p&gt;&lt;p&gt;The first thing you must do after downloading the Play framework is configure your path so that you can invoke the "play" executable:&lt;/p&gt;&lt;p&gt;&lt;code&gt;PATH=$PATH:/home/user/play-1.2.2&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Everything you do with Play is done through this &lt;code&gt;play&lt;/code&gt; executable.&lt;/p&gt;&lt;p&gt;To create a new application, run this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;play new appName&lt;/code&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3Y7o8av60tc/TmOijRr06AI/AAAAAAAAAOo/CRs2eLNDBmw/s1600/play1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="216" src="http://3.bp.blogspot.com/-3Y7o8av60tc/TmOijRr06AI/AAAAAAAAAOo/CRs2eLNDBmw/s400/play1.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;This creates a folder in the current directory with the same name as the application.  The folder is populated with the basic scaffolding needed for a Play application.&lt;/p&gt;&lt;p&gt;You can immediately run this application too.  To run the application, execute this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;play run appName&lt;/code&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-Lm6iTwo6FY4/TmOn_kQZYWI/AAAAAAAAAO4/qpKoFZ2Sspw/s1600/play6.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="216" width="400" src="http://1.bp.blogspot.com/-Lm6iTwo6FY4/TmOn_kQZYWI/AAAAAAAAAO4/qpKoFZ2Sspw/s400/play6.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;This will start up a web server and run your application at "http://localhost:9000".  You don't need any web servers of your own, everything is bundled within the framework (you don't even need a JavaEE web container like Tomcat).  The only thing you need to run Play is a JRE.&lt;/p&gt;&lt;p&gt;Note that, if you get an error that says, "ERROR ~ Could not bind on port 9000", then something on your computer must be using port 9000.  Port 9000 is the default port Play uses to run the application.  This setting is defined in "conf/application.conf" under the "http.port" setting (be sure to uncomment the line too--remove the "#" at the beginning of the line to uncomment it).  I changed the port to 9001 because 9000 didn't work for me.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-jPdClT7khWU/TmOnPMUSp_I/AAAAAAAAAOw/4gWv4JJf63M/s1600/play2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="216" width="400" src="http://4.bp.blogspot.com/-jPdClT7khWU/TmOnPMUSp_I/AAAAAAAAAOw/4gWv4JJf63M/s400/play2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Notice the line that says, "Listening for transport dt_socket at address: 8000", when the application is starting up.  This means the web application has opened a port which allows you to debug the application as it is running using an IDE like Eclipse.&lt;/p&gt;&lt;p&gt;After it successfully starts up, open a web browser and navigate to "http://localhost:9001".  When the web application gets its first request, it compiles all the Java code and template code in the application for you (it saves the .class files to the "tmp" directory).   You don't have to manually compile any of the .java files yourself like in a normal JavaEE web application.  If you change any Java code or template code while the application is running, the application will detect this and automatically recompile it for you.  You don't have to restart the application.  This is how the web application behaves in DEV (development) mode (the default).  If started in PROD (production) mode, then this is not done in order to increase performance.&lt;/p&gt;&lt;p&gt;The application's unit tests (in the "test" folder) can be run from the web application itself.  To do this, execute this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;play test appName&lt;/code&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Vm3hQVbeZxY/TmOodBvSPOI/AAAAAAAAAPA/_4lUowaSMnk/s1600/play7.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="256" width="400" src="http://4.bp.blogspot.com/-Vm3hQVbeZxY/TmOodBvSPOI/AAAAAAAAAPA/_4lUowaSMnk/s400/play7.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;This will start the web application and also enable the unit test functionality at "http://localhost:9001/@tests".  The page displays all available tests and lets you select which tests to run.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-gY3fAA651UY/TmOsOGC-ZNI/AAAAAAAAAPg/ocnwaA-R7_k/s1600/play4.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="390" src="http://4.bp.blogspot.com/-gY3fAA651UY/TmOsOGC-ZNI/AAAAAAAAAPg/ocnwaA-R7_k/s400/play4.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Eclipse Integration&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;You can edit all of the files in a basic text editor of course, but for serious development work, this can be tedious.  To configure the Play application for Eclipse, execute this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;play eclipsify appName&lt;/code&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-bEmJu_SKEAg/TmOo5aQdZuI/AAAAAAAAAPI/8KOOVakycHM/s1600/play8.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="226" width="400" src="http://4.bp.blogspot.com/-bEmJu_SKEAg/TmOo5aQdZuI/AAAAAAAAAPI/8KOOVakycHM/s400/play8.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;This will create all the necessary configuration files (such as the ".project" file) needed to open the application as an Eclipse project.  It also creates a directory named "eclipse", which contains launch configurations that can be run from Eclipse:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-p4g4ps7YB5A/TmOp1988xRI/AAAAAAAAAPQ/hn0CxukLbwo/s1600/play-eclipse-folder.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="316" src="http://1.bp.blogspot.com/-p4g4ps7YB5A/TmOp1988xRI/AAAAAAAAAPQ/hn0CxukLbwo/s400/play-eclipse-folder.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;appName.launch&lt;/strong&gt; - Running this has the same effect as running &lt;code&gt;play run appName&lt;/code&gt; from the command line (it starts up the web application).  To run it, right-click on it and select "Run As &gt; appName".&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Connect JPDA to appName.launch&lt;/strong&gt; - This will connect Eclipse to the JPDA port (8000) while the application is running and allow you to debug the application in Eclipse.  Note that the application must already be running for this to work.  To run it, right-click on it and select "Debug As &gt; Connect JPDA to appName".  Go to the "Debug" perspective in Eclipse to see if it connected properly (you can see all the different web server threads in the upper-left hand corner).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Test appName.launch&lt;/strong&gt; - Running this has the same effect as running &lt;code&gt;play test appName&lt;/code&gt; from the command line.  To run it, right-click on it and select "Run As &gt; Test appName".  Make sure that you first shut down the web application if you had already started it using "appName.launch".&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Import the project into Eclipse by going to "File &gt; Import..." and then selecting "Existing Projects into Workspace".&lt;/p&gt;&lt;p&gt;If you try to open some of the template files (the .html files in "app/views") in Eclipse, some files will display an error that says, "Character encoding "${_response_encoding}" is not supported by this platform."  This is because Eclipse considers the file to be an HTML file and it looks at the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to determine the character encoding of the file (even though the file has a ".html" extension and has HTML tags, it isn't really an HTML file--Play uses a special templating engine that has a special syntax).  Well, the character encoding here is defined as a template variable, so Eclipse sees the variable name and gets confused.  Click the "Set Encoding..." button and set the encoding to "UTF-8".&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-NoWUTr4ixJ8/TmOqmfm4t8I/AAAAAAAAAPY/Y4Ui6jYnsL4/s1600/play5.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="159" width="400" src="http://3.bp.blogspot.com/-NoWUTr4ixJ8/TmOqmfm4t8I/AAAAAAAAAPY/Y4Ui6jYnsL4/s400/play5.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4981632706275613237?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4981632706275613237/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4981632706275613237' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4981632706275613237'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4981632706275613237'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/09/play-framework.html' title='The Play Framework'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-Q26fx8kO1Lc/TmOiLTkUQGI/AAAAAAAAAOg/kFcHfh2iMX4/s72-c/logo.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2624731007935422306</id><published>2011-06-14T21:33:00.000-04:00</published><updated>2011-06-14T21:33:33.526-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='phar'/><title type='text'>PHP - Relative paths and Phar Archives</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-Tq8CUS0GnSY/TfgLZCxWQOI/AAAAAAAAAOY/p25hk2hsM08/s1600/php.png" imageanchor="1" style="clear:right; float:right; margin-left:1em; margin-bottom:1em"&gt;&lt;img border="0" height="152" width="200" src="http://1.bp.blogspot.com/-Tq8CUS0GnSY/TfgLZCxWQOI/AAAAAAAAAOY/p25hk2hsM08/s200/php.png" /&gt;&lt;/a&gt;&lt;/div&gt;Working with file paths and Phar archives in PHP can be tricky.  The PHP code inside of a Phar file will treat relative paths as being relative to the Phar archive, *not* relative to the current working directory.  Here's a short example:&lt;br /&gt;&lt;br /&gt;Say you have the following files:&lt;br /&gt;&lt;pre&gt;phar/index.php&lt;br /&gt;test.php&lt;br /&gt;my.phar&lt;br /&gt;&lt;/pre&gt;The &lt;code&gt;index.php&lt;/code&gt; file is located inside of the &lt;code&gt;phar&lt;/code&gt; directory.  It is the bootstrap file for the phar archive:&lt;br /&gt;&lt;pre class="sh_php"&gt;function does_it_exist($file){&lt;br /&gt;  return file_exists($file) ? "true" : "false";&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The bootstrap file is executed when the phar file is included from a PHP script.  Our bootstrap file will simply cause the function "does_it_exist" to be declared.&lt;br /&gt;&lt;br /&gt;The file &lt;code&gt;my.phar&lt;/code&gt; is the packaged phar archive (see the &lt;a href="https://github.com/koto/phar-util"&gt;phar-util&lt;/a&gt; project for an excellent phar-building tool).  It contains the &lt;code&gt;index.php&lt;/code&gt; file at its root.&lt;br /&gt;&lt;br /&gt;Let's try running different code inside of &lt;code&gt;test.php&lt;/code&gt; and see what the results are for each run:&lt;br /&gt;&lt;pre class="sh_php"&gt;//Run 1:&lt;br /&gt;require_once 'phar/index.php';  //PHP file&lt;br /&gt;$file = __DIR__ . "/index.php"; //absolute path&lt;br /&gt;echo does_it_exist($file);      //prints "false"&lt;br /&gt;&lt;br /&gt;//Run 2:&lt;br /&gt;require_once 'phar/index.php';  //PHP file&lt;br /&gt;$file = "index.php";            //relative path&lt;br /&gt;echo does_it_exist($file);      //prints "false"&lt;br /&gt;&lt;br /&gt;//Run 3:&lt;br /&gt;require_once 'my.phar';         //phar file&lt;br /&gt;$file = __DIR__ . "/index.php"; //absolute path&lt;br /&gt;echo does_it_exist($file);      //prints "false"&lt;br /&gt;&lt;br /&gt;//Run 4:&lt;br /&gt;require_once 'my.phar';         //phar file&lt;br /&gt;$file = "index.php";            //relative path&lt;br /&gt;echo does_it_exist($file);      //prints "true"&lt;br /&gt;&lt;/pre&gt;Look at Run 4.  This code includes the phar file and passes the function a relative path.  Relative to the current working directory, &lt;code&gt;index.php&lt;/code&gt; does not exist.  But relative to the contents of the phar archive, it does exist, which is why it prints "true"!&lt;br /&gt;&lt;br /&gt;Any file paths that you manipulate inside of a phar archive must be absolute.  If they are relative, the phar will treat the path as being relative to the phar archive, not relative to the current working directory, which may not be what you intended!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2624731007935422306?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2624731007935422306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2624731007935422306' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2624731007935422306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2624731007935422306'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/06/php-relative-paths-and-phar-archives.html' title='PHP - Relative paths and Phar Archives'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-Tq8CUS0GnSY/TfgLZCxWQOI/AAAAAAAAAOY/p25hk2hsM08/s72-c/php.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2290445374990122169</id><published>2011-05-08T10:03:00.001-04:00</published><updated>2011-05-08T10:05:17.127-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web-services'/><category scheme='http://www.blogger.com/atom/ns#' term='soap'/><category scheme='http://www.blogger.com/atom/ns#' term='wsdl'/><title type='text'>WSDL SOAP bindings confusion - RPC vs document</title><content type='html'>&lt;div style="text-align: center;"&gt;&lt;b&gt;What is the difference between RPC and document styles in SOAP web services?&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;I was asked this question at a job interview and was embarrassed to discover that I didn't know the answer (considering that I listed "SOAP web services" on my resume). I've heard the terms before, but couldn't remember what they meant. The main difference lies in what the body of the SOAP message looks like.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;&lt;b&gt;RPC vs document styles&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The body of an &lt;b&gt;RPC &lt;/b&gt;(remote procedure call) style SOAP message is constructed in a specific way, which is defined in the SOAP standard. It is built around the assumption that you want to call the web service just like you would call a normal function or method that is part of your application code. The message body contains an XML element for each "parameter" of the method. These parameter elements are wrapped in an XML element which contains the name of the method that is being called. The response returns a single value (encoded in XML), just like a programmatic method. The WSDL code for a RPC-style web service is less complex than that of a document-style web service, but this isn't a big deal since WSDLs aren't meant to be handled by humans.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;i&gt;A RPC-style request:&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;soap:envelope&amp;gt;&lt;br /&gt;  &amp;lt;soap:body&amp;gt;&lt;br /&gt;    &amp;lt;multiply&amp;gt;    &amp;lt;!-- web method name --&amp;gt;&lt;br /&gt;      &amp;lt;a&amp;gt;2.0&amp;lt;/a&amp;gt;  &amp;lt;!-- first parameter --&amp;gt;&lt;br /&gt;      &amp;lt;b&amp;gt;7&amp;lt;/b&amp;gt;    &amp;lt;!-- second parameter --&amp;gt;&lt;br /&gt;    &amp;lt;/multiply&amp;gt;&lt;br /&gt;  &amp;lt;/soap:body&amp;gt;&lt;br /&gt;&amp;lt;/soap:envelope&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A &lt;b&gt;document &lt;/b&gt;style web service, on the other hand, contains no restrictions for how the SOAP body must be constructed. It allows you to include whatever XML data you want and also to include a schema for this XML. This means that the client's and server's application code must do the&amp;nbsp;marshalling&amp;nbsp;and unmarshalling work. This contrasts with RPC in which the marshalling/unmarshalling process is part of the standard, so presumably should be handled by whatever SOAP library you are using. The WSDL code for a document-style web service is much more complex than that of a RPC-style web service, but this isn't a big deal since WSDLs aren't meant to be handled by humans.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;A document-style request:&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;soap:envelope&amp;gt;&lt;br /&gt;  &amp;lt;soap:body&amp;gt;&lt;br /&gt;    &amp;lt;!-- arbitrary XML --&amp;gt;&lt;br /&gt;    &amp;lt;movies xmlns="http://www.myfavoritemovies.com"&amp;gt;&lt;br /&gt;      &amp;lt;movie&amp;gt;&lt;br /&gt;        &amp;lt;title&amp;gt;2001: A Space Odyssey&amp;lt;/title&amp;gt;&lt;br /&gt;        &amp;lt;released&amp;gt;1968&amp;lt;/released&amp;gt;&lt;br /&gt;      &amp;lt;/movie&amp;gt;&lt;br /&gt;      &amp;lt;movie&amp;gt;&lt;br /&gt;        &amp;lt;title&amp;gt;Donnie Darko&amp;lt;/title&amp;gt;&lt;br /&gt;        &amp;lt;released&amp;gt;2001&amp;lt;/released&amp;gt;&lt;br /&gt;      &amp;lt;/movie&amp;gt;&lt;br /&gt;    &amp;lt;/movies&amp;gt;&lt;br /&gt;  &amp;lt;/soap:body&amp;gt;&lt;br /&gt;&amp;lt;/soap:envelope&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The main downside of the RPC style is that it is &lt;i&gt;tightly coupled&lt;/i&gt; to the application code (that is, if you decide you want to call these web methods like normal methods--this is not a requirement, but this is what the RPC style was designed for). This means that if you want to change the order of the parmeters or change the types of those parameters, this change will affect the definition of the web service itself (just as it would affect the definition of a normal function or method).&lt;br /&gt;&lt;br /&gt;Document style services do not have this issue because they are &lt;i&gt;loosely couple&lt;/i&gt;d with the application code--the application must handle the marshalling and unmarshalling of the XML data separately. For example, with a document style service, it doesn't matter if the programmer decides to use a "float" instead of an "int" to represent a particular parameter because it's all converted to XML text in the end.&lt;br /&gt;&lt;br /&gt;The main downside of the document style is that there is no standard way of determining which method of the web service the request is for. It's easy to get around this limitation, but, however it's done, it must be done manually by the application code. (Note: The "document/literal wrapped" style removes this limitation; read on for more details.)&lt;br /&gt;&lt;br /&gt;Another point to note about the document style is that there are no rules for how the SOAP body must be formatted. This can either be seen as a downside or a strength, depending on your perspective. It's a strength if you are looking for the freedom to handle the message the way you want, but a downside if you don't want to have to do the extra marshalling/unmarshalling work that it requires.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;&lt;b&gt;Encoded vs literal encodings&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In addition to the RPC and document styles, there are two types of encodings: "encoded" and "literal".&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Literal&lt;/b&gt; means that the SOAP body follows an XML schema, which is included in the web service's WSDL document. As long as the client has access to the WSDL, it knows exactly how each message is formatted.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Encoded&lt;/b&gt;, on the other hand, means that the SOAP body does not follow a schema, but still follows a specific format which the client is expected to already know. It is not endorsed by the WS-I standard because there can be slight differences in the way in which various programming languages and web service frameworks interpret these formatting rules, leading to incompatabilities.&lt;br /&gt;&lt;br /&gt;This makes for 4 different style/encoding combinations:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;b&gt;RPC/encoded&lt;/b&gt; - RPC-style message that formats its body according to the rules defined in the SOAP standard (which are not always exact and can lead to incompatabilities).&lt;br /&gt;&lt;b&gt;RPC/literal&lt;/b&gt; - RPC-style message that formats its body according to a schema that reflects the rules defined in the SOAP standard. This schema is included in the WSDL.&lt;br /&gt;&lt;b&gt;document/encoded&lt;/b&gt; - Document-style message that does not include a schema (nobody uses this in practice).&lt;br /&gt;&lt;b&gt;document/literal&lt;/b&gt; - Document-style message that formats its body according to a schema. This schema is included in the WSDL.&lt;/blockquote&gt;&lt;br /&gt;There's also a 5th type. It isn't an official standard but it is used a lot in practice. It came into being to compensate for document/literal's main shortcoming of not having a standard way of specifying the web method name:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;b&gt;document/literal wrapped&lt;/b&gt;&amp;nbsp;- The same as document/literal, but wraps the contents of the body in an element with the same name as the web service method (just like RPC-style messages). This is what web services implemented in Java use by default.&lt;/blockquote&gt;&lt;br /&gt;Is my understanding of all this accurate? Which approach do you think is the best? Let me know in the comments.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;References:&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://soa.sys-con.com/node/39901"&gt;http://soa.sys-con.com/node/39901&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl"&gt;http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.coderanch.com/t/148478/java-Web-Services-SCDJWS/certification/document-Vs-rpc-message-styles"&gt;http://www.coderanch.com/t/148478/java-Web-Services-SCDJWS/certification/document-Vs-rpc-message-styles&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.coderanch.com/t/148480/java-Web-Services-SCDJWS/certification/encoding-style-literal-vs-encoded"&gt;http://www.coderanch.com/t/148480/java-Web-Services-SCDJWS/certification/encoding-style-literal-vs-encoded&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sdn.sap.com/irj/scn/index?rid=/library/uuid/c018da90-0201-0010-ed85-d714ff7b7019"&gt;http://www.sdn.sap.com/irj/scn/index?rid=/library/uuid/c018da90-0201-0010-ed85-d714ff7b7019&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://johnragan.wordpress.com/2010/01/04/soap-messages-rpc-vs-document-vs-literal-vs-encoded-vs-wrapped-vs-unwrapped/"&gt;http://johnragan.wordpress.com/2010/01/04/soap-messages-rpc-vs-document-vs-literal-vs-encoded-vs-wrapped-vs-unwrapped/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2290445374990122169?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2290445374990122169/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2290445374990122169' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2290445374990122169'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2290445374990122169'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/05/wsdl-soap-bindings-confusion-rpc-vs.html' title='WSDL SOAP bindings confusion - RPC vs document'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6284141514906249455</id><published>2011-04-24T11:26:00.001-04:00</published><updated>2011-04-25T12:23:37.750-04:00</updated><title type='text'>Dropbox Tech Blog</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-90P8YA1HAL0/TbRAEeTgf0I/AAAAAAAAAN4/4iy96B90bJo/s1600/dropbox_logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-90P8YA1HAL0/TbRAEeTgf0I/AAAAAAAAAN4/4iy96B90bJo/s1600/dropbox_logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;The folks over at &lt;a href="http://www.dropbox.com/"&gt;Dropbox&lt;/a&gt; recently started a &lt;a href="http://tech.dropbox.com/"&gt;new blog&lt;/a&gt;, which is focused on the technology that the Dropbox application uses.  Their &lt;a href="http://tech.dropbox.com/?p=1"&gt;first blog post&lt;/a&gt; describes how the Dropbox team translated Dropbox into multiple languages.&lt;br /&gt;&lt;br /&gt;It starts by acknowledging the success that &lt;a href="http://www.facebook.com/"&gt;Facebook&lt;/a&gt; had using community translation to translate their site to French in 2008.  Dropbox, however, chose not to use this technique because of the significant effort required to build a framework that not only accepts community translations, but quality-checks them and converts them to the multitude of i18n file formats that Dropbox uses.&lt;br /&gt;&lt;br /&gt;Instead, they decided to go with a hybrid approach--professional translators would do the bulk of the translation, while users would have the ability to make corrections.  The idea being that since users are the ones using the application, they have a much better idea of the context in which a particular word or phrase is being used.  Professional translators on the other hand, have likely never even heard of Dropbox, so may produce translations which don't quite capture the true meaning of a word or phrase.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6284141514906249455?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6284141514906249455/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6284141514906249455' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6284141514906249455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6284141514906249455'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/04/dropbox-tech-blog.html' title='Dropbox Tech Blog'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-90P8YA1HAL0/TbRAEeTgf0I/AAAAAAAAAN4/4iy96B90bJo/s72-c/dropbox_logo.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3133631104825051394</id><published>2011-04-02T16:41:00.002-04:00</published><updated>2011-04-02T17:07:00.957-04:00</updated><title type='text'>PHP-SQLUtils released</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-0zUd-OQh-Yg/TZeJVF4SJyI/AAAAAAAAAN0/x6WDjz91VIw/s1600/github-logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-0zUd-OQh-Yg/TZeJVF4SJyI/AAAAAAAAAN0/x6WDjz91VIw/s1600/github-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;Yesterday, I created a project on github called &lt;a href="https://github.com/mangstadt/PHP-SQLUtils"&gt;PHP-SQLUtils&lt;/a&gt;.  This is a small PHP library that helps make building SQL queries easier and less error-prone.  I've been using a variant of this library at work and it has definitely come in handy.  One thing that it can do is build INSERT statements.&lt;br /&gt;&lt;br /&gt;In your typical INSERT statement, the list of column names come first, followed by the list of column values.&lt;br /&gt;&lt;pre class="sh_sql"&gt;INSERT INTO cars&lt;br /&gt;(make, model, year, purchased) VALUES&lt;br /&gt;('Ford', 'Focus', 2005, '20050520 00:00:00')&lt;br /&gt;&lt;/pre&gt;It's a little hard to tell what values go with what columns.  It gets harder as the list of columns gets longer.&lt;br /&gt;&lt;pre class="sh_sql"&gt;INSERT INTO cars&lt;br /&gt;(make, model, year, purchased, owner, seats, mpg_city, mpg_highway, color, sun_roof) VALUES&lt;br /&gt;('Ford', 'Focus', 2005, '20050520 00:00:00', 'George Washington', 5, 30.4, 35.1, 'gray', 0)&lt;br /&gt;&lt;/pre&gt;PHP-SQLUtils provides an interface for building INSERT statements.  It puts the column names right next to their values, making it easy to figure out what values belong to what columns and reducing the chance of a bug appearing:&lt;br /&gt;&lt;pre class="sh_php"&gt;$utils = new MssqlUtils();&lt;br /&gt;$insert = $utils-&amp;gt;insert("cars");&lt;br /&gt;$insert-&amp;gt;s("make", "Ford");&lt;br /&gt;$insert-&amp;gt;s("model", "Focus");&lt;br /&gt;$insert-&amp;gt;n("year", 2005);&lt;br /&gt;$insert-&amp;gt;d("purchased", strtotime("2005-05-20"));&lt;br /&gt;echo $insert-&amp;gt;done();&lt;br /&gt;//prints "INSERT INTO cars (make, model, year, purchased)&lt;br /&gt;//VALUES ('Ford', 'Focus', 2005, '20050520 00:00:00')"&lt;br /&gt;&lt;/pre&gt;Check out PHP-SQLUtils &lt;a href="https://github.com/mangstadt/PHP-SQLUtils"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3133631104825051394?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3133631104825051394/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3133631104825051394' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3133631104825051394'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3133631104825051394'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/04/php-sqlutils-released.html' title='PHP-SQLUtils released'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-0zUd-OQh-Yg/TZeJVF4SJyI/AAAAAAAAAN0/x6WDjz91VIw/s72-c/github-logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-5846979935774480314</id><published>2011-03-22T20:16:00.000-04:00</published><updated>2011-03-22T20:16:54.373-04:00</updated><title type='text'>PHP Getter/Setter method generator</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-42285bRgz1k/TYk6wDXKq8I/AAAAAAAAANs/AcDLFoNnWs0/s1600/php-elephant.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="https://lh4.googleusercontent.com/-42285bRgz1k/TYk6wDXKq8I/AAAAAAAAANs/AcDLFoNnWs0/s1600/php-elephant.png" /&gt;&lt;/a&gt;&lt;/div&gt;Here's a utility I created that generates getter and setter methods for a PHP class. &amp;nbsp;Copy and paste your PHP class into the textbox, then click generate. &amp;nbsp;It uses a regular expression to identify the private and protected fields and generates a getter/setter pair for each. &amp;nbsp;You can also customize the indentation and newline preference of the generated code:&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.mangst.com/projects/getter-setter-gen/"&gt;http://www.mangst.com/projects/getter-setter-gen/&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-5846979935774480314?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/5846979935774480314/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=5846979935774480314' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5846979935774480314'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5846979935774480314'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/03/php-gettersetter-method-generator.html' title='PHP Getter/Setter method generator'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-42285bRgz1k/TYk6wDXKq8I/AAAAAAAAANs/AcDLFoNnWs0/s72-c/php-elephant.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-1843945341705206948</id><published>2011-03-16T21:00:00.000-04:00</published><updated>2011-03-16T21:00:22.377-04:00</updated><title type='text'>Running PHPUnit from Eclipse</title><content type='html'>If you've ever written JUnit tests in Java with Eclipse, you'll know that JUnit is integrated right into the IDE. &amp;nbsp;Eclipse doesn't give you built-in unit testing support with PHP, but it's still possible to run your tests without having to leave the IDE.&lt;br /&gt;&lt;br /&gt;In this screen cast, I'll show you how to run&amp;nbsp;&lt;a href="http://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;cd=1&amp;amp;ved=0CBcQFjAA&amp;amp;url=http%3A%2F%2Fphpunit.sourceforge.net%2F&amp;amp;ei=GluBTaveG4Li0gHOua3yCA&amp;amp;usg=AFQjCNFqwpzo9El7tnDukDG-V3prLfi2jQ&amp;amp;sig2=s00qCciwXu62KT1mvR6eUA"&gt;PHPUnit&lt;/a&gt; from Eclipse.&lt;br /&gt;&lt;br /&gt;&lt;iframe allowfullscreen="" frameborder="0" height="292" src="http://www.youtube.com/embed/p7hTrig3rpw" title="YouTube video player" width="480"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-1843945341705206948?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/1843945341705206948/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=1843945341705206948' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1843945341705206948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1843945341705206948'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/03/running-phpunit-from-eclipse.html' title='Running PHPUnit from Eclipse'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://img.youtube.com/vi/p7hTrig3rpw/default.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-165546936063043395</id><published>2011-03-14T20:52:00.003-04:00</published><updated>2011-03-31T17:05:01.054-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='pear'/><title type='text'>PHP PEAR libraries and Eclipse</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-4D_eYE2HfgY/TX65IHQQ95I/AAAAAAAAANk/NELUiooxJCg/s1600/Annoying-Orange-Pear-and-Bonsai-Tree.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="https://lh3.googleusercontent.com/-4D_eYE2HfgY/TX65IHQQ95I/AAAAAAAAANk/NELUiooxJCg/s200/Annoying-Orange-Pear-and-Bonsai-Tree.jpg" width="163" /&gt;&lt;/a&gt;&lt;/div&gt;If you develop PHP applications using &lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt;, here is a tip that may come in handy. &amp;nbsp;&lt;a href="http://www.eclipse.org/pdt/"&gt;PDT&lt;/a&gt;, the Eclipse plugin for PHP development, will, among other things, display the documentation for any method, function, or field that you hover your mouse over. &amp;nbsp;And you can jump to the definition of that method, function, or field by control-clicking on it.&lt;br /&gt;&lt;br /&gt;But if your application uses libraries installed with &lt;a href="http://pear.php.net/"&gt;PEAR&lt;/a&gt;, then this extra functionality will not work for those libraries. &amp;nbsp;However, this can be fixed by providing Eclipse with the locations of these libraries.&lt;br /&gt;&lt;br /&gt;Note: Click on the screenshots below to see a larger image.&lt;br /&gt;&lt;br /&gt;1. Right click on your PHP project and select "Properties".&lt;br /&gt;&lt;br /&gt;2. Select "PHP Include Path" from the left-hand menu. &amp;nbsp;Click the "Libraries" tab, then click "Add Library...".&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-tOPR3WajShQ/TX6x-AhBwfI/AAAAAAAAANA/NrzYHj8IH8k/s1600/01.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh4.googleusercontent.com/-tOPR3WajShQ/TX6x-AhBwfI/AAAAAAAAANA/NrzYHj8IH8k/s200/01.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;3. Select "User Library" and click "Next".&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-o9YHTh0Gv7Q/TX6x-nPcpkI/AAAAAAAAANE/yeruAMmukdQ/s1600/02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh5.googleusercontent.com/-o9YHTh0Gv7Q/TX6x-nPcpkI/AAAAAAAAANE/yeruAMmukdQ/s200/02.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;4. Click "Configure...". &amp;nbsp;Another new dialog will appear.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-TC2CCxFYlJ0/TX6x-w2WDDI/AAAAAAAAANI/UrjArPrURC8/s1600/03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh4.googleusercontent.com/-TC2CCxFYlJ0/TX6x-w2WDDI/AAAAAAAAANI/UrjArPrURC8/s200/03.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;5. You want to add a new library, so click "New..." and type in a name for the library. &amp;nbsp;I'm going to add PHPUnit.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-s12l_4PnLb8/TX6x_QjCJoI/AAAAAAAAANQ/4nU1EalwFtk/s1600/05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh6.googleusercontent.com/-s12l_4PnLb8/TX6x_QjCJoI/AAAAAAAAANQ/4nU1EalwFtk/s200/05.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;6. Select the newly added Library and click "Add External folder...". &amp;nbsp;This is where we point to the file system location of the library.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-qdi-UQ6HFZQ/TX6x_rs9YyI/AAAAAAAAANU/Sq94Jcx7tHc/s1600/06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh6.googleusercontent.com/-qdi-UQ6HFZQ/TX6x_rs9YyI/AAAAAAAAANU/Sq94Jcx7tHc/s200/06.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;7. Select the path to the library. &amp;nbsp;In Windows, PEAR installs its libraries inside the "PEAR" folder under the PHP installation directory. &amp;nbsp;On Mac OSX, its location is "/usr/local/PEAR". &amp;nbsp;On Ubuntu, it should be at "/usr/share/php/PEAR". &amp;nbsp;If you can't figure out where your PEAR directory is, you can find it in the "include_path" setting in your php.ini file (when PEAR is installed, it automatically modifies the PHP include path to include itself). &amp;nbsp;Click "OK".&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-h_HlO8nUwU8/TX6x_0n686I/AAAAAAAAANY/ZwhS7p4xmYE/s1600/07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh4.googleusercontent.com/-h_HlO8nUwU8/TX6x_0n686I/AAAAAAAAANY/ZwhS7p4xmYE/s200/07.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;8. Click "OK", then click "Finish"!&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-bshFiNJ_cYU/TX6yAXv6s0I/AAAAAAAAANg/TnCrxJR-pqg/s1600/09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="https://lh6.googleusercontent.com/-bshFiNJ_cYU/TX6yAXv6s0I/AAAAAAAAANg/TnCrxJR-pqg/s200/09.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In my next post, I'll explain how to run &lt;a href="http://phpunit.sourceforge.net/"&gt;PHPUnit&lt;/a&gt; unit tests from Eclipse without needing any plugins!&lt;br /&gt;&lt;br /&gt;Do you have any tips for developing in PHP on Eclipse? &amp;nbsp;Let me know in the comments section!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-165546936063043395?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/165546936063043395/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=165546936063043395' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/165546936063043395'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/165546936063043395'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/03/php-pear-libraries-and-eclipse.html' title='PHP PEAR libraries and Eclipse'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh3.googleusercontent.com/-4D_eYE2HfgY/TX65IHQQ95I/AAAAAAAAANk/NELUiooxJCg/s72-c/Annoying-Orange-Pear-and-Bonsai-Tree.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2388898542898054476</id><published>2011-03-13T13:11:00.001-04:00</published><updated>2011-03-13T13:15:55.623-04:00</updated><title type='text'>Freeing hard disk space in Ubuntu Linux</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-IgdpiZnVSRM/TXz7pnZeCgI/AAAAAAAAAM8/jYcjXsDWfCM/s1600/funny-pictures-kitten-erases-your-hard-drive.jpeg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="150" src="https://lh5.googleusercontent.com/-IgdpiZnVSRM/TXz7pnZeCgI/AAAAAAAAAM8/jYcjXsDWfCM/s200/funny-pictures-kitten-erases-your-hard-drive.jpeg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Here are some helpful disk cleanup tips that I came across during my quest to free up space on my tiny 4GB netbook hard drive.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: large;"&gt;1. apt-get&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This command is what you use in Ubuntu to install and update the applications on your computer. &amp;nbsp;But apt-get can also be used to free up disk space:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sudo apt-get &lt;b&gt;clean&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;Deletes package files that were downloaded when installing or updating software.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sudo apt-get &lt;b&gt;autoclean&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;Like "clean", but only removes packages which are no longer in the Ubuntu repository or for which a newer version exists.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sudo apt-get &lt;b&gt;autoremove&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;Removes dependencies that are no longer used by any applications. &amp;nbsp;For example, a spell checker library may have been installed along with OpenOffice. &amp;nbsp;However, when OpenOffice is uninstalled, the spell checker might not be.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: large;"&gt;&lt;b&gt;2. df&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This command won't itself free any space, but it can show you which files and directories are the biggest hogs (maybe there's a large video file buried somewhere that you don't know about). &amp;nbsp;The following command will recursively list all files and directories in the given directory and sort the list by file size.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;du -a folder/path | sort -n&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-aASkLZbc170/TXz3ke47FEI/AAAAAAAAAMs/32DJMqpw4pQ/s1600/df-list-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="227" src="https://lh5.googleusercontent.com/-aASkLZbc170/TXz3ke47FEI/AAAAAAAAAMs/32DJMqpw4pQ/s320/df-list-3.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;It's going to list every single file, so the list might get quite long. &amp;nbsp;It might be good to direct the output to a file and reverse the sort so the largest files appear at the top of the file:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;du -a folder/path | sort -nr &amp;gt; disk-usage.txt&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: large;"&gt;3. localepurge&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you can only speak one language like me, then check out localepurge. &amp;nbsp;It will remove all help files from your system that are in languages you don't understand. &amp;nbsp;It automatically runs every time you install any new software.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sudo apt-get install localepurge&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As soon as it is installed, it will ask you for the languages you want to keep. &amp;nbsp;For example, I selected "en", which is the two letter code for "all variants of the English language" (American English, British English, etc). &amp;nbsp;I selected this instead of just "en_US" (American English) to err on the side of caution. &amp;nbsp;Note: Press the &lt;i&gt;spacebar&lt;/i&gt; to select a language from the list and then press &lt;i&gt;enter&lt;/i&gt; when you are done.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-miONqzjR5RY/TXz4EAC2EMI/AAAAAAAAAM0/lAcZMm5m8Ks/s1600/localepurge-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="227" src="https://lh3.googleusercontent.com/-miONqzjR5RY/TXz4EAC2EMI/AAAAAAAAAM0/lAcZMm5m8Ks/s320/localepurge-2.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Then, run the program:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sudo localepurge&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It will go through your hard drive and delete various files such as man pages. &amp;nbsp;It freed a good 53MB for me:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-9RRUV6kk5Ao/TXz4CBmL43I/AAAAAAAAAMw/Rd9zKfeYYUI/s1600/localepurge.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="227" src="https://lh4.googleusercontent.com/-9RRUV6kk5Ao/TXz4CBmL43I/AAAAAAAAAMw/Rd9zKfeYYUI/s320/localepurge.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: large;"&gt;&lt;b&gt;4. Language packs&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can also try removing language-related packages using the Synaptic Package Manager. &amp;nbsp;Search for "language" and remove all languages that you have no need for. &amp;nbsp;Sort by the left-most column so that the installed packages appear first in the list. &amp;nbsp;This freed even more space for me--about 200MB.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-zQQBiqtoO7o/TXz4SUXf2-I/AAAAAAAAAM4/aOmF9pZfgB4/s1600/Screenshot-Synaptic+Package+Manager+-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="172" src="https://lh5.googleusercontent.com/-zQQBiqtoO7o/TXz4SUXf2-I/AAAAAAAAAM4/aOmF9pZfgB4/s320/Screenshot-Synaptic+Package+Manager+-1.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Do you have any tips for freeing up hard drive space? &amp;nbsp;Let me know!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2388898542898054476?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2388898542898054476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2388898542898054476' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2388898542898054476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2388898542898054476'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/03/freeing-hard-disk-space-in-ubuntu-linux.html' title='Freeing hard disk space in Ubuntu Linux'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-IgdpiZnVSRM/TXz7pnZeCgI/AAAAAAAAAM8/jYcjXsDWfCM/s72-c/funny-pictures-kitten-erases-your-hard-drive.jpeg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2333636007364464551</id><published>2011-03-12T12:13:00.004-05:00</published><updated>2011-03-12T19:31:52.247-05:00</updated><title type='text'>Setting up a Virtual Host in Apache 2 on Linux (Ubuntu)</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-Dvg_nytqwXk/TXwPX687twI/AAAAAAAAAMk/oEpcKUWXW9o/s1600/apache-logo--300x106.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="70" src="http://1.bp.blogspot.com/-Dvg_nytqwXk/TXwPX687twI/AAAAAAAAAMk/oEpcKUWXW9o/s200/apache-logo--300x106.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;When you install Apache on your Linux computer, it is configured to treat all files in "/var/www" as the web root by default. To run a website locally on your machine, you could copy all files into this directory. However, this is not the best way to go if you are doing serious development work, such as designing multiple websites on the same computer.&lt;br /&gt;&lt;br /&gt;What you should do is create a virtual host. This allows you to store your website files anywhere you want:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. Configure Apache&lt;/b&gt;&lt;br /&gt;First, you have to add a virtual host to Apache. At least on my distro (Ubuntu), Apache splits its configuration settings into multiple files for organizational purposes. The "apache2.conf" file is the "root" file and imports all the other files. Virtual hosts are defined in the "/etc/conf/apache2/sites-enabled" directory, each inside their own file. The name of the file does not matter. Create a new file in this directory and enter the following text, replacing paths where necessary:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;Directory "/home/michael"&amp;gt;&lt;br /&gt; Order Deny,Allow&lt;br /&gt; Allow from all&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;br /&gt;&lt;br /&gt;NameVirtualHost  127.0.0.1&lt;br /&gt;&lt;br /&gt;&amp;lt;VirtualHost 127.0.0.1&amp;gt;&lt;br /&gt; DocumentRoot "/home/michael/testhost"&lt;br /&gt; ServerName testhost.local&lt;br /&gt;&amp;lt;/VirtualHost&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The "ServerName" will be the URL you use to access your website. ".local" is a naming convention used in the industry for running websites locally.&lt;br /&gt;&lt;br /&gt;Apache must always be restarted whenever you change its conf settings:&lt;br /&gt;&lt;pre&gt;sudo service apache2 restart&lt;/pre&gt;&lt;blockquote&gt;&lt;b&gt;Tip:&amp;nbsp;&lt;/b&gt;In order to edit these files, you have to open them as root. Using a command-line text editor can be cumbersome, especially if you need to have multiple files open at once. Instead, just open up a GUI text editor as the root user. For example:&lt;br /&gt;&lt;pre&gt;sudo gedit &amp;amp;&lt;/pre&gt;&lt;/blockquote&gt;&lt;b&gt;2. Edit hosts file&lt;/b&gt;&lt;br /&gt;Next, edit your host file. On Linux, this is located at "/etc/hosts". Add the following line at the end, replacing "testhost.local" with the "ServerName" parameter in the Apache conf file.:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;127.0.0.1 testhost.local&lt;/pre&gt;&lt;br /&gt;This will tell your browser to access this URL from your local machine, instead of trying to find it out on the Internet.&lt;br /&gt;&lt;blockquote&gt;&lt;b&gt;Tip #2:&amp;nbsp;&lt;/b&gt;In Linux, it can be hard to remember the locations of all these configuration files, especially because they can change depending on the distro. Any time you come across a new config file you haven't seen before, store its location in a centralized place, such as in a text file in your home folder. That way, you'll know exactly where to find it the next time you need it.&lt;/blockquote&gt;&lt;b&gt;3. File permissions&lt;/b&gt;&lt;br /&gt;Make sure the folder and all files in it are globally readable.  Otherwise, Apache will not be able to see them and you will get "Forbidden" errors.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;chmod -R 744 /home/michael/testhost&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2333636007364464551?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2333636007364464551/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2333636007364464551' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2333636007364464551'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2333636007364464551'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2011/03/setting-up-virtual-host-in-apache-2-on.html' title='Setting up a Virtual Host in Apache 2 on Linux (Ubuntu)'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-Dvg_nytqwXk/TXwPX687twI/AAAAAAAAAMk/oEpcKUWXW9o/s72-c/apache-logo--300x106.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4037112702075256978</id><published>2010-12-14T10:45:00.000-05:00</published><updated>2010-12-14T10:45:40.821-05:00</updated><title type='text'>Google's Cr-48 netbook</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_73vOQEMfTVI/TQeQGYK1VjI/AAAAAAAAAME/i0gNd0MAxo0/s1600/google-cr-48.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/_73vOQEMfTVI/TQeQGYK1VjI/AAAAAAAAAME/i0gNd0MAxo0/s200/google-cr-48.jpg" width="165" /&gt;&lt;/a&gt;&lt;/div&gt;If you haven't heard, Google is releasing its own operating system, called Chrome OS (check out this &lt;a href="http://www.youtube.com/watch?v=lm-Vnx58UYo&amp;amp;feature=player_embedded"&gt;marketing video&lt;/a&gt;).  It's based on Linux, but is tailored solely for use with online, web-based applications, such as Gmail and Google Docs.  They are also releasing a netbook, called Cr-48, that comes installed with Chrome OS.  The whole idea is that all your data is stored in the (mysterious, but reliable) cloud, so if something bad happens to your computer, none of your data is lost.  It's almost like we're returning to the dummy-terminal days of yore.  A pre-production version of the netbook has been &lt;a href="http://tech.slashdot.org/story/10/12/13/203237/Hands-On-With-Googles-Cr-48"&gt;given to a number of people&lt;/a&gt; so that Google can get feedback on the device and improve upon it before releasing it to the public.&lt;br /&gt;&lt;br /&gt;One thing that differentiates Cr-48 from other netbooks is the keyboard.  Google modified it slightly to better integrate it with the OS.  The "Caps Lock" key was replaced with a "Search" key, which opens a new tab in the browser.  The function keys (F1, F2, etc) were replaced with keys that perform tasks such as controling the brightness of the screen, controlling the speaker volume, and going back and forward in the browser.&lt;br /&gt;&lt;br /&gt;The touchpad works as follows: Tap it with one finger for a left mouse click, tap it with two fingers for a right mouse click, drag two fingers across its surface to scroll.  People online have expressed frustration with this setup, but this is exactly how the touchpad of my Asus Eee netbook functions and I'm satisfied with it.  Cr-48 comes with a single USB port with extremely limited functionality.  It doesn't support anything other than a mouse or keyboard, not even a thumb drive.  It comes with an SD card slot, although Chrome OS currently doesn't recognize it.  A headphone jack is included and is fully functional.  It has a 16GB solid state hard drive.&lt;br /&gt;&lt;br /&gt;In terms of internet connectivity, Wi-Fi and 3G (which is on Verizon's network) are supported.  You get a free 100MB per month of 3G bandwidth for the first two years.  The netbook doesn't have an Ethernet jack, so you're limited to wireless connections.  It comes with a VGA port, allowing you to plug in a larger monitor or a projector.&lt;br /&gt;&lt;br /&gt;The idea of an OS which stores all of its applications and data in the cloud is an interesting concept, but I'm in no hurry to start using it.  Even though Google provides very well designed online applications, I would imagine that they are not as powerful as their desktop equivalents.  Cr-48 sounds more like something you'd take with you on the road or to the coffee shop to do casual work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4037112702075256978?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4037112702075256978/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4037112702075256978' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4037112702075256978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4037112702075256978'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/12/googles-cr-48-netbook.html' title='Google&apos;s Cr-48 netbook'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/TQeQGYK1VjI/AAAAAAAAAME/i0gNd0MAxo0/s72-c/google-cr-48.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-5516863459837047460</id><published>2010-12-03T16:40:00.000-05:00</published><updated>2010-12-03T16:40:42.757-05:00</updated><title type='text'>Java: The Next Generation</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_73vOQEMfTVI/TPljHWeGqPI/AAAAAAAAALg/zVWMo62yYws/s1600/blog-java7.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/_73vOQEMfTVI/TPljHWeGqPI/AAAAAAAAALg/zVWMo62yYws/s200/blog-java7.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;A few weeks ago, plans for the next version of Java were released.  It will be split into two different versions in order to more quickly get a new version out the door.  Two key features that were originally slated to be released in &lt;a href="http://jcp.org/en/jsr/detail?id=336"&gt;Java 7&lt;/a&gt;, will be pushed back to &lt;a href="http://jcp.org/en/jsr/detail?id=337"&gt;Java 8&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One of these features is called &lt;b&gt;Lambda&lt;/b&gt;, which adds closures to Java.  From what I understand, a &lt;b&gt;closure&lt;/b&gt; is like a function that can be assigned to a variable.  This means that the closure can be passed as an argument to another function, and then called inside of the function's body.  If the closure is declared inside of another function, it has access to the parent function's local variables.  Many languages support this.  Here is an example of how closures can be used in Javascript (taken from &lt;a href="http://en.wikipedia.org/wiki/Closure_(computer_science)"&gt;Wikipedia&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// Return a list of all books with at least 'threshold' copies sold.&lt;br /&gt;function bestSellingBooks(threshold) {&lt;br /&gt;  return bookList.filter(&lt;br /&gt;      function (book) { return book.sales &amp;gt;= threshold; }&lt;br /&gt;  );&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The function filters the bookList array, returning only the books that have sold a mininum number of copies.  It does this by passing a closure into the filter() method, which gets executed on every book in the array.  If the closure returns true for a particular book, then that book is filtered out.&lt;br /&gt;&lt;br /&gt;The other major feature that was pushed back to Java 8 is called &lt;b&gt;&lt;a href="http://openjdk.java.net/projects/jigsaw/"&gt;Jigsaw&lt;/a&gt;&lt;/b&gt;.  The goal of Jigsaw is to break the JDK into separate modules.  This would help applications boot up more quickly.  It also would help in situations where a JRE is packaged with the application.  This JRE could be reduced in size by only including the modules that the application needs.&lt;br /&gt;&lt;br /&gt;Java 7 is slated for release in mid-2011 and Java 8 is to be released sometime in 2012.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-5516863459837047460?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/5516863459837047460/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=5516863459837047460' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5516863459837047460'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5516863459837047460'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/12/java-next-generation.html' title='Java: The Next Generation'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/TPljHWeGqPI/AAAAAAAAALg/zVWMo62yYws/s72-c/blog-java7.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-1690277415842705545</id><published>2010-11-19T14:32:00.000-05:00</published><updated>2010-11-19T14:32:26.644-05:00</updated><title type='text'>Linux patch increases multi-tasking performance</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s1600/linux.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s200/linux.gif" width="181" /&gt;&lt;/a&gt;&lt;/div&gt;A couple days ago, a programmer named Mike Galbraith &lt;a href="http://linux.slashdot.org/story/10/11/16/1330233/The-200-Line-Linux-Kernel-Patch-That-Does-Wonders"&gt;submitted a patch&lt;/a&gt; for the Linux kernel which significantly increases the performance of multi-tasking on the Linux desktop.  With this patch, you can run multiple, CPU-intensive applications at the same time more effectively.  Phoronix has &lt;a href="http://www.phoronix.com/scan.php?page=article&amp;amp;item=linux_2637_video&amp;amp;num=2"&gt;two videos&lt;/a&gt; demonstrating the effect of this patch.  Each video shows a computer that has number of tasks running simultaneously, such as compiling the Linux kernel and watching a high definition video.  On the computer without the patch, the video was very stuttery and looked like a still image most of the time.  But on the computer with the patch, the video plays almost perfectly.  The patch is only about 200 lines of code, which is small compared to the large improvements the patch brings.  Linus Torvalds, the developer in charge of the Linux kernel, liked the patch very much, calling it "one of those 'real improvement' patches", meaning that it creates few, if any, negative side-effects.&lt;br /&gt;&lt;br /&gt;The patch achieves these performance gains by tweaking the scheduler (the scheduler determines when each running program is allowed to get CPU time).  It works by grouping all programs by the TTY (terminal) that they were started in.  CPU time is then spread out evenly across each group, as opposed to being spread out evenly across each individual program.  Slashdot user &lt;a href="http://slashdot.org/~tinkerghost"&gt;tinkerghost&lt;/a&gt; gives a &lt;a href="http://linux.slashdot.org/comments.pl?sid=1874692&amp;amp;cid=34277444"&gt;good example&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;As an example, we need to run &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extinguish_fire&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;evacuate_building&lt;/span&gt; at the same time. &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extinguish_fire&lt;/span&gt; spawns a thread for each bucket in the brigade, while &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;evacuate_building&lt;/span&gt; only spawns a thread for each escape route. Now, if there are 96 buckets and 4 escape routes, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extinguish_fire&lt;/span&gt; will consume 96% of the CPU and choke out the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;evacuate_building&lt;/span&gt; threads...By grouping all of the threads from a program, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extinguish_fire&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;evacuate_building&lt;/span&gt; get equal footing regardless of the number of threads they spawn.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Because it groups according to the TTY, it seems that you will only notice a performance improvement if you are running command-line programs.  From what I've read, GUI programs are TTY-less.  With this patch, GUI programs would all be lumped into the same group, so there would be no performance gain.  In order to take advantage of the new scheduler, you would have to run a program from a new terminal in order to place it into another group.&lt;br /&gt;&lt;br /&gt;Today, a Red Hat developer named Lennart Poettering found out that you can &lt;a href="http://linux.slashdot.org/story/10/11/18/2246213/Alternative-To-the-200-Line-Linux-Kernel-Patch"&gt;achieve the same thing&lt;/a&gt; just by &lt;a href="http://www.webupd8.org/2010/11/alternative-to-200-lines-kernel-patch.html"&gt;running a few commands and editing a configuration file&lt;/a&gt;.  This alternative solution is implemented differently, grouping programs by session instead of TTY, but it achieves the same effect.  Linus says that this is actually how the kernel patch was originally tested, but that it should still be included in the kernel because it's such a good improvement.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-1690277415842705545?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/1690277415842705545/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=1690277415842705545' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1690277415842705545'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/1690277415842705545'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/11/linux-patch-increases-multi-tasking.html' title='Linux patch increases multi-tasking performance'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s72-c/linux.gif' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4744666244399003142</id><published>2010-11-09T15:01:00.000-05:00</published><updated>2010-11-09T15:01:00.534-05:00</updated><title type='text'>Internationalization in Java</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_73vOQEMfTVI/TNmmFVFXsrI/AAAAAAAAALI/aYVIbyh_xtE/s1600/frenchman.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/_73vOQEMfTVI/TNmmFVFXsrI/AAAAAAAAALI/aYVIbyh_xtE/s200/frenchman.gif" width="144" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;b&gt;Internationalization&lt;/b&gt; means developing your application in such a way so that people from different countries who speak different languages can still use your program.  For example, a French user, when presented with a "Yes/No" dialog, should instead be shown a "Oui/Non" dialog.  The term "internationalization" is often abbreviated to "i18n".  The "18" represents the eighteen letters between the first and last letters of the word.&lt;br /&gt;&lt;br /&gt;Java provides a very nice way of doing this.  It involves putting all of the text that your application uses into &lt;a href="http://en.wikipedia.org/wiki/.properties"&gt;&lt;b&gt;.properties files&lt;/b&gt;&lt;/a&gt;, which reside somewhere on the classpath.  Each translation has its own .properties file and is named according to the language and (optionally) the country.  For example, a German properties file would look like "messages_de.properties" ("de" being the standard, two-letter abbreviation for "German").  A British English properties file would look like "messages_en_UK.properties".&lt;br /&gt;&lt;br /&gt;The properties files are organized in a &lt;b&gt;hierarchy&lt;/b&gt;. This means that when searching for a particular string, the language/country file is looked for first (messages_en_UK.properties) followed by the language file (messages_en.properties) followed by the default file (messages.properties).  One benefit to this is that if two translations are mostly the same (like US and UK translations), the parent file can contain most of the text, while the child files can define the differences.  This helps to reduce duplication.  It also allows for "fall-back" translations.  For example, if there is no American English file, then it will use the English file.&lt;br /&gt;&lt;br /&gt;To access these files in the Java code, the &lt;b&gt;ResourceBundle&lt;/b&gt; class is used like so:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_java"&gt;ResourceBundle messages = ResourceBundle.getBundle("com/example/messages");&lt;br /&gt;System.out.println(messages.getString("hello.world"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In this example, the .properties files are located in the "com.example" package and have names that begin with "message".  It finds the property with a name of "hello.world" and prints its value to the console.&lt;br /&gt;&lt;br /&gt;To determine which language to use, the Java application looks at the &lt;b&gt;default Locale&lt;/b&gt;, which is automatically set in every Java program.  ResourceBundle uses the default Locale to determine which properties file to use.  For example, a French user using your application will automatically be shown the French translation because his or her default locale is set to France.  No extra work needs to be done by the application.&lt;br /&gt;&lt;br /&gt;Additionally, you can add&amp;nbsp;&lt;b&gt;arguments&lt;/b&gt; to your property values. This allows you to customize a message at runtime. &amp;nbsp;For example, the following property contains two arguments:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;hello.someone=Hello, {0}!  I'm {1} to see you!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To populate the arguments, you must use the &lt;b&gt;MessageFormat&lt;/b&gt; class like so:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_java"&gt;ResourceBundle messages = ResourceBundle.getBundle("com/example/messages");&lt;br /&gt;String hello = messages.getString("hello.someone");&lt;br /&gt;System.out.println(MessageFormat.format(hello, "Joe", "happy"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above code would print "Hello, Joe!  I'm happy to see you!" to the console.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4744666244399003142?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4744666244399003142/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4744666244399003142' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4744666244399003142'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4744666244399003142'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/11/internationalization-in-java.html' title='Internationalization in Java'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/TNmmFVFXsrI/AAAAAAAAALI/aYVIbyh_xtE/s72-c/frenchman.gif' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-403967287258036879</id><published>2010-11-05T15:15:00.000-04:00</published><updated>2010-11-05T15:15:10.395-04:00</updated><title type='text'>How HTTPS Works</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_73vOQEMfTVI/TNRUR51DRcI/AAAAAAAAALA/azd0xxp40ew/s1600/kalin.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://3.bp.blogspot.com/_73vOQEMfTVI/TNRUR51DRcI/AAAAAAAAALA/azd0xxp40ew/s200/kalin.jpg" width="151" /&gt;&lt;/a&gt;&lt;/div&gt;I'm currently reading the book &lt;a href="http://www.amazon.com/gp/product/059652112X?ie=UTF8&amp;amp;tag=michangsshome-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=059652112X"&gt;Java Web Services: Up and Running&lt;/a&gt;&lt;img alt="" border="0" height="1" src="http://www.assoc-amazon.com/e/ir?t=michangsshome-20&amp;amp;l=as2&amp;amp;o=1&amp;amp;a=059652112X" style="border: none !important; margin: 0px !important;" width="1" /&gt; by Martin Kalin.  In Chapter 5, he discusses issues related to security.  He starts out by giving a brief overview of &lt;b&gt;HTTPS&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;HTTPS is a secure version of HTTP, the protocol that web browsers use to access websites over the Internet. &amp;nbsp;With HTTPS, all communication is encrypted so that it can't be intercepted or altered by malicious attackers. &amp;nbsp;This is crucial to ensuring that, for example, nobody steals your credit card information when you purchase something from an online shopping site.&lt;br /&gt;&lt;br /&gt;When your browser visits an HTTPS website, it first must initiate the connection in a process known as a &lt;i&gt;handshake&lt;/i&gt;.  The browser starts by requesting the server's &lt;i&gt;digital certificate&lt;/i&gt;.  The digital certificate contains the server's public key as well as a &lt;i&gt;digital signature&lt;/i&gt;, which is said to &lt;i&gt;sign&lt;/i&gt; the certificate.  The digital signature is usually from a CA (certificate authority) such as &lt;a href="http://www.verisign.com/"&gt;VeriSign&lt;/a&gt;, but can also be self-signed.&amp;nbsp;The browser checks its trust-store to see if it has either (a) a certificate matching the server's certificate or (b) a certificate corresponding to the digital signature.  For example, the browser's trust store may not have a certificate for Amazon, but it probably does have a certificate for VeriSign, which is the CA that signed Amazon's certificate.&lt;br /&gt;&lt;br /&gt;If the browser can't find an appropriate certificate in its trust-store, then it will show a scary security warning saying that it's dangerous to proceed with the connection.  The danger is that a malicious attacker could create a certificate which tries to present itself as being from a reputable organization, like Amazon.  He or she could create a fake website which looks like the Amazon website and fool you into buying something, thus giving him or her access to your credit card information.&lt;br /&gt;&lt;br /&gt;If the certificate validates against the trust-store, the client generates a &lt;i&gt;pre-master secret key&lt;/i&gt;, which is random string of 48 bits. &amp;nbsp;It then encrypts it with the server's public key and sends it to the server.  Since only the server has the private key, only the server can decrypt it, which means that the key can't be intercepted by a malicious attacker.  Public/private key encryption is called &lt;i&gt;asymmetric encryption&lt;/i&gt;.  Then, the client and the server use the pre-master secret key to create a &lt;i&gt;master secret key&lt;/i&gt;.  Because they both used the same pre-master secret key, the master secret key will be identical on both the client and server.  This master secret key is then used to encrypt and decrypt all subsequent communication between the client and server.  This is called &lt;i&gt;symmetric encryption&lt;/i&gt; because only one key is needed to both encrypt and decrypt the data.  Symmetrical encryption is much faster than asymmetric encryption (about 1000 times faster).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-403967287258036879?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/403967287258036879/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=403967287258036879' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/403967287258036879'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/403967287258036879'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/11/how-https-works.html' title='How HTTPS Works'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/TNRUR51DRcI/AAAAAAAAALA/azd0xxp40ew/s72-c/kalin.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6140468534230159365</id><published>2010-10-30T16:31:00.001-04:00</published><updated>2010-11-07T16:25:23.618-05:00</updated><title type='text'>Screen-scraping Wikipedia</title><content type='html'>&lt;div style="text-align: right;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_73vOQEMfTVI/TNcZGDRkCBI/AAAAAAAAALE/k5YeF7rdOCY/s1600/angry-pumpkin.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="143" src="http://4.bp.blogspot.com/_73vOQEMfTVI/TNcZGDRkCBI/AAAAAAAAALE/k5YeF7rdOCY/s200/angry-pumpkin.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;In order to screen-scrape a page on &lt;a href="http://en.wikipedia.org/"&gt;Wikipedia&lt;/a&gt;, there is one extra step that you must take in order to successfully download a page for processing.  You must include a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;User-Agent&lt;/span&gt; header in your HTTP request.  Wikipedia requires that this header be included or else it will return a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;403 Forbidden&lt;/span&gt; error.  I found this out thanks to a user on the &lt;a href="irc://irc.freenode.net/wikipedia-en-help"&gt;#mediawiki&lt;/a&gt; IRC channel.  They &lt;a href="http://meta.wikimedia.org/wiki/User-Agent_policy"&gt;suggest&lt;/a&gt; that you set t&lt;span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"&gt;he User-Agent to so&lt;/span&gt;mething which uniquely identifies your program or application.  They strongly discourage using the User-Agent string of a browser because this signals that you might be doing something malicious.&lt;br /&gt;&lt;br /&gt;It is easy to s&lt;span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"&gt;et the User-Agent he&lt;/span&gt;ader in PHP.  You can either edit your PHP installation's &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;php.ini&lt;/span&gt; file or add the following line of code to your PHP script.  The &lt;a href="http://php.net/curl"&gt;cURL&lt;/a&gt; library also supports setting HTTP headers, but this library is not included in the standard PHP installation.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_php"&gt;//tell it what value to use for the User-Agent header&lt;br /&gt;ini_set('user_agent', 'My Cool Screen-Scraper (+http://www.mangst.com)');&lt;br /&gt;&lt;br /&gt;//includes the above User-Agent header in this request and all subsequent requests&lt;br /&gt;$page = file_get_contents('http://en.wikipedia.org/wiki/Pumpkin');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that this is different from the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;header()&lt;/span&gt; function.  T&lt;span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"&gt;he header() fu&lt;/span&gt;nction is used to set the headers of the HTTP response that the PHP script itself is generating.  This has nothing to do with any HTTP requests that the script makes in the process of generating its response.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6140468534230159365?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6140468534230159365/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6140468534230159365' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6140468534230159365'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6140468534230159365'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/10/screen-scraping-wikipedia.html' title='Screen-scraping Wikipedia'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/TNcZGDRkCBI/AAAAAAAAALE/k5YeF7rdOCY/s72-c/angry-pumpkin.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7948575515528344800</id><published>2010-10-25T16:42:00.000-04:00</published><updated>2010-10-25T16:42:30.993-04:00</updated><title type='text'>Poor Man's FTP</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s1600/linux.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s200/linux.gif" width="180" /&gt;&lt;/a&gt;&lt;/div&gt;In the November issue of &lt;a href="http://www.linuxjournal.com/"&gt;Linux Journal Magazine&lt;/a&gt;, Kyle Rankin wrote an article about his experience attending the DEF CON conference. &amp;nbsp;One lesson he took away was the importance of knowing how to use the basic Linux commands, such as &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;vi&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sh&lt;/span&gt;. &amp;nbsp;Being familiar with these commands means that you won't be dead in the water if you have to work on a computer with a minimal Linux install.&lt;br /&gt;&lt;br /&gt;One of these commands is netcat (&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;nc&lt;/span&gt;). &amp;nbsp;Netcat allows you to open TCP and UDP connections with other computers as well as listen for connections. &amp;nbsp;Kyle described many interesting ways that you can use this command. &amp;nbsp;My favorite was using it to transfer files. &amp;nbsp;I think that this technique would come in very handily if ssh or ftp is not installed.&lt;br /&gt;&lt;br /&gt;It's very simple. &amp;nbsp;The computer receiving the file runs this command:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;nc -l 31337 &amp;gt; output_file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And then the computer sending the file runs this command:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;nc hostname 31337 &amp;lt; input_file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This will send the file through port &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;31337&lt;/span&gt;&amp;nbsp;and automatically close the connection when the transfer is complete. &amp;nbsp;It doesn't matter what port you use, so long as the port isn't being used by another program.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7948575515528344800?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7948575515528344800/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7948575515528344800' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7948575515528344800'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7948575515528344800'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/10/poor-mans-ftp.html' title='Poor Man&apos;s FTP'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/TMXrGf6Jk3I/AAAAAAAAAK4/MLVsGWl9E4s/s72-c/linux.gif' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2717067097891323262</id><published>2010-10-22T18:21:00.000-04:00</published><updated>2010-10-22T18:21:25.831-04:00</updated><title type='text'>Starbucks Wi-Fi</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_73vOQEMfTVI/TMIMfUGcEzI/AAAAAAAAAK0/ywf_JY58qwc/s1600/Starbucks_Dog.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="151" src="http://4.bp.blogspot.com/_73vOQEMfTVI/TMIMfUGcEzI/AAAAAAAAAK0/ywf_JY58qwc/s200/Starbucks_Dog.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;I ran into a small problem at a Starbucks store the other day. &amp;nbsp;I had my netbook with me and wanted to connect to their Wi-Fi network. &amp;nbsp;The service is free, but in order to access the Internet, you must first visit a Starbucks webpage that asks you to accept their terms and conditions. &amp;nbsp;Any attempt to visit any other website will redirect you to this page.&lt;br /&gt;&lt;br /&gt;I like to have my browser reopen all the tabs from my last browsing session when it starts up. &amp;nbsp;My problem was that, because I have to accept the terms and conditions first, all my tabs would redirect to the Starbucks page. &amp;nbsp;Clicking the back button after accepting the terms and conditions doesn't return me to my original page. &amp;nbsp;I think that this is because it's a HTTP 3xx redirect response. &amp;nbsp;So I basically lose all my tabs.&lt;br /&gt;&lt;br /&gt;Getting around this wasn't too tricky. &amp;nbsp;The terms and conditions page is just an HTML form with a bunch of hidden parameters and a checkbox for "I agree". &amp;nbsp;I wrote a Java program to parse all the parameters out of the page and submit the form. &amp;nbsp;So if I run this before opening my browser, my browser will reload all its tabs no problem. &amp;nbsp;No annoying redirects to the Starbucks page.&lt;br /&gt;&lt;br /&gt;You can &lt;a href="http://www.mangst.com/projects/starbucks/Starbucks.java"&gt;download it here&lt;/a&gt;. &amp;nbsp;I put all the classes in one file to make it simpler. &amp;nbsp;I also wrote some &lt;a href="http://www.mangst.com/projects/starbucks/HtmlFormTest.java"&gt;JUnit tests&lt;/a&gt; to test the part that parses the HTML page. &amp;nbsp;To run it, just compile the file and run &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;java Starbucks -v&lt;/span&gt;. &amp;nbsp;The &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;-v&lt;/span&gt;&amp;nbsp;(verbose) is optional and will cause it to print status messages as it's working. &amp;nbsp;Run this program as soon as you connect to the Starbucks Wi-Fi network (and before you open your browser).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_73vOQEMfTVI/TMILAe23-gI/AAAAAAAAAKw/jWwDinq2Y3g/s1600/console.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="170" src="http://3.bp.blogspot.com/_73vOQEMfTVI/TMILAe23-gI/AAAAAAAAAKw/jWwDinq2Y3g/s400/console.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2717067097891323262?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2717067097891323262/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2717067097891323262' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2717067097891323262'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2717067097891323262'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/10/starbucks-wi-fi.html' title='Starbucks Wi-Fi'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/TMIMfUGcEzI/AAAAAAAAAK0/ywf_JY58qwc/s72-c/Starbucks_Dog.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6229624836335273697</id><published>2010-10-18T15:18:00.002-04:00</published><updated>2010-10-18T15:38:58.336-04:00</updated><title type='text'>Peer-to-Peer (P2P) Systems</title><content type='html'>In the current issue of the magazine Communications of the ACM, there is an article called &lt;a href="http://cacm.acm.org/magazines/2010/10/99498-peer-to-peer-systems/fulltext"&gt;Peer-to-Peer Systems by Rodrigo Rodrigues and Peter Druschel&lt;/a&gt;.  Along with discussing the pros and cons of P2P networks and including examples of how they are used, the article goes into technical detail about how they work.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_73vOQEMfTVI/TLyZzc0bn8I/AAAAAAAAAKc/P38U36fgHZY/s1600/partly-centralized.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://3.bp.blogspot.com/_73vOQEMfTVI/TLyZzc0bn8I/AAAAAAAAAKc/P38U36fgHZY/s200/partly-centralized.png" width="143" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The article divides P2P systems into two types.  One type is &lt;b&gt;partly centralized&lt;/b&gt;.  In these systems, there exists a single controller node, which keeps a list of all nodes that are connected to the network, along with the resources that each node is sharing.  A good example of this kind of P2P network would be Napster (now non-existent).  When you searched for a song on Napster, a request would be sent to a centralized server owned by the Napster folks themselves.  This server would then search its database for all computers in the P2P network that had the song, and return this list to you.  You then downloaded the song by directly connecting to the computer hosting the file.  Without this server, there would be no way to get the song that you were looking for because you wouldn't know what computers, out of all the computers in the entire Internet, are both sharing their music collection and have the song you are looking for.&lt;br /&gt;&lt;br /&gt;The other type of P2P network is &lt;b&gt;decentralized&lt;/b&gt;.  In a decentralized P2P network, there is no controller node that knows about all the computers in the network.  Computers connect to the network through a bootstrap node, which is just one computer in the network that makes its IP address publicly known.  This lack of centralization makes these types of P2P networks more robust, as the network is not dependent on a single server being functional.  But because of this lack of centralization, there is no straight-forward way of knowing what computers are connected to the network or what resources they are sharing.  This makes searching for particular resources trickier.  The article describes two ways in which a decentralized P2P network can be structured in order to solve this problem of search.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_73vOQEMfTVI/TLyajE1r6II/AAAAAAAAAKg/y_9XSbxM96I/s1600/decentralized-unstructured.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://2.bp.blogspot.com/_73vOQEMfTVI/TLyajE1r6II/AAAAAAAAAKg/y_9XSbxM96I/s200/decentralized-unstructured.png" width="151" /&gt;&lt;/a&gt;&lt;/div&gt;One way of structuring a decentralized P2P network&amp;nbsp;is by using an &lt;b&gt;unstructured overlay&lt;/b&gt; (an &lt;i&gt;overlay&amp;nbsp;&lt;/i&gt;is a graph that describes how the nodes are connected with each other).  Each computer in the network only knows about a few other computers that are also connected to the network.  To search for a file, the computer will query its neighbors first.  If none of its neighbors have the file, then it will ask its neighbor's neighbors.  If none of these computers have the file, then it will ask its neighbor's neighbor's neighbors, and so on.  This kind of decentralized network is fine if the resource you are looking for is replicated across many other nodes.  However, if the resource is rare, then finding that resource could take a very long time.  For example, imagine having to search for a file which exists on only one computer in a network of one million computers.  The odds of that computer being within a short search distance to your computer is slim.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_73vOQEMfTVI/TLyhZI0vGgI/AAAAAAAAAKs/w5SP3P5Jzn4/s1600/decentralized-structured3.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/_73vOQEMfTVI/TLyhZI0vGgI/AAAAAAAAAKs/w5SP3P5Jzn4/s200/decentralized-structured3.png" width="156" /&gt;&lt;/a&gt;&lt;/div&gt;The other way a decentralized P2P network can be structured is by using a &lt;b&gt;structured overlay&lt;/b&gt;.  I didn't quite understand the specifics of this technique, but it involves the use of unique keys.  Each computer in the network is assigned a unique key in such a way that all keys are evenly spread out in the &lt;i&gt;key space&lt;/i&gt;.  For example, if the key space is &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;0-999&lt;/span&gt;, the first node will be assigned a random key, say &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;432&lt;/span&gt;.  Then, the second node will be assigned a key around &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;932&lt;/span&gt; (&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;999/2+432&lt;/span&gt;, on the opposite side of the "circle").  The third key will be assigned a key around either &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;682&lt;/span&gt; or &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;182&lt;/span&gt;, and so on.  Each node only directly knows about its two neighbors, so the overlay graph looks like a circle.  The advantage to this type of overlay is that it makes searching much faster.  It's able to use these unique keys to quickly find a computer hosting the resource.  This is called &lt;i&gt;key-based routing&lt;/i&gt; (KBR).  Even if the resource is rare, it will still be able to find it quickly (unlike unstructured overlays, which must spend the time to ask each node directly).  However, the downside is that there exists an overhead to maintain these keys.  Extra work must be done every time a node enters or leaves the network, so if the number of computers that are connected to the network is constantly changing (called &lt;i&gt;churn&lt;/i&gt;) this may not be the best solution.&lt;br /&gt;&lt;br /&gt;It was a very good article, but I do have one criticism: it considers applications like SETI@home to be P2P.  How is this P2P?  You do not communicate with the other peers on the network.  You only communicate with the centralized SETI server in order to download new data to process.  I think that a better category for SETI@home would be "distributed computing", not P2P.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6229624836335273697?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6229624836335273697/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6229624836335273697' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6229624836335273697'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6229624836335273697'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/10/peer-to-peer-p2p-systems.html' title='Peer-to-Peer (P2P) Systems'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/TLyZzc0bn8I/AAAAAAAAAKc/P38U36fgHZY/s72-c/partly-centralized.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2931624965042183377</id><published>2010-10-02T19:57:00.000-04:00</published><updated>2010-10-02T19:57:15.900-04:00</updated><title type='text'>How to access your home computer over the Internet with VNC</title><content type='html'>The VNC protocol gives you remote control of another computer's screen.  You can see and interact with the computer as if you were sitting right in front of it.&lt;br /&gt;&lt;br /&gt;In this blog post, I'm going to describe how to set up your computer so that you can connect to it anywhere in the world through the Internet. &amp;nbsp;If your home computer is connected to a router (which it probably is), then the process is a little tricky, which is why I thought it would be helpful to write this blog post.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;&lt;b&gt;1. Install a VNC Server&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First, you must install a VNC Server on the computer you want to control.&lt;br /&gt;&lt;br /&gt;Mac OS X already comes with the necessary software, so you don't have to install anything.  To enable Mac's VNC Server, do the following:&lt;br /&gt;&lt;br /&gt;a. In System Preferences, click on "Sharing".&lt;br /&gt;b. Check the "Screen Sharing" checkbox to enable it.&lt;br /&gt;c. Click the "Computer Settings..." button.&lt;br /&gt;d. Check the box that says "VNC viewers may control screen with password".  Type in a password, then click OK.  &lt;b&gt;Remember that your computer will be visible to the world, so make sure the password is secure!&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_73vOQEMfTVI/TKfDFc5hQNI/AAAAAAAAAKU/GqVUDrQ17WM/s1600/system-preferences.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="262" src="http://1.bp.blogspot.com/_73vOQEMfTVI/TKfDFc5hQNI/AAAAAAAAAKU/GqVUDrQ17WM/s320/system-preferences.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you are running Windows, can you use &lt;a href="http://www.tightvnc.com/"&gt;TightVNC Server&lt;/a&gt; as a VNC Server.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;&lt;b&gt;2. Configure your router&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Your home computer is probably connected to a router, either through a wired, ethernet connection or a wireless connection.  A router connects all  of your computers together to form a home network and acts as the gate keeper to the Internet.  But if your computer is connected to a router, then it doesn't have its own IP address, which is what you need in order for VNC to connect to your computer.&lt;br /&gt;&lt;br /&gt;To get around this, you must tell your router to forward VNC traffic to the computer you want to control.  VNC communicates over port &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5900&lt;/span&gt;,&amp;nbsp;so you must tell your router to forward all data it receives from this port to the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5900&lt;/span&gt; port on your computer.  Here is how I did this with my Belkin router:&lt;br /&gt;&lt;br /&gt;First, open the router's configuration web page by typing its private IP address in a web browser.  My router's private IP is &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;192.168.2.1&lt;/span&gt;. (Private IP addresses are only visible within your home network--they are not visible from the Internet.)&lt;br /&gt;&lt;br /&gt;Then, click on the "Virtual Servers" menu option under the "Firewall" category. &amp;nbsp;It will ask you for a password.  If you haven't configured the router with a password, then just click "Submit". &amp;nbsp;This page lists all the data that the router will forward to other computers on the network.  Pick an empty row and enter &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5900&lt;/span&gt; for the Inbound and Private port fields. Then, enter the private IP address of the computer you want to control.  Finally, click on the "Enable" checkbox, then click "Apply Changes".  As you can see in the screenshot, you can do this for other services too like SSH, FTP, or HTTP.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_73vOQEMfTVI/TKfDtxaPQxI/AAAAAAAAAKY/4iJctCmMm0g/s1600/virtual-servers.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/_73vOQEMfTVI/TKfDtxaPQxI/AAAAAAAAAKY/4iJctCmMm0g/s320/virtual-servers.png" width="290" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;&lt;b&gt;3. Test it out&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You'll need a VNC Viewer in order to connect to the computer.  &lt;a href="http://sourceforge.net/projects/cotvnc/"&gt;Chicken of the VNC&lt;/a&gt; is a good VNC Viewer for Mac.  For Windows, you can use &lt;a href="http://www.tightvnc.com/"&gt;TightVNC Viewer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;You'll also need the IP address of your router.  An easy way to get the IP address of your router is to visit &lt;a href="http://www.whatismyip.com/"&gt;whatismyip.com&lt;/a&gt; from one of the computers that are connected to the router.&lt;br /&gt;&lt;br /&gt;If the VNC Viewer asks for a display number, enter "0".  Display "0" maps to port &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5900&lt;/span&gt;, display "1" maps to port &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5901&lt;/span&gt;, display "2" maps to port &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;5902&lt;/span&gt;, etc.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;&lt;b&gt;4. Get a free domain name (optional)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.dyndns.com/"&gt;DynDNS&lt;/a&gt; is a free service that maps your IP address to a domain name like &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;foobar.dyndns.com&lt;/span&gt;.  Check to see if your router supports this service.  My router will automatically update my DynDNS account whenever the router's IP address changes (which can happen often).  Using DynDNS means you don't have to memorize your IP address or worry about it changing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2931624965042183377?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2931624965042183377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2931624965042183377' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2931624965042183377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2931624965042183377'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/10/how-to-access-your-home-computer-over.html' title='How to access your home computer over the Internet with VNC'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/TKfDFc5hQNI/AAAAAAAAAKU/GqVUDrQ17WM/s72-c/system-preferences.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8210054306877964118</id><published>2010-09-22T17:26:00.001-04:00</published><updated>2010-09-22T17:28:37.554-04:00</updated><title type='text'>More Screen Scraping!</title><content type='html'>The website &lt;a href="http://celebritybooksigningsandevents.com/events"&gt;CelebrityBookSigningsAndEvents.com&lt;/a&gt; contains a list of book signing appearances that different celebrities are making across the country.  I like to go to this website occasionally to see if there is anybody I would be interested in seeing.  However, it does not contain any search functionality that allows you to see what celebrities are visiting your area.  I thought that it would be fun to screen scrape the webpage and &lt;a href="http://www.mangst.com/projects/book-signings"&gt;make the data more searchable&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;big&gt;&lt;strong&gt;Screen Scraping Overview&lt;/strong&gt;&lt;/big&gt;&lt;br /&gt;&lt;br /&gt;Screen scraping means taking the HTML source code of a webpage and extracting data out of it.  For example, one thing that I wanted to extract from &lt;a href="http://celebritybooksigningsandevents.com/events"&gt;CelebrityBookSigningsAndEvents.com&lt;/a&gt; was the titles of all the books.  Looking at the source code, I could see a pattern to where the titles were located in the HTML.  They all looked something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;div class="modWrap"&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;    &amp;lt;font size="4"&amp;gt;&lt;br /&gt;      &amp;lt;strong&amp;gt;&lt;br /&gt;        &amp;lt;em&amp;gt;&lt;br /&gt;          &amp;lt;u&amp;gt;&lt;br /&gt;            &amp;lt;font color="#0000ff"&amp;gt;Mysterious Galaxy&amp;lt;/font&amp;gt;&lt;br /&gt;          &amp;lt;/u&amp;gt;&lt;br /&gt;        &amp;lt;/em&amp;gt;&lt;br /&gt;     &amp;lt;/strong&amp;gt;&lt;br /&gt;    &amp;lt;/font&amp;gt;&lt;br /&gt;  ...&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;  ...&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;    &amp;lt;font size="4"&amp;gt;&lt;br /&gt;      &amp;lt;strong&amp;gt;&lt;br /&gt;        &amp;lt;em&amp;gt;&lt;br /&gt;          &amp;lt;u&amp;gt;&lt;br /&gt;            &amp;lt;font color="#0000ff"&amp;gt;Called To Coach&amp;lt;/font&amp;gt;&lt;br /&gt;          &amp;lt;/u&amp;gt;&lt;br /&gt;        &amp;lt;/em&amp;gt;&lt;br /&gt;     &amp;lt;/strong&amp;gt;&lt;br /&gt;    &amp;lt;/font&amp;gt;&lt;br /&gt;  ...&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;  ... more tag hierarchies like these&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To extract these book titles, I used XPath queries.  This is a query language that allows you to pull data from specific parts of an XML document.  To get all the book titles from my example, the XPath query would look like the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;//div[@class="modWrap"]/p/font/strong/em/u/font&lt;/pre&gt;&lt;br /&gt;This will return a list all the &lt;code&gt;font&lt;/code&gt; tags that are nested within this particular hierarchy of tags.  The two slashes are the beginning mean that it doesn't matter what tags come before the &lt;code&gt;div&lt;/code&gt; tag.  The &lt;code&gt;[@class='modWrap']&lt;/code&gt; part returns only those &lt;code&gt;div&lt;/code&gt; tags that have a &lt;code&gt;class&lt;/code&gt; attribute with a value of "modWrap".  The PHP code to run this query would look like the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_php"&gt;//load the HTML source code into a DOM&lt;br /&gt;$html = file_get_contents('http://www.celebritybooksigningsandevents.com/events');&lt;br /&gt;$dom = new DOMDocument();&lt;br /&gt;$dom-&gt;loadHTML($html);&lt;br /&gt;&lt;br /&gt;//run the XPath query&lt;br /&gt;$xpath = new DOMXPath($dom);&lt;br /&gt;$bookTitleNodes = $xpath-&gt;query('//div[@class="modWrap"]/p/font/strong/em/u/font');&lt;br /&gt;foreach ($bookTitleNodes as $node){&lt;br /&gt;  echo $node-&gt;textContent; //the title is the text within the "font" tag&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;big&gt;&lt;strong&gt;Problems&lt;/strong&gt;&lt;/big&gt;&lt;br /&gt;&lt;br /&gt;Aside from slight inconsistencies in the structure of the HTML, which I could account for by tweaking the XPath queries, the biggest problem I ran into was the fact that the source code was littered with these two strange characters--ASCII 160 and ASCII 194.  Having ASCII values greater than 127, they were not part of the normal ASCII character set, which gave me problems in places where I needed to access individual characters in a string.  They appeared as spaces in my web browser, but were not treated as spaces in my PHP code.  Simply replacing all of these characters with spaces before creating the DOM fixed this issue.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;DOMDocument::loadHTML()&lt;/code&gt; function was throwing warnings that didn't affect the screen scraping results, but that I didn't want appearing on my webpage.  You can silence the error messages that a function generates using PHP's &lt;code&gt;@&lt;/code&gt; operator: &lt;code&gt;@$dom-&gt;loadHTML($html);&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I also ran into a problem of my web server running an earlier version of PHP than my local computer.  It took me forever to track down.  FYI: &lt;code&gt;DateTime::getTimestamp()&lt;/code&gt; is only supported in PHP versions 5.3 and above...&lt;br /&gt;&lt;br /&gt;&lt;big&gt;&lt;strong&gt;Implementation&lt;/strong&gt;&lt;/big&gt;&lt;br /&gt;&lt;br /&gt;After scraping the page, I save the data to an XML file and use it as a cache.  If the file gets to be more than an hour old, it will refresh the cache by re-scraping the original webpage.  This keeps the cache up to date with any changes that were made to the original webpage.  And by using a cache, &lt;a href="http://celebritybooksigningsandevents.com/events"&gt;CelebrityBookSigningsAndEvents.com&lt;/a&gt; is not constantly harassed by requests from my website.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8210054306877964118?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8210054306877964118/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8210054306877964118' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8210054306877964118'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8210054306877964118'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/09/more-screen-scraping.html' title='More Screen Scraping!'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7622153108919242995</id><published>2010-08-31T14:25:00.000-04:00</published><updated>2010-08-31T14:25:58.080-04:00</updated><title type='text'>Bemuled!</title><content type='html'>Yesterday, I finished designing a small game written in Java.  It is called &lt;a href="http://www.mangst.com/projects/bemuled"&gt;Bemuled!&lt;/a&gt; and it's based on the infamous game Bejeweled.  The rules of the game differ slightly in that you are only given ten moves in which to make the most amount of points that you can.  It uses Java's Web Start technology, so you can launch it simply by clicking on a link in your browser.  It took about a week to make and I had a lot of fun making it.  You can read more about the technical aspects of the application by reading the &lt;a href="http://www.mangst.com/projects/bemuled/bemuled-technical.pdf"&gt;Technical Overview WHITEPAPER&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_73vOQEMfTVI/TH1I7sAhY8I/AAAAAAAAAKM/37b0iiDayN4/s1600/screenshot.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_73vOQEMfTVI/TH1I7sAhY8I/AAAAAAAAAKM/37b0iiDayN4/s320/screenshot.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7622153108919242995?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7622153108919242995/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7622153108919242995' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7622153108919242995'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7622153108919242995'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/08/bemuled.html' title='Bemuled!'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/TH1I7sAhY8I/AAAAAAAAAKM/37b0iiDayN4/s72-c/screenshot.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-613267879016463002</id><published>2010-07-25T11:38:00.001-04:00</published><updated>2010-09-02T16:14:44.672-04:00</updated><title type='text'>Does screen brightness significantly affect battery life?</title><content type='html'>&lt;style type="text/css"&gt;table.data { border-collapse: collapse;}table.data th { border-width: 1px; padding: 5px; border-style: solid; border-color: gray;}table.data td { border-width: 1px; padding: 5px; border-style: solid; border-color: gray;}&lt;/style&gt;&lt;br /&gt;&lt;br /&gt;Many laptops will automatically dim the screen when running on battery power in order to conserve energy.  But does this significantly increase the battery life?  How much power is actually saved when the screen's brightness is turned down?  My guess has always been that the energy savings are negligible, that a darkened screen might give the laptop 10 more minutes of battery life, nothing more.&lt;br /&gt;&lt;br /&gt;To discover the answer, I ran a small experiment.  I ran a script which polled the battery data on my netbook every 5 seconds.  I monitored this data for 2 minutes with the screen on its brightest setting and 2 minutes with the screen on its darkest setting.  I disabled my netbook's wireless network card and muted the speakers to avoid any interference in the data.  The results were marginally better than I expected:&lt;br /&gt;&lt;br /&gt;&lt;table class="data"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Avg. mA&lt;/th&gt;&lt;th&gt;Bat. life&lt;br /&gt;(4200 mAh battery)&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Brightest&lt;/th&gt;&lt;td&gt;1120.8&lt;/td&gt;&lt;td&gt;3h 42m&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Darkest&lt;/th&gt;&lt;td&gt;1018.6&lt;/td&gt;&lt;td&gt;4h 7m&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;b&gt;If kept on its darkest screen setting, my netbook battery would last about 25 minutes longer&lt;/b&gt;.  If the brightness were to be set somewhere in between the two extremes to a level where the screen would actually be readable, the energy savings would probably be about half that--12.5 minutes.&lt;br /&gt;&lt;br /&gt;The battery data was collected from "/proc/acpi/battery/BAT0/state".  The script used to poll the data was written in PHP and is shown below:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_php"&gt;&amp;lt;?php&lt;br /&gt;while(true){&lt;br /&gt; $state = file_get_contents('/proc/acpi/battery/BAT0/state');&lt;br /&gt;&lt;br /&gt; preg_match('/present rate:\\s+(\\d+)/', $state, $matches);&lt;br /&gt; $presentRate = $matches[1];&lt;br /&gt;&lt;br /&gt; echo date('M d G:i:s'), ' ', $presentRate, "\n";&lt;br /&gt;&lt;br /&gt; sleep(5);&lt;br /&gt;} &lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-613267879016463002?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/613267879016463002/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=613267879016463002' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/613267879016463002'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/613267879016463002'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/07/does-screen-brightness-significantly.html' title='Does screen brightness significantly affect battery life?'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4126494302653357087</id><published>2010-07-21T17:37:00.001-04:00</published><updated>2010-07-21T17:39:52.208-04:00</updated><title type='text'>UNetbootin Troubles</title><content type='html'>&lt;style type="text/css"&gt;table { border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}table th { border-width: 1px; padding: 5px; border-style: solid; border-color: gray;}table td { border-width: 1px; padding: 5px; border-style: solid; border-color: gray;}&lt;/style&gt;&lt;br /&gt;If you are having trouble running the &lt;a href="http://unetbootin.sourceforge.net/unetbootin-linux-latest"&gt;UNetbootin Linux binary&lt;/a&gt;, you can try installing it using apt-get.  This will automatically download and install any required dependencies.&lt;br /&gt;&lt;br /&gt;1. Add the following lines to "/etc/apt/sources.list".&lt;br /&gt;&lt;pre&gt;deb http://ppa.launchpad.net/gezakovacs/ppa/ubuntu [name] main&lt;br /&gt;deb-src http://ppa.launchpad.net/gezakovacs/ppa/ubuntu [name] main&lt;br /&gt;&lt;/pre&gt;Where "[name]" is the name of your Ubuntu version:&lt;br /&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Version&lt;/th&gt;&lt;th&gt;Name&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10.04&lt;/td&gt;&lt;td&gt;lucid&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9.04&lt;/td&gt;&lt;td&gt;jaunty&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9.10&lt;/td&gt;&lt;td&gt;karmic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8.10&lt;/td&gt;&lt;td&gt;intrepid&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8.04&lt;/td&gt;&lt;td&gt;hardy&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;2. Add the GPG key.&lt;br /&gt;&lt;pre&gt;sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 72D340A3&lt;/pre&gt;3. Get the list of packages from the newly added source.&lt;br /&gt;&lt;pre&gt;sudo apt-get update&lt;/pre&gt;4. Install UNetbootin.  It will appear in the "System Tools" menu.&lt;br /&gt;&lt;pre&gt;sudo apt-get install unetbootin&lt;/pre&gt;&lt;b&gt;Resources:&lt;/b&gt;&lt;br /&gt;1. &lt;a href="http://ubuntuguide.net/how-to-install-unetbootin-in-ubuntu"&gt;How to install UNetbootin in ubuntu&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4126494302653357087?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4126494302653357087/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4126494302653357087' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4126494302653357087'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4126494302653357087'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/07/unetbootin-troubles.html' title='UNetbootin Troubles'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2853977955371732047</id><published>2010-05-31T21:16:00.001-04:00</published><updated>2010-05-31T21:17:20.248-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='php java'/><title type='text'>Flair Additions</title><content type='html'>So I made some improvements to my &lt;a href="http://www.mangst.com/flair"&gt;developer forum flair&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Firstly&lt;/b&gt;, I added a &lt;a href="http://forums.sun.com/"&gt;Sun Forums&lt;/a&gt; flair, which displays your username, number of posts, and number of Dukes.&lt;br /&gt;&lt;br /&gt;&lt;iframe frameborder="0" height="100" marginheight="0" marginwidth="0" scrolling="no" src="http://www.mangst.com/flair/sun?id=1071155&amp;amp;type=html" width="220"&gt;&lt;/iframe&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre class="sh_php"&gt;//tidy the page&lt;br /&gt;$tidy = new tidy("http://forums.sun.com/profile.jspa?userID=$id");&lt;br /&gt;$tidy-&gt;cleanRepair();&lt;br /&gt;$tidy = preg_replace('/&amp;lt;\/?nobr&amp;gt;/', '', $tidy); //&amp;lt;nobr&amp;gt; tags must be removed for DOMDocument&lt;br /&gt;&lt;br /&gt;//load the page&lt;br /&gt;$html = new DOMDocument();&lt;br /&gt;$html-&gt;loadHTML($tidy);&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Secondly&lt;/b&gt;, I added the ability to view the flair as a dynamically created image (nerdgasm!!!). Just change the "type" parameter to "png" or "jpeg":&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;img src="http://www.mangst.com/flair/sun?id=1071155&amp;amp;type=png" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;img src="http://www.mangst.com/flair/sun?id=1071155&amp;amp;type=png" /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2853977955371732047?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2853977955371732047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2853977955371732047' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2853977955371732047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2853977955371732047'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/05/flair-additions.html' title='Flair Additions'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2262111801504273344</id><published>2010-05-25T18:14:00.000-04:00</published><updated>2010-05-25T18:14:06.030-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java php'/><title type='text'>JavaRanch Flair</title><content type='html'>Check out my JavaRanch flair!&lt;br /&gt;&lt;br /&gt;&lt;iframe  src="http://www.mangst.com/flair/javaranch?id=209694&amp;type=html"  marginwidth="0"  marginheight="0"   frameborder="0"  scrolling="no"  width="220"  height="100"&gt;&lt;br /&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;I was so inspired by the flair over at &lt;a href="http://www.stackoverflow.com/users/flair"&gt;Stackoverflow&lt;/a&gt; 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:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Javascript&lt;/b&gt; - You can include it using a &amp;lt;script&amp;gt; tag, which injects the flair into the DOM via Javascript.&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;script&lt;br /&gt;  src="http://www.mangst.com/flair/javaranch?id=209694&amp;type=js"&lt;br /&gt;  type="text/javascript"&amp;gt;&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;HTML&lt;/b&gt; - You can include it using an &amp;lt;iframe&amp;gt;, which loads the flair as HTML into the frame.&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;iframe&lt;br /&gt;  src="http://www.mangst.com/flair/javaranch?id=209694&amp;type=html"&lt;br /&gt;  marginwidth="0"&lt;br /&gt;  marginheight="0" &lt;br /&gt;  frameborder="0"&lt;br /&gt;  scrolling="no"&lt;br /&gt;  width="220"&lt;br /&gt;  height="100"&amp;gt;&lt;br /&gt;&amp;lt;/iframe&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;JSON&lt;/b&gt; - You can get the raw data with JSON and handle the data however you wish with Javascript.&lt;br /&gt;&lt;pre&gt;JSON URL: http://www.mangst.com/flair/javaranch?id=209694&amp;type=json&lt;/pre&gt;&lt;/ul&gt;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.&lt;pre class="sh_php"&gt;$html = new DOMDocument();&lt;br /&gt;$html-&gt;loadHtmlFile("http://www.coderanch.com/forums/user/profile/$id");&lt;br /&gt;$xpath = new DOMXPath($html);&lt;br /&gt;$username = $xpath-&gt;query("//span[@id='profileUserName']")-&gt;item(0)-&gt;textContent;&lt;br /&gt;//...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2262111801504273344?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2262111801504273344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2262111801504273344' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2262111801504273344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2262111801504273344'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/05/javaranch-flair.html' title='JavaRanch Flair'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2564640063753200735</id><published>2010-05-07T16:10:00.000-04:00</published><updated>2010-05-07T16:10:29.391-04:00</updated><title type='text'>SimpleDateFormat and Thread Safety</title><content type='html'>&lt;b&gt;&lt;span style="font-size:1.2em"&gt;SimpleDateFormat&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre class="sh_java"&gt;DateFormat df = new SimpleDateFormat("MM/dd/yyyy");&lt;br /&gt;Date d = df.parse("04/15/2010");&lt;br /&gt;String s = df.format(d);&lt;br /&gt;"04/15/2010".equals(s); //true&lt;br /&gt;&lt;/pre&gt;This class is documented as not being thread-safe, but I decided to see for myself if this was true.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-size:1.2em"&gt;Thread-safety proof&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;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 &lt;b&gt;local instance&lt;/b&gt; of SimpleDateFormat that no other thread has access to, and using a &lt;b&gt;static instance&lt;/b&gt; 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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-size:1.2em"&gt;Usage&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;The following command will create ten threads, each of which will generate twenty dates:&lt;br /&gt;&lt;code&gt;java SimpleDateFormatThreadSafe 10 20&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-size:1.2em"&gt;Source code&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;pre class="sh_java"&gt;import java.text.DateFormat;&lt;br /&gt;import java.text.SimpleDateFormat;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Calendar;&lt;br /&gt;import java.util.Date;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Proves that SimpleDateFormat is indeed not thread safe (as documented in the&lt;br /&gt; * javadocs).&lt;br /&gt; * @author mangstadt&lt;br /&gt; */&lt;br /&gt;public class SimpleDateFormatThreadSafe {&lt;br /&gt;  private static final String format = "MM/dd/yy";&lt;br /&gt;  private static final DateFormat staticDf = new SimpleDateFormat(format);&lt;br /&gt;  private static int numThreads = 10;&lt;br /&gt;  private static int numLoopsPerThread = 10;&lt;br /&gt;&lt;br /&gt;  public static void main(String args[]) throws Exception {&lt;br /&gt;    //get the arguments&lt;br /&gt;    if (args.length &gt; 0){&lt;br /&gt;      numThreads = Integer.parseInt(args[0]);&lt;br /&gt;      if (args.length &gt; 1){&lt;br /&gt;        numLoopsPerThread = Integer.parseInt(args[1]);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    //create the threads&lt;br /&gt;    MyThread threads[] = new MyThread[numThreads];&lt;br /&gt;    for (int i = 0; i &lt; threads.length; ++i) {&lt;br /&gt;      threads[i] = new MyThread();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    //start the threads&lt;br /&gt;    for (MyThread t : threads) {&lt;br /&gt;      t.start();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    //check the results&lt;br /&gt;    boolean allIdentical = true;&lt;br /&gt;    for (MyThread t : threads) {&lt;br /&gt;      t.join();&lt;br /&gt;      if (!t.localList.equals(t.staticList)){&lt;br /&gt;        System.out.println(t.getName() + " lists are different:");&lt;br /&gt;        System.out.println("local:  " + t.localList);&lt;br /&gt;        System.out.println("static: " + t.staticList);&lt;br /&gt;        System.out.println();&lt;br /&gt;        allIdentical = false;&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    if (allIdentical){&lt;br /&gt;      System.out.println("All lists are identical.");&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static class MyThread extends Thread {&lt;br /&gt;    public final List&lt;String&gt; localList = new ArrayList&lt;String&gt;();&lt;br /&gt;    public final List&lt;String&gt; staticList = new ArrayList&lt;String&gt;();&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void run() {&lt;br /&gt;      DateFormat localDf = new SimpleDateFormat(format);&lt;br /&gt;      for (int i = 0; i &lt; numLoopsPerThread; ++i) {&lt;br /&gt;        //create a random Date&lt;br /&gt;        Calendar c = Calendar.getInstance();&lt;br /&gt;        c.set(Calendar.MONTH, randInt(0, 12));&lt;br /&gt;        c.set(Calendar.DATE, randInt(1, 21));&lt;br /&gt;        c.set(Calendar.YEAR, randInt(1990, 2011));&lt;br /&gt;        Date d = c.getTime();&lt;br /&gt;&lt;br /&gt;        //add formatted dates to lists&lt;br /&gt;        localList.add(localDf.format(d));&lt;br /&gt;        //synchronized (staticDf){&lt;br /&gt;        staticList.add(staticDf.format(d));&lt;br /&gt;        //}&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static int randInt(int min, int max) {&lt;br /&gt;    return (int) (Math.random() * (max - min) + min);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2564640063753200735?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2564640063753200735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2564640063753200735' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2564640063753200735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2564640063753200735'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/05/simpledateformat-and-thread-safety.html' title='SimpleDateFormat and Thread Safety'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-422796619759971600</id><published>2010-04-11T16:48:00.015-04:00</published><updated>2010-04-13T22:07:45.746-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web-programming'/><title type='text'>The Data URI</title><content type='html'>Typically, an image is included in a web page by referencing an image file:&lt;div align="center"&gt;&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;img src="take-this.jpg" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 325px; height: 261px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/S8UZELlPj9I/AAAAAAAAAIg/bYkQBIjbq78/s400/take-this.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5459797682875764690" /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;But it's also possible to put the image directly &lt;em&gt;inside&lt;/em&gt; the web page source.  This is done using a &lt;strong&gt;data URI&lt;/strong&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;pre class="sh_html"&gt;&amp;lt;img src="data:image/jpeg;base64,&lt;em&gt;image-data-goes-here&lt;/em&gt;" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QB2RXhpZgAASUkqAAgAAAABAGmHBAABAAAAGgAAAAAAAAABAIaSBwBCAAAALAAAAAAAAABBU0NJSQAAAENSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDc1CgD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAEFAUUDASIAAhEBAxEB/8QAHAAAAQUBAQEAAAAAAAAAAAAAAAMEBQYHAgEI/8QARhAAAQQBAgQDBAYHBQYHAQAAAgADBBIFASIGEzJCEVJiBxQjciExM1GCoggVJEFhcZJDRHPS4RY0k6OxslNUgZGhwtHi/8QAGgEBAAMBAQEAAAAAAAAAAAAAAAIDBAEFBv/EACkRAQEAAgEDAwQCAgMAAAAAAAACAxIBBBEiEyFCFDEyUQVBUpEVIzP/2gAMAwEAAhEDEQA/APjJCEIBCEIO2xseg+Ph46+CmNMEeoCWj2vhr6P9VCjr4a6aq04ueJxR8w7VDJt8UpMP1Fru/aOn0f6pT/Z7SlvfPp8Onlf6qZZcsNa9S6uXNqXSs/rUnrKqPQRbKvO/KkyjBp/bflUvkoRm4Tg7QrZR3uoadRErZydzmeDVxkQ0+1ElyLXj3JWg6n4AK7LSoiIqezmpqY66a+CdRYXP0+18Pwrh5rZZK449yVXj7OTPv5HjeD0P6pP5P9UqPDumv971/wCF/qnsXXanzeqyVmuWuMMUhC4f00/vmv8Aw/8AVIuYan1yNf6P9VY3BsKavaKHHUX+1n08fpW3sfy/7Xx/CmjgUKqnpg7VCSx8HFqxZOaZM0TP2IIQhXqAhCEAhCEAhCEAvfBeiJa/Utl9iHsqc4i1azOZbJvGiVgHX+1UarUZjjeGs1PDmRoD5D99E2yuKmYsxbmN6tlr+7VfdGPgYzGwwiRYbTYANR2rBv0qsZEZ/V8mMwDZkRWIR6lCMu1J1OrAEIQrUAhCEAhCEAhCEAhCEAhCEAhCEApTBOgEnlOFXQu5Ra6HXUC0LRKGgNxBGrgkkpQUfEu1ccPyffIXjodiHaQpWdoWgiPcvPqdaXyTcatDK3lVckAX0iKsbZ+LRD5kzmRh0aI67upSl1B0ENxbV4y1ciMkq8HMcqKWHSgVVmwavDtTSPrSQpBxMJGlHtNVbCFJ6Ge0VINnYVEY8vEBJSDZeArFknya8P4nY67Vw5tHpSXNqvHHrKqZadjWUKhMgG6ym3it2qMnBfTVasH5MmeUZVFUFp4FVeeC2sI8F4lRYdPpbLX/ANEozEkumQNNOEWn7tBTaQ2QpVnAZN3pjOf0py5wpmQIR90c3fwXNpEH4JxHYJzTQR+si0UhIwU9lrQjZIfD6S29K1D2W+yXIcRvtPv/AAIgdZl3WLtUKyTKWtGvsX9mL3EuTjSZzRDjmt7ttty8q+oG2GMdACHEYbaaaERHQR2jVOMTh4mBxLWPx7dRaHam8jlmfKIiLzVWWrq6aZntKMlOlqRVLcsi/SQH3nhKK7Xpf6i+Va3K08TqA7VlP6Sge7cBx27VIpQ7fwqeOfJXknxfNZdWq8Qha1AQhCAQhCAQhCAQhCAQhCAQhCAQux0semn36rwtPAqoJLAZBzHzRMdwa9Wit7zgyKkPSQ+VUBm3M0r9aveD1F6ELolvHtVGaZ+6yXIjbtrUkjI1HcBbdS2inxNXIi6UjIjOPUIa/MqJWIYmalzKJX3QjCwjVSjcEiCpdqWjxyZKpjYSXdhA+5kZVEd3lTGbAd102juFW6Uy2TouN9XlXLkFx4rA2REPcIqU0hSs48DAKl1CpAddqmXMJJIQNuMXiQ+VNCgmLtCEvV6VG/Jdjyaoxw69ySJ1O50JwRIhG1Ux5XhtUNVvq93Ljppk8ZJ4W0h29ScRcU/NIhjN2qW4lZHirqtkA42RltElZOEeFZeSlCbrRC19aueB4IAQsQcx2vUtB4dwjEEWiMOWNaqdZvFTp5G3CfAeN0YH9mbIq9RKwt+z3Hi/ZqM22ZdwirHg2BaIREdqurMNuo1HdVYaz1K+ccs2j8DCDFRa328qct8Mcp0RNgS/CtTGELpCVa6bbIkY9rUrV3KP1Dvpsx/2Sxrz9HYjZDWpbepXXh+MEaMEZgOW0A7R8qVlQyaIiEaruDIo7WvV3JWSktUjOjWaES2gW5QkwGhaJtoqh3F3Ep+U83JCwl1bfwqCyzJc8RYGwD22/NZa5/FVM+SNjt2dJ2hVHaKw79LGdysfh8VYbukUk9P4dI//AG/pW8TJIi1yGC6dxL5q/SnnMTOKYQMW/Z4vKLb3WsrcXlRnYqhdFrquVpZAhCEAhCEAhCEAhCEAhCEAhCECjH2wfNonE4PA7VSEb/eG/n0/6pxkC31UK/J34mgfQaufDskSjiP9NVTB08dfrVjwJi213CmX8XZTpOlzaj+JSePaHShEI1LqUS3qRP7ht6lPQREmKuNkNuklipc5kNNsnYSsK8JnxC1hqu3voJdC8PL3jZRSRcpxpndYfxLRfYvGxucfO7QlyOrcss4kjOGF2OlN+A+JcnwzmQfiCJaEVTEu5WzKun18MDFNCDfuMavypjlOCeGcv8RyC20752tqjeH82OXxLGQbISuO4fKpiPLFohsO3zCueSak5j2PXInMfMacEux1UnKeyfOMukRQbVLqBb4zkGCL4clu3l7k998u0PNErdpJs6+T3uAcuzMJp+K4I9VqrQOE+E2o0Dut3bVquW05tm+WJCXcoVsSZdry9qrrK7qjsbiB5oUGoltJWUcM07HoIjYdwpKOBUImu79yfYluYLpEO7TyqismyerrGwH2gO3UBK549m7QmPpSWHjCdSMRt3CrBj4dQ2jtHcqKpdMm4iIDUe5dCHjUk5Fi79q7V5kNORylGZqkvZD5KO44NRFQpMF71UdtepXR5kRj/ZluULKgEViEa2V+qlFbtDEQLYRbvSKVnaiEN0hEbVrZdvMOMiVR3F0pHpa5bo7RJXTWrmvyV/AgXN2sEVj3W7RWFfphM8niDGi00LYExYiEeol9J42M2EwnHC+Evnr9LiS3MykSLGaGzQbne2q0Yq8leenziS5SjgVLXTTW38UmtrIEIQgEIQgEIQgEIQgEIQgEIQg7b18DEv4rt4gPW1i11SKECrOgEe5WLGiIU2lQlW29SEtqsuJMTAeYKrtKU3DEhK35lOQxKo/E3eVRsPRsRG27RScdoh3CVvwrHS53OaKwkO0k3G3SXV8qkqi81ttYU1LaNa7hJRDdwRIKuB+Kqg8phzMebFbqStYsCQW9KWistU2qzZykD7NeM5nDmZ5WQdcKEZUMfL6l9N40wkRwcGr4GIkBeYV8ycVYRiSJSYgVPuFWr2G8euYqaHDOfd/ZDqEZ0/7MrJsTX7bfI/VoOb4rrRegqp7FqYE3FJwhHtIrLqd7oTVS6vMJKPx5+EkhYkkVe3psi2p+UncweVHIXCGyhCdbLcVbfMpPKSxrV9hwT6dw7SUFF54PlyxEgt0rPkl2aWXh+M5JrQOruU7RyHKaEmOruFJcLtPgAkAk38w7VbpGPblsDZzlH5hVWqc0UhxhIQdU3DZbGoj0km8ONSADVxcKtrWS0HUm3TaPt7lKcepVFyZFoSr5ly5FaeGrg2qlnK0JQPHmYHD8LzX71eJohAvVXar5iUJ2V/jrj/C8PtGBGTtCqVOkdqoUX2tBJfsEQSj26gITIVkXHUPJ8RzcTGJ2SUJ139qeaaIxDcNjqPlUZheBeKIPGkhjDYzKTsYDpNBJOKbQugNqnu6bf/Zafo5032UevW3bV9W4XJNZOKL4gQiY28SFMs4ZAPwm099nePfwnCrUbKjV2tq9y4zBXdqI7V5uT9Nk14oqDL6bj0rMv0hOHhzXDhymtrrW75lprwUKw9Iqle0aSIYR63k6fMrcFVNKssz2fGMyMTLuofv/AHpuWldVYOJWvCU4RutjuLaIKBLQfPZepNbMRNCEKQEIQgEIQgEIQgEIQgEIQgEIQgUZ1qas+HrUSsKqwa+Gqs2FPaNeWXzKu0pW2HyxHb1KWim301qoSCFyKvUpiPHKo16llpbJ0yHiJVFcVbdkbusUszqTReUlw9vK3L/EoujpHl7RSrcMtdwFYfMKa1J0iEh39tlMYuxlX7I/V0klJEnIJ8qpiO7uVU4mwP1Sm/hkHctO92cNixCO1Q2UZY1aNotw+VV7O6rz7FcuOc4aIJWS97msDUhqO0e1Pc5DksTRfH4YelYz7Ocw7wlx8065X3V/Y5p+LqX0BxAWNyUdl+I/17iVhNePZCOPuHyveSIR7d1VK42MBPgTpbOqwqsZqc0zMESIqbRqKlcLMdl1baq2HmJVB97QuN3cDFaxuFqMh3bzddwiPmWGzPaPxDPzYxmJeSmmR1EGjMSP5RFbL7VOB8lkghSsKwT5CNSEO6wqD9kvsTyWHzzOczGZKC9HduwzEKzv4i7VqwTi+ajLv/SR4N9p2RgO42W0045jJAi05G5hkQl59xW6lv8Aj8zGnxmpdhbsIkQ9yjcXwfw03EZiwcQw1yiuJ13W8xF825RvEEEcOddzYlusKzZ8s/Bqw4/8liHN/tRtNiRAoniRn9dkEYnBp5SUUWWabJl0TsXTVcQ8qP6zYKSVWiPqWX1dvZtnBr7rZw/hhxUDkMRmmhHdYRFPHobpiPLfK1e1Psg7zITRNEIskPV5kybmDo1UajXpWmeNpY9kY8zJ0Iubu9ShMoBiVa1FWCRNsJWIfmVXy0w3nSFsulV+lrTlZDKY74B07Vjntm4gfhwDYaG1tu5azOMgArWJfP3twkiDok++yO7aO6y145lVkph+YmFIlGRCPio+ykJ0hh8ysHylWqYOD4FtWyWZwhCFICEIQCEIQCEIQCEIQCEIQdDp466afeujAg13Lxv7TT+aePNWD1LlUlM9zH96sPD7g6hy7WJQNN1S2qTxIUkCQmQqNuLxi+nTdXarBBc8RGyg8WAugJD1KxQQEaiQLJS6T3wGQFS2+VNHmCaKwkVvUng1IrCVR6apR76WqlUvUq3UeItlYnRH5k7g8y1hLdbaKQILdNSH0pxFaEjGtrD6qpTsrJHluEx2j6VAZzlmREPV3VUqyyQtVIrD6iTTIQBMLDt+VQWM8zjLoSOe0JWArCVVqnsvyrkzBkT7hXIum3SqNmGC1YIDtXd0r3gPNsYY3Ykmw2Lar5/FVX5LrnAdcy4t/aCX71N4uSUUAbsRF8qrxTGJcsH2rEIqyw4YmIk/Vqw2qO4lXqktOLyM58gbGS5tK1RIloeDac1DmujyyIe5Z9wuDTJi4002IdxEViVtkZEmTadBwnPSHaoapd1hLmtShdbmcsukRTjjpt+fwu6TDbTsqu1Vcs/GMWq2dIi3VHaKknOIRjxa8torDVRrH7dl2Otfdm+FyzESQbGVAWpA2+EfUpOLLjZqaAsFUBO21V/2oZDDPRXn5zTDDvabXX//AEs/9nfGGSi6HDHGS5IiZEBiNbCq/Qp68XtL6wenw2May1exAHSqdKzovEJ80mq9I+ZZaPHmTnC80MNyG8BgLfN77dStfDOOl5HLGUpwXGgPbXyq+Z1edXTXP3W1nIjIAmyHaXcuHo7YgRNkSWyDUaEI12jVVzMZ6NDad5h9I9PmVmrBWso/jDORsdBd5joivmX2iZQZsp1/3rntW7d1VYPbBxS/PO8OUQxTIh2bSEh8yyCXKf0k8witb93bqr8eNVVG0r6TsJW0L1JHx8dPBOnQ0JrngOwiqQ+Uk01+taZVvEIQgEIQgEIQgEIQgEIQgEIQgVZ+1H+eikC02qPY+2D+eikisfSqsn3W4/sj5GlTTzHmNwFzoSMoPHT6UjFMgdsOq7+Uuc/k0jC05Q8oiqrNHLwata34rKm8MyANgSIeZ8wq1M7QsFREvMKzZHT1mteok9bC4bTEkyEaNWsI/KncUmtAs6IkoJS5cbJshcEvzJ/H5ZEPNAbV6h3KNmGNx22FO4cjaI3ESUU0kTo6VENq5cN10a1/Kkyc5u1shsPpSkcTIbE64Kjq6hMlFdtuDb6lBTMQMmrbTW8i29yt8zV0iIbCQj5kkzHJqOTu1t4+kvKKsmkaRWBnFjJAwXD5jw9JdrXy+pXjGmTwDyi6urd3LPc4yINEIk5Yu0Fb+D55x4LMNweXIf8AyilOLxixfuIMOFYulWPwkg0MSpEJDvNMuGyYAObZuo7R+ZTscxIbGQ2Ivyokr0jFZWpPwS5ZD0iPSoPNQuJzdZF2S420Q7jDqFaEWQbhjYqkPpSvNh5WGQsFYx7UqWjpskba0xiDDM+L3iycPlvCI9e4SHzD+JXf3KNo0JNNNj8qSlQCPKE7uckABVv1CPcI/lTdwpwiLdhETVO1S+mx4MdxPPBq2y1plBfFgXHWiIgt09NVo/BoizCdfMhsI7iHzVVFwcWphJcs5UtyuEGY01wo6QtkJmZbR6kxeVKf5eonF4q5xZnnXpBtNEXcPUsp4+zL8CGb5OkTpdIiVle844TbBuDzO5fP/tcyfvEhmL3DYiqS18PktfkruQyTs6Q8w/tF/eO3uUDKsZa/eK9Ey0ZIrdO1cPa7y9QrRM6qhFcLR2v7j2kk3hIXdRJcj1JWVuIT17tFMIIQhAIQhAIQhAIQhAIQhAIQhAqx9sHzaKTLWvUopv6DEv4p+R3qKqycLcdFqWaIiFRzg0d//FLsj4h1baptOZHXTaq4rXlK5+Sa4RmCLtSMbdtxWhw9R1a0s6LpEVa1qsfxsko7o7q7lqHC8hqSwDm0iL1FtUckupitS3W+VSEWurQ9P4hTQtGzKtScr5SSzLtBrWo/1KoOyYapbbb5VwLpAYiTVhLuqKWhjzhrQurqJdSgFrb9oflqiRwzyzHoFtFqAVCtbypCPo4ZCJ9PlUiWhCA8sW7JqIqRYS+JZN3HiuAmNgHpGy9yQHo6TjopvDDmfaD3bbIHoxRcPm0Ea7hQzAIJhENrEBbhTpvU2WNoiQ+ok5bmC58MqtkQ9qIlIL2TZhg1Gf791laYc/MjHFywltVai11aKo7rW6lauH5I05BD3blJGiT0zIGwfMac9ShIfEczE5YHSJxtry9Vlo0fkSGyE61L8yr/ABRhI0wD5Ddkc/GjH2gZKdlsdBznDQuDIjl+0gHUQ/KmjfHOP91AHcNkikDXbyu75lGY39a4WUfKImw8o9ymRz5mJC7j23C9QqOuz0cHXen48rEL0mS1FjRhiCD4C6ZNO35Vu0iqO5PZhMNMNQR3V6iHuJVuDJycx0BFsYzR9WgjXap/ltMDzOoq7rKWONUer6z1/ZUuLmXRjkLTpLL+IOFIeQaLmj8XzD1LVeKJjRlRvqVccJppq5Fu7VT1FVPvKfQ4pqedmB8R8LTsU2ZCOrjOpeP0D9Iqtu/aa/R/BfQOQZbkWuIlb9yzTjjhf3e8yGFa7jFWdP1e3jSHVdDrO8KJ3Loi26D5V4Wm5eF1L0HluUL3wXiAQhCAQhCAQhCAQhCAQhCD0erRLifckP3r3X60S4Scc+kbbU+JuwqEjuVIVMRX/GorLknVpitpIuRibc1dbHcKsPC+U92dESIvVYlH1bMCt821N245VuNrF/2qO233Kxtkw70WYxdshcqPUKkXGR5Q1bErLOvZ7kXGhFgem26y1jGtOSBBzl9qjTiGcbdARIRqP3CuXHxDpLf6lYpGIdNoiEVVeIsa7GaFwQ6OpR2c1TEN0dm0XLKTKohardVm8XPyWR5DUZywlW5bRT//AGwiRwECs46XlJd7bHp1KwZze1/Z/KJKIjsnqQjtqSbN51jJWFghEB3ERJWC98ezZiS6jSXbBsGxbru8y9bCsgSISt2pJt43txE2Ij2qQE2+QIudaiiWEy0+zEfDzCpXGvUIQIt5bVGx+WIiIjt6lJRwt8QRRYteNOwiNvSIqwQY4GxUhsqZj3iAgItvpVmw83waLdtRGpP5WEiGX2QpqWBiaEQg2O7qUv742VSLpRKeY5REJCOqkrRUiOw0QWERMBqKh8oZNDX+kktxBlG40e75VAS3eHaqjksxzwB2M+Lkc+kl3bVZM1kpGcSS2Af3GInZVjKTy0NlsukiFPeIseU8ydYkuNvebqH+lUzNPZCCcYZzQkFxq8HTt83lWHL5U93p8ekduVwEhMRruTfJY8Xo5E7WpJrh5InUrbR6ku5M98fOu4Q2qlsqfHVkXGmA9wlHIjD4x9S/pVY8NfDxW75THhLik04NhJY9xNjDxmQNquwvpBer0ufee3Lwet6X0+e/CIXK6XK2PNCEIQCEIQCEIQCEIQCEIQC6suUIOvFOI75ASba6I0SvJLiuyfiyB6eqyVkPcuGVerpH8Sr7bzgeFSUrizKfNix+3QvElmrFr5L5ybey88G40haaMhLtJbZwfq29FH07Vn+BZEI4DWu1W7CvFGdGhbCWScm1dmvnBrOy++7jQdvaovLYtuQBjQSspXFvc1gbdSdOaCXaua+SjuxfiDhxqPI15gEQkqfnsWw0wZCNTLyrc+JMaMlg+WNiH0rLuIscToG0Q1IepK8WrFW89lb4Vaae6yHarB7sQ9JEI+UVS8TzIfEIA5ZwbVqO0VpbY/AH4dir5lbsx5Z8jWK2YmJUqP8AUpOOPNL4liIfSmIm4279mQ2TgSKthIql1KSuUqz1bVKx7CNi6aqHh32iLY29RKQZNxst+63aoppyO40bFiLd0qQi6H2lUKqEb1a1Ed1SEhS72UbiNcsXNxFXcmosfvRNNb+mtUlMmNkFydGvlWZZ7jRxuUURhzmEJVEl3i52QybAk65sJS21c9GqWfOS2HGNjhOGYkJeVUJuX7ofux2qHSSufuIR4ZCJERepRUfCfrAz5Y1NZ78pa8GuKkO3PaMtpiX8l5MjxcnCdiyREhdRlsD7vKJtwRsPcKiHIslkrMOEPzLL2qae9j1ySr3Pk4GU7BmbgL7B7tr6lLYF4TYt1ERJtxA65MxrkOYxu17xFQ/Cc8mJBRXy3tbR+VSqdp24QmqmvJfhAtR3Kn+0zDe84k5LYb2N1vSrnBebKhESX4iCK9jHbVrWpCpdPVTTnV4pyY+XzV4LlOZIjo+4I/VoWqb6fWvcfJ1Pbl4hCERCEIQCEIQCEIQCEIQdafWuuXr968b+0H+af8pRqtUp47mgslquXGiFSA6Cgg2kRKO6WqNHRWTgKMTuT1dEbCKrRdeq0j2axhCFo6XUZKHUXrjW9JG2VoOJjlqGhF0+VTg1aaDlFuExUdD1EAHtTknbiVV5OO/J7dT4NBxLxiwFu0dyknDEeolSmcw3pCdEXREqdxK3Y3T3vER3zId4iS17bPLrHUkZzggYuNkNSGypvG2Oo6EpsRofVVW+C34wnxc5ZE0RCP4VSs9LmOc2CJCTIlZRuuNUsE1v7Mn4whOxZ7U5oLD5iJXLAvDLxzJ8xsSIakIpLiLGlLhG2TdttlD8FyTbJ2C6QiTRbRUceTb2W9Tir8lwbhvu923tXpQ5Wjm4bfKvG8g4yBW7S2iIp7By9yqQjVXMROOZAIiVrp+LogfmrVOeUw4HM27RUNmDNl0qEPVavpTbX3dmdq1KvZblf09Qqu5zJSTYN/mV03JbwI2qCJCSbyoThtEJN9SzVn8nq4uh8fdR8tkhD47YO3Ar2ISEVqns5eLIYll2u0twkKrTfDbUkKutkQF2kStHAfKwEf3FwKgJbC9Kl600qy4Klc/czMdw2FcxYYsukLo1AkqzmmnNrRjtFOBq7DdddK1h2rk1+mfUxy2EhuxifhtjfuqSokoGhfMHCqQlVXbE5WIMeREdlC3W3Us3ylZ+WkSQlEFiqNSTLM692/oc9RXYrkIjBtWHcKpOSwxOSjKGQi8BWBW/3CUQfDmEXqIVCNtu42U6Ml299wn5llmqepdzSPx+elQv2bIRnRMdoloFrJ3mMlJnY11qMLg3EtxCnUV0ZLty3fMlnguNRV01Oymoqp1YrMZcYfNp3rTfXTVXbjvEEH7Y2G3Tq1VL00Xs4r3l8t1GLnHfZwhCFNQEIQgEIQgEIQgEIQgUZ+1H5tFI08e5RzP2wfNon5EVlVkW4yogOi4ma0josPckZx2AfuUZ/JKjFaNwHMDkst2+gRWdafWpjhmeUaWIWqJKWeNpOmyaW2huf41EU9F7pqKpuLmXMRtuJWmCXiI7bD5l49ePs93H5SVlFdgu1WzC8ROsYhgXxKrQ1Eh7lVXtB6S2ptj3QF8o0snOVa4iKTkqUrxzU+62N5KSQul79UHSv+VN2+UZ7XCcIuok2bDHmQ1KqkY4RGa0Lao1VUtxTEz7EnordC3EqJxZjZMCQGVh/WJWMa9S0ttoHNw9KY5CCL4kFRES2pjrWkMs+or+NyUaTAB8qkRD2+ZPmQYIhcrUVX/cnMTPJhwbRHS2l5SU7HZEfs3RLbuFbZrZ42XF6dJdmQRsEDe621IZCG5owLpjYh7krhwbaPS3cpuUIOsE16UpHHWtd1MbMLdXSpBt6GFeeQimWaje5mRCO0lVMk1kJMrmxnNoj0ksWv8Ak9zjJ4d5aC25GL7IhqlSZacHtqszGdkYHxHRcEB7h3KQh8W7a81svVZPT/Tnrz8lveZJotlhH0kvCOc6zyjlP8rtESUZHzgvCO4SHzJ63kWHRrZcqad2n+ybjA0IS3fMoadD6ibsJ9pDtU6480YCQ7vlSDmrWoj5lxfj0QjeuTaHli/YfWKaZTR2RHJqSx9FdphuqrCQNl0kgWm+4bfMu99V1RKiYmQTMr3N2w+Wwqzx4wnXd1JzmMdDfatQRP0qEjz/AHB8oj7nykn5O8eJ7nsQMqA60VamJCsPnMax5jzHj48s9R8VsWaz7EaERk6I7dorHpzpPynHy/tDIl6XR7dvJ8//ACmtVxyaIQhbnkBCEIBCEIBCEIBCEIOw+gxL+Kdc5Ml1bX79Vyp2SmtT3neOq5e3gmwuEK95m2qjpqls4XTZkB6GPVoXik10pq2i8E6uSnwMR6h3LTsezQBGqz/2VRiOJzyHaP0D6low7BGq8bP/AOj6boo2gjkm/h7epUvP51zCtBI5d99CFXGcZVWWe0934cdsfvt4KPTztevLvWf9eLutOJ48xkqouVE/LVWWLnor1eUVl86ePgnUefMja2akGPykt9dHPxeVj/kKn7vpeHlLiIiSlI8gT3barAuFeLZLTotPubiLq1WrYHJtyWB3blgyY6x17vTw55yprPQxkwjIbemqYY2HzGhcFwiLuIVPw6usW9KYY9kQyjoALgiXq6VLHSrq8c6hsCjkJCdqp0zIO4l1D5U+KI4A27VHSNdxVDpU/UeZr4kMw0DwiJdyjHIVKiPSpeQfMilbqFJNmGoCSozy9Po68dUE9HK3L7PUqtxJwvjpgm6xaJJ87W0S+YVoLzI8kiLzKElQykHYtrQ9IiuY6qfdty44qezLG8VxTEOkaRqY227upOxe4zYrzYerug/v2rSxhi0GwdxdyalELSzhK36v/KWSeh/VKOzxHmI41ktNtf4u1daccA0dZDZW+8dwqxOY0pjpE+PMAe0hSg8PQXftGG6+Wop6uL5LPps3w5R0PjTGODp+0COvq2qZh56JIHmBJbL5SUdkeBsS+3rqLPLP7w2qicScKTMToUmK6TjI/hIVbM4sn25RvL1GCe9cd2sTMiwTVhIelZZx3lRenAEd3cHVqKro5SeLeoe8OeHT4WTIiLUrEVtVpx9NM13YOp/kvUntwUfkuu/aOGf80ivSXNlpeZVVX3coQhSRCEIQCEIQCEIQCEIQdt/Sen80uTJJJj6Xg+bRSpNj5VXkrVbjnZH8n0rkmVJE2OiOUNVCciVY0OWngVV0AkZaaafXrr4LuRr8Uqqb4GxumRzbYn4ctveStqvHZDHG1ata4Ix4wsPHZ003aaWU+4OxcY3Rplr/ALVxOljoBVXh5K257vrOnn057GmQdoKyXjaSMjKaN9QgrxxJlRailuWfEZPPm6Q9RK/pp152Yf5HL46odxkNemqZFpQ/BWwWW6bhH8QqNycUT1+E3X+S9CMjxLxocdS0PQhV+4C4irqEZ0949KoBDqBeGv1pWK+cd8XW9fDUdVLLj9SXcGXnFb6Xw83nAI2UtHrpkWiJsantWZcE5nSVHaPQ9wiNloWPmN/BdIugty8iZ1vtT3b1yYtuFwEyOjfL2ocxDbtubtt5e5LR5LRA05YSAhT2Q63yGur5lb6Xls8mufZX52HYjjVtsq1+ayz3PZJvE5I4j7oiRbhEiqtF4gyARYZvuuiI1t1bl8xe1HiJ3O8SOOCNGWNjW2pK+cU5HZzVi/prTOYaeARFxd+8D42WD47P5GEWnLeIx8uqs+P49qNZTBCXmHcq66O5+zXj/kI5/JrAvtmA7knKJvkD6lQYfG+OMhs7y/mFSz3E0F2LYJbRD86orBf98NkdXH9crE2LegbV0NdFTY/GEEHaG+P9SdN8Y4rU90ppPp65aMfV45XmCzzg6h2qG4ijtFHdA9w1ISUUzx1g2dddBmaDr6RVU4y45GUDsbHkRX6jVmPp62V5+txaqJP0HSW54fVck3+tekV9SIur61yvVfLZK2ruF4hCki5QhCAQhCAQhCAQhCAQhCBaN/vDfz6f9VPiA67lXmddBdDUunTVTDU2MOn0u/8AwSpyzsvxV2OybHypvKq00RVXRZCH2u/lJNMjKjvM+DTv0/d4aqmYrZZkqdfZGOFY9dVYuAprcPMeJ+Glxroq3quhLUdfHTXw10Wqp2nVRF61s3xvKN6sCVh6VF5TKiAF8SorMoHEU5kNGjkbNPMNks5l2nS8XZVvwEvO+krh7H/JTzKTy0l2a6VS2JvHZ3bki3lIHhukf8sl2OWx2n94/IStnHU/0x5Mk18jlwdtVyIDXckCyuO/8z+TX/Kgspjtf7x/yyTWv0r2nui83EoXNEVEj9ascjIY55vUCkfkJQEjRrRzXlHcfvr4LXi5r5M+XX4pXhfLuYuZprbXl69Wi2jhnKtSWh+IJAQ9K+fx6lO8M8QyMVI0sREz+8fuWfqem9Tyls6Tq9PCn0VzvdquRHCLuqRJ9M4nfehcoWOWVa28qyhnjzDaj8SdqJf4R/5U4Hjrh2lSyOn/AAXf/wAWPtmn21btcFV378f7Wacbsk+Y+6ThVqKrmdwkDIxz0kRh9LunUP4l0PHPCxBvyfgX+A7/AJVGzuL+H5J1LJ6C15dGXf8AKqpjNt37ctNX0tT278f7UjM8LSosnXSHr7236Pr0UG9GfYOjrRAX3a6LUB4n4W0HQf1j9H+Af+VcSs3wVJaq7ObL+cd3/Kt8Zsvyl52To8PPvN8f7ZYjx/irHxGxw7p4u4jI6Fr/AOHq0f0/++ira2cc9+Hm3GnPbuLI+tHhp5l0NdO5dV93ooXha6fevNvmUUgvC1XviK87lJEIQvPFB4hCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEAhCEH//2Q==" /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The URI contains the content-type of the image ("image/jpeg" in this case, since this image is a JPEG) and the image data encoded in base64.&lt;br /&gt;&lt;br /&gt;This is supported &lt;a href="http://en.wikipedia.org/wiki/Data_URI_scheme#Web_browser_support"&gt;by most browsers&lt;/a&gt;.  Internet Explorer support lags behind, with only version 8 supporting it (&lt;a href="http://msdn.microsoft.com/en-us/library/cc848897%28VS.85%29.aspx"&gt;to a limited extent&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;This technique shouldn't be used on a regular basis, since it bloats the size of the web page considerably and makes it take longer to load.  Including images as separate files (the typical way) lets the user explore the page while the more bandwidth intensive images are loading.&lt;br /&gt;&lt;br /&gt;Check out my &lt;a href="http://www.mangst.com/projects/data-uri"&gt;Data URI Generator&lt;/a&gt; to generate a data URI from an image of your choosing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-422796619759971600?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/422796619759971600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=422796619759971600' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/422796619759971600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/422796619759971600'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/04/data-uri.html' title='The Data URI'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/S8UZELlPj9I/AAAAAAAAAIg/bYkQBIjbq78/s72-c/take-this.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4649365347267927995</id><published>2010-04-08T20:23:00.003-04:00</published><updated>2010-04-08T20:39:52.380-04:00</updated><title type='text'>Bad HealthVault Method Schema URLs</title><content type='html'>One thing to note when working with the HealthVault XML method schemas is that the links to these schemas in the HealthVault Developer Center (&lt;a href="http://developer.healthvault.com/methods/methods.aspx"&gt;http://developer.healthvault.com/methods/methods.aspx&lt;/a&gt;), as well as in the GetServiceDefinition response, are not always correct.  Some versions of some methods refer to the version 1 schemas, when they actually have their own schemas.&lt;br /&gt;&lt;br /&gt;For example, the URL to the GetThings3 request schema is listed as:&lt;br /&gt;&lt;br /&gt;https://platform.healthvault-ppe.com/platform/XSD/method-getthings.xsd&lt;br /&gt;&lt;br /&gt;This URL incorrectly points to the version 1 schema.  The version number must be added to the end of the file name in order to get the correct schema:&lt;br /&gt;&lt;br /&gt;https://platform.healthvault-ppe.com/platform/XSD/method-getthings3.xsd&lt;br /&gt;&lt;br /&gt;This holds true for the following methods:&lt;pre&gt;&lt;br /&gt;CreateConnectPackage2  request&lt;br /&gt;GetServiceDefinition2  response&lt;br /&gt;GetThings3             request, response&lt;br /&gt;OverwriteThings2       request&lt;br /&gt;PutThings2             request&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4649365347267927995?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4649365347267927995/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4649365347267927995' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4649365347267927995'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4649365347267927995'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/04/bad-healthvault-method-schema-urls.html' title='Bad HealthVault Method Schema URLs'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-182433948632309226</id><published>2010-03-27T21:26:00.009-04:00</published><updated>2010-03-27T22:12:05.227-04:00</updated><title type='text'>Creating a Connect-Request in HealthVault</title><content type='html'>I've been doing a lot of work with &lt;a href="http://www.healthvault.com"&gt;Microsoft HealthVault&lt;/a&gt; at my job lately.  I did some experimental work with the platform when I first started working there and since then, I've sort of been the team's HealthVault expert.  It's been fun learning all about it and challenging as well, since most of the HealthVault libraries, tutorials, etc are centered around .NET (we use Java).  So HealthVault is something I think would be fun to blog about.&lt;br /&gt;&lt;br /&gt;This blog post will be about how to use what are called &lt;strong&gt;connect-requests&lt;/strong&gt; in HealthVault.  It assumes that you already have some knowledge of HealthVault from a developer's perspective.  It includes Java code samples that use the JAX-B classes from the &lt;a href="http://healthvaultjavalib.codeplex.com/"&gt;HealthVault Java Library&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Connect-requests&lt;/strong&gt; are used by non web-based applications to create a connection to a HealthVault record.  The process must only be completed once--not every time the application wants to access the record.  They work like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/S6633AGS1KI/AAAAAAAAAIQ/e_nqt2t1j6Y/s1600/diagram.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 341px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/S6633AGS1KI/AAAAAAAAAIQ/e_nqt2t1j6Y/s400/diagram.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5453498354339337378" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size:1.5em; border-right:2px solid #000; padding-right:5px"&gt;1&lt;/span&gt; The application prompts the user for the following information:&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Friendly name&lt;/strong&gt; - This can be anything, but should be the name that's on the HealthVault record.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Question&lt;/strong&gt; - A question of the user's choosing (such as "What high school did I go to?").&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Answer&lt;/strong&gt; - The answer to the above question.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size:1.5em; border-right:2px solid #000; padding-right:5px"&gt;2&lt;/span&gt; The application uses the above information, along with an &lt;strong&gt;external-id&lt;/strong&gt;, to create a connect-request.  The external-id can be any value that uniquely identifies the connect-request (the current time in milliseconds works fine).  This value &lt;em&gt;must&lt;/em&gt; be saved somewhere, as the application will need to use it again later (see step 5).  Using these four bits of information, the application sends the request to HealthVault:&lt;br /&gt;&lt;pre class="sh_java"&gt;//create the request&lt;br /&gt;CreateConnectRequestRequest request = new CreateConnectRequestRequest();&lt;br /&gt;request.setExternalId(System.currentTimeMillis() + "");&lt;br /&gt;request.setQuestion("Question?");&lt;br /&gt;request.setAnswer("Answer");&lt;br /&gt;request.setFriendlyName("Joe Smith");&lt;br /&gt;&lt;br /&gt;//send the request / get the response&lt;br /&gt;SimpleRequestTemplate srt = new SimpleRequestTemplate(ConnectionFactory.getConnection());&lt;br /&gt;CreateConnectRequestResponse response = (CreateConnectRequestResponse) srt.makeRequest(request);&lt;br /&gt;String identityCode = response.getIdentityCode();&lt;/pre&gt;&lt;table style="border:3px solid #aa3;background-color:#eee;font-family:arial" cellspacing="5"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td style="vertical-align:top"&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 48px; height: 48px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/S661Ggk_stI/AAAAAAAAAII/OiQVA0huoCc/s400/note.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5453495322221195986" /&gt;&lt;/td&gt;&lt;br /&gt;  &lt;td style="vertical-align:top"&gt;Because we're using the JAX-B request classes, be sure to use&lt;br /&gt;&lt;code&gt;com.microsoft.hsg.methods.jaxb.SimpleRequestTemplate&lt;/code&gt;&lt;br /&gt;and &lt;em&gt;not&lt;/em&gt;&lt;br /&gt;&lt;code&gt;com.microsoft.hsg.request.SimpleRequestTemplate&lt;/code&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size:1.5em; border-right:2px solid #000; padding-right:5px"&gt;3&lt;/span&gt; HealthVault returns an &lt;strong&gt;identity-code&lt;/strong&gt;, which is a sequence of 20 random letters separated into five groups of four:&lt;br /&gt;&lt;pre&gt;DHLE-ELHP-AQLG-TLPZ-PQKD&lt;/pre&gt;The application must show this to the user.  It should also show (what I call) the &lt;strong&gt;patient connect URL&lt;/strong&gt;.  The user will have to visit this URL in order to validate the connect-request:&lt;br /&gt;&lt;pre&gt;&lt;a href="https://account.healthvault-ppe.com/patientwelcome.aspx"&gt;https://account.healthvault-ppe.com/patientwelcome.aspx&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;Remove the "-ppe" when going live of course.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size:1.5em; border-right:2px solid #000; padding-right:5px"&gt;4&lt;/span&gt; The user visits the patient connect URL in a web browser.  This page will step the user through a process, requiring her to (1) login to her HealthVault account, (2) enter the identity-code, (3) enter the question/answer, and (4) choose which record in her account to grant the application access to.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size:1.5em; border-right:2px solid #000; padding-right:5px"&gt;5&lt;/span&gt; Once the user has done this, the application is able to retrieve the &lt;strong&gt;person-id&lt;/strong&gt; and &lt;strong&gt;record-id&lt;/strong&gt; of the user's HealthVault record:&lt;pre class="sh_java"&gt;String externalId = //retrieve the external-id you saved in step 2&lt;br /&gt;GetAuthorizedConnectRequestsRequest request = new GetAuthorizedConnectRequestsRequest();&lt;br /&gt;SimpleRequestTemplate srt = new SimpleRequestTemplate(ConnectionFactory.getConnection());&lt;br /&gt;GetAuthorizedConnectRequestsResponse response = (GetAuthorizedConnectRequestsResponse) srt.makeRequest(request);&lt;br /&gt;for (ConnectRequest cr : response.getConnectRequest()) {&lt;br /&gt;  if (cr.getExternalId().equals(externalId)) {&lt;br /&gt;    String personId = cr.getPersonId();&lt;br /&gt;    String recordId = cr.getRecordId();&lt;br /&gt;    //save to persistent storage (like a database)&lt;br /&gt;    break;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;However, the application has no way of knowing when the user will approve the application (when she completed step 4).  So, the application must continually poll HealthVault for newly approved connect-requests (e.g. a separate thread which calls GetAuthorizedConnectRequests every 30 seconds or so).&lt;table style="border:3px solid #aa3;background-color:#eee;font-family:arial" cellspacing="5"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td style="vertical-align:top"&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 48px; height: 48px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/S661Ggk_stI/AAAAAAAAAII/OiQVA0huoCc/s400/note.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5453495322221195986" /&gt;&lt;/td&gt;&lt;br /&gt;  &lt;td style="vertical-align:top"&gt;The CreateConnectRequest method has a "call-back-url" parameter, which is a URL that HealthVault is supposed to call when the connect-request is validated by the user.  However, at the time of this writing, it is not supported.&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-182433948632309226?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/182433948632309226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=182433948632309226' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/182433948632309226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/182433948632309226'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/03/creating-connect-request-in-healthvault.html' title='Creating a Connect-Request in HealthVault'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/S6633AGS1KI/AAAAAAAAAIQ/e_nqt2t1j6Y/s72-c/diagram.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-139248140655580590</id><published>2010-03-22T19:52:00.005-04:00</published><updated>2010-03-22T20:06:29.013-04:00</updated><title type='text'>Mac OS X Mouse Acceleration</title><content type='html'>If the mouse acceleration settings of Mac OS X ever frustrate you, check out the &lt;a href="http://triq.net/software/mouse-acceleration-preference-pane-mac-os-x"&gt;Mouse Acceleration Preference Pane&lt;/a&gt;.  This is a utility created by Christian Zuckschwerdt which lets you adjust these settings to your liking.&lt;br /&gt;&lt;br /&gt;Apple changed around OS X's mouse API when version 10.6 was released, which made existing mouse acceleration tools useless.  However, Christian just recently released an updated version supporting 10.6!  My hand feels less cramped already...&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/S6gFdhtzI4I/AAAAAAAAAH0/_UWqvp_qlWo/s1600-h/mouse-acceleration.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 268px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/S6gFdhtzI4I/AAAAAAAAAH0/_UWqvp_qlWo/s400/mouse-acceleration.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5451613353756795778" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-139248140655580590?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/139248140655580590/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=139248140655580590' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/139248140655580590'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/139248140655580590'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/03/mac-os-x-mouse-acceleration.html' title='Mac OS X Mouse Acceleration'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/S6gFdhtzI4I/AAAAAAAAAH0/_UWqvp_qlWo/s72-c/mouse-acceleration.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2818355441189120095</id><published>2010-03-14T20:50:00.002-04:00</published><updated>2010-03-14T21:01:04.232-04:00</updated><title type='text'>Working with Scala and Maven</title><content type='html'>In order to get &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; to properly recognize &lt;a href="http://www.scala-lang.org"&gt;Scala&lt;/a&gt; code, there are are a number of steps you must take.  Included below are pom.xml samples, along with explanations.&lt;br /&gt;&lt;br /&gt;&lt;div style="border:0px 0px 1px 0px; border-bottom: 1px solid #000"&gt;&lt;b&gt;&lt;big&gt;1.&lt;/big&gt;&lt;/b&gt; Name your source directories properly:&lt;/div&gt;&lt;pre class="sh_xml"&gt;&amp;lt;project&amp;gt;&amp;lt;build&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;sourceDirectory&amp;gt;src/main/scala&amp;lt;/sourceDirectory&amp;gt;&lt;br /&gt;  &amp;lt;testSourceDirectory&amp;gt;src/test/scala&amp;lt;/testSourceDirectory&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/build&amp;gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:0 0 1 0; border-bottom: 1px solid #000"&gt;&lt;b&gt;&lt;big&gt;2.&lt;/big&gt;&lt;/b&gt; Add the scala-tools.org repositories.&lt;/div&gt;&lt;br /&gt;These are required in order to download the necessary Scala dependencies (see step #3):&lt;pre class="sh_xml"&gt;&amp;lt;project&amp;gt;&lt;br /&gt;  &amp;lt;repositories&amp;gt;&lt;br /&gt;    &amp;lt;repository&amp;gt;&lt;br /&gt;      &amp;lt;id&amp;gt;scala-tools.org&amp;lt;/id&amp;gt;&lt;br /&gt;      &amp;lt;name&amp;gt;Scala-tools Maven2 Repository&amp;lt;/name&amp;gt;&lt;br /&gt;      &amp;lt;url&amp;gt;http://scala-tools.org/repo-releases&amp;lt;/url&amp;gt;&lt;br /&gt;    &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;/repositories&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;pluginRepositories&amp;gt;&lt;br /&gt;    &amp;lt;pluginRepository&amp;gt;&lt;br /&gt;      &amp;lt;id&amp;gt;scala-tools.org&amp;lt;/id&amp;gt;&lt;br /&gt;      &amp;lt;name&amp;gt;Scala-tools Maven2 Repository&amp;lt;/name&amp;gt;&lt;br /&gt;      &amp;lt;url&amp;gt;http://scala-tools.org/repo-releases&amp;lt;/url&amp;gt;&lt;br /&gt;    &amp;lt;/pluginRepository&amp;gt;&lt;br /&gt;  &amp;lt;/pluginRepositories&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:0 0 1 0; border-bottom: 1px solid #000"&gt;&lt;b&gt;&lt;big&gt;3.&lt;/big&gt;&lt;/b&gt; Add the appropriate dependencies:&lt;/div&gt;&lt;pre class="sh_xml"&gt;&amp;lt;project&amp;gt;&amp;lt;dependencies&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!--&lt;br /&gt;  This contains the scala compiler, which Maven will use to compile your code and run your unit tests.&lt;br /&gt;  --&amp;gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.scala-lang&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;scala-library&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;2.7.7&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!--&lt;br /&gt;  This is Scala's unit testing framework.  It's also possible to use JUnit, but when in Rome, right?&lt;br /&gt;  --&amp;gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.scalatest&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;scalatest&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!--&lt;br /&gt;    If you want to use scalatest, unfortunately you also need to include JUnit as a dependency (see step #5).&lt;br /&gt;  --&amp;gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;4.8.1&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;dependencies&amp;gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:0 0 1 0; border-bottom: 1px solid #000"&gt;&lt;b&gt;&lt;big&gt;4.&lt;/big&gt;&lt;/b&gt; Add the scala-tools plugin:&lt;/div&gt;&lt;pre class="sh_xml"&gt;&amp;lt;project&amp;gt;&amp;lt;build&amp;gt;&amp;lt;plugins&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.scala-tools&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;maven-scala-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;executions&amp;gt;&lt;br /&gt;      &amp;lt;execution&amp;gt;&lt;br /&gt;        &amp;lt;goals&amp;gt;&lt;br /&gt;          &amp;lt;goal&amp;gt;compile&amp;lt;/goal&amp;gt;&lt;br /&gt;          &amp;lt;goal&amp;gt;testCompile&amp;lt;/goal&amp;gt;&lt;br /&gt;        &amp;lt;/goals&amp;gt;&lt;br /&gt;      &amp;lt;/execution&amp;gt;&lt;br /&gt;    &amp;lt;/executions&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/plugins&amp;gt;&amp;lt;/build&amp;gt;&amp;lt;/projects&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:0 0 1 0; border-bottom: 1px solid #000"&gt;&lt;b&gt;&lt;big&gt;5.&lt;/big&gt;&lt;/b&gt; Add the @RunWith annotation to each unit test.&lt;/div&gt;&lt;br /&gt;If you are using &lt;a href="http://www.scalatest.org"&gt;scalatest&lt;/a&gt; as your unit testing framework, you must trick Maven into thinking that your tests are JUnit tests.  Otherwise, Maven will not run your tests:&lt;pre class="sh_scala"&gt;import org.junit.runner.RunWith&lt;br /&gt;import org.scalatest.junit.JUnitRunner&lt;br /&gt;import org.scalatest.FunSuite&lt;br /&gt;&lt;br /&gt;@RunWith(classOf[JUnitRunner])&lt;br /&gt;class MyScalaTest extends FunSuite{&lt;br /&gt;  //...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:1px solid red;background-color:#eee;padding:5px;font-family:arial;"&gt;&lt;b&gt;Tip:&lt;/b&gt; If you're using Eclipse, you can right click on one of these unit tests and select "Run As &gt; JUnit Test" to manually run the test.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2818355441189120095?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2818355441189120095/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2818355441189120095' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2818355441189120095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2818355441189120095'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/03/working-with-scala-and-maven.html' title='Working with Scala and Maven'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6453404773451799109</id><published>2010-03-10T20:45:00.017-05:00</published><updated>2010-03-17T11:35:29.725-04:00</updated><title type='text'>XPath and Java</title><content type='html'>XPath is a &lt;b&gt;domain specific language&lt;/b&gt; which is used to extract data from an XML document.  It's supported by many different &lt;b&gt;general purpose languages&lt;/b&gt; like C#, PHP, and Java.  Take the following XML document for example:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;library&amp;gt;&lt;br /&gt;  &amp;lt;book&amp;gt;&lt;br /&gt;    &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;Solaris&amp;lt;/title&amp;gt;&lt;br /&gt;    &amp;lt;author&amp;gt;Stanislaw Lem&amp;lt;/author&amp;gt;&lt;br /&gt;  &amp;lt;/book&amp;gt;&lt;br /&gt;  &amp;lt;book&amp;gt;&lt;br /&gt;    &amp;lt;language&amp;gt;fr&amp;lt;/language&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;Le Petit Prince&amp;lt;/title&amp;gt;&lt;br /&gt;    &amp;lt;author&amp;gt;Antoine de Saint-Exupéry&amp;lt;/author&amp;gt;&lt;br /&gt;  &amp;lt;/book&amp;gt;&lt;br /&gt;  &amp;lt;book&amp;gt;&lt;br /&gt;    &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;Dune&amp;lt;/title&amp;gt;&lt;br /&gt;    &amp;lt;author&amp;gt;Frank Herbert&amp;lt;/author&amp;gt;&lt;br /&gt;  &amp;lt;/book&amp;gt;&lt;br /&gt;&amp;lt;/library&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The following XPath query retrieves the titles of all English-language books:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/library/book[language='en']/title&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Java makes working with XPath (and XML in general) kind of complicated, since many different classes are involved.  First, the XML document must be loaded into a DOM (Document Object Model).&lt;br /&gt;&lt;pre class="sh_java"&gt;&lt;br /&gt;StreamSource source = new StreamSource(new File("books.xml"));&lt;br /&gt;DOMResult result = new DOMResult();&lt;br /&gt;Transformer transformer = TransformerFactory.newInstance().newTransformer();&lt;br /&gt;transformer.transform(source, result);&lt;br /&gt;Node documentRoot = result.getNode();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This means that the XML text is read into memory and organized into a tree of &lt;b&gt;nodes&lt;/b&gt; where each tag is an &lt;b&gt;element node&lt;/b&gt;.  The top element node would be the &amp;lt;library&amp;gt; element, which would have three &lt;b&gt;child&lt;/b&gt; element nodes (&amp;lt;book&amp;gt;), and so on.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/S5rUUE3-pqI/AAAAAAAAAHs/rCBUzbblGKo/s1600-h/diagram.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 576px; height: 230px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/S5rUUE3-pqI/AAAAAAAAAHs/rCBUzbblGKo/s400/diagram.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5447900140629042850" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;To demonstrate the power of XPath, this is what Java code might look like if XPath did not exist.  The programmer would have to manually iterate through the entire DOM to get what she needed.&lt;br /&gt;&lt;pre class="sh_java"&gt;&lt;br /&gt;List&amp;lt;String&amp;gt; englishTitles = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;Node books = documentRoot.getFirstChild();&lt;br /&gt;if (books.getNodeName().equals("library")){&lt;br /&gt;  for (int i = 0; i &lt; books.getChildNodes().getLength(); ++i){&lt;br /&gt;    Node book = books.getChildNodes().item(i);&lt;br /&gt;    if (book.getNodeName().equals("book")){&lt;br /&gt;      boolean english = false;&lt;br /&gt;      String title = null;&lt;br /&gt;      for (int j = 0; j &lt; book.getChildNodes().getLength(); ++j){&lt;br /&gt;        Node bookChild = book.getChildNodes().item(j);&lt;br /&gt;        if (bookChild.getNodeName().equals("language") &amp;&amp;&lt;br /&gt;            bookChild.getTextContent().equals("en")){&lt;br /&gt;          english = true;&lt;br /&gt;        }&lt;br /&gt;        if (bookChild.getNodeName().equals("title")){&lt;br /&gt;          title = bookChild.getTextContent();&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;      if (english &amp;&amp; title != null){&lt;br /&gt;        englishTitles.add(title);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;for (String title : englishTitles){&lt;br /&gt;  System.out.println(title);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, this is very very tedious and error prone!  XPath is the better solution by far:&lt;br /&gt;&lt;pre class="sh_java"&gt;&lt;br /&gt;XPath xpath = XPathFactory.newInstance().newXPath();&lt;br /&gt;NodeList nodeList = (NodeList)xpath.evaluate("/library/book[language='en']/title",&lt;br /&gt;documentRoot, XPathConstants.NODESET);&lt;br /&gt;for (int i = 0; i &lt; nodeList.getLength(); ++i){&lt;br /&gt;  Node node = nodeList.item(i);&lt;br /&gt;  System.out.println(node.getTextContent());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;big&gt;&lt;b&gt;Namespaces&lt;/b&gt;&lt;/big&gt;&lt;br /&gt;&lt;br /&gt;XML documents often use &lt;b&gt;namespaces&lt;/b&gt;.  These are sort of like Java packages--they group related elements together and prevent name collisions from occurring.  Let's say that each &amp;lt;language&amp;gt; element belonged to a namespace:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;book&amp;gt;&lt;br /&gt;  &amp;lt;language xmlns="&lt;b&gt;&lt;font color="red"&gt;http://translate.google.com&lt;/font&gt;&lt;/b&gt;"&amp;gt;en&amp;lt;/language&amp;gt;&lt;br /&gt;  &amp;lt;title&amp;gt;Solaris&amp;lt;/title&amp;gt;&lt;br /&gt;  &amp;lt;author&amp;gt;Stanislaw Lem&amp;lt;/author&amp;gt;&lt;br /&gt;&amp;lt;/book&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:1px solid red;background-color:#eee;padding:5px;font-family:arial;"&gt;&lt;b&gt;Note:&lt;/b&gt; While namespaces technically can be anything (like "abc123" for example), they should be &lt;b&gt;globally unique&lt;/b&gt;.  There's no way to enforce this, so the convention is to use a URI belonging to the person or company creating the namespace.  For example, if Oracle wants to use a namespace, they can be fairly certain that no one else in the entire world is using one starting with "http://www.oracle.com".&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;To make Java aware of namespaces,a NamespaceContext object must be created and added to the XPath object.&lt;br /&gt;&lt;pre class="sh_java"&gt;&lt;br /&gt;XPath xpath = XPathFactory.newInstance().newXPath();&lt;br /&gt;xpath.setNamespaceContext(new NamespaceContext() {&lt;br /&gt;  public String getNamespaceURI(String prefix) {&lt;br /&gt;    if ("tr".equals(prefix)){&lt;br /&gt;      return "http://translate.google.com";&lt;br /&gt;    }&lt;br /&gt;    return null;&lt;br /&gt;  }&lt;br /&gt;  public Iterator&lt;String&gt; getPrefixes(String uri) {&lt;br /&gt;    return null;&lt;br /&gt;  }&lt;br /&gt;  public String getPrefix(String uri) {&lt;br /&gt;    return null;&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This will assign the prefix "tr" to the namespace "http://translate.google.com".  The prefix can be anything, but the namespace must match the one in the XML document.&lt;br /&gt;&lt;pre class="sh_java"&gt;&lt;br /&gt;NodeList nodeList = (NodeList)xpath.evaluate("/library/book[tr:language='en']/title",&lt;br /&gt;documentRoot, XPathConstants.NODESET);&lt;br /&gt;for (int i = 0; i &lt; nodeList.getLength(); ++i){&lt;br /&gt;  Node node = nodeList.item(i);&lt;br /&gt;  System.out.println(node.getTextContent());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To learn more about XPath, you can visit &lt;a href="http://w3schools.com/xpath/default.asp"&gt;w3schools.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6453404773451799109?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6453404773451799109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6453404773451799109' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6453404773451799109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6453404773451799109'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/03/xpath-and-java.html' title='XPath and Java'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/S5rUUE3-pqI/AAAAAAAAAHs/rCBUzbblGKo/s72-c/diagram.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-5775723443156540778</id><published>2010-03-09T19:59:00.004-05:00</published><updated>2010-03-09T20:18:32.058-05:00</updated><title type='text'>Dropbox</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/S5bzIo6BRVI/AAAAAAAAAHk/NxTMqqcH8HQ/s1600-h/dropbox_logo.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 128px; height: 128px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/S5bzIo6BRVI/AAAAAAAAAHk/NxTMqqcH8HQ/s400/dropbox_logo.png" alt="" id="BLOGGER_PHOTO_ID_5446808129096467794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I just found out about this cool service called &lt;a href="http://www.dropbox.com/"&gt;Dropbox&lt;/a&gt;, which I thought I'd write about.  I read a blurb about it in the March issue of &lt;a href="http://www.linuxjournal.com/"&gt;Linux Journal&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Dropbox is a free service that lets you store files online.  That sounds pretty boring.  But it's more than just a free FTP server.  What happens is, you install a small app on your computer, which creates a special "dropbox" folder in your home directory (or in My Documents if you're using Windows).  The app monitors this folder for any changes and syncs these changes with the Dropbox server.  For example, when you copy a file (Word document, MP3, whatever) to this folder, it will immediately upload it to your Dropbox account.  If you delete a file, it will delete it from your account.&lt;br /&gt;&lt;br /&gt;But the really cool thing is that you can connect multiple computers to your account by installing the Dropbox application on each one (there are Windows, Mac, Linux, and iPhone versions).  So if you add a file to your Dropbox folder on your desktop PC, it will immediately download to your MacBook laptop, Linux Netbook, and whatever else.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/S5byTcNaB7I/AAAAAAAAAHc/7giKdKay8qw/s1600-h/dropbox.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 218px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/S5byTcNaB7I/AAAAAAAAAHc/7giKdKay8qw/s400/dropbox.png" alt="" id="BLOGGER_PHOTO_ID_5446807215155054514" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-5775723443156540778?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/5775723443156540778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=5775723443156540778' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5775723443156540778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5775723443156540778'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2010/03/dropbox.html' title='Dropbox'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/S5bzIo6BRVI/AAAAAAAAAHk/NxTMqqcH8HQ/s72-c/dropbox_logo.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-5408666276877279381</id><published>2009-10-22T21:19:00.010-04:00</published><updated>2009-10-24T21:22:34.669-04:00</updated><title type='text'>An Amazon Affiliate Tutorial</title><content type='html'>Ok, so I'm a little behind the times.  Amazon's Affiliate Program has been around for a while--since 1996 to be exact.  Everyone and their grandma must have an account by now, making millions off sales of "The Secret" that they linked to on their blog (this is actually the secret that the book talks about).  But incase you happen to be Amish, here's a short tutorial on the whole process of signing up for an account and building affiliate links.&lt;br /&gt;&lt;br /&gt;1. Visit &lt;a href="https://affiliate-program.amazon.com/"&gt;https://affiliate-program.amazon.com/&lt;/a&gt; and click the "Join now for FREE!" button.  It requires that you create an Amazon account first if you don't already have one.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SuEEyux8NQI/AAAAAAAAAGg/F8n5ddpp2Jk/s1600-h/1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 218px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SuEEyux8NQI/AAAAAAAAAGg/F8n5ddpp2Jk/s400/1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395599098164032770" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;2. It then asks you for your contact information and has some questions about the website/blog where you'll be putting your affiliate links.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/SuEE5B8982I/AAAAAAAAAGo/EoLBlnkthnw/s1600-h/2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 357px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/SuEE5B8982I/AAAAAAAAAGo/EoLBlnkthnw/s400/2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395599206389773154" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;3. And that's all the information they need to get your account set up.  You don't even have to submit your payment information (although, you'll want to do this eventually...after all, making a few bucks is why you're here, right?).  As you can see in the screenshot, Amazon assigns you a unique "Associates ID".  This important identifier will be stuffed into all the affiliate URLs you create so that Amazon knows who give money to when a sale is made.  To start making links, click the "Learn More" link in the "Product Links" box.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/SuEE_7a1P_I/AAAAAAAAAGw/I4A8li3ojAg/s1600-h/3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 285px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/SuEE_7a1P_I/AAAAAAAAAGw/I4A8li3ojAg/s400/3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395599324895068146" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;4. It's really easy to create affiliate links.  It just takes a few clicks of the mouse and some copy and pasting.  First, search for the product you want to link to.  I chose a book I recently read.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/SuEFGbNynPI/AAAAAAAAAG4/BqP80-fiPps/s1600-h/4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 216px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/SuEFGbNynPI/AAAAAAAAAG4/BqP80-fiPps/s400/4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395599436509519090" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;5. Next, find the product in the search results and click the "Get Link" button.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/SuEFM4-YCiI/AAAAAAAAAHA/5EpYnqFgFO4/s1600-h/5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 256px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/SuEFM4-YCiI/AAAAAAAAAHA/5EpYnqFgFO4/s400/5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395599547577141794" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;6. This will take you to the page that lets you build your link.  There's a lot of options here you can play with, so take some time to explore this page on your own.  Once you're done crafting the link to your liking, copy the HTML code in the "step 3" box and paste it on your site.  It's no secret--it's that simple!&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/SuOSQWseVXI/AAAAAAAAAHQ/9oNpLtC9Xvs/s1600-h/6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 307px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/SuOSQWseVXI/AAAAAAAAAHQ/9oNpLtC9Xvs/s400/6.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5396317588187665778" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-5408666276877279381?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/5408666276877279381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=5408666276877279381' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5408666276877279381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/5408666276877279381'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/10/amazon-affiliate-tutorial.html' title='An Amazon Affiliate Tutorial'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/SuEEyux8NQI/AAAAAAAAAGg/F8n5ddpp2Jk/s72-c/1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-154660564503432610</id><published>2009-10-06T22:46:00.028-04:00</published><updated>2010-03-14T21:26:21.538-04:00</updated><title type='text'>Using Magpie to parse Blogger feeds</title><content type='html'>I recently redesigned my website and one thing I changed was putting my blog on the front page instead of just linking to it.  But what's cool (at least, according to my own geeky tastes) is that my blog is hosted on &lt;a href="http://www.blogger.com"&gt;Blogger&lt;/a&gt;--not on my website!  In this post, I'm going to walk through how I did this.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;big&gt;1. Blogger setup&lt;/big&gt;&lt;/b&gt;&lt;br /&gt;First, you need to make sure your Blogger feeds are configured to include the &lt;b&gt;full&lt;/b&gt; content of each blog post (as opposed to just the first paragraph or whatever).  I think this is the default setting, but to make sure, to go the Settings page and click on the "Site Feed" tab.  The "Allow Blog Feeds" option should be set to "Full".&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/Ss-InI4jaGI/AAAAAAAAAGY/hiPQPqGTWZY/s1600-h/blogger-setup.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 204px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/Ss-InI4jaGI/AAAAAAAAAGY/hiPQPqGTWZY/s400/blogger-setup.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5390677484966733922" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;big&gt;2. Atom feed URL&lt;/big&gt;&lt;/b&gt;&lt;br /&gt;Now, you must get the URL of your blog's Atom feed.  Go to your blog's homepage and view the source (in Firefox, this is under "View &gt; Page Source").  Look for a "link" tag that looks like the one below, and grab the value of its "href" attribute.&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;link rel="alternate" type="application/atom+xml" title="mangstacular - Atom" href="&lt;b&gt;http://mangstacular.blogspot.com/feeds/posts/default&lt;/b&gt;" /&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="border:1px solid red;background-color:#eee;padding:5px;font-family:arial;"&gt;&lt;b&gt;Note:&lt;/b&gt; In this tutorial, I use the Atom feed.  The RSS feed also has all the same information--it's just arranged differently.&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;big&gt;3. Magpie setup&lt;/big&gt;&lt;/b&gt;&lt;br /&gt;Download &lt;a href="http://magpierss.sourceforge.net/"&gt;Magpie&lt;/a&gt;.  This is a wonderfully easy-to-use RSS/Atom parser written in PHP which we'll use to parse the Atom feed.&lt;br /&gt;&lt;br /&gt;You'll want to configure Magpie to cache the Atom feed so that it only downloads it when your blog changes in some way (i.e. when there's a new post or a new comment).  This way, your website won't have to download the feed from Blogger every time someone visits your page.  Open the "rss_fetch.inc" file and add these two lines somewhere at the top:&lt;br /&gt;&lt;pre class="sh_php"&gt;define('MAGPIE_CACHE_ON', true);&lt;br /&gt;define('MAGPIE_CACHE_DIR', 'cache');&lt;/pre&gt;&lt;br /&gt;This will turn on caching and instruct Magpie to save the cached Atom file in the directory you specify.&lt;br /&gt;&lt;br /&gt;&lt;div style="border:1px solid red;background-color:#eee;padding:5px;font-family:arial;"&gt;&lt;b&gt;Note:&lt;/b&gt; Be sure that the permissions of the cache directory allow your web server to write to it.  The way you do this varies from server to server, based on the user that your PHP process runs under, but here are the commands that I had to run:&lt;br /&gt;&lt;pre class="sh_sh"&gt;chmod 775 cache&lt;br /&gt;chgrp web cache&lt;/pre&gt;&lt;br /&gt;What you do NOT want to do is set the folder to be globally writable.  While this would work, it would also allow &lt;b&gt;anyone on the Internet&lt;/b&gt; to write to that directory--not a good thing.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;big&gt;4. Parse the Feed&lt;/big&gt;&lt;/b&gt;&lt;br /&gt;Now you're ready to write the code that fetches and parses the feed.  Simply calling Magpie's "fetch_rss" function will parse the feed and return all the data in an associative array (despite "rss" being in the function name, it will also parse Atom feeds).  No need to deal with any XML.  Below is some sample code.&lt;br /&gt;&lt;br /&gt;&lt;div style="border:1px solid red;background-color:#eee;padding:5px;font-family:arial;"&gt;&lt;b&gt;Note:&lt;/b&gt; There are a couple quirks, which may be Blogger-specific--be sure to read the code comments.&lt;/div&gt;&lt;br /&gt;&lt;pre class="sh_php"&gt;require_once('magpierss/rss_fetch.inc');&lt;br /&gt;&lt;br /&gt;$atom = fetch_rss('http://mangstacular.blogspot.com/feeds/posts/default');&lt;br /&gt;foreach ($atom-&amp;gt;items as $item){&lt;br /&gt;  //var_dump($item); //see all the stuff that's in each item&lt;br /&gt;&lt;br /&gt;  $date = date('F j, Y', strtotime($item['published']));&lt;br /&gt;  $title = $item['title'];&lt;br /&gt;  $content = $item['atom_content']; //no need to run html_entity_decode() or anything&lt;br /&gt;  $url = $item['link'];&lt;br /&gt;&lt;br /&gt;  //because there are two &amp;lt;link&amp;gt; tags whose "rel" attributes are the same...&lt;br /&gt;  //...it stuffs the "href" attributes from both tags into this one string...&lt;br /&gt;  //...so you must extract the URL you want...&lt;br /&gt;  //...which in my case is the URL to the comments page&lt;br /&gt;  $commentsUrl = substr($item['link_replies'], strpos($item['link_replies'], 'https'));&lt;br /&gt;&lt;br /&gt;  $numberOfComments = $item['thr']['total'];&lt;br /&gt;&lt;br /&gt;  //generate HTML for the entry&lt;br /&gt;  //...&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And now it looks like you're running fancy Wordpress software! ;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-154660564503432610?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/154660564503432610/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=154660564503432610' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/154660564503432610'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/154660564503432610'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/10/using-magpie-to-parse-blogger-feeds.html' title='Using Magpie to parse Blogger feeds'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/Ss-InI4jaGI/AAAAAAAAAGY/hiPQPqGTWZY/s72-c/blogger-setup.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-3921119122446168104</id><published>2009-09-29T19:08:00.005-04:00</published><updated>2009-09-29T19:16:22.900-04:00</updated><title type='text'>Google Maps and its Traffic overlay</title><content type='html'>Ever since I started my new job, I've gotten into the habit of turning on the morning news while getting ready for work in the morning.  They do reports on rush-hour traffic and this morning, there was one whopper of an accident.  A tractor-trailer had slide-lined three cars, completely blocking the flow of traffic in that direction.  It was on a road I take everyday to work, but I wasn't sure exactly where on the road it was, so didn't know if it would impact my commute.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SsKUAGUB4hI/AAAAAAAAAGI/ZahfQVpyAW0/s1600-h/traffic.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 382px; height: 259px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SsKUAGUB4hI/AAAAAAAAAGI/ZahfQVpyAW0/s400/traffic.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5387030833704067602" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Then, I remembered that Google Maps has a live traffic feature that overlays the amount of traffic congestion on the map.  I could see where the congestion was and figure out if I needed to find an alternate route.  Oddly enough, it reported all roads as being more or less clear, which I knew was not the case.  The roadside webcams (another Google Maps overlay) also didn't show any signs of an accident.&lt;br /&gt;&lt;br /&gt;It turns out that the data just isn't very "live" after all.  Not only was the traffic overlay not reporting the current traffic situation, but the webcam images weren't current either--they were anywhere from 30-60 minutes old.  Clicking on the image takes you to the website that operates the webcams, and only going there can you get an up-to-the-minute view of traffic.&lt;br /&gt;&lt;br /&gt;So it turned out that my drive wasn't affected.  And I learned that, while the traffic overlay on Google Maps is great for seeing general traffic trends, it's not so good for live traffic updates.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-3921119122446168104?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/3921119122446168104/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=3921119122446168104' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3921119122446168104'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/3921119122446168104'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/09/google-maps-and-its-traffic-overlay.html' title='Google Maps and its Traffic overlay'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/SsKUAGUB4hI/AAAAAAAAAGI/ZahfQVpyAW0/s72-c/traffic.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2125560806177100165</id><published>2009-08-16T18:04:00.004-04:00</published><updated>2010-03-12T22:59:27.894-05:00</updated><title type='text'>Using phpDocumentor with Eclipse</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/SoiEao4Ef8I/AAAAAAAAAFI/1SPRZ61PV88/s1600-h/PHPDocumentor.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 250px; height: 150px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/SoiEao4Ef8I/AAAAAAAAAFI/1SPRZ61PV88/s400/PHPDocumentor.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5370688148823375810" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I just finished making improvements to a PHP/MySQL-powered &lt;a href="http://www.mangst.com/photography/main"&gt;photo gallery website&lt;/a&gt; I had written during college (it kind of felt good to see how much my coding skills have improved since then).  Besides moving all the SQL into a DAO, cleaning up the UI, and learning how to do file uploads in PHP, I went through all the code and formally documented all the functions and classes.  I decided to use &lt;a href="http://www.phpdoc.org/"&gt;phpDocumentor&lt;/a&gt; because the format it requires the comments to be in is nearly identical to Java's Javadoc, which I was already familiar with.&lt;br /&gt;&lt;br /&gt;I wanted to set up an Ant target that I could run that would generate the HTML documentation.  It was pretty straight-forward--just create a target using the Exec task to run a console command.  However, even though I could call the "phpdoc" command straight from the command line, Ant had problems with that.  I think this is because "phpdoc" is not an executable, but a PHP script.  At any rate, it worked fine once I found the script that the command was linked to and used the "php" command to run the script.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;target name="phpdocs" description="Generates phpDocumentor docs"&amp;gt;&lt;br /&gt;  &amp;lt;delete dir="${doc.dir}" /&amp;gt;&lt;br /&gt;  &amp;lt;exec executable="php"&amp;gt;&lt;br /&gt;    &amp;lt;arg value="/usr/local/PEAR/PhpDocumentor/phpDocumentor/phpdoc.inc" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="-f" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="*.php" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="-t" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="${doc.dir}" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="-ti" /&amp;gt;&lt;br /&gt;    &amp;lt;arg value="Photo Gallery Documentation" /&amp;gt;&lt;br /&gt;  &amp;lt;/exec&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2125560806177100165?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2125560806177100165/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2125560806177100165' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2125560806177100165'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2125560806177100165'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/08/using-phpdocumentor-with-eclipse.html' title='Using phpDocumentor with Eclipse'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/SoiEao4Ef8I/AAAAAAAAAFI/1SPRZ61PV88/s72-c/PHPDocumentor.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7070998086368482223</id><published>2009-07-15T18:42:00.005-04:00</published><updated>2009-07-15T19:03:11.142-04:00</updated><title type='text'>SCJP Book Errata</title><content type='html'>I recently read the book &lt;a href="http://www.amazon.com/Programmers-Guide-Java-SCJP-Certification/dp/0321556054/ref=sr_1_3?ie=UTF8&amp;qid=1247697814&amp;sr=8-3"&gt;A Programmer's Guide to Java SCJP&lt;br /&gt;Certification: A Comprehensive Primer (Third Edition)&lt;/a&gt; by Khalid Mughal and Rolf Rasmussen in order to prepare for the SCJP exam (which I took and passed last week).  I sent the authors of list of mistakes that I found and some have made it into the &lt;a href="http://www.ii.uib.no/~khalid/pgjc3e/errata.html"&gt;book's online errata&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/Sl5fYvH8ZvI/AAAAAAAAAEo/IUnqaRwjMAk/s1600-h/errata.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 72px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/Sl5fYvH8ZvI/AAAAAAAAAEo/IUnqaRwjMAk/s400/errata.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5358825485188753138" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;How cool is that! :-P&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7070998086368482223?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7070998086368482223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7070998086368482223' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7070998086368482223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7070998086368482223'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/07/scjp-book-errata.html' title='SCJP Book Errata'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/Sl5fYvH8ZvI/AAAAAAAAAEo/IUnqaRwjMAk/s72-c/errata.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-6428813949490611124</id><published>2009-07-14T18:43:00.008-04:00</published><updated>2009-07-14T21:26:27.186-04:00</updated><title type='text'>OpenOffice and DOC</title><content type='html'>Sorry, I need to rant a little. &gt;:(&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.openoffice.org"&gt;OpenOffice&lt;/a&gt; isn't as great as everyone says it is.  Or at least as great as I though it was.  I'm looking for a job and have been using the open source office suite to update my resume.  Most recruiters want resumes in Microsoft Word's ".doc" format, but have no fear!  OpenOffice supports that format...&lt;br /&gt;&lt;br /&gt;...mostly...sorta kinda.&lt;br /&gt;&lt;br /&gt;My resume is pretty basic in terms of features used.  It uses two tables, a few bulleted lists, and three drawn lines, along with text of various sizes and fonts.  But apparently that's too complex.  It can never quite seem to get the bullets in the bulleted lists right:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/Sl0WWLsA7UI/AAAAAAAAAEY/t-RNYYcr3_M/s1600-h/comparison.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 256px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/Sl0WWLsA7UI/AAAAAAAAAEY/t-RNYYcr3_M/s400/comparison.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5358463701991091522" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;One time, it used an icon of those things used in movies at the start of a take.  Just completely random.&lt;br /&gt;&lt;br /&gt;For throw-away documents, this is bearable.  But for something important like a resume, you want your .doc to come out exactly right.  And OpenOffice fails to do this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-6428813949490611124?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/6428813949490611124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=6428813949490611124' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6428813949490611124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/6428813949490611124'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/07/openoffice-and-doc.html' title='OpenOffice and DOC'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/Sl0WWLsA7UI/AAAAAAAAAEY/t-RNYYcr3_M/s72-c/comparison.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7250845078579933344</id><published>2009-03-02T11:04:00.004-05:00</published><updated>2009-03-02T11:13:25.877-05:00</updated><title type='text'>NetBeans and Workspaces</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SawEtPPPMNI/AAAAAAAAAEQ/K1MVEojfOmI/s1600-h/netbeans.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 206px; height: 45px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SawEtPPPMNI/AAAAAAAAAEQ/K1MVEojfOmI/s400/netbeans.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5308623235994038482" /&gt;&lt;/a&gt;&lt;br /&gt;If you're familiar with both &lt;a href="http://www.netbeans.org"&gt;Netbeans&lt;/a&gt; and &lt;a href="http://www.eclipse.org"&gt;Eclipse&lt;/a&gt;, you'll probably know of one obvious difference which sets the two apart: Eclipse supports "workspaces", while Netbeans does not (or at least, doesn't appear to--read on).  A workspace is sort of like the "state" of the IDE--it's a collection of open project folders and IDE settings.  Workspaces are useful when you are working on two unrelated projects which use different code and require the IDE to be configured differently.&lt;br /&gt;&lt;br /&gt;By default, Netbeans only allows you to have one workspace.  That is, when you exit Netbeans, all your open projects and settings will be restored when it is launched again.  This is different from Eclipse, which asks you to specify a workspace every time it is launched.  There is a way to specify your workspace with Netbeans as well, though it's not through an easy-to-use UI.  An extra parameter must be included when invoking the Netbeans executable:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;--userdir C:\path\to\workspace&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here is how to do use this parameter in Windows:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create a shortcut on your desktop to the Netbeans executable: &lt;code&gt;C:\Program Files\NetBeans x.x\bin\netbeans.exe&lt;/code&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Right-click on the shortcut and click "Properties".&lt;/li&gt;&lt;br /&gt;&lt;li&gt;In the "Target" textbox, add the extra parameter to the very end:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;--userdir C:\path\to\new_workspace&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click "OK" to exit the Properties window and double click the shortcut.  Netbeans will launch and create/load the workspace at that location.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;b&gt;Tip:&lt;/b&gt; I like to use my default workspace as a "jumping-off point" for all my new workspaces.  This saves me time I would otherwise have to spend resetting all my IDE settings, like SVN and code formatting preferences.  Just make a copy of the default workspace folder and create a shortcut pointing to that folder, as shown above.  The default workspace is located here:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;~/.netbeans/&amp;lt;version&amp;gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7250845078579933344?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7250845078579933344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7250845078579933344' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7250845078579933344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7250845078579933344'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/03/netbeans-and-workspaces.html' title='NetBeans and Workspaces'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/SawEtPPPMNI/AAAAAAAAAEQ/K1MVEojfOmI/s72-c/netbeans.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2609812845509497311</id><published>2009-02-27T15:43:00.004-05:00</published><updated>2009-02-27T15:46:45.851-05:00</updated><title type='text'>Clever Clock UI</title><content type='html'>There was a power outage in my apartment building the other day, so I went to reset the clock on my stove.&lt;br /&gt;&lt;br /&gt;Instead of making you select the hour and minute in two separate steps like most appliances do, you do it in just one.  Press the up/down buttons once, and the clock will increment/decrement by one minute.  &lt;i&gt;Hold&lt;/i&gt; the buttons down, and it moves in ten minute steps for as long as it's pressed.&lt;br /&gt;&lt;br /&gt;I thought that was kind of clever.  It keeps the task of "Setting the Time" consolidated into a single step, as opposed to breaking it up into as many as three (setting the hour, minute, and am/pm).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/SahQhy7rzaI/AAAAAAAAAEA/ZLqWFFAX0Mg/s1600-h/stove.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 164px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/SahQhy7rzaI/AAAAAAAAAEA/ZLqWFFAX0Mg/s400/stove.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5307580702394142114" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2609812845509497311?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2609812845509497311/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2609812845509497311' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2609812845509497311'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2609812845509497311'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/02/clever-clock-ui.html' title='Clever Clock UI'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/SahQhy7rzaI/AAAAAAAAAEA/ZLqWFFAX0Mg/s72-c/stove.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-7966106635430993848</id><published>2009-01-18T13:24:00.004-05:00</published><updated>2010-03-12T23:00:31.331-05:00</updated><title type='text'>Creating websites WITHOUT using HTML</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_73vOQEMfTVI/SXN2OSE-KVI/AAAAAAAAADo/Gw0tOFBYgiI/s1600-h/end_life.jpg"&gt;&lt;img style="float:right;margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 177px; height: 252px;" src="http://1.bp.blogspot.com/_73vOQEMfTVI/SXN2OSE-KVI/AAAAAAAAADo/Gw0tOFBYgiI/s400/end_life.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5292703974833400146" /&gt;&lt;/a&gt;&lt;br /&gt;Want to see something that will blow your mind?  Something that will shoot your brain out the back of your skull, through the window, and onto your neighbor's freshly waxed Pinto?&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;?xml-stylesheet type="text/xsl" href="layout/frontpage.xsl"?&amp;gt;&lt;br /&gt;&amp;lt;page lang="en_us"&amp;gt;&lt;br /&gt;  &amp;lt;frontpage/&amp;gt;&lt;br /&gt;&amp;lt;/page&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is the source code for a website's &lt;a href="http://starcraft2.com"&gt;entire home page&lt;/a&gt; (which is that of the upcoming computer game Starcraft 2 by &lt;a href="http://www.blizzard.com/"&gt;Blizzard Entertainment&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;I could be in the minority, but I've never seen XSLT used to transform XML documents into web pages...and I've never realized how powerful it can be.  After exploring the source code of the website, here are some advantages and disadvantages of this technique that I've discovered:&lt;br /&gt;&lt;br /&gt;&lt;div style="font-weight:bold;color:#090"&gt;Advantage - Separation of concerns&lt;/div&gt;&lt;br /&gt;Using XSLT, you can more effectively separate the content of your website from the presentation.  The Starcraft 2 website has *all* of its text separated out into XML files (pretty cool!).  For example, you can find all the text on the &lt;a href="http://starcraft2.com/features/terran/index.xml"&gt;Terran page&lt;/a&gt;, in a file devoted solely to storing Terran-related text (&lt;a href="http://starcraft2.com/strings/en_us/terran.xml"&gt;terran.xml&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;div style="font-weight:bold;color:#090"&gt;Advantage - i18n&lt;/div&gt;&lt;br /&gt;By separating the content from the presentation, another benefit is that it makes it easy to internationalize the website.  This is what Blizzard has done.  The XML files used to store text are located in their own directory, named according to the language of the text (such as "fr_fr" for French).  The language of the guy viewing the website is stored in the "lang" attribute of "page" tag of each individual web page (see the above code snippet).  This attribute is used to determine which directory to retrieve the text from.&lt;br /&gt;&lt;br /&gt;Also, you can do things that you would normally think could only be done using server-side languages:&lt;br /&gt;&lt;br /&gt;&lt;div style="font-weight:bold;color:#090"&gt;Advantage - Access to special functions&lt;/div&gt;&lt;br /&gt;Since XSLT uses the &lt;a href="http://www.w3schools.com/xpath/default.asp"&gt;XPath&lt;/a&gt; language, you get to use all the &lt;a href="http://www.w3schools.com/xpath/xpath_functions.asp"&gt;special functions&lt;/a&gt; that XPath comes with.  Tasks like string manipulation, date/time formatting, rounding numbers--all things that one might typically associate only with server-side languages, can all be done with XPath.  For example, the following code, taken from a shared file called &lt;a href="http://starcraft2.com/layout/includes.xsl"&gt;includes.xsl&lt;/a&gt;, uses the "concat" function to build a URI that points to a language-specific version of an XML file:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;xsl:variable name="terran" select="document(concat('/strings/',$lang,'/terran.xml'))"/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="font-weight:bold;color:#090"&gt;Advantage - Avoiding code duplication&lt;/div&gt;&lt;br /&gt;Just as you might define a function in a server-side script that performs some task, you can define function-like constructs with XSLT that "output" HTML.  If you look at &lt;a href="http://starcraft2.com/layout/movies.xsl"&gt;movies.xsl&lt;/a&gt; (a file used to generate the &lt;a href="http://starcraft2.com/movies.xml"&gt;Movies&lt;/a&gt; page) you can see that for each movie listed on the page, a template is called:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;xsl:call-template name="movies-entry"&amp;gt;&lt;br /&gt; &amp;lt;xsl:with-param name="title" select="$loc/strs/str[@id='sc2.labels.movies.movie6.title']"/&amp;gt;&lt;br /&gt; &amp;lt;xsl:with-param name="movieid" select="'6'"/&amp;gt;&lt;br /&gt; ... more parameters ...&lt;br /&gt;&amp;lt;/xsl:call-template&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The template, defined in a shared file called &lt;a href="http://starcraft2.com/layout/includes.xsl"&gt;includes.xsl&lt;/a&gt;, contains the HTML code used to display the movie:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&lt;br /&gt;&amp;lt;xsl:template name="movies-entry"&amp;gt;&lt;br /&gt; &amp;lt;xsl:param name="title"/&amp;gt;&lt;br /&gt; &amp;lt;xsl:param name="movieid"/&amp;gt;&lt;br /&gt; ... more parameters ...&lt;br /&gt; ... HTML/XSL code used to display the movie ...&lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So instead of having to do a lot of copying and pasting, as you would have to do if using plain HTML, the code stays centralized in one place, making bug fixes and other maintenance tasks easier to handle.&lt;br /&gt;&lt;br /&gt;&lt;div style="font-weight:bold;color:#900"&gt;Disadvantage - Backward-compatibility&lt;/div&gt;&lt;br /&gt;XSLT didn't become a W3C recommendation until 1999, so any browser created before or around that time probably won't have strong support for the language (this includes IE 5 and earlier).  So you have to consider the software your audience is likely to be using before building your site.  Blizzard probably thought it safe to use this technology, since gamers tend to use the latest and greatest browsers and are therefore unlikely to run into any compatibility issues.&lt;br /&gt;&lt;br /&gt;So in conclusion, I've found that using XSLT to build web sites gives you many advantages that you don't get when developing with vanilla HTML.  Perhaps I will use it for my next web development project!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-7966106635430993848?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/7966106635430993848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=7966106635430993848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7966106635430993848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/7966106635430993848'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/01/creating-websites-without-using-html.html' title='Creating websites WITHOUT using HTML'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_73vOQEMfTVI/SXN2OSE-KVI/AAAAAAAAADo/Gw0tOFBYgiI/s72-c/end_life.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-2174412763160899476</id><published>2009-01-11T12:42:00.005-05:00</published><updated>2009-01-11T13:07:23.216-05:00</updated><title type='text'>SudokuSolver released</title><content type='html'>I've developed a bit of an addiction recently.  Thankfully, it's not alcohol-related.  But like drinking, it does take up time and money.&lt;br /&gt;&lt;br /&gt;If you're not familiar with the puzzle game Sudoku, you've probably at least seen it either in your daily newspaper or somewhere online.  The goal is: given a square board 9 cells high and 9 cells wide, fill each row, column, and 3 by 3 sub-square with all the numbers 1 through 9.  No number can be repeated in any of those three places.  For example, the number 5 can't appear more than once in the same row.  The board starts with some cells already filled in to give you a starting point.  The less completed cells you start with, the harder the puzzle is.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/SWoxjSTd2zI/AAAAAAAAADg/64irvTIz_B8/s1600-h/Screenshot-Sudoku.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 399px; height: 400px;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/SWoxjSTd2zI/AAAAAAAAADg/64irvTIz_B8/s400/Screenshot-Sudoku.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5290095194578737970" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;After solving enough of these, I thought it would be an interesting exercise to write a program which solves them automatically!  As I got deep into creating the algorithm, one data structure stood out as being the keystone--sets.&lt;br /&gt;&lt;br /&gt;In mathematics, a set is a collection of numbers where:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;the order the numbers are in does not matter and&lt;/li&gt;&lt;br /&gt;&lt;li&gt;no number can be repeated.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;For example, this is a set:&lt;br /&gt;&lt;code&gt;{ 3, 7, 5, 9 }&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Since order doesn't matter, this is the same set as the one above:&lt;br /&gt;&lt;code&gt;{ 9, 3, 7, 5 }&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But this is NOT a set because there are two 7s:&lt;br /&gt;&lt;code&gt;{ 9, 3, 7, 5, 7 }&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;In this algorithm, sets are useful when trying to figure out what number a cell should be.  The algorithm can be summarized with this pseudo-code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;while board is not complete&lt;br /&gt;  for each cell in board&lt;br /&gt;    if cell is empty&lt;br /&gt;      possibleValues = findPossibleValues(cell)&lt;br /&gt;      if size of possibleValues == 1&lt;br /&gt;        cell.empty = false&lt;br /&gt;        cell.value = possibleValues[0];&lt;br /&gt;&lt;br /&gt;function findPossibleValues(cell)&lt;br /&gt;  possibleValues = new set{1,2,3,4,5,6,7,8,9}&lt;br /&gt;&lt;br /&gt;  for each rowcell in cell's row&lt;br /&gt;    if rowcell is not empty&lt;br /&gt;      //if possibleValues doesn't contain rowcell.value, then nothing happens&lt;br /&gt;      possibleValues -= rowcell.value&lt;br /&gt;&lt;br /&gt;  for each colcell in cell's column&lt;br /&gt;    if colcell is not empty&lt;br /&gt;      possibleValues -= colcell.value&lt;br /&gt;&lt;br /&gt;  for each squarecell in cell's square&lt;br /&gt;    if squarecell is not empty&lt;br /&gt;      possibleValues -= squarecell.value&lt;br /&gt;&lt;br /&gt;  return possibleValues &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Given a current board state, for each cell in the board, a set containing all values the cell could possibly be is created.  If this set only contains one number, then that's the only number the cell can be, so we fill in the cell with that number.  This keeps repeating until all cells are filled in. (There are some other tricks that have to be taken into account, but this is the most fundamental part of the algorithm.)&lt;br /&gt;&lt;br /&gt;If you'd like to try it out or look at the source code, you can download it here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mangst.com/projects/sudoku-solver"&gt;http://www.mangst.com/projects/sudoku-solver&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-2174412763160899476?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/2174412763160899476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=2174412763160899476' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2174412763160899476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/2174412763160899476'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2009/01/sudokusolver-released.html' title='SudokuSolver released'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/SWoxjSTd2zI/AAAAAAAAADg/64irvTIz_B8/s72-c/Screenshot-Sudoku.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-647359355282693766</id><published>2008-05-11T18:05:00.004-04:00</published><updated>2008-12-13T07:54:32.266-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='managment'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Right vs. Right</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SCdz5niSPNI/AAAAAAAAABE/Mc1V9LOJaOA/s1600-h/managing-humans.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SCdz5niSPNI/AAAAAAAAABE/Mc1V9LOJaOA/s400/managing-humans.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5199251728525049042" /&gt;&lt;/a&gt;&lt;br /&gt;Somewhere, deep in the bowels of a florescent-lit cubical farm, a battle wages on.  A battle that has been fought since the dawn of computer programming.  A battle of..."right vs. right"?&lt;br /&gt;&lt;br /&gt;This sums up the approach that Michael Lopp, better known as &lt;a href="http://www.randsinrepose.com/"&gt;"Rands"&lt;/a&gt; in the blogosphere, takes to software engineering.  In his book &lt;a href="http://www.managinghumans.com/"&gt;&lt;i&gt;Managing Humans: Biting and Humorous Tales of a Software Engineering Manager&lt;/i&gt;&lt;/a&gt;, the battle he describes is one of &lt;a href="http://www.randsinrepose.com/archives/2003/08/05/incrementalists_completionists.html"&gt;Incrementalists vs. Completionists&lt;/a&gt;--those who want to get things done &lt;em&gt;quickly&lt;/em&gt; verses those who want to get things done &lt;em&gt;correctly&lt;/em&gt;.  Both mindsets are equally "right" in Lopp's eyes.  It's the balanced combination of the two that makes for a solid software team.&lt;br /&gt;&lt;br /&gt;Lopp takes this even-handed, "ying/yang" approach throughout his book, which is mostly made up of posts from his blog, &lt;a href="http://www.randsinrepose.com/"&gt;Rands in Repose&lt;/a&gt;.  As with the incrementalists and completionlists, his characterizations of office personality types are colorful, insightful, and even-handed.  In the chapter entitled &lt;a href="http://www.randsinrepose.com/archives/2006/11/17/meeting_creatures.html"&gt;&lt;i&gt;Meeting Creatures&lt;/i&gt;&lt;/a&gt;, Lopp takes you on a wild safari, describing the different kinds of people you'll encounter around the conference table at work.  These meetings, he describes, are a rich ecosystem made up of unique, but interdependent personalities.  You never have any idea what a "Curveball Kurt" is talking about, for example, which is why you need a "Translator Tim" to help out.&lt;br /&gt;&lt;br /&gt;In regards to writing style, each one of the short, thirty-four chapters are written in a light, conversational tone that's easy to digest.  Being a collection of blog posts, it's  easy to pick up and put down without getting involved like you might with a novel.&lt;br /&gt;&lt;br /&gt;So, the book is great, but it should be noted that people may find it hard to relate to some of Lopp's stories. Lopp works in the fast-paced, high-energy Silicon Valley, where startups come and go like contestants on American Idol.  This can make for some quite stressful times.  He talks, for example, about &lt;a href="http://www.randsinrepose.com/archives/2002/06/17/the_big_qa_freak_out.html"&gt;The Monday Freakout&lt;/a&gt; which describes the deluge of yelling and screaming that comes pouring out of a project lead after a weekend spent worrying about the project.  I think it's safe to say that not everyone works in an atmosphere like this.  Many readers may have a job that, while engaging, doesn't keep them stressing over the weekend.&lt;br /&gt;&lt;br /&gt;But the bottom line is that no matter where you work, if your job is somehow related to software development, you can learn something from this book.  Read on!  It's the "right" thing to do.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-647359355282693766?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/647359355282693766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=647359355282693766' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/647359355282693766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/647359355282693766'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2008/05/right-vs-right.html' title='Right vs. Right'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/SCdz5niSPNI/AAAAAAAAABE/Mc1V9LOJaOA/s72-c/managing-humans.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-507339251196136693</id><published>2008-05-05T22:47:00.000-04:00</published><updated>2008-12-13T07:54:32.483-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='photography'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><title type='text'>ThumbThru released</title><content type='html'>I just recently finished putting together a small utility I had written a while ago for some websites I had designed during college.  I spent a few days cleaning it up and turning it into something generic that anyone could download and start using right away.  I've called it...**drumroll**&lt;br /&gt;&lt;br /&gt;&lt;p style="text-align: center; font-size: 1.2em;"&gt;&lt;a href="http://www.mangst.com/projects/thumb-thru/"&gt;ThumbThru&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_73vOQEMfTVI/SB_B38MC3II/AAAAAAAAAA8/J_3ReT4WaU8/s1600-h/thumb-thru.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_73vOQEMfTVI/SB_B38MC3II/AAAAAAAAAA8/J_3ReT4WaU8/s400/thumb-thru.png" alt="" id="BLOGGER_PHOTO_ID_5197085661802454146" border="0" /&gt;&lt;/a&gt;It's a simple photo gallery utility coded entirely in Javascript and written in such a way so that it takes as little time as possible to get up and running.  It's a lot like the galleries you'll find on the &lt;a href="http://news.bbc.co.uk/2/hi/in_pictures/"&gt;In Pictures&lt;/a&gt; section of the BBC News website--in fact, you could say that's where I got my "inspiration".&lt;br /&gt;&lt;br /&gt;Some things I learned when writing this:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;How to create a Javascript class&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The difference between the &lt;a href="http://www.gnu.org/licenses/gpl.html"&gt;GPL&lt;/a&gt; and the &lt;a href="http://www.gnu.org/licenses/lgpl.html"&gt;LGPL&lt;/a&gt; (I've licensed it under the LGPL, btw)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The fact that &lt;a href="http://ant.apache.org/"&gt;Ant&lt;/a&gt; can create ZIP files (pretty cool!)&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;All future blog posts perhaps...?&lt;br /&gt;&lt;br /&gt;Anyway, feel free to &lt;a href="http://www.mangst.com/projects/thumb-thru/index.php#download"&gt;download ThumbThru&lt;/a&gt; and give it a try!  Or at least check out the live &lt;a href="http://www.mangst.com/projects/thumb-thru/index.php#demo"&gt;demo&lt;/a&gt;!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-507339251196136693?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/507339251196136693/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=507339251196136693' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/507339251196136693'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/507339251196136693'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2008/05/thumbthru-released.html' title='ThumbThru released'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_73vOQEMfTVI/SB_B38MC3II/AAAAAAAAAA8/J_3ReT4WaU8/s72-c/thumb-thru.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-977108913856098104</id><published>2008-05-02T22:16:00.003-04:00</published><updated>2008-12-13T07:54:32.673-05:00</updated><title type='text'>I understand you</title><content type='html'>So, I go to check my &lt;a href="http://mail.google.com/"&gt;Gmail&lt;/a&gt; account like I do on a regular basis.  But when I logged in, I noticed something different.&lt;br /&gt;&lt;br /&gt;There was this red bar above my inbox:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SBvLasMC3HI/AAAAAAAAAA0/9rwieI2tDyE/s1600-h/gmail_firebug.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SBvLasMC3HI/AAAAAAAAAA0/9rwieI2tDyE/s400/gmail_firebug.png" alt="" id="BLOGGER_PHOTO_ID_5195970254500715634" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It turns out that I did indeed have &lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt; (a Firefox extension) enabled!  And I wouldn't have known it otherwise.  Google probably decided to include this feature after getting complaints from users who didn't realize that it was &lt;a href="http://en.wikipedia.org/wiki/Dumb_blonde"&gt;their own stupid fault&lt;/a&gt; their mail was loading slowly.  Or maybe Google decided to include it &lt;a href="http://en.wikipedia.org/wiki/Don%27t_be_evil"&gt;out of the kindness of their hearts&lt;/a&gt;.  Either way, it's pretty cool to see a huge corporation that certainly doesn't &lt;em&gt;need&lt;/em&gt; do something like this, take the time to do it.  They could have buried the issue somewhere deep inside a FAQ page and left it at that.&lt;br /&gt;&lt;br /&gt;In the short-term, spending time to include something that only benefits a small minority of users a small minority of the time is a waste of money.  Some developer might have spent a day designing this feature.  Google's stock price certainly won't rise the next day because of it.  He should have been working on &lt;a href="http://www.google.com/adsense"&gt;Adsense&lt;/a&gt; or something!&lt;br /&gt;&lt;br /&gt;But it's the longer term impact that is affected by small enhancements like this.  What this is saying isn't "turn off Firebug".  It's saying "I know you want to get to your email quickly and Firebug may interfere with that." Or more simply:&lt;br /&gt;&lt;br /&gt;"I understand you."&lt;br /&gt;&lt;br /&gt;When another product of equal or better quality comes around, if you've been regularly reminding your users that you understand them, they'll be more likely to stick with you during tough times.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-977108913856098104?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/977108913856098104/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=977108913856098104' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/977108913856098104'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/977108913856098104'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2008/05/i-understand-you.html' title='I understand you'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_73vOQEMfTVI/SBvLasMC3HI/AAAAAAAAAA0/9rwieI2tDyE/s72-c/gmail_firebug.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-8836700280709384379</id><published>2008-04-27T22:16:00.007-04:00</published><updated>2008-12-13T07:54:33.130-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='music'/><category scheme='http://www.blogger.com/atom/ns#' term='amazon'/><category scheme='http://www.blogger.com/atom/ns#' term='drm. mp3'/><category scheme='http://www.blogger.com/atom/ns#' term='itunes'/><title type='text'>Music from the Amazon</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_73vOQEMfTVI/SBU8_sMC3EI/AAAAAAAAAAY/jbIQlCaFru8/s1600-h/amazon_logo.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 232px; height: 117px;" src="http://4.bp.blogspot.com/_73vOQEMfTVI/SBU8_sMC3EI/AAAAAAAAAAY/jbIQlCaFru8/s320/amazon_logo.png" alt="" id="BLOGGER_PHOTO_ID_5194124810132839490" border="0" /&gt;&lt;/a&gt;For the longest time, the &lt;a href="http://www.apple.com/itunes/"&gt;iTunes&lt;/a&gt; music store has pretty much dominated the music download market.  Apple's service has a gigantic selection of music to choose from and they make it super easy to find that song you want to hear.  But there's one downside to all this: most of the songs are protected by &lt;a href="http://en.wikipedia.org/wiki/Digital_rights_management"&gt;digital rights management&lt;/a&gt; or DRM.  This puts limits on things like on what devices the songs will play and how many times a song can be copied.  A song without these limitations is certainly a better deal for the customer, who can instead copy the song to as many devices as he desires and play the song on all of them.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.amazon.com/"&gt;Amazon&lt;/a&gt;, traditionally the book-selling behemoth of the interwebs, fairly recently started providing music for download.  While it doesn't have the extensive selection iTunes is known for, all of Amazon's music is DRM free and encoded in the generic MP3 file format, giving you more freedom over how you can use the music you paid for.&lt;br /&gt;&lt;br /&gt;Just today, I downloaded my first album off of this service.  It was a pretty painless procedure, though there's a few things you should be aware of:&lt;br /&gt;&lt;br /&gt;One - If you want to purchase an entire album, you've got to download this &lt;b&gt;special "Amazon MP3 Downloader" software&lt;/b&gt;.  It's always annoying when you have to install an entire application to do something as simple as downloading a couple of files.  But one feature that takes the edge off of this inconvenience is that it automatically puts a copy of your songs in either your iTunes or Media Player (depending on your OS) library, saving you the small, but sometimes tedious, step of organizing your music by hand.&lt;br /&gt;&lt;br /&gt;Two - &lt;b&gt;They have no refund policy&lt;/b&gt;.  Once your credit card is charged, there's no way to get your money back.  Makes sense, since the product you're paying for can't get damaged the way it could if sent through the mail.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_73vOQEMfTVI/SBU9kMMC3FI/AAAAAAAAAAg/ZRJHVcgjbew/s1600-h/earbuds.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 198px; height: 198px;" src="http://2.bp.blogspot.com/_73vOQEMfTVI/SBU9kMMC3FI/AAAAAAAAAAg/ZRJHVcgjbew/s320/earbuds.jpg" alt="" id="BLOGGER_PHOTO_ID_5194125437198064722" border="0" /&gt;&lt;/a&gt;Three - &lt;b&gt;You can only download your songs once&lt;/b&gt;.  If your hard drive decides to stop working one day, that's it, they're gone.  You can't download them again without paying a second time.  While it doesn't take much effort on your part to make a backup, this still seems like a silly policy.  Because the music is DRM-free and can be copied ad infinitum, it seems that what you're really paying for is &lt;em&gt;permission to play&lt;/em&gt; the music, as opposed to the music itself.&lt;br /&gt;&lt;br /&gt;This is what I like to think of &lt;a href="http://www.steampowered.com/"&gt;Valve's Steam&lt;/a&gt; doing.  Steam charges for downloadable content just like Amazon and iTunes does, only instead of charging for downloadable music, it charges for downloadable computer games.  Since Steam lets you download the games you've paid for as many times as you want, it's charging you for &lt;em&gt;permission to play&lt;/em&gt; the game--not necessarily the game itself.  I don't see why Amazon couldn't implement a scheme like this as well.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;In conclusion&lt;/b&gt;, despite the software it forces down your throat and the one time download limitation, I do indeed recommend Amazon's music download service.  Its policy of providing DRM-free music for purchase is something that would be nice see amongst the larger music sellers.  Always search Amazon for that song you want before looking anywhere else.  DRM-free is the way to be!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-8836700280709384379?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/8836700280709384379/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=8836700280709384379' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8836700280709384379'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/8836700280709384379'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2008/04/music-from-amazon.html' title='Music from the Amazon'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_73vOQEMfTVI/SBU8_sMC3EI/AAAAAAAAAAY/jbIQlCaFru8/s72-c/amazon_logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5682413770770674096.post-4579335664771876029</id><published>2008-04-24T21:02:00.000-04:00</published><updated>2008-04-24T21:32:45.513-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cms'/><category scheme='http://www.blogger.com/atom/ns#' term='drupal'/><title type='text'>Upgrading Drupal</title><content type='html'>&lt;img src="http://www.mangst.com/images/blog/democrat.gif" align="right" /&gt;In light of the up and coming (at the time) Pennsylvania Democratic primaries, I was asked by my &lt;a href="http://www.ship.edu"&gt;alma mater's&lt;/a&gt; student Democrat organization to create a &lt;a href="http://www.shipdems.org/"&gt;website for their group&lt;/a&gt;.  I thought this would be a great opportunity to give an open source CMS (content management system) a try.  I ended up choosing &lt;a href="http://www.drupal.org/"&gt;Drupal&lt;/a&gt; and was able to get it up and running with relative ease.  To my surprise (and frustration), only a week later, the Drupal developers released a new version, compelling me to upgrade.  In this post, I'm going to talk a bit about the Drupal upgrade process and upgrade processes in general.&lt;br /&gt;&lt;br /&gt;Upgrading Drupal, or any application for that matter, is often much harder than performing an installation from scratch.  Two major hurtles come to mind.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;(1) Compatibility&lt;/b&gt;.  Will all your stuff still work with the new version?&lt;br /&gt;&lt;br /&gt;When Microsoft releases a new version of their Office suite, for example, you want to know that *all* your Word documents created by previous versions can still be opened.&lt;br /&gt;&lt;br /&gt;Drupal, unfortunately, doesn't make things that convenient.  In the cosmic balance between the software pragmatists and the software idealists, the Drupal team, unlike Microsoft, fights for the idealists.  Any time they release a new "major" version (that is, 5.x to 6.0, for example), they pretty much guarantee that all existing custom themes or modules will *not* work.  This is because they are very much willing to throw away a large part of the codebase if they think there's a better way of doing something.&lt;br /&gt;&lt;br /&gt;These compatibility issues, however, are not an issue with "minor" upgrades (such moving from 5.4 to 5.5).  This is because minor upgrades usually deal with security fixes and implementation details that your custom modules and themes don't care much about.  Luckily, the upgrade I wanted to perform, 6.1 to 6.2, was a minor upgrade, so I knew with a high degree of confidence that all my custom themes and modules would still work (and they do).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;(2) Data preservation&lt;/b&gt;.  Will any of your data be lost in the upgrade process?&lt;br /&gt;&lt;br /&gt;Of course, you want this answer to be a resounding "NO WAY!".  For example, if you upgrade to a new version of Firefox, you don't want to load up your new version, only to find that all your bookmarks are gone.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.mangst.com/images/blog/drupal.jpg" align="left" /&gt;In Drupal's case, this means not loosing your custom themes, custom modules, site content, and files your users uploaded.  What makes this a bit complicated when upgrading Drupal is that the Drupal team recommends you completely remove all files from your existing Drupal installation and then do a fresh copy of all the files from the new version (as opposed to just overwriting everything).  This means that you must be aware of all the ways in which your Drupal installation differs from the "base" or "initial" installation.  That is, you must know about your custom themes, custom modules, etc. so you can put them back after you've copied over the new version.&lt;br /&gt;&lt;br /&gt;I find it helpful to maintain a list of all of these differences.  That way, whenever I have to upgrade, I know exactly what files to put back into the new version to keep my site running smoothly.&lt;br /&gt;&lt;br /&gt;Now that you've got an overview of what to expect from your Drupal upgrade, you might want to do some reading on the &lt;a href="http://www.drupal.org/"&gt;Drupal website&lt;/a&gt; for more of the technical details.  Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5682413770770674096-4579335664771876029?l=mangstacular.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mangstacular.blogspot.com/feeds/4579335664771876029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5682413770770674096&amp;postID=4579335664771876029' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4579335664771876029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5682413770770674096/posts/default/4579335664771876029'/><link rel='alternate' type='text/html' href='http://mangstacular.blogspot.com/2008/04/upgrading-drupal.html' title='Upgrading Drupal'/><author><name>Michael Angstadt</name><uri>http://www.blogger.com/profile/04809821580827426849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/-oK1_Kmkd7Z4/TnKOwAA4l9I/AAAAAAAAAQA/yZAlO7m4RJg/s220/portrait-sml.jpg'/></author><thr:total>0</thr:total></entry></feed>
