The next challenge is to provide a method that confirms zip codes for the United States. The method needs to accommodate punctuation, a space, or no delimiter at all between the five-digit and four-digit parts of the zip code. It needs to accommodate zip codes that are only five digits long. Suddenly, there's requirements creep: It now needs to validate zip codes for Canada, the United Kingdom, Argentina, Sweden, Japan, and the Netherlands as well.
The first thing I do is search the Web for patterns, starting at http://www.regexlib.com. This returns regular expressions for all of the countries previously mentioned. Next, I take those regular expressions and create entries in the regex.properties file, so I can use the RegexProperties class from Chapter 4.
The point of doing so, of course, is to externalize the expressions themselves and to avoid having to double-delimit special characters. I decide to use intelligent keys for the property keys. That is, I'm anticipating that I'll have access to the country code for each of these regex patterns. Therefore, I can define the property file keys based on that country code. For example, since the country code for Japan is JP, I define the key to the zip code pattern for Japan as zipJP. Listing 5-2 summarizes the entries made to the regex.properties file.
#Japanese postal codes
zipJP=^\d{3}-\d{4}$
#US postal codes
zipUS=^\d{5}\p{Punct}?\s?(?:\d{4})?$
#Dutch postal code
zipNL=^[0-9]{4}\s*[a-zA-Z]{2}$
#Argentinean postal code
zipAR=^\d{3}-\d{4}$
#Swedish postal code
zipSE=^(s-|S-){0,1}[0-9]{3}\s?[0-9]{2}$
#Canadian postal code
zipCA=^([A-Z]\d[A-Z]\s\d[A-Z]\d)$
#UK postal code
zipUK=^[a-zA-Z]{1,2}[0-9][0-9A-Za-z]{0,1} {0,1}[0-9][A-Za-z]{2}$
Finally, I write the code. The algorithm is to look up the appropriate regex for a given country given the appropriate country code, apply the pattern, and return true or false as appropriate. Listing 5-3 shows the code that does this.
01 import java.io.*;
02 import java.util.logging.Logger;
03 import java.util.regex.*;
04 /**
05 *Validates zip codes from the given country.
06 *@author M Habibi
07 */
08 public class MatchZipCodes{
09 private static Logger log = Logger.getAnonymousLogger();
10 private static final String ZIP_PATTERN="zip";
11 private static RegexProperties regexProperties;
12 //load the regex properties file
13 //do this at the class level
14 static
15 {
16 try
17 {
18 regexProperties = new RegexProperties();
19 regexProperties.load("../regex.properties");
20 }
21 catch(Exception e)
22 {
23 e.printStackTrace();
24 }
25 }
26 public static void main(String args[]){
27 String msg = "usage: java MatchZipCodes countryCode Zip";
28 if (args != null && args.length == 2)
29 msg = ""+isZipValid(args[0],args[1]);
30 //output either the usage message, or the results
31 //of running the isZipValid method
32 System.out.println(msg);
33 }
34 /**
35 * Confirms that the format for the given zip code is valid.
36 * @param the <code>String</code> countryCode
37 * @param the <code>String</code> zip
38 * @return <code>boolean</code>
39 *
40 * @author M Habibi
41 */
42 public static boolean isZipValid(String countryCode, String zip)
43 {
44 boolean retval=false;
45 //use the country code to form a unique into the regex
46 //properties file
47 String zipPatternKey = ZIP_PATTERN + countryCode.toUpperCase();
48 //extract the regex pattern for the given country code
49 String zipPattern = regexProperties.getProperty(zipPatternKey);
50 //if there was some sort of problem, don't bother trying
51 //to execute the regex
52 if (zipPattern != null)
53 retval = zip.trim().matches(zipPattern);
54 else
55 {
56 String msg = "regex for country code "+countryCode;
57 msg+= " not found in property file ";
58 log.warning(msg);
59 }
60 //create log report
61 String msg = "regex="+zipPattern +
62 "\nzip="+zip+"\nCountryCode="+
63 countryCode+"\nmatch result="+retval;
64 log.finest(msg);
65 return retval;
66 }
67 }
Outside of the comments and such, the real work in this method is done in three lines. Line 47 forms the proper key based on the country code:
For example, zipPatternKey equals zipUS for the US country code. Next, line 49 extracts the relevant pattern based on that key:
49 String zipPattern = regexProperties.getProperty(zipPatternKey);
Line 53 actually compares the pattern against the key:
53 retval = zip.trim().matches(zipPattern);
The only regex change I made in this example was to make the actual pattern just a little more memory efficient and a little more lenient, as shown in Table 5-4. Specifically, leniency means that the pattern will accept any punctuation, a space, or no delimiter at all between the first five digits and the last four digits of a U.S. zip code. The pattern will also accept five digits as a sufficient U.S. zip code.
Because the regex patterns are externalized, they can be tweaked later to become more accommodating for the various regions. Better yet, more country codes can be added without requiring code changes: Simply add the appropriate entries to the regex.properties file.
The point here is that even using generic regex patterns found online, I still have a very Java-like flavor to the code. It's modular, adaptable, scalable, and clear.