Sunday, February 26, 2006

Annoyances: java.sun.com

Why are the people that run the java.sun.com site such idiots? Try to get to the JavaMail part of the J2EE stack from java.sun.com w/o using the search dialog box. Go ahead, try it! I'll wait .....

Can't be done huh? WTF you say? It's because they are idiots! I used to be able to get to any named Java technology 2 clicks from the home page but they changed it and change is good. Bullshit!!

As a company Sun is infamous for small annoyances this is one of them. And because I spend (or will be spending) so much time in Java (Sun) land the annoyances are beginning to add up. Even their own employees complain. Argh!!!!!!!!!!!

It's like there are two competing groups of people working at Sun. Some really brilliant, thoughtful, articulate people and a bunch of morons. The morons are winning! If you think I'm kidding take a look at some of the J2SE and J2EE source code. There are some parts of it where I get the feeling, these people really know what they are doing. Then there are other parts where I have to go, WTF?!!! The patients have definitely taken over the asylum.

Saturday, February 25, 2006

QOTD: The 'f' in framework

I love the English language. You can get away with saying almost anything.

Wednesday, February 15, 2006

The Long Way Around, An Exercise in Concurrent Programming

Adopting the mindset to fully utilize the classes in java.util.concurrent can be quite challenging. One of the side effects of concurrent programming is that it can require more code than it would take to achieve the same results using mutual exclusion locking.

Is concurrency worth writing more code? My current answer to the question is hell yes because I think most Java programmers need the practice it's going to take to get one's head around all the issues associated with concurrency. And if the programmer doesn't practice, how is he/she going to be able to fully exploit concurrency in the cases where it is decisive? With that in mind I would like to talk about some code that I've written, but first some background info.

Better event handling

I think Java's current event model sucks! It's biggest problem is that it tightly couples the firing of events with there processing. In other words, the thread that does the firing is usually the same thread that handles the event. This is not a very stable or scalable solution because all it takes to bring event firing/handling to its knees is one badly written event handler (think infinite loop or blocking I/O). It is for this reason that the Swing team introduce the Swing worker thread so the programmer could separate the handling of events from the firing of events without taking the main Swing thread hostage.

There is a better way based on asynchronous messages. It is not fundamentally new. It is based on how asynchronous network I/O is done on Unix operating systems. You tell the kernel your are interested in a set of events then you can either go about your business and periodically check if the things you are interested has happened (poll(2)) or you can sit around and wait until something you are interested in does happen (select(2)). The choice is yours. Plus you can be interested in as many things as you want without a) requiring a copy of yourself for each interest set or b) hogging the kernel. This is because all the kernel does is tell you that what you are interested in is ready, it is your job to do something useful with it. So how do we implement the functionality in Java? Queues!

To wait or not to wait? That is the question!

The basic idea is a class/service that fires events will provide methods for threads to receive notification of these events by registering java.util.Queue objects. You can use any Queue object but I highly recommend java.util.concurrent.BlockingQueue objects. Why BlockingQueue[s]? Simple! A BlockingQueue allows us to choose to wait indefinitely, wait temporarily, or not at all for events (take(), poll(long,TimeUnit), poll()). It's the decoupler see? It unbinds the firing of events from the processing of events. It's the service/class 's responsibility to fire the event and it's the registrant's responsibility to process them. Tada!

The code dammit! The code!

Before I present any code I need to put it in context so it makes sense to you because I'm not going to include a complete project, just snippets. The snippets are part of a class that provides availability testing services. What that means is if a thread tries to make a connection to some host on the network and the connection fails, the thread can register the host with the service and the service will continue to try connecting to the host until some pre-configured timeout has elapsed or it manages to establish a connection. The registrant will be notified of the outcome of the connection testing by the service via the notification mechanism I've been talking about.

Now that I've through all the background info lets look at some code:
    /** 

37    * Container for listeners interested all test results. 
38    */ 
39   private final Collection<Queue<Result>> allResults = new ConcurrentHashSet<Queue<Result>>(); 

