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 }