Add support for more structured GWT versions.

Patch by: jat
Review by: rjrjr


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6618 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/About.java b/dev/core/src/com/google/gwt/dev/About.java
index 6b4f6f5..0359c3d 100644
--- a/dev/core/src/com/google/gwt/dev/About.java
+++ b/dev/core/src/com/google/gwt/dev/About.java
@@ -46,16 +46,27 @@
   public static String GWT_VERSION;
 
   /**
-   * @deprecated use {@link #getGwtVersionArray()} or
+   * @deprecated use {@link #getGwtVersionObject()} or
    *             {@link #getGwtVersionNum()} instead.
    */
   @Deprecated
   public static String GWT_VERSION_NUM;
 
+  /**
+   * Tag used for text replacement of the SVN version (split up to avoid
+   * replacing it here).
+   */ 
+  private static final String GWT_SVNREV_TAG = "@GWT_" + "SVNREV@";
+
+  /**
+   * Tag used for text replacement of the GWT version (split up to avoid
+   * replacing it here).
+   */ 
+  private static final String GWT_VERSION_TAG = "@GWT_" + "VERSION@";
+
   private static final String gwtName = "Google Web Toolkit";
   private static final String gwtSvnRev;
-  private static int[] gwtVersionArray = null;
-  private static final String gwtVersionNum;
+  private static final GwtVersion gwtVersion;
 
   static {
     Properties props = new Properties();
@@ -68,19 +79,19 @@
 
     String tmp;
     tmp = props.getProperty("gwt.svnrev");
-    // Check for null or sentinel value (break up to avoid text replace)
-    if (tmp == null || tmp.equals("@GWT_" + "SVNREV@")) {
+    // Check for null or sentinel value
+    if (tmp == null || tmp.equals(GWT_SVNREV_TAG)) {
       gwtSvnRev = "unknown";
     } else {
       gwtSvnRev = tmp;
     }
 
     tmp = props.getProperty("gwt.version");
-    // Check for null or sentinel value (break up to avoid text replace)
-    if (tmp == null || tmp.equals("@GWT_" + "VERSION@")) {
-      gwtVersionNum = "0.0.0";
+    // Check for null or sentinel value
+    if (tmp == null || tmp.equals(GWT_VERSION_TAG)) {
+      gwtVersion = new GwtVersion();
     } else {
-      gwtVersionNum = tmp;
+      gwtVersion = new GwtVersion(tmp);
     }
 
     // Initialize deprecated constants
@@ -121,10 +132,7 @@
    *         determined at build time.
    */
   public static int[] getGwtVersionArray() {
-    if (gwtVersionArray == null) {
-      gwtVersionArray = parseGwtVersionString(getGwtVersionNum());
-    }
-    return gwtVersionArray;
+    return gwtVersion.getComponents();
   }
 
   /**
@@ -134,61 +142,18 @@
    *         determined at build time.
    */
   public static String getGwtVersionNum() {
-    return gwtVersionNum;
+    return gwtVersion.toString();
   }
 
   /**
-   * Takes a string formatted as 3 numbers separated by periods and returns an 3
-   * element array. Non-numeric prefixes and suffixes are stripped.
+   * The Google Web Toolkit release number.
    * 
-   * @param versionString A string formatted as 3 numbers.
-   * @return a 3 element array of the parsed string
-   * @throws NumberFormatException if the string is malformed
+   * @return the release number or a version equivalent to "0.0.0" if the value
+   *     couldn't be determined at build time.
    */
-  public static int[] parseGwtVersionString(String versionString)
-      throws NumberFormatException {
-    int[] version = {0, 0, 0};
-    if (versionString == null) {
-      return version;
-    }
-    int len = versionString.length();
-    int index = 0;
-    // Skip leading characters that are not digits to support a
-    // non-numeric prefix on a version string.
-    for (; index < len; ++index) {
-      if (Character.isDigit(versionString.charAt(index))) {
-        break;
-      }
-    }
-    int part = 0;
-    int v = 0;
-    for (; index < len; ++index) {
-      char ch = versionString.charAt(index);
-      if (ch == '.') {
-        if (part >= version.length) {
-          throw new NumberFormatException("Too many period chracters");
-        }
-        version[part++] = v;
-        v = 0;
-      } else if (Character.isDigit(ch)) {
-        int digit = Character.digit(ch, 10);
-        if (digit < 0) {
-          throw new NumberFormatException("Negative number encountered");
-        }
-        v = v * 10 + digit;
-      } else {
-        // end the parse to support a non-numeric suffix
-        break;
-      }
-    }
-    if (part >= version.length) {
-      throw new NumberFormatException("Too many digits in string. Expected 3");
-    }
-    version[part++] = v;
-    if (part != version.length) {
-      throw new NumberFormatException("Expected 3 elements in array");
-    }
-    return version;
+  public static GwtVersion getGwtVersionObject() {
+    // This is public because CheckForUpdates and WebAppCreator need access.
+    return gwtVersion;
   }
 
   private About() {
diff --git a/dev/core/src/com/google/gwt/dev/GwtVersion.java b/dev/core/src/com/google/gwt/dev/GwtVersion.java
new file mode 100644
index 0000000..246ee49
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/GwtVersion.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import java.util.Arrays;
+
+/**
+ * Represents a GWT version.
+ */
+public final class GwtVersion implements Comparable<GwtVersion> {
+
+  private static final int NO_NAG = 999;
+  private static final String DEFAULT_NO_NAG_VERSION = "0.0." + NO_NAG;
+
+  private static final int COMPONENT_COUNT = 3;
+
+  /**
+   *  Array of 3 integers.
+   */
+  private final int[] components = new int[COMPONENT_COUNT];
+
+  /**
+   * The suffix of the release, such as -ms1, -rc2, or random garbage.
+   */
+  private final String suffix;
+  
+  /**
+   * Create a version that avoids any nagging -- "0.0.999".
+   */
+  public GwtVersion() {
+    this(DEFAULT_NO_NAG_VERSION);
+  }
+
+  /**
+   * Parse a version number as a string. An empty or null string are
+   * explicitly allowed and are equivalent to "0.0.0".
+   * 
+   * <p>Acceptable format:
+   * <ul>
+   * <li>prefix before first digit is ignored
+   * <li>one or more digits or strings separated by a period
+   * <li>optional release number suffix, such as -ms1, -rc3, etc.
+   * <li>stops parsing at first space or dash
+   * </ul>
+   * 
+   * <p>The returned version always contains at least 3 components (padding with
+   * "0" to 3 components) followed by a release number (which is always last).
+   * 
+   * @param versionString GWT version in string form, ex: "2.1.0-rc2"
+   * @throws NumberFormatException
+   */
+  public GwtVersion(String versionString) throws NumberFormatException {
+    suffix = parse(versionString);
+  }
+
+  public int compareTo(GwtVersion other) {
+    for (int i = 0; i < COMPONENT_COUNT; ++i) {
+      int c = components[i] - other.components[i];
+      if (c != 0) {
+        return c;
+      }
+    }
+    return compareSuffixes(suffix, other.suffix);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GwtVersion)) {
+      return false;
+    }
+    GwtVersion other = (GwtVersion) o;
+    if (!Arrays.equals(components, other.components)) {
+      return false;
+    }
+    return compareSuffixes(suffix, other.suffix) == 0;
+  }
+
+  /**
+   * @return a copy of the array of version components, always exactly length 3.
+   */
+  public int[] getComponents() {
+    return Arrays.copyOf(components, COMPONENT_COUNT);
+  }
+  
+  /**
+   * @return the suffix of this version.  Null indicates no suffix and that this
+   * is a released version.
+   */
+  public String getSuffix() {
+    return suffix;
+  }
+
+  @Override
+  public int hashCode() {
+    // all non-null suffixes are treated identically
+    return Arrays.hashCode(components) * 2 + (suffix == null ? 0 : 1);
+  }
+
+  /**
+   * @return true if this version is a special no-nag version (where the user
+   * isn't notified that a newer version is available).  This is defined as any
+   * version number with 999 in the third component.
+   */
+  public boolean isNoNagVersion() {
+    return components[2] == NO_NAG;
+  }
+  
+  @Override
+  public String toString() {
+    StringBuilder buf = new StringBuilder();
+    String prefix = "";
+    for (int i = 0; i < COMPONENT_COUNT; ++i) {
+      buf.append(prefix).append(components[i]);
+      prefix = ".";
+    }
+    if (suffix != null) {
+      buf.append(suffix);
+    }
+    return buf.toString();
+  }
+
+  /**
+   * Compare two version number suffixes.  A null suffix is considered a
+   * released version and comes after any with a suffix, and all non-null
+   * suffixes are considered equal.
+   *  
+   * @param suffix1
+   * @param suffix2
+   * @return negative if suffix1 < suffix2, positive if suffix2 > suffix1,
+   *     or 0 if they are considered equal
+   */
+  private int compareSuffixes(String suffix1, String suffix2) {
+    if (suffix1 == null) {
+      return suffix2 == null ? 0 : 1;
+    }
+    if (suffix2 == null) {
+      return -1;
+    }
+    return 0;
+  }
+
+  /**
+   * Parse a string containing a GwtVersion.
+   * 
+   * <p>Acceptable format:
+   * <ul>
+   * <li>prefix before first digit is ignored
+   * <li>one or more digits or strings separated by a period (at most 3 sets of
+   * digits)
+   * <li>optional release number suffix, such as -ms1, -rc3, etc.
+   * </ul>
+   * 
+   * <p>The returned version always contains at least 3 components (padding with
+   * "0" to 3 components) followed by a release number (which is always last).
+   * 
+   * @param versionString GWT version in string form, ex: "2.1.0-rc2"
+   * @return the trailing suffix, or null if none
+   */
+  private String parse(String versionString) {
+    components[0] = components[1] = components[2] = 0;
+    int len = versionString == null ? 0 : versionString.length();
+    // Skip leading characters that are not digits to support a
+    // non-numeric prefix on a version string.
+    int index = 0;
+    for (; index < len; ++index) {
+      if (Character.isDigit(versionString.charAt(index))) {
+        break;
+      }
+    }
+    for (int component = 0; component < COMPONENT_COUNT; ++component) {
+      int componentStart = index;
+      while (index < len && Character.isDigit(versionString.charAt(index))) {
+        ++index;
+      }
+      if (index > componentStart) {
+        components[component] = Integer.parseInt(versionString.substring(
+            componentStart, index));
+      }
+      if (index >= len || versionString.charAt(index) != '.') {
+        break;
+      }
+      ++index;
+    }
+    return index < len ? versionString.substring(index) : null;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java b/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
index ac9bf00..51fe903 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
 import com.google.gwt.dev.About;
+import com.google.gwt.dev.GwtVersion;
 import com.google.gwt.dev.shell.ie.CheckForUpdatesIE6;
 
 import org.w3c.dom.Document;
@@ -57,85 +58,6 @@
 public class CheckForUpdates {
 
   /**
-   * Represents a GWT version.
-   */
-  public static class GwtVersion implements Comparable<GwtVersion> {
-
-    /**
-     *  Always a 3 element array. 
-     */
-    private final int[] version;
-
-    /**
-     * Create a version that avoids any nagging -- "0.0.999".
-     */
-    public GwtVersion() {
-      version = new int[3];
-      version[2] = 999;
-    }
-
-    /**
-     * Parse a version number as a string. An empty or null string are
-     * explicitly allowed and are equivalent to "0.0.0".
-     * 
-     * @param versionString
-     * @throws NumberFormatException
-     */
-    public GwtVersion(String versionString) throws NumberFormatException {
-     version = About.parseGwtVersionString(versionString);
-    }
-
-    public int compareTo(GwtVersion o) {
-      for (int i = 0; i <= 2; ++i) {
-        if (version[i] != o.version[i]) {
-          return version[i] - o.version[i];
-        }
-      }
-      return 0;
-    }
-    
-    @Override
-    public boolean equals(Object o) {
-      if (!(o instanceof GwtVersion)) {
-        return false;
-      }
-      GwtVersion other = (GwtVersion) o;
-      for (int i = 0; i <= 2; ++i) {
-        if (version[i] != other.version[i]) {
-          return false;
-        }
-      }
-      return true;
-    }
-    
-    public int getPart(int part) {
-      // TODO: something besides IORE here?
-      return version[part];
-    }
-
-    @Override
-    public int hashCode() {
-      return (version[0] * 31 + version[1]) * 31 + version[2];
-    }
-
-    public boolean isNoNagVersion() {
-      return version[2] == 999;
-    }
-
-    public boolean isUnspecified() {
-      return version[0] == 0 && version[1] == 0 && version[2] == 0;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder buf = new StringBuilder();
-      buf.append(version[0]).append('.').append(version[1]).append('.');
-      buf.append(version[2]);
-      return buf.toString();
-    }
-  }
-
-  /**
    * Returns the result of an update check.
    */
   public interface UpdateResult {
@@ -312,12 +234,7 @@
   public CheckForUpdates(TreeLogger logger, String entryPoint) {
     this.logger = logger;
     this.entryPoint = entryPoint;
-    try {
-      myVersion = new GwtVersion(About.getGwtVersionNum());
-    } catch (NumberFormatException e) {
-      // if our build version number is bogus, use one that avoids nagging
-      myVersion = new GwtVersion();
-    }
+    myVersion = About.getGwtVersionObject();
   }
 
   /**
@@ -401,7 +318,7 @@
 
       // See if new version is available.
       //
-      String url = queryURL + "?v=" + About.getGwtVersionNum() + "&id="
+      String url = queryURL + "?v=" + myVersion.toString() + "&id="
           + firstLaunch + "&r=" + About.getGwtSvnRev();
       if (entryPoint != null) {
         url += "&e=" + entryPoint;
diff --git a/dev/core/test/com/google/gwt/dev/AboutTest.java b/dev/core/test/com/google/gwt/dev/AboutTest.java
index 98c8d6c..6e1faa2 100644
--- a/dev/core/test/com/google/gwt/dev/AboutTest.java
+++ b/dev/core/test/com/google/gwt/dev/AboutTest.java
@@ -23,7 +23,7 @@
 public class AboutTest extends TestCase {
 
   @SuppressWarnings("deprecation")
-  public void testDepreciatedConstants() {
+  public void testDeprecatedConstants() {
     assertEquals("GWT_NAME", About.getGwtName(), About.GWT_NAME); 
     assertEquals("GWT_VERSION", About.getGwtVersion(), About.GWT_VERSION);
     assertEquals("GWT_VERSION_NUM", About.getGwtVersionNum(), About.GWT_VERSION_NUM);
@@ -44,81 +44,17 @@
     String result = About.getGwtVersion();
     assertFalse(result.length() == 0);
     String compare = About.getGwtName() + " " + About.getGwtVersionNum();
-    
+    assertEquals(compare, result);
   }
   
   public void testGwtVersionNum() {
     String result = About.getGwtVersionNum();
     assertFalse(result.length() == 0);
   }
-  
-  public void testParseGwtVersionString() {
-    int[] result;
-    result = About.parseGwtVersionString("0.0.0");
-    assertEquals("result.length", 3, result.length);
-    assertEquals("0.0.0 - 0", 0, result[0]);
-    assertEquals("0.0.0 - 1", 0, result[1]);
-    assertEquals("0.0.0 - 2", 0, result[2]);
-    
-    result = About.parseGwtVersionString(null);
-    assertEquals("result.length", 3, result.length);
-    assertEquals("null - 0", 0, result[0]);
-    assertEquals("null - 1", 0, result[1]);
-    assertEquals("null - 2", 0, result[2]);    
-    
-    result = About.parseGwtVersionString("1.5.4");
-    assertEquals("result.length", 3, result.length);
-    assertEquals("1.5.4 - 0", 1, result[0]);
-    assertEquals("1.5.4 - 1", 5, result[1]);
-    assertEquals("1.5.4 - 2", 4, result[2]);
-    
-    result = About.parseGwtVersionString("prefix1.5.4");
-    assertEquals("prefix1.5.4 - 0", 1, result[0]);
-    assertEquals("prefix1.5.4 - 1", 5, result[1]);
-    assertEquals("prefix1.5.4 - 2", 4, result[2]);
-    
-    result = About.parseGwtVersionString("1.5.4-suffix0");
-    assertEquals("result.length", 3, result.length);
-    assertEquals("1.5.4-suffix0 - 0", 1, result[0]);
-    assertEquals("1.5.4-suffix0 - 1", 5, result[1]);
-    assertEquals("1.5.4-suffix0 - 2", 4, result[2]);
-    
-    result = About.parseGwtVersionString("1.5.4-patch0");
-    assertEquals("result.length", 3, result.length);
-    assertEquals("1.5.4-patch0 - 0", 1, result[0]);
-    assertEquals("1.5.4-patch0 - 1", 5, result[1]);
-    assertEquals("1.5.4-patch0 - 2", 4, result[2]);
 
-    try {
-      result = About.parseGwtVersionString("1.5.4.2");
-      fail("Should have thrown exception parsing 1.5.4.2. Got "
-          + result[0] +", " + result[1] + ", " + result[2]);
-    } catch (NumberFormatException ex) {
-      // OK
-    }        
-    
-    try {
-      result = About.parseGwtVersionString("1.5baloney");
-      fail("Should have thrown exception parsing 1.5baloney Got "
-          + result[0] +", " + result[1] + ", " + result[2]);
-    } catch (NumberFormatException ex) {
-      // OK
-    }            
-    
-    try {
-      result = About.parseGwtVersionString("1");
-      fail("Should have thrown exception parsing 1 Got "
-          + result[0] +", " + result[1] + ", " + result[2]);
-    } catch (NumberFormatException ex) {
-      // OK
-    }
-    
-    try {
-      result = About.parseGwtVersionString("1.5");
-      fail("Should have thrown exception parsing 1.5 Got "
-          + result[0] +", " + result[1] + ", " + result[2]);
-    } catch (NumberFormatException ex) {
-      // OK
-    }
+  public void testGwtVersionObject() {
+    GwtVersion version = About.getGwtVersionObject();
+    assertNotNull(version);
+    assertFalse(version.toString().length() == 0);
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/GwtVersionTest.java b/dev/core/test/com/google/gwt/dev/GwtVersionTest.java
new file mode 100644
index 0000000..a0545a8
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GwtVersionTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests GwtVersion.
+ */
+public class GwtVersionTest extends TestCase {
+
+  /**
+   * Check for compatibility between compareTo, equals, and hashCode.
+   */
+  public void testCompareEqualsHashCode() {
+    checkCompareEqualsHashCode("0.0.0", "0.0.0");
+    checkCompareEqualsHashCode("0.0.0", "0");
+    checkCompareEqualsHashCode("1.2.3", "001.002.003");
+    checkCompareEqualsHashCode("1.2.3", "001.002.004");
+    checkCompareEqualsHashCode("1.2.4", "001.002.003");
+    checkCompareEqualsHashCode("1.2.4-ms1", "1.2.4-ms1");
+    checkCompareEqualsHashCode("1.2.4-ms2", "1.2.4-ms2");
+    checkCompareEqualsHashCode("1.2.4-ms2", "1.2.4-rc1");
+  }
+  
+  /**
+   * Test that GwtVersion.compareTo produced expected results.
+   */
+  public void testCompareTo() {
+    GwtVersion v1 = new GwtVersion("0.0.0");
+    assertEquals(0, v1.compareTo(v1));
+    GwtVersion v2 = new GwtVersion("0.0.0");
+    assertEquals(0, v1.compareTo(v2));
+    assertEquals(0, v2.compareTo(v1));
+    v2 = new GwtVersion("0.0.0b");
+    assertTrue(v1.compareTo(v2) > 0);
+    assertTrue(v2.compareTo(v1) < 0);
+    v1 = new GwtVersion("0.0.0c");
+    v2 = new GwtVersion("0.0.0b");
+    assertEquals(0, v1.compareTo(v2));
+    assertEquals(0, v2.compareTo(v1));
+    v1 = new GwtVersion("1.9.41");
+    v2 = new GwtVersion("1.11.12");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
+    v1 = new GwtVersion("1.9.41");
+    v2 = new GwtVersion("1.4.12");
+    assertTrue(v1.compareTo(v2) > 0);
+    assertTrue(v2.compareTo(v1) < 0);
+    v1 = new GwtVersion("1.9.41");
+    v2 = new GwtVersion("2.0.0-ms1");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
+    v1 = new GwtVersion("2.0.0-ms2");
+    v2 = new GwtVersion("2.0.0-rc1");
+    assertEquals(0, v1.compareTo(v2));
+    assertEquals(0, v2.compareTo(v1));
+    v1 = new GwtVersion("001.002.099");
+    v2 = new GwtVersion("1.2.99");
+    assertEquals(0, v1.compareTo(v2));
+    assertEquals(0, v2.compareTo(v1));
+  }
+
+  /**
+   * Test that GwtVersion.compareTo produced expected results.
+   */
+  public void testEquals() {
+    GwtVersion v1 = new GwtVersion("0.0.0");
+    assertEquals(v1, v1);
+    GwtVersion v2 = new GwtVersion("0.0.0");
+    assertEquals(v1, v2);
+    assertEquals(v2, v1);
+    v2 = new GwtVersion("");
+    assertEquals(v1, v2);
+    assertEquals(v2, v1);
+    v2 = new GwtVersion("1.2.3");
+    assertFalse(v1.equals(v2));
+    assertFalse(v2.equals(v1));
+  }
+
+  /**
+   * Test that various versions are properly detected as to whether or not they
+   * are "no-nag" versions. 
+   */
+  public void testIsNoNagVersion() {
+    GwtVersion version = new GwtVersion("0.0.0");
+    assertFalse(version.isNoNagVersion());
+    version = new GwtVersion("0.0.999");
+    assertTrue(version.isNoNagVersion());
+    version = new GwtVersion("2.0.999");
+    assertTrue(version.isNoNagVersion());
+    version = new GwtVersion("2.0.999-rc1");
+    assertTrue(version.isNoNagVersion());
+    version = new GwtVersion("2.0.999-ms2");
+    assertTrue(version.isNoNagVersion());
+    version = new GwtVersion("0.999.0");
+    assertFalse(version.isNoNagVersion());
+    version = new GwtVersion("2.999.0-rc1");
+    assertFalse(version.isNoNagVersion());
+  }
+
+  /**
+   * Verify that bogus version numbers don't fail.
+   */
+  public void testParseBad() {
+    checkAllZerosVersion("", null);
+    checkAllZerosVersion("bogus", null); // we skip leading garbage
+    checkAllZerosVersion("0.x.x", "x.x");
+    GwtVersion version = new GwtVersion("1.x.x");
+    int[] components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(0, components[1]);
+    assertEquals(0, components[2]);
+    assertEquals("x.x", version.getSuffix());
+    version = new GwtVersion("1.2.x");
+    components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(0, components[2]);
+    assertEquals("x", version.getSuffix());
+    version = new GwtVersion("1.2.3x");
+    components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(3, components[2]);
+    assertEquals("x", version.getSuffix());
+  }
+
+  /**
+   * Tests parsing various version numbers.
+   */
+  public void testParseBasic() {
+    GwtVersion version = new GwtVersion("1.2.3");
+    int[] components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(3, components[2]);
+    assertNull(version.getSuffix());
+    version = new GwtVersion("1.2.3-ms1");
+    components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(3, components[2]);
+    assertEquals("-ms1", version.getSuffix());
+    version = new GwtVersion("1.2.3-rc2");
+    components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(3, components[2]);
+    assertEquals("-rc2", version.getSuffix());
+    version = new GwtVersion("1.2.3-RC1");
+    components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(1, components[0]);
+    assertEquals(2, components[1]);
+    assertEquals(3, components[2]);
+    assertEquals("-RC1", version.getSuffix());
+  }
+
+  /**
+   * Tests various ways you can get a version number of 0.0.0.
+   */
+  public void testParseZeros() {
+    checkAllZerosVersion("0.0.0", null);
+    checkAllZerosVersion("0.0", null);
+    checkAllZerosVersion("0", null);
+    checkAllZerosVersion("", null);
+    checkAllZerosVersion(null, null);
+    checkAllZerosVersion("foo0.0.0", null);
+    checkAllZerosVersion("foo0.0.0 bar", " bar");
+  }
+ 
+  /**
+   * Test that GwtVersion.toString() returns expected results.
+   */
+  public void testToString() {
+    String versionString = "0.0.0";
+    GwtVersion version = new GwtVersion(versionString);
+    assertEquals(versionString, version.toString());
+    versionString = "0.0.0a";
+    version = new GwtVersion(versionString);
+    assertEquals(versionString, version.toString());
+    versionString = "foo 0.0.0a";
+    version = new GwtVersion(versionString);
+    assertEquals("0.0.0a", version.toString());
+    versionString = "1.2.3";
+    version = new GwtVersion(versionString);
+    assertEquals(versionString, version.toString());
+    versionString = "1.2.3-rc1";
+    version = new GwtVersion(versionString);
+    assertEquals(versionString, version.toString());
+    versionString = "1.2.3-ms2";
+    version = new GwtVersion(versionString);
+    assertEquals(versionString, version.toString());
+  }
+
+  /**
+   * Verify that the version string is treated equivalently to 0.0.0.
+   * 
+   * @param versionString version number in string form
+   * @param expectedSuffix expected suffix of the version
+   */
+  private void checkAllZerosVersion(String versionString,
+      String expectedSuffix) {
+    GwtVersion version = new GwtVersion(versionString);
+    int[] components = version.getComponents();
+    assertEquals(3, components.length);
+    assertEquals(0, components[0]);
+    assertEquals(0, components[1]);
+    assertEquals(0, components[2]);
+    assertEquals(expectedSuffix, version.getSuffix());
+  }
+
+  /**
+   * Check that compareTo, equals, and hashCode are compatible for a pair of
+   * versions.
+   * 
+   * @param v1String string format version number to test
+   * @param v2String string format version number to test
+   */
+  private void checkCompareEqualsHashCode(String v1String, String v2String) {
+    GwtVersion v1 = new GwtVersion(v1String);
+    GwtVersion v2 = new GwtVersion(v2String);
+    int h1 = v1.hashCode();
+    int h2 = v2.hashCode();
+    int c12 = v1.compareTo(v2);
+    int c21 = v2.compareTo(v1);
+    boolean e12 = v1.equals(v2);
+    boolean e21 = v2.equals(v1);
+    assertEquals("equals not symmetric", e12, e21);
+    assertEquals("compareTo not symmetric", c12, -c21);
+    assertEquals("compareTo/equals don't match", e12, c12 == 0);
+    assertEquals("hashCode/equals don't match", e12, h1 == h2);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java b/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java
deleted file mode 100644
index ae605e6..0000000
--- a/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.google.gwt.dev.shell;
-
-import junit.framework.TestCase;
-import com.google.gwt.dev.shell.CheckForUpdates.GwtVersion;
-
-public class CheckForUpdatesTest extends TestCase {
-
-  /*
-   * Test GwtVersion comparisons
-   */
-  public final void testVersionComparison() {
-    GwtVersion v1 = new GwtVersion(null);
-    assertEquals(0, v1.compareTo(v1));
-
-    v1 = new GwtVersion("1.2.3");
-    GwtVersion v2 = new GwtVersion(null);
-    assertTrue(v1.compareTo(v2) > 0);
-    assertTrue(v2.compareTo(v1) < 0);
-
-    v1 = new GwtVersion("1.2.3");
-    v2 = new GwtVersion("2.0.0");
-    assertTrue(v1.compareTo(v2) < 0);
-    assertTrue(v2.compareTo(v1) > 0);
-
-    v1 = new GwtVersion("1.2.99");
-    v2 = new GwtVersion("2.0.0");
-    assertTrue(v1.compareTo(v2) < 0);
-    assertTrue(v2.compareTo(v1) > 0);
-
-    v1 = new GwtVersion("1.2.99");
-    v2 = new GwtVersion("1.3.0");
-    assertTrue(v1.compareTo(v2) < 0);
-    assertTrue(v2.compareTo(v1) > 0);
-
-    v1 = new GwtVersion("001.002.099");
-    v2 = new GwtVersion("1.2.99");
-    assertEquals(0, v1.compareTo(v2));
-
-    // TODO: more tests
-//    assertFalse(CheckForUpdates.isServerVersionNewer("2", "1.2.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2"));
-//
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2.3.4.5"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3.4", "2.3.4"));
-//
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-//      "1000.2000.3000"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("001.002.003", "1.2.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "001.002.003"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "2.2.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("2.2.3", "1.2.3"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.3.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.3.3", "1.2.3"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.4"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.4", "1.2.3"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-//      "1000.2000.4000"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.4000",
-//      "1000.2000.3000"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-//      "1000.2000.3001"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3001",
-//      "1000.2000.3000"));
-//
-//    assertTrue(CheckForUpdates.isServerVersionNewer("0.2.3", "1.1.3"));
-//    assertFalse(CheckForUpdates.isServerVersionNewer("1.1.3", "0.2.3"));
-  }
-}
diff --git a/user/src/com/google/gwt/user/tools/WebAppCreator.java b/user/src/com/google/gwt/user/tools/WebAppCreator.java
index b6f4530..77e7ab2 100644
--- a/user/src/com/google/gwt/user/tools/WebAppCreator.java
+++ b/user/src/com/google/gwt/user/tools/WebAppCreator.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dev.ArgProcessorBase;
 import com.google.gwt.dev.Compiler;
 import com.google.gwt.dev.DevMode;
+import com.google.gwt.dev.GwtVersion;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.user.tools.util.ArgHandlerIgnore;
 import com.google.gwt.user.tools.util.ArgHandlerOverwrite;
@@ -219,9 +220,8 @@
 
     // Public builds generate a DTD reference.
     String gwtModuleDtd = "";
-    int gwtVersion[] = About.getGwtVersionArray();
-    if (gwtVersion[2] == 999
-        && !(gwtVersion[0] == 0 && gwtVersion[1] == 0)) {
+    GwtVersion gwtVersion = About.getGwtVersionObject();
+    if (gwtVersion.isNoNagVersion()) {
       gwtModuleDtd = "\n<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD Google Web Toolkit "
           + About.getGwtVersionNum()
           + "//EN\" \"http://google-web-toolkit.googlecode.com/svn/tags/"