From 2dc09dd0800e0fca575133775e4b97dc5f711104 Mon Sep 17 00:00:00 2001 From: simonbrandhof Date: Fri, 25 Feb 2011 02:05:24 +0100 Subject: [PATCH] Improve KeyValueFormat: support typed parsing --- .../org/sonar/api/utils/KeyValueFormat.java | 270 ++++++++++++++++-- .../utils/DeprecatedKeyValueFormatTest.java | 121 ++++++++ .../sonar/api/utils/KeyValueFormatTest.java | 132 +++++---- 3 files changed, 437 insertions(+), 86 deletions(-) create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/utils/DeprecatedKeyValueFormatTest.java diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java index 787162c228c..cd44e7a9ba7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java @@ -19,35 +19,246 @@ */ 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 { + + public static final String PAIR_SEPARATOR = ";"; + public static final String FIELD_SEPARATOR = "="; + + private Converter keyConverter; + private Converter valueConverter; + + private KeyValueFormat(Converter keyConverter, Converter valueConverter) { + this.keyConverter = keyConverter; + this.valueConverter = valueConverter; + } + + public static abstract class Converter { + abstract String toString(TYPE type); + + abstract TYPE fromString(String s); + } + + public static final class StringConverter extends Converter { + 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 { + 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 { + 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 { + 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 { + 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 KeyValueFormat create(Converter keyConverter, Converter valueConverter) { + return new KeyValueFormat(keyConverter, valueConverter); + } + + public static KeyValueFormat createStringString() { + return new KeyValueFormat(StringConverter.INSTANCE, StringConverter.INSTANCE); + } + + public static KeyValueFormat createStringDate() { + return new KeyValueFormat(StringConverter.INSTANCE, new DateConverter()); + } + + public static KeyValueFormat createStringDateTime() { + return new KeyValueFormat(StringConverter.INSTANCE, new DateTimeConverter()); + } + + public static KeyValueFormat createIntString() { + return new KeyValueFormat(IntegerConverter.INSTANCE, StringConverter.INSTANCE); + } + + public static KeyValueFormat createIntInt() { + return new KeyValueFormat(IntegerConverter.INSTANCE, IntegerConverter.INSTANCE); + } + + public static KeyValueFormat createIntDouble() { + return new KeyValueFormat(IntegerConverter.INSTANCE, DoubleConverter.INSTANCE); + } + + public static KeyValueFormat createIntDate() { + return new KeyValueFormat(IntegerConverter.INSTANCE, new DateConverter()); + } + + public static KeyValueFormat createIntDateTime() { + return new KeyValueFormat(IntegerConverter.INSTANCE, new DateTimeConverter()); + } + + public String toString(Map map) { + return toString(map.entrySet()); + } + + public String toString(Multimap multimap) { + return toString(multimap.entries()); + } + + private String toString(Collection> entries) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry 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 toSortedMap(String data) { + SortedMap map = new TreeMap(); + 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 toSortedMultimap(String data) { + SortedSetMultimap 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. 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 + * @deprecated since 2.7. Use instance methods instead of static methods, for example KeyValueFormat.createIntString().parse(String) */ + @Deprecated public static Map parse(String data, Transformer transformer) { Map rawData = parse(data); Map map = new HashMap(); @@ -63,15 +274,17 @@ public final class KeyValueFormat { /** * Transforms a string with the following format : "key1=value1;key2=value2..." * into a Map - * + * * @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 parse(String data) { Map map = new HashMap(); - 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 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 String format(Map 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 { KeyValue transform(String key, String value); } @@ -187,8 +407,8 @@ public final class KeyValueFormat { /** * Implementation of Transformer */ + @Deprecated public static class StringNumberPairTransformer implements Transformer { - public KeyValue transform(String key, String value) { return new KeyValue(key, toDouble(value)); } @@ -197,8 +417,8 @@ public final class KeyValueFormat { /** * Implementation of Transformer */ + @Deprecated public static class DoubleNumbersPairTransformer implements Transformer { - public KeyValue transform(String key, String value) { return new KeyValue(toDouble(key), toDouble(value)); } @@ -207,16 +427,18 @@ public final class KeyValueFormat { /** * Implementation of Transformer */ + @Deprecated public static class IntegerNumbersPairTransformer implements Transformer { - public KeyValue transform(String key, String value) { return new KeyValue(toInteger(key), toInteger(value)); } } + /** * Implementation of Transformer */ + @Deprecated public static class RulePriorityNumbersPairTransformer implements Transformer { public KeyValue 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 index 00000000000..8c2a1a1a8c0 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DeprecatedKeyValueFormatTest.java @@ -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 map = new TreeMap(); + 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 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 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 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 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 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)); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/KeyValueFormatTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/KeyValueFormatTest.java index 6c041417eca..58b2fa9bf53 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/KeyValueFormatTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/KeyValueFormatTest.java @@ -19,103 +19,111 @@ */ 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 map = new TreeMap(); - 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 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 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 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 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 map = Maps.newTreeMap(); + String s = KeyValueFormat.createIntString().toString(map); + assertThat(s, is("")); } @Test - public void parse() { - Map 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 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 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 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 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 map = KeyValueFormat.createStringString().toSortedMap(""); + assertThat(map.size(), is(0)); } @Test - public void parseWithIntegerNumbersTransformation() { - Map 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 map = KeyValueFormat.createStringString().toSortedMap(null); + assertThat(map.size(), is(0)); } @Test - public void parseWithRulePriorityNumbersTransformation() { - Map map = KeyValueFormat.parse("BLOCKER=1;MAJOR=;INFO=5", new KeyValueFormat.RulePriorityNumbersPairTransformer()); + public void shouldParseEmptyFields() throws ParseException { + SortedMap 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()); } } -- 2.39.5