40    
41   /** 
42    * Container for listeners interested in specific test results. 
43    */ 
44   private final ConcurrentMap<String, Collection<Queue<Result>>> hostResults = 

45           new ConcurrentHashMap<String, Collection<Queue<Result>>>(); 
46    
47   /** 
48    * Registers <tt>listener</tt> with <tt>this</tt> object to receive {@link Result} 

49    * notifications for all hosts. 
50    * 
51    * @param listener  The listener. 
52    * @return  <tt>true</tt> if the listener was registered, <tt>false</tt> otherwise. 

53    */ 
54   public boolean register( Queue<Result> listener ) 
55   { 
56       return allResults.add( listener ); 
57   } 

58    
59   /** 
60    * Registers <tt>listener</tt> with <tt>this</tt> object to receive {@link Result} 

61    * notifications for hosts in <tt>hosts</tt>. 
62    * 
63    * @param hosts The interest set. 
64    * @param listener  The listener. 

65    * @return  The registration results. 
66    */ 
67   public boolean[] register( String[] hosts, Queue<Result> listener ) 
68   { 

69       boolean[] registrations = new boolean[ hosts.length ]; 
70       for( int i = registrations.length; --i >= 0; ) 
71       { 

72           String host = hosts[ i ]; 
73           Collection<Queue<Result>> listeners = hostResults.get( host ); 
74           if( null == listeners ) 
75           { 

76               listeners = new ConcurrentHashSet<Queue<Result>>(); 
77               Collection<Queue<Result>> preempt = hostResults.putIfAbsent( host, listeners ); 
78               if( null != preempt ) 

79                   listeners = preempt; 
80           } 
81           registrations[ i ] = listeners.add( listener ); 
82       } 
83       return registrations; 
84   } 

85    
86   /** 
87    * Unregisters <tt>listener</tt>. 
88    * @param listener  The listener that was registered with this object via {@link #register(Queue)}. 

89    */ 
90   public void unregister( Queue<Result> listener ) 
91   { 
92       allResults.remove( listener ); 
93   } 

94    
95   /** 
96    * Unregister's <tt>listener</tt> from all <tt>hosts</tt>. 
97    * @param hosts 

98    * @param listener 
99    */ 
100  public void unregister( String[] hosts, Queue<Result> listener ) 
101  { 

102      for( String host : hosts ) 
103      { 
104          Collection<Queue<Result>> c = hostResults.get( host ); 
105          if( null != c ) 

106          { 
107              c.remove( listener ); 
108              if( c.isEmpty() ) 
109              { 
110                  Queue<Result> temp = new ConcurrentLinkedQueue<Result>(); 

111                  boolean b = register( temp ); 
112                  assert b : "IMPOSSIBLE!";// but i like to check anyway. 
113                  hostResults.remove( host ); 
114                  if( !c.isEmpty() ) 

115                  { 
116                      Collection<Queue<Result>> preempt = hostResults.putIfAbsent( host, c ); 
117                      if( null != preempt ) 
118                      { 

119                          preempt.addAll( c ); 
120                          unregister( temp ); 
121                          c = preempt; 
122                      } 
123   
124                      for( Result result : temp ) 

125                          if( result.host.equals( host ) ) 
126                              for( Queue<Result> queue : c ) 
127                                  queue.offer( result ); 
128                  } 

129                  unregister( temp ); 
130              } 
131          } 
132      } 
133  } 
134   
135  /** 

136   * Notifies registered listeners of a new event. 
137   * @param result    The event. 
138   */ 
139  private void fire( Result result ) 
140  { 

141      Collection<Queue<Result>> listeners = hostResults.get( result.host ); 
142      if( null != listeners ) 
143          for( Queue<Result> listener : listeners ) 

144              listener.offer( result ); 
145   
146      for( Queue<Result> results : allResults ) 
147          results.offer( result ); 
148  }

