]> source.dussan.org Git - sonarqube.git/commitdiff
Improve KeyValueFormat: support typed parsing
authorsimonbrandhof <simon.brandhof@gmail.com>
Fri, 25 Feb 2011 01:05:24 +0000 (02:05 +0100)
committersimonbrandhof <simon.brandhof@gmail.com>
Fri, 25 Feb 2011 01:05:24 +0000 (02:05 +0100)
sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java
sonar-plugin-api/src/test/java/org/sonar/api/utils/DeprecatedKeyValueFormatTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/utils/KeyValueFormatTest.java

index 787162c228cd97881a15c152a094a9b764cc3f14..cd44e7a9ba75b057ccaa747758ec5aafb0f6259a 100644 (file)
  */
 package org.sonar.api.utils;
 
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Multiset;
+import com.google.common.collect.SortedSetMultimap;
+import com.google.common.collect.TreeMultimap;
 import org.apache.commons.collections.Bag;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.math.NumberUtils;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.rules.RulePriority;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 /**
  * Util class to format key/value data. Output is a string representation ready to be
  * injected into the database
- * 
+ *
  * @since 1.10
  */
-public final class KeyValueFormat {
+public final class KeyValueFormat<K extends Comparable, V extends Comparable> {
+
+  public static final String PAIR_SEPARATOR = ";";
+  public static final String FIELD_SEPARATOR = "=";
+
+  private Converter<K> keyConverter;
+  private Converter<V> valueConverter;
+
+  private KeyValueFormat(Converter<K> keyConverter, Converter<V> valueConverter) {
+    this.keyConverter = keyConverter;
+    this.valueConverter = valueConverter;
+  }
+
+  public static abstract class Converter<TYPE> {
+    abstract String toString(TYPE type);
+
+    abstract TYPE fromString(String s);
+  }
+
+  public static final class StringConverter extends Converter<String> {
+    static final StringConverter INSTANCE = new StringConverter();
+
+    private StringConverter() {
+    }
+
+    @Override
+    String toString(String s) {
+      return s;
+    }
+
+    @Override
+    String fromString(String s) {
+      return s;
+    }
+  }
+
+  public static final class IntegerConverter extends Converter<Integer> {
+    static final IntegerConverter INSTANCE = new IntegerConverter();
+
+    private IntegerConverter() {
+    }
+
+    @Override
+    String toString(Integer s) {
+      return (s == null ? "" : String.valueOf(s));
+    }
+
+    @Override
+    Integer fromString(String s) {
+      return StringUtils.isBlank(s) ? null : NumberUtils.toInt(s);
+    }
+  }
+
+  public static final class SeverityConverter extends Converter<RulePriority> {
+    static final SeverityConverter INSTANCE = new SeverityConverter();
+
+    private SeverityConverter() {
+    }
+
+    @Override
+    String toString(RulePriority s) {
+      return (s == null ? "" : s.toString());
+    }
+
+    @Override
+    RulePriority fromString(String s) {
+      return StringUtils.isBlank(s) ? null : RulePriority.valueOf(s);
+    }
+  }
+
+  public static final class DoubleConverter extends Converter<Double> {
+    static final DoubleConverter INSTANCE = new DoubleConverter();
+
+    private DoubleConverter() {
+    }
+
+    @Override
+    String toString(Double d) {
+      return (d == null ? "" : String.valueOf(d));
+    }
+
+    @Override
+    Double fromString(String s) {
+      return StringUtils.isBlank(s) ? null : NumberUtils.toDouble(s);
+    }
+  }
+
+  public static class DateConverter extends Converter<Date> {
+    private DateFormat dateFormat;
+
+    public DateConverter() {
+      this("yyyy-MM-dd");
+    }
+
+    DateConverter(String format) {
+      this.dateFormat = new SimpleDateFormat(format);
+    }
+
+    @Override
+    String toString(Date d) {
+      return (d == null ? "" : dateFormat.format(d));
+    }
+
+    @Override
+    Date fromString(String s) {
+      try {
+        return StringUtils.isBlank(s) ? null : dateFormat.parse(s);
+      } catch (ParseException e) {
+        throw new SonarException("Not a date: " + s, e);
+      }
+    }
+  }
 
-  private KeyValueFormat() {
+  public static class DateTimeConverter extends DateConverter {
+    public DateTimeConverter() {
+      super("yyyy-MM-dd'T'HH:mm:ssZ");
+    }
   }
 
+  public static <K extends Comparable, V extends Comparable> KeyValueFormat<K, V> create(Converter<K> keyConverter, Converter<V> valueConverter) {
+    return new KeyValueFormat<K, V>(keyConverter, valueConverter);
+  }
+
+  public static KeyValueFormat<String, String> createStringString() {
+    return new KeyValueFormat<String, String>(StringConverter.INSTANCE, StringConverter.INSTANCE);
+  }
+
+  public static KeyValueFormat<String, Date> createStringDate() {
+    return new KeyValueFormat<String, Date>(StringConverter.INSTANCE, new DateConverter());
+  }
+
+  public static KeyValueFormat<String, Date> createStringDateTime() {
+    return new KeyValueFormat<String, Date>(StringConverter.INSTANCE, new DateTimeConverter());
+  }
+
+  public static KeyValueFormat<Integer, String> createIntString() {
+    return new KeyValueFormat<Integer, String>(IntegerConverter.INSTANCE, StringConverter.INSTANCE);
+  }
+
+  public static KeyValueFormat<Integer, Integer> createIntInt() {
+    return new KeyValueFormat<Integer, Integer>(IntegerConverter.INSTANCE, IntegerConverter.INSTANCE);
+  }
+
+  public static KeyValueFormat<Integer, Double> createIntDouble() {
+    return new KeyValueFormat<Integer, Double>(IntegerConverter.INSTANCE, DoubleConverter.INSTANCE);
+  }
+
+  public static KeyValueFormat<Integer, Date> createIntDate() {
+    return new KeyValueFormat<Integer, Date>(IntegerConverter.INSTANCE, new DateConverter());
+  }
+
+  public static KeyValueFormat<Integer, Date> createIntDateTime() {
+    return new KeyValueFormat<Integer, Date>(IntegerConverter.INSTANCE, new DateTimeConverter());
+  }
+
+  public String toString(Map<K, V> map) {
+    return toString(map.entrySet());
+  }
+
+  public String toString(Multimap<K, V> multimap) {
+    return toString(multimap.entries());
+  }
+
+  private String toString(Collection<Map.Entry<K, V>> entries) {
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (Map.Entry<K, V> entry : entries) {
+      if (!first) {
+        sb.append(PAIR_SEPARATOR);
+      }
+      sb.append(keyConverter.toString(entry.getKey()));
+      sb.append(FIELD_SEPARATOR);
+      if (entry.getValue() != null) {
+        sb.append(valueConverter.toString(entry.getValue()));
+      }
+      first = false;
+    }
+    return sb.toString();
+  }
+
+  public SortedMap<K, V> toSortedMap(String data) {
+    SortedMap<K, V> map = new TreeMap<K, V>();
+    if (data != null) {
+      String[] pairs = StringUtils.split(data, PAIR_SEPARATOR);
+      for (String pair : pairs) {
+        String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR);
+        String key = keyValue[0];
+        String value = (keyValue.length == 2 ? keyValue[1] : "");
+        map.put(keyConverter.fromString(key), valueConverter.fromString(value));
+      }
+    }
+    return map;
+  }
+
+  public SortedSetMultimap<K, V> toSortedMultimap(String data) {
+    SortedSetMultimap<K, V> map = TreeMultimap.create();
+    if (data != null) {
+      String[] pairs = StringUtils.split(data, PAIR_SEPARATOR);
+      for (String pair : pairs) {
+        String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR);
+        String key = keyValue[0];
+        String value = (keyValue.length == 2 ? keyValue[1] : "");
+        map.put(keyConverter.fromString(key), valueConverter.fromString(value));
+      }
+    }
+    return map;
+  }
+
+
   /**
    * Transforms a string with the following format : "key1=value1;key2=value2..."
    * into a Map<KEY, VALUE>. Requires to implement the transform(key,value) method
-   * 
-   * @param data the input string
+   *
+   * @param data        the input string
    * @param transformer the interface to implement
    * @return a Map of <key, value>
+   * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String)
    */
+  @Deprecated
   public static <KEY, VALUE> Map<KEY, VALUE> parse(String data, Transformer<KEY, VALUE> transformer) {
     Map<String, String> rawData = parse(data);
     Map<KEY, VALUE> map = new HashMap<KEY, VALUE>();
@@ -63,15 +274,17 @@ public final class KeyValueFormat {
   /**
    * Transforms a string with the following format : "key1=value1;key2=value2..."
    * into a Map<String,String>
-   * 
+   *
    * @param data the string to parse
    * @return a map
+   * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String)
    */
+  @Deprecated
   public static Map<String, String> parse(String data) {
     Map<String, String> map = new HashMap<String, String>();
-    String[] pairs = StringUtils.split(data, ";");
+    String[] pairs = StringUtils.split(data, PAIR_SEPARATOR);
     for (String pair : pairs) {
-      String[] keyValue = StringUtils.split(pair, "=");
+      String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR);
       String key = keyValue[0];
       String value = (keyValue.length == 2 ? keyValue[1] : "");
       map.put(key, value);
@@ -81,19 +294,21 @@ public final class KeyValueFormat {
 
   /**
    * Transforms a map<KEY,VALUE> into a string with the format : "key1=value1;key2=value2..."
-   * 
+   *
    * @param map the map to transform
    * @return the formatted string
+   * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String)
    */
+  @Deprecated
   public static <KEY, VALUE> String format(Map<KEY, VALUE> map) {
     StringBuilder sb = new StringBuilder();
     boolean first = true;
     for (Map.Entry<?, ?> entry : map.entrySet()) {
       if (!first) {
-        sb.append(";");
+        sb.append(PAIR_SEPARATOR);
       }
       sb.append(entry.getKey().toString());
-      sb.append("=");
+      sb.append(FIELD_SEPARATOR);
       if (entry.getValue() != null) {
         sb.append(entry.getValue());
       }
@@ -123,10 +338,10 @@ public final class KeyValueFormat {
       boolean first = true;
       for (Object obj : bag.uniqueSet()) {
         if (!first) {
-          sb.append(";");
+          sb.append(PAIR_SEPARATOR);
         }
         sb.append(obj.toString());
-        sb.append("=");
+        sb.append(FIELD_SEPARATOR);
         sb.append(bag.getCount(obj) + var);
         first = false;
       }
@@ -136,20 +351,22 @@ public final class KeyValueFormat {
 
   /**
    * Transforms a Multiset<?> into a string with the format : "key1=count1;key2=count2..."
-   * 
+   *
    * @param set the set to transform
    * @return the formatted string
+   * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String)
    */
+  @Deprecated
   public static String format(Multiset<?> set) {
     StringBuilder sb = new StringBuilder();
     if (set != null) {
       boolean first = true;
       for (Multiset.Entry<?> entry : set.entrySet()) {
         if (!first) {
-          sb.append(";");
+          sb.append(PAIR_SEPARATOR);
         }
         sb.append(entry.getElement().toString());
-        sb.append("=");
+        sb.append(FIELD_SEPARATOR);
         sb.append(entry.getCount());
         first = false;
       }
@@ -159,20 +376,22 @@ public final class KeyValueFormat {
 
   /**
    * Transforms a Object... into a string with the format : "object1=object2;object3=object4..."
-   * 
+   *
    * @param objects the object list to transform
    * @return the formatted string
+   * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String)
    */
+  @Deprecated
   public static String format(Object... objects) {
     StringBuilder sb = new StringBuilder();
     boolean first = true;
     if (objects != null) {
       for (int i = 0; i < objects.length; i++) {
         if (!first) {
-          sb.append(";");
+          sb.append(PAIR_SEPARATOR);
         }
         sb.append(objects[i++].toString());
-        sb.append("=");
+        sb.append(FIELD_SEPARATOR);
         sb.append(objects[i]);
         first = false;
       }
@@ -180,6 +399,7 @@ public final class KeyValueFormat {
     return sb.toString();
   }
 
+  @Deprecated
   public interface Transformer<KEY, VALUE> {
     KeyValue<KEY, VALUE> transform(String key, String value);
   }
@@ -187,8 +407,8 @@ public final class KeyValueFormat {
   /**
    * Implementation of Transformer<String, Double>
    */
+  @Deprecated
   public static class StringNumberPairTransformer implements Transformer<String, Double> {
-
     public KeyValue<String, Double> transform(String key, String value) {
       return new KeyValue<String, Double>(key, toDouble(value));
     }
@@ -197,8 +417,8 @@ public final class KeyValueFormat {
   /**
    * Implementation of Transformer<Double, Double>
    */
+  @Deprecated
   public static class DoubleNumbersPairTransformer implements Transformer<Double, Double> {
-
     public KeyValue<Double, Double> transform(String key, String value) {
       return new KeyValue<Double, Double>(toDouble(key), toDouble(value));
     }
@@ -207,16 +427,18 @@ public final class KeyValueFormat {
   /**
    * Implementation of Transformer<Integer, Integer>
    */
+  @Deprecated
   public static class IntegerNumbersPairTransformer implements Transformer<Integer, Integer> {
-
     public KeyValue<Integer, Integer> transform(String key, String value) {
       return new KeyValue<Integer, Integer>(toInteger(key), toInteger(value));
     }
   }
 
+
   /**
    * Implementation of Transformer<RulePriority, Integer>
    */
+  @Deprecated
   public static class RulePriorityNumbersPairTransformer implements Transformer<RulePriority, Integer> {
 
     public KeyValue<RulePriority, Integer> transform(String key, String value) {
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/DeprecatedKeyValueFormatTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DeprecatedKeyValueFormatTest.java
new file mode 100644 (file)
index 0000000..8c2a1a1
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.utils;
+
+import com.google.common.collect.Multiset;
+import com.google.common.collect.TreeMultiset;
+import static junit.framework.Assert.assertEquals;
+import org.apache.commons.collections.bag.TreeBag;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.sonar.api.rules.RulePriority;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class DeprecatedKeyValueFormatTest {
+
+  @Test
+  public void formatMap() {
+    Map<String, String> map = new TreeMap<String, String>();
+    map.put("hello", "world");
+    map.put("key1", "val1");
+    map.put("key2", "");
+    map.put("key3", "val3");
+    assertThat(KeyValueFormat.format(map), is("hello=world;key1=val1;key2=;key3=val3"));
+  }
+
+  @Test
+  public void formatBag() {
+    TreeBag bag = new TreeBag();
+    bag.add("hello", 1);
+    bag.add("key", 3);
+    assertThat(KeyValueFormat.format(bag), is("hello=1;key=3"));
+  }
+
+  @Test
+  public void formatBagWithVariationHack() {
+    TreeBag bag = new TreeBag();
+    bag.add("hello", 1);
+    bag.add("key", 3);
+    assertThat(KeyValueFormat.format(bag, -1), is("hello=0;key=2"));
+  }
+
+  @Test
+  public void formatMultiset() {
+    Multiset set = TreeMultiset.create();
+    set.add("hello", 1);
+    set.add("key", 3);
+    assertThat(KeyValueFormat.format(set), is("hello=1;key=3"));
+  }
+
+  @Test
+  public void formatVarargs() {
+    assertThat(KeyValueFormat.format("hello", 1, "key", 3), is("hello=1;key=3"));
+  }
+
+  @Test
+  public void parse() {
+    Map<String, String> map = KeyValueFormat.parse("hello=world;key1=val1;key2=;key3=val3");
+    assertThat(map.size(), is(4));
+    assertEquals("world", map.get("hello"));
+    assertEquals("val1", map.get("key1"));
+    assertEquals("", map.get("key2"));
+    assertEquals("val3", map.get("key3"));
+  }
+
+  @Test
+  public void parseWithStringNumberTransformation() {
+    Map<String, Double> map = KeyValueFormat.parse("hello=1;key1=;key2=3;key3=5.1", new KeyValueFormat.StringNumberPairTransformer());
+    assertThat(map.size(), is(4));
+    assertEquals(new Double(1), map.get("hello"));
+    assertEquals(null, map.get("key1"));
+    assertEquals(new Double(3), map.get("key2"));
+    assertEquals(new Double(5.1), map.get("key3"));
+  }
+
+  @Test
+  public void parseWithDoubleNumbersTransformation() {
+    Map<Double, Double> map = KeyValueFormat.parse("0=1;10=;60=5.1", new KeyValueFormat.DoubleNumbersPairTransformer());
+    assertThat(map.size(), is(3));
+    assertEquals(new Double(1), map.get(new Double(0)));
+    assertEquals(null, map.get(10));
+    assertEquals(new Double(5.1), map.get(new Double(60)));
+  }
+
+  @Test
+  public void parseWithIntegerNumbersTransformation() {
+    Map<Integer, Integer> map = KeyValueFormat.parse("0=1;10=;60=5", new KeyValueFormat.IntegerNumbersPairTransformer());
+    assertThat(map.size(), is(3));
+    assertEquals(new Integer(1), map.get(new Integer(0)));
+    assertEquals(null, map.get(10));
+    assertEquals(new Integer(5), map.get(new Integer(60)));
+  }
+
+  @Test
+  public void parseWithRulePriorityNumbersTransformation() {
+    Map<RulePriority, Integer> map = KeyValueFormat.parse("BLOCKER=1;MAJOR=;INFO=5", new KeyValueFormat.RulePriorityNumbersPairTransformer());
+    assertThat(map.size(), is(3));
+    assertEquals(new Integer(1), map.get(RulePriority.BLOCKER));
+    assertEquals(new Integer(0), map.get(RulePriority.MAJOR));
+    assertEquals(new Integer(5), map.get(RulePriority.INFO));
+  }
+}
index 6c041417eca34514bd279c6c7221ec777de07410..58b2fa9bf53b5b93fc98a2464f921ae0a80ee948 100644 (file)
  */
 package org.sonar.api.utils;
 
-import com.google.common.collect.Multiset;
-import com.google.common.collect.TreeMultiset;
-import static junit.framework.Assert.assertEquals;
-import org.apache.commons.collections.bag.TreeBag;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import com.google.common.collect.Maps;
 import org.junit.Test;
 import org.sonar.api.rules.RulePriority;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.Map;
-import java.util.TreeMap;
+import java.util.SortedMap;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
 
 public class KeyValueFormatTest {
 
   @Test
-  public void formatMap() {
-    Map<String, String> map = new TreeMap<String, String>();
-    map.put("hello", "world");
-    map.put("key1", "val1");
-    map.put("key2", "");
-    map.put("key3", "val3");
-    assertThat(KeyValueFormat.format(map), is("hello=world;key1=val1;key2=;key3=val3"));
+  public void shouldFormatMapOfStrings() {
+    Map<String,String> map = Maps.newLinkedHashMap();
+    map.put("lucky", "luke");
+    map.put("aste", "rix");
+    String s = KeyValueFormat.createStringString().toString(map);
+    assertThat(s, is("lucky=luke;aste=rix"));// same order
   }
 
   @Test
-  public void formatBag() {
-    TreeBag bag = new TreeBag();
-    bag.add("hello", 1);
-    bag.add("key", 3);
-    assertThat(KeyValueFormat.format(bag), is("hello=1;key=3"));
+  public void shouldFormatMapOfIntegerString() {
+    Map<Integer,String> map = Maps.newLinkedHashMap();
+    map.put(3, "three");
+    map.put(5, "five");
+    String s = KeyValueFormat.createIntString().toString(map);
+    assertThat(s, is("3=three;5=five"));// same order
   }
 
   @Test
-  public void formatBagWithVariationHack() {
-    TreeBag bag = new TreeBag();
-    bag.add("hello", 1);
-    bag.add("key", 3);
-    assertThat(KeyValueFormat.format(bag, -1), is("hello=0;key=2"));
+  public void shouldFormatMapOfIntDouble() {
+    Map<Integer,Double> map = Maps.newLinkedHashMap();
+    map.put(13, 2.0);
+    map.put(5, 5.75);
+    String s = KeyValueFormat.createIntDouble().toString(map);
+    assertThat(s, is("13=2.0;5=5.75"));// same order
   }
 
   @Test
-  public void formatMultiset() {
-    Multiset set = TreeMultiset.create();
-    set.add("hello", 1);
-    set.add("key", 3);
-    assertThat(KeyValueFormat.format(set), is("hello=1;key=3"));
+  public void shouldSetEmptyFieldWhenNullValue() {
+    Map<Integer,Double> map = Maps.newLinkedHashMap();
+    map.put(13, null);
+    map.put(5, 5.75);
+    String s = KeyValueFormat.createIntDouble().toString(map);
+    assertThat(s, is("13=;5=5.75"));// same order
   }
 
   @Test
-  public void formatVarargs() {
-    assertThat(KeyValueFormat.format("hello", 1, "key", 3), is("hello=1;key=3"));
+  public void shouldFormatBlank() {
+    Map<Integer,String> map = Maps.newTreeMap();
+    String s = KeyValueFormat.createIntString().toString(map);
+    assertThat(s, is(""));
   }
 
   @Test
-  public void parse() {
-    Map<String, String> map = KeyValueFormat.parse("hello=world;key1=val1;key2=;key3=val3");
-    assertThat(map.size(), is(4));
-    assertEquals("world", map.get("hello"));
-    assertEquals("val1", map.get("key1"));
-    assertEquals("", map.get("key2"));
-    assertEquals("val3", map.get("key3"));
+  public void shouldFormatDate() throws ParseException {
+    Map<Integer,Date> map = Maps.newLinkedHashMap();
+    map.put(4, new SimpleDateFormat("yyyy-MM-dd").parse("2010-12-25"));
+    map.put(20, new SimpleDateFormat("yyyy-MM-dd").parse("2009-05-28"));
+    map.put(12, null);
+    String s = KeyValueFormat.createIntDate().toString(map);
+    assertThat(s, is("4=2010-12-25;20=2009-05-28;12="));
   }
 
   @Test
-  public void parseWithStringNumberTransformation() {
-    Map<String, Double> map = KeyValueFormat.parse("hello=1;key1=;key2=3;key3=5.1", new KeyValueFormat.StringNumberPairTransformer());
-    assertThat(map.size(), is(4));
-    assertEquals(new Double(1), map.get("hello"));
-    assertEquals(null, map.get("key1"));
-    assertEquals(new Double(3), map.get("key2"));
-    assertEquals(new Double(5.1), map.get("key3"));
+  public void shouldParseStrings() throws ParseException {
+    SortedMap<String,String> map = KeyValueFormat.createStringString().toSortedMap("one=un;two=deux");
+    assertThat(map.size(), is(2));
+    assertThat(map.get("one"), is("un"));
+    assertThat(map.get("two"), is("deux"));
+    assertThat(map.keySet().iterator().next(), is("one"));//same order as in string
   }
 
   @Test
-  public void parseWithDoubleNumbersTransformation() {
-    Map<Double, Double> map = KeyValueFormat.parse("0=1;10=;60=5.1", new KeyValueFormat.DoubleNumbersPairTransformer());
-    assertThat(map.size(), is(3));
-    assertEquals(new Double(1), map.get(new Double(0)));
-    assertEquals(null, map.get(10));
-    assertEquals(new Double(5.1), map.get(new Double(60)));
+  public void shouldParseBlank() throws ParseException {
+    SortedMap<String,String> map = KeyValueFormat.createStringString().toSortedMap("");
+    assertThat(map.size(), is(0));
   }
 
   @Test
-  public void parseWithIntegerNumbersTransformation() {
-    Map<Integer, Integer> map = KeyValueFormat.parse("0=1;10=;60=5", new KeyValueFormat.IntegerNumbersPairTransformer());
-    assertThat(map.size(), is(3));
-    assertEquals(new Integer(1), map.get(new Integer(0)));
-    assertEquals(null, map.get(10));
-    assertEquals(new Integer(5), map.get(new Integer(60)));
+  public void shouldParseNull() throws ParseException {
+    SortedMap<String,String> map = KeyValueFormat.createStringString().toSortedMap(null);
+    assertThat(map.size(), is(0));
   }
 
   @Test
-  public void parseWithRulePriorityNumbersTransformation() {
-    Map<RulePriority, Integer> map = KeyValueFormat.parse("BLOCKER=1;MAJOR=;INFO=5", new KeyValueFormat.RulePriorityNumbersPairTransformer());
+  public void shouldParseEmptyFields() throws ParseException {
+    SortedMap<Integer,Double> map = KeyValueFormat.createIntDouble().toSortedMap("4=4.2;2=;6=6.68");
     assertThat(map.size(), is(3));
-    assertEquals(new Integer(1), map.get(RulePriority.BLOCKER));
-    assertEquals(new Integer(0), map.get(RulePriority.MAJOR));
-    assertEquals(new Integer(5), map.get(RulePriority.INFO));
+    assertThat(map.get(4), is(4.2));
+    assertThat(map.get(2), nullValue());
+    assertThat(map.get(6), is(6.68));
+  }
+
+  @Test
+  public void shouldConvertSeverity() {
+    assertThat(KeyValueFormat.SeverityConverter.INSTANCE.toString(RulePriority.BLOCKER), is("BLOCKER"));
+    assertThat(KeyValueFormat.SeverityConverter.INSTANCE.toString(null), is(""));
+
+    assertThat(KeyValueFormat.SeverityConverter.INSTANCE.fromString("MAJOR"), is(RulePriority.MAJOR));
+    assertThat(KeyValueFormat.SeverityConverter.INSTANCE.fromString(""), nullValue());
   }
 }