001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 */
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 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<KEY, VALUE> {
481    KeyValue<KEY, VALUE> 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}