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