From d2b2600e5e3dc43bf4dbae6b14168b49f959e83f Mon Sep 17 00:00:00 2001
From: zab <zab@mail.i2p>
Date: Mon, 10 Dec 2012 10:07:34 +0000
Subject: [PATCH] VersionComparator w/o object churn, ticket #789 	tests

---
 .../src/net/i2p/util/VersionComparator.java   | 148 +++++++++++-------
 .../net/i2p/util/VersionComparatorSpec.scala  |  46 ++++--
 2 files changed, 120 insertions(+), 74 deletions(-)

diff --git a/core/java/src/net/i2p/util/VersionComparator.java b/core/java/src/net/i2p/util/VersionComparator.java
index ad7c20a092..51e39170cc 100644
--- a/core/java/src/net/i2p/util/VersionComparator.java
+++ b/core/java/src/net/i2p/util/VersionComparator.java
@@ -1,76 +1,104 @@
 package net.i2p.util;
 
 import java.util.Comparator;
-import java.util.StringTokenizer;
 
-/**
- * Compares versions.
- * Characters other than [0-9.-_] are ignored.
- * I2P only uses '.' but Sun Java uses '_' and plugins may use any of '.-_'
- * Moved from TrustedUpdate.java
- * @since 0.7.10
- */
 public class VersionComparator implements Comparator<String> {
-    /** l and r non-null */
+
+    @Override
     public int compare(String l, String r) {
-        // try it the easy way first
+        
         if (l.equals(r))
             return 0;
-        StringTokenizer lTokens = new StringTokenizer(sanitize(l), VALID_SEPARATOR_CHARS);
-        StringTokenizer rTokens = new StringTokenizer(sanitize(r), VALID_SEPARATOR_CHARS);
-
-        while (lTokens.hasMoreTokens() && rTokens.hasMoreTokens()) {
-            String lNumber = lTokens.nextToken();
-            String rNumber = rTokens.nextToken();
-            int diff = longCompare(lNumber, rNumber);
-            if (diff != 0)
-                return diff;
+        
+        final int ll = l.length();
+        final int rl = r.length();
+        int il = 0, ir = 0;
+        int nl = 0, nr = 0;
+        
+        while(true) {
+            
+            // are we at end of strings?
+            if (il >= ll) {
+                if (ir >= rl)
+                    return 0;
+                return -1;
+            } else if (ir >= rl)
+                return 1;
+            
+            long lv = -1;
+            while(lv == -1 && il < ll) {
+                nl = nextSeparator(l, il);
+                lv = parseLong(l,il,nl);
+                il = nl + 1;
+            }
+            
+            long rv = -1;
+            while(rv == -1 && ir < rl) {
+                nr = nextSeparator(r, ir);
+                rv = parseLong(r,ir,nr);
+                ir = nr + 1;
+            }
+            
+            if (lv < rv)
+                return -1;
+            else if (lv > rv)
+                return 1;
+            
         }
-
-        if (lTokens.hasMoreTokens() && !rTokens.hasMoreTokens())
-            return 1;
-        if (rTokens.hasMoreTokens() && !lTokens.hasMoreTokens())
-            return -1;
-        return 0;
     }
-
-    private static final int longCompare(String lop, String rop) {
-        long left, right;
-        try {
-            left = Long.parseLong(lop);
-        } catch (NumberFormatException nfe) {
-            return -1;
-        }
-        try {
-            right = Long.parseLong(rop);
-        } catch (NumberFormatException nfe) {
-            return 1;
+    
+    private static boolean isSeparator(char c) {
+        switch(c) {
+        case '.':
+        case '_':
+        case '-':
+            return true;
+        default :
+            return false;
         }
-        if (left < right)
-            return -1;
-        if (left > right)
-            return 1;
-        return 0;
     }
-
-    private static final String VALID_SEPARATOR_CHARS = ".-_";
-    private static final String VALID_VERSION_CHARS = "0123456789" + VALID_SEPARATOR_CHARS;
-
-    private static final String sanitize(String versionString) {
-        StringBuilder versionStringBuilder = new StringBuilder(versionString);
-
-        for (int i = 0; i < versionStringBuilder.length(); i++) {
-            if (VALID_VERSION_CHARS.indexOf(versionStringBuilder.charAt(i)) == -1) {
-                versionStringBuilder.deleteCharAt(i);
-                i--;
-            }
+    
+    private static boolean isDigit(char c) {
+        return c >= '0' && c <= '9';
+    }
+    
+    private static int getDigit(char c) {
+        return c - '0';
+    }
+    
+    /**
+     * @param s string to process
+     * @param start starting index in the string to process
+     * @return the index of the next separator character, or end of string.
+     */
+    private static int nextSeparator(String s, int start) {
+        while( start < s.length()) {
+            if (isSeparator(s.charAt(start)))
+                return start;
+            start++;
         }
-
-        return versionStringBuilder.toString();
+        return start;
     }
-
-    public static void main(String[] args) {
-        System.out.println("" + (new VersionComparator()).compare(args[0], args[1]));
+    
+    /**
+     * Parses a long, ignoring any non-digit characters.
+     * @param s string to parse from
+     * @param start index in the string to start
+     * @param end index in the string to stop at
+     * @return the parsed value, or -1 if nothing was parsed or there was a problem.
+     */
+    private static long parseLong(String s, int start, int end) {
+        long rv = 0;
+        boolean parsedAny = false;
+        for (int i = start; i < end && rv >= 0; i++) {
+            final char c = s.charAt(i);
+            if (!isDigit(c))
+                continue;
+            parsedAny = true;
+            rv = rv * 10 + getDigit(c);
+        }
+        if (!parsedAny)
+            return -1;
+        return rv;
     }
 }
-
diff --git a/core/java/test/scalatest/net/i2p/util/VersionComparatorSpec.scala b/core/java/test/scalatest/net/i2p/util/VersionComparatorSpec.scala
index 66342861d0..8cc5de312b 100644
--- a/core/java/test/scalatest/net/i2p/util/VersionComparatorSpec.scala
+++ b/core/java/test/scalatest/net/i2p/util/VersionComparatorSpec.scala
@@ -23,50 +23,68 @@ class VersionComparatorSpec extends FunSpec with ShouldMatchers {
     private def same(A : String, B : String) =
       comp("equals", A, B, 0)
     
-    private def invalid(A : String, B : String) = {
-      it("should throw IAE while comparing "+A+" and "+B) {
-          intercept[IllegalArgumentException] {
-              vc.compare(A,B)
-          }
-      }
-    }
-    
     describe("A VersionComparator") {
         same("0.1.2","0.1.2")
+
         less("0.1.2","0.1.3")
         more("0.1.3","0.1.2")
+
         more("0.1.2.3.4", "0.1.2")
         less("0.1.2", "0.1.2.3.4")
+
         more("0.1.3", "0.1.2.3.4")
+	less("0.1.2.3.4", "0.1.3")
+
         same("0.1.2","0-1-2")
+	same("0-1-2","0.1.2")
+
         same("0.1.2","0_1_2")
+	same("0_1_2","0.1.2")
+
         same("0.1.2-foo", "0.1.2-bar")
+	same("0.1.2-bar", "0.1.2-foo")
+
         same("0.1-asdf3","0_1.3fdsa")
+	same("0_1.3fdsa","0.1-asdf3")
 
-        // this should be the same, no? --zab
         less("0.1.2","0.1.2.0") 
+        more("0.1.2.0","0.1.2")
         
         /*********
         I think everything below this line should be invalid --zab.
         *********/
-        same("",".")
-        less("-0.1.2", "-0.1.3") 
+        less("",".")
+        more(".","")
+
+        less("-0.1.2", "-0.1.3")
+        more("-0.1.3", "-0.1.2")
+ 
         more("0..2", "0.1.2") 
         less("0.1.2", "0..2") 
-        same("asdf","fdsa") 
+
+        same("asdf","fdsa")
+        same("fdsa","asdf")
+
         same("---","___")
+        same("___","---")
+
         same("1.2.3","0001.0002.0003")
+        same("0001.0002.0003","1.2.3")
+
         more("as123$Aw4423-234","asdfq45#11--_")
-        
+        less("asdfq45#11--_","as123$Aw4423-234")
+       
         // non-ascii string
         val byteArray = new Array[Byte](10)
         byteArray(5) = 1
         byteArray(6) = 10
         val nonAscii = new String(byteArray)
-        same(nonAscii,"")
+        more(nonAscii,"")
+        less("",nonAscii)
         
         // huge value, can't fit in a long
         val huge = String.valueOf(Long.MaxValue)+"0000.0";
         less(huge,"1.2")
+        more("1.2",huge)
     }
 }
-- 
GitLab