If you've simply skimmed pass the code you may want to go back and take a minute to digest it because I'm going to rewrite parts of it as it would have been written without the concurrency tools.

No concurrency

private final Collection<Queue<Result>> allResults = 

37       Collections.synchronizedCollection( new HashSet<Queue<Result>>() ); 
38    
39   private final Map<String, Collection<Queue<Result>>> hostResults = 

40       new Hashtable<String, Collection<Queue<Result>>>(); 
41    
42   public boolean register( Queue<Result> listener ) 
43   { 

44       return allResults.add( listener ); 
45   } 
46    
47   public boolean[] register( String[] hosts, Queue<Result> listener ) 
48   { 
49       boolean[] registrations = new boolean[ hosts.length ]; 

50       for( int i = registrations.length; --i >= 0; ) 
51       { 
52           String host = hosts[ i ]; 
53           Collection<Queue<Result>> listeners; 

54           synchronized( hostResults ) 
55           { 
56               listeners = hostResults.get( host ); 
57               if( null == listeners ) 
58               { 

59                   listeners = Collections.synchronizedCollection( new HashSet<Queue<Result>>() ); 
60                   hostResults.put( host, listeners ); 
61               } 
62           } 

63           registrations[ i ] = listeners.add( listener ); 
64       } 
65       return registrations; 
66   } 
67    
68   public void unregister( Queue<Result> listener ) 

69   { 
70       allResults.remove( listener ); 
71   } 
72    
73   public void unregister( String[] hosts, Queue<Result> listener ) 
74   { 

75       for( String host : hosts ) 
76       { 
77           synchronized( hostResults ) 
78           { 
79               Collection<Queue<Result>> c = hostResults.get( host ); 

80               if( null != c ) 
81               { 
82                   synchronized( c ) 
83                   { 
84                       c.remove( listener ); 

85                       if( c.isEmpty() ) 
86                           hostResults.remove( host ); 
87                   } 
88               } 
89           } 
90       } 

91   } 
92    
93   private void fire( Result result ) 
94   { 
95       Collection<Queue<Result>> listeners = hostResults.get( result.host ); 
96       if( null != listeners ) 

97       { 
98           synchronized( listeners ) 
99           { 
100              for( Queue<Result> listener : listeners ) 

101                  listener.offer( result ); 
102          } 
103      } 
104   
105      synchronized( allResults ) 
106      { 

107          for( Queue<Result> results : allResults ) 
108              results.offer( result ); 
109      } 
110  }

Let's compare

For the most part the non concurrent version looks pretty much like the concurrent version in terms of lines of code and complexity, except for public void unregister( String[], Queue).

It was because of this method that I started this blog entry in the first place, because I realized how much easier a synchronized version would have been to write. It just seemed to be the long way around for removing an entry from a Map. Lets walk through it.

First off, let me explain what it's supposed to be doing then (hopefully) it will make sense why it does what it does.

Basically, one host can have multiple listeners. So when all the listeners for a host have been unregistered we want to remove the mapping for the host. Otherwise, we are going to have an object retention bug on our hands. The problem is how do we remove the mapping in a thread safe way? Because we can't simply say,

if( c.isEmpty() )
    hostResults.remove( host );
and stop there because it is entirely possible that between the test and the removal another thread has registered for host. This is the kind of problem that mutual exclusion (synchronized) solves. But since we aren't using mutual exclusion we have to get creative. What we do is create a dummy Queue and register it to receive all events that get fired this way when we actual remove the mapping, if we are wrong about it being empty we can put it back and re-fire all the events that got fired between the time we removed the mapping and the time we put it back. The only draw back to all this (beside the additional lines of code) is that it is possible for a listener to receive duplicate events in the gap between,
preempt.addAll( c );
and
unregister( temp );

