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