001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 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        // 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    }