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 }