1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.tomgibara.pronto.util;
19
20 import java.util.ArrayList;
21 import java.util.LinkedHashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 /**
28 * A collection of static utility methods for processing strings.
29 *
30 * @author Tom Gibara
31 */
32
33 public final class Strings {
34
35 /** Pattern for splitting at commas. */
36 private static final Pattern COMMA_SPLIT = Pattern.compile("\\s*,\\s*");
37
38 /** Pattern for matching colon separated key-value pairs. */
39 private static final Pattern KEY_VALUE = Pattern.compile("\\s*(\\S+)\\s*:(.*)");
40
41 /** Pattern for splitting values at a semi colon. */
42 private static final Pattern SEMI_SPLIT = Pattern.compile(";");
43
44 /**
45 * Splits a single string into a list of strings as follows:
46 *
47 * 1) The string is split at each comma. 2) Each resulting substring is
48 * trimmed and added to the list
49 *
50 * Note that there is no way of escaping a comma within a value.
51 *
52 * @param str
53 * the string to be parsed
54 * @return a list of substrings
55 */
56
57 public static List<String> splitCommas(final String str) {
58 ArrayList<String> list = new ArrayList<String>();
59 Matcher matcher = COMMA_SPLIT.matcher(str);
60 int last = 0;
61 while (true) {
62 if (matcher.find()) {
63 String value = str.substring(last, matcher.start());
64 if (last == 0) value = value.trim();
65
66 list.add(value);
67 last = matcher.end();
68 } else {
69 String value = str.substring(last).trim();
70
71
72 list.add(value);
73 break;
74 }
75
76 }
77 return list;
78 }
79
80 /**
81 * Constructs a map of strings from a single string as follows:
82 *
83 * 1) The string is split at each semicolon. 2) Each component is split at a
84 * colon into a key and a value. 3) Each key-value pair is trimmed and added
85 * to the map
86 *
87 * This encoding should be superficially familiar to those familiar with CSS
88 * style properties. Note that there are no mechanisms for escaping colons
89 * and semicolons. Keys may not be duplicated.
90 *
91 * @param propsStr
92 * the string to be parsed
93 * @return a map of string properties
94 */
95
96 public static Map<String, String> parseProperties(final String propsStr) {
97 LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
98 String[] propArr = SEMI_SPLIT.split(propsStr);
99 for (String propStr : propArr) {
100 Matcher kvMatcher = KEY_VALUE.matcher(propStr);
101 if (!kvMatcher.matches()) {
102
103 if (propStr.trim().length() == 0) continue;
104 throw new IllegalArgumentException(String.format("Illegal key/value pair: %s", propStr));
105 }
106 String key = kvMatcher.group(1);
107 if (map.containsKey(key)) throw new IllegalArgumentException(String.format("Duplicate key: %s", key));
108 String value = kvMatcher.group(2).trim();
109 map.put(key, value);
110 }
111 return map;
112 }
113
114 private Strings() {
115 }
116
117 /**
118 * Joins the string representations of an array of objects into a single
119 * string, separating them with the supplied delimiter. A zero length array
120 * results in an empty string.
121 *
122 * @param array
123 * the array of objects to be joined
124 * @param delimiter
125 * string to be inserted between array element
126 *
127 * @return all the elements of the array concatenated into a single string,
128 * never null
129 *
130 */
131
132 public static String join(final Object[] array, final String delimiter) {
133 Arguments.notNull(array, "array");
134 Arguments.notNull(delimiter, "delimiter");
135
136 switch (array.length) {
137 case 0:
138 return "";
139 case 1:
140 return array[0] == null ? "null" : array[0].toString();
141 case 2:
142 return array[0] + delimiter + array[1];
143 default:
144 StringBuilder sb = new StringBuilder();
145 for (Object obj : array) {
146 if (sb.length() != 0) sb.append(delimiter);
147 sb.append(obj);
148 }
149 return sb.toString();
150 }
151 }
152
153 }