Bladeren bron

StringUtils#uncamelize - performance
StringUtils - remove Locale parameters

Ranides Atterwim 10 jaren geleden
bovenliggende
commit
c88d44a17b

+ 100 - 32
assira/src/main/java/net/ranides/assira/text/StringUtils.java

@@ -10,6 +10,7 @@ package net.ranides.assira.text;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
+import java.net.URLEncoder;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -285,6 +286,18 @@ public final class StringUtils {
 			throw new AssertionError("Unsupported charset: " + charset, cause);
         }
     }
+    
+    public static String encodeURL(String input) {
+        return encodeURL(input, Charsets.UTF8);
+    }
+    
+    public static String encodeURL(String input, Charset charset) {
+        try {
+            return URLEncoder.encode(input, charset.name());
+        } catch(UnsupportedEncodingException cause) {
+			throw new AssertionError("Unsupported charset: " + charset, cause);
+        }
+    }
 	
     public static String remove(String text, String fragment) {
 		if(fragment.isEmpty()) {
@@ -450,50 +463,38 @@ public final class StringUtils {
 	}
 	
 	public static String capitalize(String value) {
-		return capitalize(value, Locale.ROOT);
-	}
-	
-	public static String capitalize(String value, Locale locale) {
         if( value.isEmpty() ) {
             return "";
         }
         if( 1==value.length() ) {
-            return value.toUpperCase(locale);
+            return value.toUpperCase(Locale.ROOT);
         }
         return new StringBuilder(value.length())
-            .append( value.substring(0, 1).toUpperCase(locale) )
+            .append( value.substring(0, 1).toUpperCase(Locale.ROOT) )
             .append(value.substring(1))
             .toString();
     }
 	
-	public static String uncapitalize(String value) {
-		return uncapitalize(value, Locale.ROOT);
-	}
-
-    public static String uncapitalize(String value, Locale locale) {
+    public static String uncapitalize(String value) {
         if( value.isEmpty() ) {
             return "";
         }
         if( 1==value.length() ) {
-            return value.toLowerCase(locale);
+            return value.toLowerCase(Locale.ROOT);
         }
         return new StringBuilder(value.length())
-            .append( value.substring(0, 1).toLowerCase(locale) )
+            .append( value.substring(0, 1).toLowerCase(Locale.ROOT) )
             .append(value.substring(1))
             .toString();
     }
 	
-	public static String camelize(String value) {
-		return camelize(value, Locale.ROOT);
-	}
-	
-    public static String camelize(String value, Locale locale) {
+    public static String camelize(String value) {
         String[] parts = value.split("_+");
         if(0==parts.length) {
             return "";
         }
         if(1==parts.length) {
-            return parts[0].toLowerCase(locale);
+            return parts[0].toLowerCase(Locale.ROOT);
         }
 
         StringBuilder result = new StringBuilder(value.length()-parts.length);
@@ -501,26 +502,93 @@ public final class StringUtils {
 		for(;i<parts.length; i++) {
 			if(!parts[i].isEmpty()) { break; }
 		}
-		result.append(parts[i++].toLowerCase(locale));
+		result.append(parts[i++].toLowerCase(Locale.ROOT));
 		
 		for(;i<parts.length; i++) {
-			result.append(parts[i].substring(0, 1).toUpperCase(locale));
-			result.append(parts[i].substring(1).toLowerCase(locale));
+			result.append(parts[i].substring(0, 1).toUpperCase(Locale.ROOT));
+			result.append(parts[i].substring(1).toLowerCase(Locale.ROOT));
 		}
         return result.toString();
     }
-	
-	public static String uncamelize(String value) {
-		return uncamelize(value, Locale.ROOT);
-	}
+    
+    public static String uncamelize(String value) {
+        if(value.isEmpty()) {
+            return value;
+        }
+        int max = value.length();
+        char prv = value.charAt(0);
+        boolean inu = Character.isUpperCase(prv);
+        if(inu) {
+            prv = Character.toLowerCase(prv);
+        }
+        
+        CBuilder builder = new CBuilder(2*max, prv);
+        
+        for(int i=1; i<max; i++) {
+            char c = value.charAt(i);
+            if(Character.isUpperCase(c)) {
+                if(inu) {
+                    builder.append();
+                } else {
+                    builder.separate_append();
+                    inu = true;
+                }
+                builder.next(Character.toLowerCase(c));
+            } else {
+                if(inu) {
+                    builder.separate();
+                    inu = false;
+                }
+                builder.append();
+                builder.next(c);
+            }
+            
+        }
+        return builder.end();
+    }
+    
+    static final class CBuilder {
+        
+        private final char[] buffer;
+        private int index;
+        private boolean separated;
+        
+        private char prv;
+        
+        public CBuilder(int size, char first) {
+            this.buffer = new char[size];
+            this.separated = true;
+            this.prv = first;
+        }
+        
+        public void next(char c) {
+            this.prv = c;
+        }
+        
+        public void separate() {
+            if(!separated) {
+                separated = true;
+                buffer[index++] = '_';
+            }
+        }
+        
+        public void separate_append() { //NOPMD name convention
+            buffer[index++] = prv;
+            separate();
+        }
+        
+        public void append() {
+            buffer[index++] = prv;
+            separated = false;
+        }
+
+        public String end() {
+            buffer[index++] = prv;
+            return new String(buffer, 0, index);
+        }
 
-    public static String uncamelize(String value, Locale locale) {
-        return replace(value, Pattern.compile("(\\p{Lu})"), hit -> {
-			String v = hit.group(1).toLowerCase(locale);
-			return (0 == hit.start(1)) ? v : "_"+v;
-        });
     }
-	
+    
 	public static String clip(String text, int size) {
         return text.substring(0, MathUtils.clip(size, 0, text.length()));
     }

+ 243 - 0
assira/src/test/java/net/ranides/assira/text/StringUtilsBenchmark.java

@@ -0,0 +1,243 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.text;
+
+import com.google.caliper.SimpleBenchmark;
+import java.util.Locale;
+import java.util.Random;
+import java.util.regex.Pattern;
+import net.ranides.assira.junit.BenchmarkRunner;
+
+/**
+ * Benchmark for StringUtils.
+ * 
+ *  CC: CBuilder + count    = 100
+ *  2C: CBuilder            =  61
+ *  RE: regex               = 870
+ *  2B: StringBuilder       = 103
+ * 
+ * StringBuilder is slower because it checks (on every append) if reallocation is needed
+ * 
+ * @todo (assira #1) StrBuffer (faster StrBuilder)
+ *      fixed capacity
+ *      no null checks
+ *      no nested configuration
+ *      no chaining ?
+ * 
+ */
+public class StringUtilsBenchmark extends SimpleBenchmark {
+    
+    private String[] text(int count, int size) {
+        Random rand = new Random(777);
+        String[] array = new String[count];
+        for(int i=0; i<count; i++) {
+            char str[] = new char[size];
+            for(int j=0; j<size; j++) {
+                char c = (char)('a' + rand.nextInt(20));
+                if(rand.nextInt(100) < 25) {
+                    c = Character.toUpperCase(c);
+                }
+                str[j] = c;
+            }
+            array[i] = new String(str);
+        }
+        return array;
+    }
+   
+    public int timeUncamelizeCC(int reps) {
+        String[] array = text(100, 100);
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            for(String s : array) {
+                ret += uncamelizeCC(s).charAt(99);
+            }
+        }
+        return ret;
+    }
+    
+    public int timeUncamelize2C(int reps) {
+        String[] array = text(100, 100);
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            for(String s : array) {
+                ret += uncamelize2C(s).charAt(99);
+            }
+        }
+        return ret;
+    }
+    
+    public int timeUncamelizeRE(int reps) {
+        String[] array = text(100, 100);
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            for(String s : array) {
+                ret += uncamelizeRE(s).charAt(99);
+            }
+        }
+        return ret;
+    }
+    
+    public int timeUncamelize2B(int reps) {
+        String[] array = text(100, 100);
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            for(String s : array) {
+                ret += uncamelize2B(s).charAt(99);
+            }
+        }
+        return ret;
+    }
+    
+    // count + CBuilder
+    static String uncamelizeCC(String value) {
+        int u = countUpper(value);
+        if(u==0) {
+            return value;
+        }
+        
+        int max = value.length();
+        char prv = value.charAt(0);
+        boolean inu = Character.isUpperCase(prv);
+        if(inu) {
+            prv = Character.toLowerCase(prv);
+        }
+        
+        StringUtils.CBuilder builder = new StringUtils.CBuilder(max+u, prv);
+        
+        for(int i=1; i<max; i++) {
+            char c = value.charAt(i);
+            if(Character.isUpperCase(c)) {
+                if(inu) {
+                    builder.append();
+                } else {
+                    builder.separate_append();
+                    inu = true;
+                }
+                builder.next(Character.toLowerCase(c));
+            } else {
+                if(inu) {
+                    builder.separate();
+                    inu = false;
+                }
+                builder.append();
+                builder.next(c);
+            }
+            
+        }
+        return builder.end();
+    }
+       
+    // CBuilder
+    static String uncamelize2C(String value) {
+        if(value.isEmpty()) {
+            return value;
+        }
+        int max = value.length();
+        char prv = value.charAt(0);
+        boolean inu = Character.isUpperCase(prv);
+        if(inu) {
+            prv = Character.toLowerCase(prv);
+        }
+        
+        StringUtils.CBuilder builder = new StringUtils.CBuilder(2*max, prv);
+        
+        for(int i=1; i<max; i++) {
+            char c = value.charAt(i);
+            if(Character.isUpperCase(c)) {
+                if(inu) {
+                    builder.append();
+                } else {
+                    builder.separate_append();
+                    inu = true;
+                }
+                builder.next(Character.toLowerCase(c));
+            } else {
+                if(inu) {
+                    builder.separate();
+                    inu = false;
+                }
+                builder.append();
+                builder.next(c);
+            }
+            
+        }
+        return builder.end();
+    }
+    
+    // regex
+    static String uncamelizeRE(String value) {
+        return StringUtils.replace(value, Pattern.compile("(\\p{Lu}*)(\\p{Lu})"), hit -> {
+            String m1 = hit.group(1).toLowerCase(Locale.ROOT);
+            String m2 = hit.group(2).toLowerCase(Locale.ROOT);
+            String p1 = (0 == hit.start(2)) ? "" : "_";
+            String p2 = m1.isEmpty() || hit.end(2)==value.length() ? "" : "_";
+            return p1 + m1 + p2 + m2;
+        });
+    }
+    
+    // count + StringBuilder
+    static String uncamelize2B(String value) {
+        if(value.isEmpty()) {
+            return value;
+        }
+        
+        int max = value.length();
+        char prv = value.charAt(0);
+        boolean sep = false;
+        boolean inu = Character.isUpperCase(prv);
+        if(inu) {
+            prv = Character.toLowerCase(prv);
+        }
+        
+        StringBuilder builder = new StringBuilder(2*max);
+        
+        for(int i=1; i<max; i++) {
+            char c = value.charAt(i);
+            if(Character.isUpperCase(c)) {
+                if(inu) {
+                    builder.append(prv);
+                    sep = true;
+                } else {
+                    builder.append(prv);
+                    if(sep) {
+                        builder.append('_');
+                        sep = false;
+                    }
+                    inu = true;
+                }
+                prv = Character.toLowerCase(c);
+            } else {
+                if(inu) {
+                    if(sep) {
+                        builder.append('_');
+                    }
+                    inu = false;
+                }
+                builder.append(prv);
+                prv = c;
+                sep = true;
+            }
+            
+        }
+        return builder.append(prv).toString();
+    }
+    
+    static int countUpper(String value) {
+        int r = 0;
+        for(int i=0, n=value.length(); i<n; i++) {
+            if(Character.isUpperCase(value.charAt(i))) {
+                r++;
+            }
+        }
+        return r;
+    }
+
+    public static void main(String[] args) {
+        BenchmarkRunner.run(StringUtilsBenchmark.class);
+    }
+    
+}

+ 34 - 0
assira/src/test/java/net/ranides/assira/text/StringUtilsTest.java

@@ -145,9 +145,13 @@ public class StringUtilsTest {
 	
 	@Test
 	public void testReplaceChar() {
+        assertEquals("", StringUtils.replace("", '#', '$'));
+        
 		assertEquals("A=$1 B=$2 C=$3", StringUtils.replace("A=#1 B=#2 C=#3", '#', '$'));
 		
 		assertEquals("A=1 B=2 C=3", StringUtils.replace("A=#1 B=#2 C=#3", '#', ""));
+        
+        assertEquals("A=#1 B=#2 C=#3", StringUtils.replace("A=#1 B=#2 C=#3", '?', ""));
 		
 		assertEquals("A=d1 B=d2 C=d3", StringUtils.replace("A=#1 B=#2 C=#3", '#', "d"));
 		
@@ -223,16 +227,46 @@ public class StringUtilsTest {
 		assertEquals("hELLo", StringUtils.uncapitalize("HELLo"));
 		assertEquals("hello", StringUtils.uncapitalize("hello"));
 	}
+    
+    @Test
+    public void testEncodeURL() {
+        assertEquals("hello%3Aworld%3Fparam%3D5", StringUtils.encodeURL("hello:world?param=5"));
+    }
+    
+    @Test
+    public void testDecodeURL() {
+        assertEquals("hello:world?param=5", StringUtils.decodeURL("hello%3Aworld%3Fparam%3D5"));
+    }
 	
 	@Test
 	public void testCamelize() {
 		assertEquals("", StringUtils.camelize(""));
+        assertEquals("", StringUtils.camelize("_"));
+        assertEquals("", StringUtils.camelize("___"));
+        
 		assertEquals("do", StringUtils.camelize("do"));
 		assertEquals("doOpen", StringUtils.camelize("do_open"));
 		assertEquals("doOpenFile", StringUtils.camelize("do_open_file"));
 		
 		assertEquals("doOpenFile", StringUtils.camelize("do_open_file_"));
 		assertEquals("doOpenFile", StringUtils.camelize("_do_open_file_"));
+        assertEquals("doOpenFile", StringUtils.camelize("_do__open___file_"));
 	}
+    
+    @Test
+	public void testUncamelize() {
+        assertEquals("", StringUtils.uncamelize(""));
+        assertEquals("load", StringUtils.uncamelize("load"));
+        assertEquals("load", StringUtils.uncamelize("Load"));
+        
+        assertEquals("load_file", StringUtils.uncamelize("loadFile"));
+        assertEquals("load_file_name", StringUtils.uncamelize("loadFileName"));
+        assertEquals("load_file_name", StringUtils.uncamelize("LoadFileName"));
+
+        assertEquals("open_usb", StringUtils.uncamelize("openUSB"));
+        assertEquals("open_usb_port", StringUtils.uncamelize("openUSBPort"));
+        assertEquals("open_usb_port_name", StringUtils.uncamelize("openUSBPortName"));
+    }
+    
 	
 }