So is the additional complexity worth it? Maybe. If we did things the synchronized way the entire event notification mechanism can quickly become the bottle neck if a lot of threads are using it, especially if they are registering and unregistering for the same host. Now, we could replace the synchronized keyword with java.util.concurrent.locks.ReentrantLock[s]. It's a more scalable mutual exclusion mechanism than synchronized. But I've spoken to Doug Lea recently and he told me that the (Sun's) JVM team is working hard at making the synchronized keyword as performant as ReentrantLock. Ya gotta love that competition. I think everyone should email Doug and thank him and the rest of the jsr166 team for embarrassing the JVM team into trying to make contended monitor acquisition more performant.

One last thing

I want to point out there is no general purpose lock free solution/pattern to the problem that I was solving in the unregister method. I was able to get away with not using locks because of the class' support for listeners to be notified of all events regardless of host. This support wasn't put in there just for concurrency's sake but is simply a feature requirement of the class that I've leveraged to avoid locking.

Tuesday, February 14, 2006

Matching a Backslash in Java

I've done it a million times in the past but for whatever reason I keep screwing up how to properly write a backslash (\) in a regular expression. I wasted 20+ minutes yesterday trying to figure out why the character class in this
[\";:()\\[\\]<>@.\\,]
regular expression wouldn't match strings containing commas. I'm blogging the solution so I'll have a permanent record and hopefully, I'll once and for all stop screwing it up!

Solution:

[\";:()\\[\\]<>@.\\\\,]

Monday, February 13, 2006

Part 2's a come'n

I've promised a part 2 to this entry and I will deliver. Granted, nobody has complained about it so far but that is probably because you and them have better things to do with your time. Losers! Just kidding.

In the mean time I've fixed a minor bug in the code. The entry has been updated.

It was a really D'oh! (think Homer Simpson) type of bug. The code (comments) promised to return MX records sorted by preference but in reality it used lexicographical sorting only. So if a domain had two exchangers with preferences of 77 and 100 the 100 would appear before the 77. D'oh!

Saturday, February 11, 2006

Java too ubiquitous? (My Response)

I second CJAN. I think it's long overdue. I'm sick of tired of the growing size of the SDK and it's ilk (i.e. J2EE). At the rate they are going they are going to have to release a 4 DVD box set just to get the SDK to us and then we are going to have to FedEx our customers a USB external harddrive to store the SDK and the additional libraries to run the programs we write.

Back to Gentoo

I'm going back to Gentoo. As soon as my ThinkPad Z60m replacement gets here I'm putting Gentoo on it. Ubuntu/Debian is just not my bag. Even though it's GNU/Linux it feels alien to me. I think I'm just spoiled. Gentoo forces you to know your system. It's not just a black box. I feel empowered by that. I can't tell you what Gentoo has done for my sysadmin skills, though I try to avoid system adminstration at every turn. I prefer to spend my paying hours coding.

My litmus test of a good distro is how easy is it to build and install your own kernel. Nuff said! Gentoo rocks fo' shizzle!

Friday, February 10, 2006

I Am Spam No More

I'm no longer SPAM. Yeah!
Hello, Your blog has been reviewed, verified, and whitelisted so that it will no longer appear as potential spam. If you sign out of Blogger and sign back in again, you should be able to post as normal. Thanks for your patience, and we apologize for any inconvenience this has caused. Sincerely, Blogger Support

Observations, Linking Trends

Over the last year or so I've noticed that less and less of the sites I visit force new windows open or popups when I click on offsite links. The new site simply replaces the current site and a nice ordered history is maintained. This is the way it should have always been. Sometimes I get so engrossed by the content of the new site that I forget how I got there or why I'm there. So I'm really appreciative of the ability to click on the little drop down arrow to the right of my browser's back button so at a glance, I can be reminded of why I'm there.

I know the motivation for new windows and popups was to keep people on a site. Granted, it created more confusion than stickyness, but it had a purpose. So what is the motivation for this new style of site design?

Anyway, maybe I'm hallucinating and it's the same as it always was or maybe my browsing tastes of changed and the sites I frequent now are better designed. Come to think of it, I do consume significantly less Internet porn now that I'm 30.

I Am Spam

blogger.com says I am spam. Now everytime I want to post an entry I have to complete Word Verification.

Warning, edited to make it sound worse than it is!

Spam blogs can be recognized by their irrelevant, repetitive, or nonsensical text, along with a large number of links, usually all pointing to a single site.

Link to original text.

God forbid hypertext (this blog) should have hyperlinks in it. That's what I get for talking crap about big daddy (or more recently, Big Brother) Google.

3 Most Annoying Things About Firefox

  1. Press F3 to bring up the search dialog but it doesn't give the search box focus. What's the point!?! And don't tell me about [CTRL-F]
  2. Why can't I search my history, not just the title but the contents of the pages. I know it caches the data so why don't they just get it over with and make the damn thing searchable!?! I don't want a hear a peep about the dman Google toolbar. Google has enough information on me. The amount of internet porn I consume is my business and my business only! You see what Yahoo is doing to the Chinese. Google could be next. The dollar$ associated with a billion+ pontential users is damn good reason to do evil.
  3. When I scroll down a long page and click on a link on the page then go back to the previous page it starts me from the top of the page again. What the f@ck!?!

Tuesday, February 07, 2006

Making Progress

I'm happy to report that I'm making progress. It seems my breakthrough epiphany is bearing fruit. I still don't have the big picture breakdown like I want but thanx to my incessant note taking I have a bunch of little pictures that I can work on until I discover a way to tie it all together.

In the mean time I am happy to report the DNS querying code that I posted in A Breakthrough! has graduated from the Whiteboard and found it's own home. It's been fully tricked out with hostname resolution caching and all. There are a few concurrency related goodies in the code that I want to talk about to those readers still trying to figure out what the java.util.concurrency packages are good for. Since the code is 183 lines long (with comments) I'm splitting its discussion into two entries. The first entry is the one you are reading now and it contains the source code. The second entry will discuss the concurrency goodies and I'll post that sometime later today or maybe tomorrow.

Here is the code, enjoy:

(Updated 13 Feb 06, 10:14)


6    import javax.naming.CommunicationException;
7    import javax.naming.NameNotFoundException;
8    import javax.naming.NamingException;
9    import javax.naming.ServiceUnavailableException;
10   
11   import javax.naming.directory.InitialDirContext;
12   
13   import java.net.InetAddress;
14   import java.net.PortUnreachableException;
15   import java.net.UnknownHostException;
16   
17   import java.util.Enumeration;
18   import java.util.Queue;
19   import java.util.TreeSet;
20   
21   import java.util.concurrent.ConcurrentHashMap;
22   import java.util.concurrent.ConcurrentLinkedQueue;
23   import java.util.concurrent.ConcurrentMap;
24   import java.util.concurrent.CountDownLatch;
25   
26   /**
27    * Lookup service for DNS MX records.
28    * <p>
29    * <b>Thread safe:</b> yes
30    * </p>
31    *
32    * @author HashiDiKo</a>
33    */
34   public final class MXRecordQueryService
35   {
36       private static final String URL = "dns:/";
37   
38       private static final String MXATT = "MX";
39   
40       private static final String[] MXFILTER = {MXATT};
41   
42       private static final MXRecordQueryService THIS = new MXRecordQueryService();
43   
44       /**
45        * Cache queries.
46        * @see MXRecordQueryService.CacheEntry
47        */
48       private final ConcurrentMap<String, CacheEntry> cache = new ConcurrentHashMap<String, CacheEntry>();
49   
50       /**
51        * Provides a mechanism for ensuring that no 2 (or more) threads will race to query the same domain but at the same
52        * time allow concurrent queries of different domains.
53        */
54       private final ConcurrentMap<String, CountDownLatch> domainLocks = new ConcurrentHashMap<String, CountDownLatch>();
55   
56       private MXRecordQueryService()
57       {
58       }
59   
60       public static MXRecordQueryService getInstance()
61       {
62           return THIS;
63       }
64   
65       /**
66        * Returns the IP addresses of the MX records for <tt>domain</tt> sorted by preference or <tt>null</tt> if no valid
67        * hostnames exits for the MX records.
68        *
69        * @param domain The domain name.
70        *
71        * @return The  IP addresses of the MX records for <tt>domain</tt> sorted by preference or <tt>null</tt> if no valid
72        *         hostnames exits for the MX records.
73        *
74        * @throws NameNotFoundException       Indicates that the <tt>domain</tt> is invalid.
75        * @throws ServiceUnavailableException Indicates that <tt>domain</tt> is valid but no MX records exists for it.
76        * @throws CommunicationException      Indicates a problem contacting the configured DNS server(s).
77        * @throws RuntimeException            If some other <tt>javax.naming.NamingException</tt> occurs.
78        */
79       public Queue<String> get( String domain )
80               throws ServiceUnavailableException, NameNotFoundException, CommunicationException, InterruptedException
81       {
82           CacheEntry cached = cache.get( domain );
83           if( null != cached )
84           {
85               if( System.currentTimeMillis() <= cached.expiration )
86                   return cached.object;
87               cache.remove( domain, cached );
88           }
89   
90           CountDownLatch lock = domainLocks.get( domain );
91           if( null == lock )
92           {
93               lock = new CountDownLatch( 1 );
94               CountDownLatch prempted = domainLocks.putIfAbsent( domain, lock );
95               if( null == prempted )
96               {   /* Take another look because we may have preempted and it's cheaper to look than to lookup. */
97                   cached = cache.get( domain );
98                   if( null != cached && System.currentTimeMillis() <= cached.expiration )
99                       return cached.object;
100  
101                  try
102                  {
103                      TreeSet<MXRecord> sorter = new TreeSet<MXRecord>();
104                      String url = URL.concat( domain );
105                      for( Enumeration en = new InitialDirContext().getAttributes( url, MXFILTER ).get( MXATT ).getAll();
106                           en.hasMoreElements(); )
107                          sorter.add( new MXRecord( (String)en.nextElement() ) );
108  
109                      Queue<String> queue = new ConcurrentLinkedQueue<String>();
110                      for( MXRecord mx : sorter )
111                      {
112                          try
113                          {
114                              for( InetAddress addr : InetAddress.getAllByName( mx.hostname ) )
115                                  queue.offer( addr.getHostAddress() );
116                          }
117                          catch( UnknownHostException ignore )
118                          {
119                          }
120                      }
121                      cache.put( domain, new CacheEntry( queue ) );
122                      return queue;
123                  }
124                  catch( NameNotFoundException nex )
125                  {
126                      throw nex;
127                  }
128                  catch( ServiceUnavailableException sux )
129                  {
130                      throw sux;
131                  }
132                  catch( CommunicationException comx )
133                  {
134                      if( comx.getRootCause() instanceof PortUnreachableException )
135                          throw comx;
136                      throw new RuntimeException( comx );
137                  }
138                  catch( NamingException nex )
139                  {
140                      throw new RuntimeException( nex );
141                  }
142                  finally
143                  {
144                      lock.countDown();
145                      boolean invariant = domainLocks.remove( domain, lock );
146                      assert invariant : "Impossible!";
147                  }
148              }
149              else
150                  lock = prempted;
151          }
152          lock.await();
153          return get( domain );
154      }
155  
156      private static final class CacheEntry
157      {
158          final Queue<String> object;
159  
160          final long expiration;
161  
162          CacheEntry( Queue<String> object )
163          {
164              this.object = object;
165              expiration = System.currentTimeMillis() + 60000;
166          }
167      }
168  
169      /**
170       * Utility class for extracting the different parts of a MX record string.
171       */
172      private static final class MXRecord implements Comparable<MXRecord>
173      {
174          final String hostname;
175  
176          private final int pref;
177  
178          MXRecord( String mx )
179          {
180              assert mx.matches( "\\d++\\s++.+\\." ) : "Invalid MX record format: ".concat( mx );
181  
182              int offset = 0;
183              int len = mx.length() - 1;// Strip trailing dot.
184  
185              /* Extract preference integer. */
186              while( offset < len && Character.isDigit( mx.charAt( offset ) ) )
187                  offset++;
188              pref = Integer.parseInt( mx.substring( 0, offset ) );
189  
190              /* Extract domain name. */
191              while( offset < len && Character.isWhitespace( mx.charAt( offset ) ) )
192                  offset++;
193  
194              hostname = mx.substring( offset, len );
195          }
196  
197          public int compareTo( MXRecord o )
198          {
199              int diff = pref - o.pref;
200              return 0 == diff ? hostname.compareTo( o.hostname ) : diff;
201          }
202      }
203  }
204  

Monday, February 06, 2006

JNDI vs dnsjava

I've finished the benchmarks and JNDI kicked dnsjava's ass! JNDI is approximately 4x faster than dnsjava. Since we are talking about milliseconds not microseconds or nanoseconds, JNDI is the clear winner for my needs. If you are completely confused should read my previous entry.

A Breakthrough!

I've had a breakthrough! I woke up this morning with the realization that part of my (code) writer's block is centered on my (current) inability to decompose the problem into a set of objects and methods, the root of OO design. Usually I'm very good at this. It's the way I've always worked and it's never failed me (this badly) before. Anyway, my breakthrough is "screw the design!" There are certain things the program is going to need to do. I don't know what all of them are but I do know some. For example, DNS queries are going to be critical to the program. How and where it fits into the big scheme of things is unclear at the moment I know it's needs to be in there somewhere. So I've created a class called Whiteboard where I can temporarily stick some of this stuff until I find a permanent home for them.

More About DNS Queries

Normally if the Java standard library has the ability to doing something for which there exists external libraries I will use the standard library even if the standard library's approach is inelegant, so long as there isn't a performance reason not to. Today I've arrived at such an junction.

In the standard library, if you want to issue DNS queries for anything besides A and PTR records, you must use JNDI. JNDI was really designed for use with LDAP but Sun managed to shoehorn some DNS into it. On the other hand there is dnsjava. An open-source java library for issuing all types of DNS queries.

Say I wanted to lookup the MX records for blogger.com. Using JNDI it would look like this:

String[] MX_RECORD = {"MX"};

/**
 * Returns the MX records for domain sorted by preference or
 * null if no MX records where found.
 *
 * @param domain The domain name.
 *
 * @return The MX records for domain.
 *
 * @throws NamingException
 */
public List getMX( String domain ) throws NamingException
{
    DirContext ctx = new InitialDirContext();
    try
    {
        String query = "dns:/".concat( domain );
        Attribute att = ctx.getAttributes( query, MX_RECORD ).get( "MX" );
        if( null == att )
            return null;

        Collection sorter = new TreeSet();
        for( Enumeration e = att.getAll(); e.hasMoreElements(); )
            sorter.add( ((String)e.nextElement()).trim() );

        List sorted = new ArrayList( sorter.size() );

        for( String mx : sorter )
        {
            /* Strip off preference integer. */
            int offset = 0;
            int len = mx.length();
            
            for( char c;
                 offset < len
                 && (Character.isDigit( c = mx.charAt( offset ) )
                    || Character.isWhitespace( c )); )
                 offset++;
     
            /* Strip trailing dot (.) if it exists. */
            if( '.' == mx.charAt( len - 1 ) )
                len -= 1;
            mx = mx.substring( offset, len );

            sorted.add( mx );
        }
        return sorted;
    }
    finally
    {
        try
        {
            ctx.close();
        }
        catch( NamingException ignore )
        {
        }
    }
}

Doing the same with dnsjava it would look like this:

static Comparator MX_SORTER = new Comparator()
{
    public int compare( Object r1, Object r2 )
    {
        return ((MXRecord)r1).getPriority() - ((MXRecord)r2).getPriority();
    }
};

/**
 * Returns the MX records for domain sorted by preference or
 * null if no MX records where found.
 *
 * @param domain The domain name.
 *
 * @return The MX records for domain.
 */
public static List getMX( String domain )
{
    try
    {
        Record[] records = new Lookup( domain, Type.MX ).run();
        if( null == records )
            return null;
        
        Arrays.sort( records, MX_SORTER );
        List mx = new ArrayList( records.length );
        
        for( Record record : records )
        {
            String host = ((MXRecord)record).getTarget().toString();
            int len = host.length();
            if( '.' == host.charAt( len - 1 ) )
                host = host.substring( 0, len - 1 );
            mx.add( host );
        }        
        
        return mx;
    }
    catch( TextParseException e )
    {
        throw new RuntimeException( e );
    }
}

I'm praying that dnsjava outpeforms JNDI. I'm off to perform some micro benchmarks. I'll let you know how it went.

Sunday, February 05, 2006

Blogger can't Spell blog

Before publishing my previous post I ran it through blogger's spell checker. It complained that blog is a misspelled word for which it has no suggestions. Of all the words that blogger's dictionary doesn't know about I certainly didn't expect blog to be one of them!

(Code) Writer's Block

I am supposed to be redoing a project I completed almost 3 years ago but I've caught (code) writers block. This is first time it's been this bad. I mean I'm really, really, really, mentally stuck. So far I've completed 99% of my data storage design and I've got a few notes (to self) and requirements jotted down in a text file. I've written only one class. I managed to squeeze the one class out as a matter of trying to break my writers block by writing something, anything! It wouldn't be so bad if the one class was one of the central classes of the system but it isn't. It's not even close. It's a throw-away class.

The way things normally work is once I have a general idea what a program is supposed to do my brain will formulate an outline. I then code the outline, test, and refine. Sometimes I rip all the current code out and start from scratch with an entirely new design based upon whatever knowledge I gleaned from the old design(s). The point is, once I know what the program is supposed to do, some other part of my brain take over and I find it easy to write code. Today that hasn't happened. I know what the program is supposed to do. As a matter of fact, I've known what the program is supposed to do for years, yet I'm stuck like a woolly mammoth in a tar pit. But I'm not giving up.

I know what you are thinking. "Why doesn't he just use the old code as the basis for the new code"? I can't. Most of the old code got destroyed due to human error and shoddy back up media. What I have been able to salvage, other than the UI, is useless to me because I'm going in an entirely new direction this time around.

Since I can't figure out how to write the meat of the program I'm going to try doing the UI. The problem there is I'm graphically challenged. For some reason I'm incapable of conceptualizing graphical user interfaces. Color, positioning, icons, etc ... forget about it! My brain cannot construct a mental image of what a GUI is supposed to look like. A few weeks ago I was browsing the Ubuntu Linux website when I came across this requirement specification document. Throughout the spec the author had images of hand drawn pictures of what the UI would look like. My jaw dropped. I was absolutely impressed and green with envy. Not because the drawings were drop dead gorgeous or anything like that but because he is able to manifest his thoughts in pictures. It's easier to discuss something that is graphically represented than something that is not.

The good news is most of the screens already exists. The original project netted 4 screens. This version will net 7. In the original version I had very little to do with the design of the screens other than to approve them. In this version I'm hell bent on overcoming my GUI handicap by tweaking the original 4 and doing, from scratch, the remaining 3. So dear reader, if know of any good places for the graphically challenged to learn design with XHTML/CSS, please leave me a comment. In the meantime I'm going to cruise over to A List Apart and see if I can't learn something. I'll keep you posted on my progress. After all, this is a blog about my design decisions.