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