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    }