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        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    }