001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.api.utils; 021 022 import com.google.common.collect.LinkedHashMultiset; 023 import com.google.common.collect.Maps; 024 import com.google.common.collect.Multimap; 025 import com.google.common.collect.Multiset; 026 import org.apache.commons.collections.Bag; 027 import org.apache.commons.lang.StringUtils; 028 import org.apache.commons.lang.math.NumberUtils; 029 import org.slf4j.LoggerFactory; 030 import org.sonar.api.rules.RulePriority; 031 032 import java.text.ParseException; 033 import java.text.SimpleDateFormat; 034 import java.util.*; 035 036 /** 037 * Formats and parses key/value pairs with the string representation : "key1=value1;key2=value2". Conversion 038 * of fields is supported and can be extended. 039 * 040 * @since 1.10 041 */ 042 public final class KeyValueFormat { 043 044 public static final String PAIR_SEPARATOR = ";"; 045 public static final String FIELD_SEPARATOR = "="; 046 047 private KeyValueFormat() { 048 // only static methods 049 } 050 051 public static abstract class Converter<TYPE> { 052 abstract String format(TYPE type); 053 054 abstract TYPE parse(String s); 055 } 056 057 public static final class StringConverter extends Converter<String> { 058 static final StringConverter INSTANCE = new StringConverter(); 059 060 private StringConverter() { 061 } 062 063 @Override 064 String format(String s) { 065 return s; 066 } 067 068 @Override 069 String parse(String s) { 070 return s; 071 } 072 } 073 074 public static final class ToStringConverter extends Converter<Object> { 075 static final ToStringConverter INSTANCE = new ToStringConverter(); 076 077 private ToStringConverter() { 078 } 079 080 @Override 081 String format(Object o) { 082 return o.toString(); 083 } 084 085 @Override 086 String parse(String s) { 087 throw new IllegalStateException("Can not parse with ToStringConverter: " + s); 088 } 089 } 090 091 public static final class IntegerConverter extends Converter<Integer> { 092 static final IntegerConverter INSTANCE = new IntegerConverter(); 093 094 private IntegerConverter() { 095 } 096 097 @Override 098 String format(Integer s) { 099 return (s == null ? "" : String.valueOf(s)); 100 } 101 102 @Override 103 Integer parse(String s) { 104 return StringUtils.isBlank(s) ? null : NumberUtils.toInt(s); 105 } 106 } 107 108 public static final class PriorityConverter extends Converter<RulePriority> { 109 static final PriorityConverter INSTANCE = new PriorityConverter(); 110 111 private PriorityConverter() { 112 } 113 114 @Override 115 String format(RulePriority s) { 116 return (s == null ? "" : s.toString()); 117 } 118 119 @Override 120 RulePriority parse(String s) { 121 return StringUtils.isBlank(s) ? null : RulePriority.valueOf(s); 122 } 123 } 124 125 public static final class DoubleConverter extends Converter<Double> { 126 static final DoubleConverter INSTANCE = new DoubleConverter(); 127 128 private DoubleConverter() { 129 } 130 131 @Override 132 String format(Double d) { 133 return (d == null ? "" : String.valueOf(d)); 134 } 135 136 @Override 137 Double parse(String s) { 138 return StringUtils.isBlank(s) ? null : NumberUtils.toDouble(s); 139 } 140 } 141 142 public static class DateConverter extends Converter<Date> { 143 private SimpleDateFormat dateFormat; 144 145 public DateConverter() { 146 this(DateUtils.DATE_FORMAT); 147 } 148 149 DateConverter(String format) { 150 this.dateFormat = new SimpleDateFormat(format); 151 } 152 153 @Override 154 String format(Date d) { 155 return (d == null ? "" : dateFormat.format(d)); 156 } 157 158 @Override 159 Date parse(String s) { 160 try { 161 return StringUtils.isBlank(s) ? null : dateFormat.parse(s); 162 } catch (ParseException e) { 163 throw new SonarException("Not a date with format: " + dateFormat.toPattern(), e); 164 } 165 } 166 } 167 168 public static class DateTimeConverter extends DateConverter { 169 public DateTimeConverter() { 170 super(DateUtils.DATETIME_FORMAT); 171 } 172 } 173 174 public static <K, V> Map<K, V> parse(String data, Converter<K> keyConverter, Converter<V> valueConverter) { 175 Map<K, V> map = Maps.newLinkedHashMap(); 176 if (data != null) { 177 String[] pairs = StringUtils.split(data, PAIR_SEPARATOR); 178 for (String pair : pairs) { 179 String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR); 180 String key = keyValue[0]; 181 String value = (keyValue.length == 2 ? keyValue[1] : ""); 182 map.put(keyConverter.parse(key), valueConverter.parse(value)); 183 } 184 } 185 return map; 186 } 187 188 public static Map parse(String data) { 189 return parse(data, StringConverter.INSTANCE, StringConverter.INSTANCE); 190 } 191 192 /** 193 * @since 2.7 194 */ 195 public static Map<String, Integer> parseStringInt(String data) { 196 return parse(data, StringConverter.INSTANCE, IntegerConverter.INSTANCE); 197 } 198 199 /** 200 * @since 2.7 201 */ 202 public static Map<String, Double> parseStringDouble(String data) { 203 return parse(data, StringConverter.INSTANCE, DoubleConverter.INSTANCE); 204 } 205 206 /** 207 * @since 2.7 208 */ 209 public static Map<Integer, String> parseIntString(String data) { 210 return parse(data, IntegerConverter.INSTANCE, StringConverter.INSTANCE); 211 } 212 213 /** 214 * @since 2.7 215 */ 216 public static Map<Integer, Double> parseIntDouble(String data) { 217 return parse(data, IntegerConverter.INSTANCE, DoubleConverter.INSTANCE); 218 } 219 220 /** 221 * @since 2.7 222 */ 223 public static Map<Integer, Date> parseIntDate(String data) { 224 return parse(data, IntegerConverter.INSTANCE, new DateConverter()); 225 } 226 227 /** 228 * @since 2.7 229 */ 230 public static Map<Integer, Integer> parseIntInt(String data) { 231 return parse(data, IntegerConverter.INSTANCE, IntegerConverter.INSTANCE); 232 } 233 234 /** 235 * @since 2.7 236 */ 237 public static Map<Integer, Date> parseIntDateTime(String data) { 238 return parse(data, IntegerConverter.INSTANCE, new DateTimeConverter()); 239 } 240 241 /** 242 * Value of pairs is the occurrences of the same single key. A multiset is sometimes called a bag. 243 * For example parsing "foo=2;bar=1" creates a multiset with 3 elements : foo, foo and bar. 244 */ 245 /** 246 * @since 2.7 247 */ 248 public static <K> Multiset<K> parseMultiset(String data, Converter<K> keyConverter) { 249 Multiset<K> multiset = LinkedHashMultiset.create();// to keep the same order 250 if (data != null) { 251 String[] pairs = StringUtils.split(data, PAIR_SEPARATOR); 252 for (String pair : pairs) { 253 String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR); 254 String key = keyValue[0]; 255 String value = (keyValue.length == 2 ? keyValue[1] : "0"); 256 multiset.add(keyConverter.parse(key), IntegerConverter.INSTANCE.parse(value)); 257 } 258 } 259 return multiset; 260 } 261 262 263 /** 264 * @since 2.7 265 */ 266 public static Multiset<Integer> parseIntegerMultiset(String data) { 267 return parseMultiset(data, IntegerConverter.INSTANCE); 268 } 269 270 /** 271 * @since 2.7 272 */ 273 public static Multiset<String> parseMultiset(String data) { 274 return parseMultiset(data, StringConverter.INSTANCE); 275 } 276 277 /** 278 * Transforms a string with the following format : "key1=value1;key2=value2..." 279 * into a Map<KEY, VALUE>. Requires to implement the transform(key,value) method 280 * 281 * @param data the input string 282 * @param transformer the interface to implement 283 * @return a Map of <key, value> 284 * @deprecated since 2.7 285 */ 286 @Deprecated 287 public static <KEY, VALUE> Map<KEY, VALUE> parse(String data, Transformer<KEY, VALUE> transformer) { 288 Map<String, String> rawData = parse(data); 289 Map<KEY, VALUE> map = new HashMap<KEY, VALUE>(); 290 for (Map.Entry<String, String> entry : rawData.entrySet()) { 291 KeyValue<KEY, VALUE> keyVal = transformer.transform(entry.getKey(), entry.getValue()); 292 if (keyVal != null) { 293 map.put(keyVal.getKey(), keyVal.getValue()); 294 } 295 } 296 return map; 297 } 298 299 private static <K, V> String formatEntries(Collection<Map.Entry<K, V>> entries, Converter<K> keyConverter, Converter<V> valueConverter) { 300 StringBuilder sb = new StringBuilder(); 301 boolean first = true; 302 for (Map.Entry<K, V> entry : entries) { 303 if (!first) { 304 sb.append(PAIR_SEPARATOR); 305 } 306 sb.append(keyConverter.format(entry.getKey())); 307 sb.append(FIELD_SEPARATOR); 308 if (entry.getValue() != null) { 309 sb.append(valueConverter.format(entry.getValue())); 310 } 311 first = false; 312 } 313 return sb.toString(); 314 } 315 316 private static <K> String formatEntries(Set<Multiset.Entry<K>> entries, Converter<K> keyConverter) { 317 StringBuilder sb = new StringBuilder(); 318 boolean first = true; 319 for (Multiset.Entry<K> entry : entries) { 320 if (!first) { 321 sb.append(PAIR_SEPARATOR); 322 } 323 sb.append(keyConverter.format(entry.getElement())); 324 sb.append(FIELD_SEPARATOR); 325 sb.append(IntegerConverter.INSTANCE.format(entry.getCount())); 326 first = false; 327 } 328 return sb.toString(); 329 } 330 331 332 /** 333 * @since 2.7 334 */ 335 public static <K, V> String format(Map<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) { 336 return formatEntries(map.entrySet(), keyConverter, valueConverter); 337 } 338 339 /** 340 * @since 2.7 341 */ 342 public static String format(Map map) { 343 return format(map, ToStringConverter.INSTANCE, ToStringConverter.INSTANCE); 344 } 345 346 /** 347 * @since 2.7 348 */ 349 public static String formatIntString(Map<Integer, String> map) { 350 return format(map, IntegerConverter.INSTANCE, StringConverter.INSTANCE); 351 } 352 353 /** 354 * @since 2.7 355 */ 356 public static String formatIntDouble(Map<Integer, Double> map) { 357 return format(map, IntegerConverter.INSTANCE, DoubleConverter.INSTANCE); 358 } 359 360 /** 361 * @since 2.7 362 */ 363 public static String formatIntDate(Map<Integer, Date> map) { 364 return format(map, IntegerConverter.INSTANCE, new DateConverter()); 365 } 366 367 /** 368 * @since 2.7 369 */ 370 public static String formatIntDateTime(Map<Integer, Date> map) { 371 return format(map, IntegerConverter.INSTANCE, new DateTimeConverter()); 372 } 373 374 /** 375 * @since 2.7 376 */ 377 public static String formatStringInt(Map<String, Integer> map) { 378 return format(map, StringConverter.INSTANCE, IntegerConverter.INSTANCE); 379 } 380 381 /** 382 * Limitation: there's currently no methods to parse into Multimap. 383 * @since 2.7 384 */ 385 public static <K, V> String format(Multimap<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) { 386 return formatEntries(map.entries(), keyConverter, valueConverter); 387 } 388 389 /** 390 * @since 2.7 391 */ 392 public static <K> String format(Multiset<K> multiset, Converter<K> keyConverter) { 393 return formatEntries(multiset.entrySet(), keyConverter); 394 } 395 396 public static String format(Multiset multiset) { 397 return formatEntries(multiset.entrySet(), ToStringConverter.INSTANCE); 398 } 399 400 401 402 /** 403 * @since 1.11 404 * @deprecated use Multiset from google collections instead of commons-collections bags 405 */ 406 @Deprecated 407 public static String format(Bag bag) { 408 return format(bag, 0); 409 } 410 411 /** 412 * @since 1.11 413 * @deprecated use Multiset from google collections instead of commons-collections bags 414 */ 415 @Deprecated 416 public static String format(Bag bag, int var) { 417 StringBuilder sb = new StringBuilder(); 418 if (bag != null) { 419 boolean first = true; 420 for (Object obj : bag.uniqueSet()) { 421 if (!first) { 422 sb.append(PAIR_SEPARATOR); 423 } 424 sb.append(obj.toString()); 425 sb.append(FIELD_SEPARATOR); 426 sb.append(bag.getCount(obj) + var); 427 first = false; 428 } 429 } 430 return sb.toString(); 431 } 432 433 434 /** 435 * @deprecated since 2.7. Replaced by Converter 436 */ 437 @Deprecated 438 public interface Transformer<KEY, VALUE> { 439 KeyValue<KEY, VALUE> transform(String key, String value); 440 } 441 442 /** 443 * Implementation of Transformer<String, Double> 444 * @deprecated since 2.7 replaced by Converter 445 */ 446 @Deprecated 447 public static class StringNumberPairTransformer implements Transformer<String, Double> { 448 public KeyValue<String, Double> transform(String key, String value) { 449 return new KeyValue<String, Double>(key, toDouble(value)); 450 } 451 } 452 453 /** 454 * Implementation of Transformer<Double, Double> 455 * @deprecated since 2.7. Replaced by Converter 456 */ 457 @Deprecated 458 public static class DoubleNumbersPairTransformer implements Transformer<Double, Double> { 459 public KeyValue<Double, Double> transform(String key, String value) { 460 return new KeyValue<Double, Double>(toDouble(key), toDouble(value)); 461 } 462 } 463 464 /** 465 * Implementation of Transformer<Integer, Integer> 466 * @deprecated since 2.7. Replaced by Converter 467 */ 468 @Deprecated 469 public static class IntegerNumbersPairTransformer implements Transformer<Integer, Integer> { 470 public KeyValue<Integer, Integer> transform(String key, String value) { 471 return new KeyValue<Integer, Integer>(toInteger(key), toInteger(value)); 472 } 473 } 474 475 476 /** 477 * Implementation of Transformer<RulePriority, Integer> 478 * @deprecated since 2.7. Replaced by Converter 479 */ 480 @Deprecated 481 public static class RulePriorityNumbersPairTransformer implements Transformer<RulePriority, Integer> { 482 483 public KeyValue<RulePriority, Integer> transform(String key, String value) { 484 try { 485 if (StringUtils.isBlank(value)) { 486 value = "0"; 487 } 488 return new KeyValue<RulePriority, Integer>(RulePriority.valueOf(key.toUpperCase()), Integer.parseInt(value)); 489 } catch (Exception e) { 490 LoggerFactory.getLogger(RulePriorityNumbersPairTransformer.class).warn("Property " + key + " has invalid value: " + value, e); 491 return null; 492 } 493 } 494 } 495 496 private static Double toDouble(String value) { 497 return StringUtils.isBlank(value) ? null : NumberUtils.toDouble(value); 498 } 499 500 private static Integer toInteger(String value) { 501 return StringUtils.isBlank(value) ? null : NumberUtils.toInt(value); 502 } 503 }