001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 abstract static 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 }