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