off-by-one error
[nikiroo-utils.git] / src / be / nikiroo / utils / resources / BundleHelper.java
1 package be.nikiroo.utils.resources;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 /**
7 * Internal class used to convert data to/from {@link String}s in the context of
8 * {@link Bundle}s.
9 *
10 * @author niki
11 */
12 class BundleHelper {
13 /**
14 * Convert the given {@link String} into a {@link Boolean} if it represents
15 * a {@link Boolean}, or NULL if it doesn't.
16 * <p>
17 * Note: null, "strange text", ""... will all be converted to NULL.
18 *
19 * @param str
20 * the input {@link String}
21 *
22 * @return the converted {@link Boolean} or NULL
23 */
24 static public Boolean parseBoolean(String str) {
25 if (str != null && str.length() > 0) {
26 if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("on")
27 || str.equalsIgnoreCase("yes"))
28 return true;
29 if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("off")
30 || str.equalsIgnoreCase("no"))
31 return false;
32
33 }
34
35 return null;
36 }
37
38 /**
39 * Return a {@link String} representation of the given {@link Boolean}.
40 *
41 * @param value
42 * the input value
43 *
44 * @return the raw {@link String} value that correspond to it
45 */
46 static public String fromBoolean(boolean value) {
47 return Boolean.toString(value);
48 }
49
50 /**
51 * Convert the given {@link String} into a {@link Integer} if it represents
52 * a {@link Integer}, or NULL if it doesn't.
53 * <p>
54 * Note: null, "strange text", ""... will all be converted to NULL.
55 *
56 * @param str
57 * the input {@link String}
58 *
59 * @return the converted {@link Integer} or NULL
60 */
61 static public Integer parseInteger(String str) {
62 try {
63 return Integer.parseInt(str);
64 } catch (Exception e) {
65 }
66
67 return null;
68 }
69
70 /**
71 * Return a {@link String} representation of the given {@link Integer}.
72 *
73 * @param value
74 * the input value
75 *
76 * @return the raw {@link String} value that correspond to it
77 */
78 static public String fromInteger(int value) {
79 return Integer.toString(value);
80 }
81
82 /**
83 * Return a {@link String} representation of the given {@link Integer}.
84 *
85 * @param value
86 * the input value
87 *
88 * @return the raw {@link String} value that correspond to it
89 */
90 static public String fromBoolean(int value) {
91 return Integer.toString(value);
92 }
93
94 /**
95 * Convert the given {@link String} into a {@link Character} if it
96 * represents a {@link Character}, or NULL if it doesn't.
97 * <p>
98 * Note: null, "strange text", ""... will all be converted to NULL
99 * (remember: any {@link String} whose length is not 1 is <b>not</b> a
100 * {@link Character}).
101 *
102 * @param str
103 * the input {@link String}
104 *
105 * @return the converted {@link Character} or NULL
106 */
107 static public Character parseCharacter(String str) {
108 String s = str.trim();
109 if (s.length() == 1) {
110 return s.charAt(0);
111 }
112
113 return null;
114 }
115
116 /**
117 * Return a {@link String} representation of the given {@link Boolean}.
118 *
119 * @param value
120 * the input value
121 *
122 * @return the raw {@link String} value that correspond to it
123 */
124 static public String fromCharacter(char value) {
125 return Character.toString(value);
126 }
127
128 /**
129 * Convert the given {@link String} into a colour (represented here as an
130 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
131 * <p>
132 * The returned colour value is an ARGB value.
133 *
134 * @param str
135 * the input {@link String}
136 *
137 * @return the converted colour as an {@link Integer} value or NULL
138 */
139 static Integer parseColor(String str) {
140 Integer rep = null;
141
142 str = str.trim();
143 int r = 0, g = 0, b = 0, a = -1;
144 if (str.startsWith("#") && (str.length() == 7 || str.length() == 9)) {
145 try {
146 r = Integer.parseInt(str.substring(1, 3), 16);
147 g = Integer.parseInt(str.substring(3, 5), 16);
148 b = Integer.parseInt(str.substring(5, 7), 16);
149 if (str.length() == 9) {
150 a = Integer.parseInt(str.substring(7, 9), 16);
151 } else {
152 a = 255;
153 }
154
155 } catch (NumberFormatException e) {
156 // no changes
157 }
158 }
159
160 // Try by name if still not found
161 if (a == -1) {
162 if ("black".equalsIgnoreCase(str)) {
163 a = 255;
164 r = 0;
165 g = 0;
166 b = 0;
167 } else if ("white".equalsIgnoreCase(str)) {
168 a = 255;
169 r = 255;
170 g = 255;
171 b = 255;
172 } else if ("red".equalsIgnoreCase(str)) {
173 a = 255;
174 r = 255;
175 g = 0;
176 b = 0;
177 } else if ("green".equalsIgnoreCase(str)) {
178 a = 255;
179 r = 0;
180 g = 255;
181 b = 0;
182 } else if ("blue".equalsIgnoreCase(str)) {
183 a = 255;
184 r = 0;
185 g = 0;
186 b = 255;
187 } else if ("grey".equalsIgnoreCase(str)
188 || "gray".equalsIgnoreCase(str)) {
189 a = 255;
190 r = 128;
191 g = 128;
192 b = 128;
193 } else if ("cyan".equalsIgnoreCase(str)) {
194 a = 255;
195 r = 0;
196 g = 255;
197 b = 255;
198 } else if ("magenta".equalsIgnoreCase(str)) {
199 a = 255;
200 r = 255;
201 g = 0;
202 b = 255;
203 } else if ("yellow".equalsIgnoreCase(str)) {
204 a = 255;
205 r = 255;
206 g = 255;
207 b = 0;
208 }
209 }
210
211 if (a != -1) {
212 rep = ((a & 0xFF) << 24) //
213 | ((r & 0xFF) << 16) //
214 | ((g & 0xFF) << 8) //
215 | ((b & 0xFF) << 0);
216 }
217
218 return rep;
219 }
220
221 /**
222 * Return a {@link String} representation of the given colour.
223 * <p>
224 * The colour value is interpreted as an ARGB value.
225 *
226 * @param color
227 * the ARGB colour value
228 * @return the raw {@link String} value that correspond to it
229 */
230 static public String fromColor(int color) {
231 int a = (color >> 24) & 0xFF;
232 int r = (color >> 16) & 0xFF;
233 int g = (color >> 8) & 0xFF;
234 int b = (color >> 0) & 0xFF;
235
236 String rs = Integer.toString(r, 16);
237 String gs = Integer.toString(g, 16);
238 String bs = Integer.toString(b, 16);
239 String as = "";
240 if (a < 255) {
241 as = Integer.toString(a, 16);
242 }
243
244 return "#" + rs + gs + bs + as;
245 }
246
247 /**
248 * Return a {@link String} representation of the given list of values.
249 * <p>
250 * The list of values is comma-separated and each value is surrounded by
251 * double-quotes; backslashes and double-quotes are escaped by a backslash.
252 *
253 * @param str
254 * the input value
255 * @return the raw {@link String} value that correspond to it
256 */
257 static public List<String> parseList(String str) {
258 if (str == null) {
259 return null;
260 }
261 List<String> list = new ArrayList<String>();
262 try {
263 boolean inQuote = false;
264 boolean prevIsBackSlash = false;
265 StringBuilder builder = new StringBuilder();
266 for (int i = 0; i < str.length(); i++) {
267 char car = str.charAt(i);
268
269 if (prevIsBackSlash) {
270 // We don't process it here
271 builder.append(car);
272 prevIsBackSlash = false;
273 } else {
274 switch (car) {
275 case '"':
276 // We don't process it here
277 builder.append(car);
278
279 if (inQuote) {
280 list.add(unescape(builder.toString()));
281 builder.setLength(0);
282 }
283
284 inQuote = !inQuote;
285 break;
286 case '\\':
287 // We don't process it here
288 builder.append(car);
289 prevIsBackSlash = true;
290 break;
291 case ' ':
292 case '\n':
293 case '\r':
294 if (inQuote) {
295 builder.append(car);
296 }
297 break;
298
299 case ',':
300 if (!inQuote) {
301 break;
302 }
303 // continue to default
304 default:
305 if (!inQuote) {
306 // Bad format!
307 return null;
308 }
309
310 builder.append(car);
311 break;
312 }
313 }
314 }
315
316 if (inQuote || prevIsBackSlash) {
317 // Bad format!
318 return null;
319 }
320
321 } catch (Exception e) {
322 return null;
323 }
324
325 return list;
326 }
327
328 /**
329 * Return a {@link String} representation of the given list of values.
330 *
331 * @param list
332 * the input value
333 *
334 * @return the raw {@link String} value that correspond to it
335 */
336 static public String fromList(List<String> list) {
337 StringBuilder builder = new StringBuilder();
338 for (String item : list) {
339 if (builder.length() > 0) {
340 builder.append(", ");
341 }
342 builder.append(escape(item));
343 }
344
345 return builder.toString();
346 }
347
348 /**
349 * Escape the given value for list formating (no \\, no \n).
350 * <p>
351 * You can unescape it with {@link BundleHelper#unescape(String)}
352 *
353 * @param value
354 * the value to escape
355 *
356 * @return an escaped value that can unquoted by the reverse operation
357 * {@link BundleHelper#unescape(String)}
358 */
359 static public String escape(String value) {
360 return '"' + value//
361 .replace("\\", "\\\\") //
362 .replace("\"", "\\\"") //
363 .replace("\n", "\\\n") //
364 .replace("\r", "\\\r") //
365 + '"';
366 }
367
368 /**
369 * Unescape the given value for list formating (change \\n into \n and so
370 * on).
371 * <p>
372 * You can escape it with {@link BundleHelper#escape(String)}
373 *
374 * @param value
375 * the value to escape
376 *
377 * @return an unescaped value that can reverted by the reverse operation
378 * {@link BundleHelper#escape(String)}, or NULL if it was badly
379 * formated
380 */
381 static public String unescape(String value) {
382 if (value.length() < 2 || !value.startsWith("\"")
383 || !value.endsWith("\"")) {
384 // Bad format
385 return null;
386 }
387
388 value = value.substring(1, value.length() - 1);
389
390 boolean prevIsBackslash = false;
391 StringBuilder builder = new StringBuilder();
392 for (char car : value.toCharArray()) {
393 if (prevIsBackslash) {
394 switch (car) {
395 case 'n':
396 case 'N':
397 builder.append('\n');
398 break;
399 case 'r':
400 case 'R':
401 builder.append('\r');
402 break;
403 default: // includes \ and "
404 builder.append(car);
405 break;
406 }
407 } else {
408 if (car == '\\') {
409 prevIsBackslash = true;
410 } else {
411 builder.append(car);
412 }
413 }
414 }
415
416 if (prevIsBackslash) {
417 // Bad format
418 return null;
419 }
420
421 return builder.toString();
422 }
423 }