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 */
020package org.sonar.api.utils;
021
022import com.google.common.collect.LinkedHashMultiset;
023import com.google.common.collect.Maps;
024import com.google.common.collect.Multimap;
025import com.google.common.collect.Multiset;
026import org.apache.commons.collections.Bag;
027import org.apache.commons.lang.StringUtils;
028import org.apache.commons.lang.math.NumberUtils;
029import org.slf4j.LoggerFactory;
030import org.sonar.api.rules.RulePriority;
031
032import java.text.ParseException;
033import java.text.SimpleDateFormat;
034import 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 */
044public 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    // to keep the same order
292    Multiset<K> multiset = LinkedHashMultiset.create();
293    if (data != null) {
294      String[] pairs = StringUtils.split(data, PAIR_SEPARATOR);
295      for (String pair : pairs) {
296        String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR);
297        String key = keyValue[0];
298        String value = keyValue.length == 2 ? keyValue[1] : "0";
299        multiset.add(keyConverter.parse(key), new IntegerConverter().parse(value));
300      }
301    }
302    return multiset;
303  }
304
305
306  /**
307   * @since 2.7
308   */
309  public static Multiset<Integer> parseIntegerMultiset(String data) {
310    return parseMultiset(data, newIntegerConverter());
311  }
312
313  /**
314   * @since 2.7
315   */
316  public static Multiset<String> parseMultiset(String data) {
317    return parseMultiset(data, newStringConverter());
318  }
319
320  /**
321   * Transforms a string with the following format: "key1=value1;key2=value2..."
322   * into a Map<KEY, VALUE>. Requires to implement the transform(key,value) method
323   *
324   * @param data        the input string
325   * @param transformer the interface to implement
326   * @return a Map of <key, value>
327   * @deprecated since 2.7
328   */
329  @Deprecated
330  public static <K, V> Map<K, V> parse(String data, Transformer<K, V> transformer) {
331    Map<String, String> rawData = parse(data);
332    Map<K, V> map = new HashMap<K, V>();
333    for (Map.Entry<String, String> entry : rawData.entrySet()) {
334      KeyValue<K, V> keyVal = transformer.transform(entry.getKey(), entry.getValue());
335      if (keyVal != null) {
336        map.put(keyVal.getKey(), keyVal.getValue());
337      }
338    }
339    return map;
340  }
341
342  private static <K, V> String formatEntries(Collection<Map.Entry<K, V>> entries, Converter<K> keyConverter, Converter<V> valueConverter) {
343    StringBuilder sb = new StringBuilder();
344    boolean first = true;
345    for (Map.Entry<K, V> entry : entries) {
346      if (!first) {
347        sb.append(PAIR_SEPARATOR);
348      }
349      sb.append(keyConverter.format(entry.getKey()));
350      sb.append(FIELD_SEPARATOR);
351      if (entry.getValue() != null) {
352        sb.append(valueConverter.format(entry.getValue()));
353      }
354      first = false;
355    }
356    return sb.toString();
357  }
358
359  private static <K> String formatEntries(Set<Multiset.Entry<K>> entries, Converter<K> keyConverter) {
360    StringBuilder sb = new StringBuilder();
361    boolean first = true;
362    for (Multiset.Entry<K> entry : entries) {
363      if (!first) {
364        sb.append(PAIR_SEPARATOR);
365      }
366      sb.append(keyConverter.format(entry.getElement()));
367      sb.append(FIELD_SEPARATOR);
368      sb.append(new IntegerConverter().format(entry.getCount()));
369      first = false;
370    }
371    return sb.toString();
372  }
373
374
375  /**
376   * @since 2.7
377   */
378  public static <K, V> String format(Map<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) {
379    return formatEntries(map.entrySet(), keyConverter, valueConverter);
380  }
381
382  /**
383   * @since 2.7
384   */
385  public static String format(Map map) {
386    return format(map, newToStringConverter(), newToStringConverter());
387  }
388
389  /**
390   * @since 2.7
391   */
392  public static String formatIntString(Map<Integer, String> map) {
393    return format(map, newIntegerConverter(), newStringConverter());
394  }
395
396  /**
397   * @since 2.7
398   */
399  public static String formatIntDouble(Map<Integer, Double> map) {
400    return format(map, newIntegerConverter(), newDoubleConverter());
401  }
402
403  /**
404   * @since 2.7
405   */
406  public static String formatIntDate(Map<Integer, Date> map) {
407    return format(map, newIntegerConverter(), newDateConverter());
408  }
409
410  /**
411   * @since 2.7
412   */
413  public static String formatIntDateTime(Map<Integer, Date> map) {
414    return format(map, newIntegerConverter(), newDateTimeConverter());
415  }
416
417  /**
418   * @since 2.7
419   */
420  public static String formatStringInt(Map<String, Integer> map) {
421    return format(map, newStringConverter(), newIntegerConverter());
422  }
423
424  /**
425   * Limitation: there's currently no methods to parse into Multimap.
426   *
427   * @since 2.7
428   */
429  public static <K, V> String format(Multimap<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) {
430    return formatEntries(map.entries(), keyConverter, valueConverter);
431  }
432
433  /**
434   * @since 2.7
435   */
436  public static <K> String format(Multiset<K> multiset, Converter<K> keyConverter) {
437    return formatEntries(multiset.entrySet(), keyConverter);
438  }
439
440  public static String format(Multiset multiset) {
441    return formatEntries(multiset.entrySet(), newToStringConverter());
442  }
443
444
445  /**
446   * @since 1.11
447   * @deprecated use Multiset from google collections instead of commons-collections bags
448   */
449  @Deprecated
450  public static String format(Bag bag) {
451    return format(bag, 0);
452  }
453
454  /**
455   * @since 1.11
456   * @deprecated use Multiset from google collections instead of commons-collections bags
457   */
458  @Deprecated
459  public static String format(Bag bag, int var) {
460    StringBuilder sb = new StringBuilder();
461    if (bag != null) {
462      boolean first = true;
463      for (Object obj : bag.uniqueSet()) {
464        if (!first) {
465          sb.append(PAIR_SEPARATOR);
466        }
467        sb.append(obj.toString());
468        sb.append(FIELD_SEPARATOR);
469        sb.append(bag.getCount(obj) + var);
470        first = false;
471      }
472    }
473    return sb.toString();
474  }
475
476
477  /**
478   * @deprecated since 2.7. Replaced by Converter
479   */
480  @Deprecated
481  public interface Transformer<K, V> {
482    KeyValue<K, V> transform(String key, String value);
483  }
484
485  /**
486   * Implementation of Transformer<String, Double>
487   *
488   * @deprecated since 2.7 replaced by Converter
489   */
490  @Deprecated
491  public static class StringNumberPairTransformer implements Transformer<String, Double> {
492    public KeyValue<String, Double> transform(String key, String value) {
493      return new KeyValue<String, Double>(key, toDouble(value));
494    }
495  }
496
497  /**
498   * Implementation of Transformer<Double, Double>
499   *
500   * @deprecated since 2.7. Replaced by Converter
501   */
502  @Deprecated
503  public static class DoubleNumbersPairTransformer implements Transformer<Double, Double> {
504    public KeyValue<Double, Double> transform(String key, String value) {
505      return new KeyValue<Double, Double>(toDouble(key), toDouble(value));
506    }
507  }
508
509  /**
510   * Implementation of Transformer<Integer, Integer>
511   *
512   * @deprecated since 2.7. Replaced by Converter
513   */
514  @Deprecated
515  public static class IntegerNumbersPairTransformer implements Transformer<Integer, Integer> {
516    public KeyValue<Integer, Integer> transform(String key, String value) {
517      return new KeyValue<Integer, Integer>(toInteger(key), toInteger(value));
518    }
519  }
520
521
522  /**
523   * Implementation of Transformer<RulePriority, Integer>
524   *
525   * @deprecated since 2.7. Replaced by Converter
526   */
527  @Deprecated
528  public static class RulePriorityNumbersPairTransformer implements Transformer<RulePriority, Integer> {
529
530    public KeyValue<RulePriority, Integer> transform(String key, String value) {
531      try {
532        if (StringUtils.isBlank(value)) {
533          value = "0";
534        }
535        return new KeyValue<RulePriority, Integer>(RulePriority.valueOf(key.toUpperCase()), Integer.parseInt(value));
536      } catch (Exception e) {
537        LoggerFactory.getLogger(RulePriorityNumbersPairTransformer.class).warn("Property " + key + " has invalid value: " + value, e);
538        return null;
539      }
540    }
541  }
542
543  private static Double toDouble(String value) {
544    return StringUtils.isBlank(value) ? null : NumberUtils.toDouble(value);
545  }
546
547  private static Integer toInteger(String value) {
548    return StringUtils.isBlank(value) ? null : NumberUtils.toInt(value);
549  }
550}