001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 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 // to keep the same order
292 Multiset<K> multiset = LinkedHashMultiset.create();
293 if (data != null) {
294 String[] pairs = StringUtils.split(data, PAIR_SEPARATOR);
295 for (String pair : pairs) {
296 String[] keyValue = StringUtils.split(pair, FIELD_SEPARATOR);
297 String key = keyValue[0];
298 String value = keyValue.length == 2 ? keyValue[1] : "0";
299 multiset.add(keyConverter.parse(key), new IntegerConverter().parse(value));
300 }
301 }
302 return multiset;
303 }
304
305
306 /**
307 * @since 2.7
308 */
309 public static Multiset<Integer> parseIntegerMultiset(String data) {
310 return parseMultiset(data, newIntegerConverter());
311 }
312
313 /**
314 * @since 2.7
315 */
316 public static Multiset<String> parseMultiset(String data) {
317 return parseMultiset(data, newStringConverter());
318 }
319
320 /**
321 * Transforms a string with the following format: "key1=value1;key2=value2..."
322 * into a Map<KEY, VALUE>. Requires to implement the transform(key,value) method
323 *
324 * @param data the input string
325 * @param transformer the interface to implement
326 * @return a Map of <key, value>
327 * @deprecated since 2.7
328 */
329 @Deprecated
330 public static <K, V> Map<K, V> parse(String data, Transformer<K, V> transformer) {
331 Map<String, String> rawData = parse(data);
332 Map<K, V> map = new HashMap<K, V>();
333 for (Map.Entry<String, String> entry : rawData.entrySet()) {
334 KeyValue<K, V> keyVal = transformer.transform(entry.getKey(), entry.getValue());
335 if (keyVal != null) {
336 map.put(keyVal.getKey(), keyVal.getValue());
337 }
338 }
339 return map;
340 }
341
342 private static <K, V> String formatEntries(Collection<Map.Entry<K, V>> entries, Converter<K> keyConverter, Converter<V> valueConverter) {
343 StringBuilder sb = new StringBuilder();
344 boolean first = true;
345 for (Map.Entry<K, V> entry : entries) {
346 if (!first) {
347 sb.append(PAIR_SEPARATOR);
348 }
349 sb.append(keyConverter.format(entry.getKey()));
350 sb.append(FIELD_SEPARATOR);
351 if (entry.getValue() != null) {
352 sb.append(valueConverter.format(entry.getValue()));
353 }
354 first = false;
355 }
356 return sb.toString();
357 }
358
359 private static <K> String formatEntries(Set<Multiset.Entry<K>> entries, Converter<K> keyConverter) {
360 StringBuilder sb = new StringBuilder();
361 boolean first = true;
362 for (Multiset.Entry<K> entry : entries) {
363 if (!first) {
364 sb.append(PAIR_SEPARATOR);
365 }
366 sb.append(keyConverter.format(entry.getElement()));
367 sb.append(FIELD_SEPARATOR);
368 sb.append(new IntegerConverter().format(entry.getCount()));
369 first = false;
370 }
371 return sb.toString();
372 }
373
374
375 /**
376 * @since 2.7
377 */
378 public static <K, V> String format(Map<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) {
379 return formatEntries(map.entrySet(), keyConverter, valueConverter);
380 }
381
382 /**
383 * @since 2.7
384 */
385 public static String format(Map map) {
386 return format(map, newToStringConverter(), newToStringConverter());
387 }
388
389 /**
390 * @since 2.7
391 */
392 public static String formatIntString(Map<Integer, String> map) {
393 return format(map, newIntegerConverter(), newStringConverter());
394 }
395
396 /**
397 * @since 2.7
398 */
399 public static String formatIntDouble(Map<Integer, Double> map) {
400 return format(map, newIntegerConverter(), newDoubleConverter());
401 }
402
403 /**
404 * @since 2.7
405 */
406 public static String formatIntDate(Map<Integer, Date> map) {
407 return format(map, newIntegerConverter(), newDateConverter());
408 }
409
410 /**
411 * @since 2.7
412 */
413 public static String formatIntDateTime(Map<Integer, Date> map) {
414 return format(map, newIntegerConverter(), newDateTimeConverter());
415 }
416
417 /**
418 * @since 2.7
419 */
420 public static String formatStringInt(Map<String, Integer> map) {
421 return format(map, newStringConverter(), newIntegerConverter());
422 }
423
424 /**
425 * Limitation: there's currently no methods to parse into Multimap.
426 *
427 * @since 2.7
428 */
429 public static <K, V> String format(Multimap<K, V> map, Converter<K> keyConverter, Converter<V> valueConverter) {
430 return formatEntries(map.entries(), keyConverter, valueConverter);
431 }
432
433 /**
434 * @since 2.7
435 */
436 public static <K> String format(Multiset<K> multiset, Converter<K> keyConverter) {
437 return formatEntries(multiset.entrySet(), keyConverter);
438 }
439
440 public static String format(Multiset multiset) {
441 return formatEntries(multiset.entrySet(), newToStringConverter());
442 }
443
444
445 /**
446 * @since 1.11
447 * @deprecated use Multiset from google collections instead of commons-collections bags
448 */
449 @Deprecated
450 public static String format(Bag bag) {
451 return format(bag, 0);
452 }
453
454 /**
455 * @since 1.11
456 * @deprecated use Multiset from google collections instead of commons-collections bags
457 */
458 @Deprecated
459 public static String format(Bag bag, int var) {
460 StringBuilder sb = new StringBuilder();
461 if (bag != null) {
462 boolean first = true;
463 for (Object obj : bag.uniqueSet()) {
464 if (!first) {
465 sb.append(PAIR_SEPARATOR);
466 }
467 sb.append(obj.toString());
468 sb.append(FIELD_SEPARATOR);
469 sb.append(bag.getCount(obj) + var);
470 first = false;
471 }
472 }
473 return sb.toString();
474 }
475
476
477 /**
478 * @deprecated since 2.7. Replaced by Converter
479 */
480 @Deprecated
481 public interface Transformer<K, V> {
482 KeyValue<K, V> transform(String key, String value);
483 }
484
485 /**
486 * Implementation of Transformer<String, Double>
487 *
488 * @deprecated since 2.7 replaced by Converter
489 */
490 @Deprecated
491 public static class StringNumberPairTransformer implements Transformer<String, Double> {
492 public KeyValue<String, Double> transform(String key, String value) {
493 return new KeyValue<String, Double>(key, toDouble(value));
494 }
495 }
496
497 /**
498 * Implementation of Transformer<Double, Double>
499 *
500 * @deprecated since 2.7. Replaced by Converter
501 */
502 @Deprecated
503 public static class DoubleNumbersPairTransformer implements Transformer<Double, Double> {
504 public KeyValue<Double, Double> transform(String key, String value) {
505 return new KeyValue<Double, Double>(toDouble(key), toDouble(value));
506 }
507 }
508
509 /**
510 * Implementation of Transformer<Integer, Integer>
511 *
512 * @deprecated since 2.7. Replaced by Converter
513 */
514 @Deprecated
515 public static class IntegerNumbersPairTransformer implements Transformer<Integer, Integer> {
516 public KeyValue<Integer, Integer> transform(String key, String value) {
517 return new KeyValue<Integer, Integer>(toInteger(key), toInteger(value));
518 }
519 }
520
521
522 /**
523 * Implementation of Transformer<RulePriority, Integer>
524 *
525 * @deprecated since 2.7. Replaced by Converter
526 */
527 @Deprecated
528 public static class RulePriorityNumbersPairTransformer implements Transformer<RulePriority, Integer> {
529
530 public KeyValue<RulePriority, Integer> transform(String key, String value) {
531 try {
532 if (StringUtils.isBlank(value)) {
533 value = "0";
534 }
535 return new KeyValue<RulePriority, Integer>(RulePriority.valueOf(key.toUpperCase()), Integer.parseInt(value));
536 } catch (Exception e) {
537 LoggerFactory.getLogger(RulePriorityNumbersPairTransformer.class).warn("Property " + key + " has invalid value: " + value, e);
538 return null;
539 }
540 }
541 }
542
543 private static Double toDouble(String value) {
544 return StringUtils.isBlank(value) ? null : NumberUtils.toDouble(value);
545 }
546
547 private static Integer toInteger(String value) {
548 return StringUtils.isBlank(value) ? null : NumberUtils.toInt(value);
549 }
550 }