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