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