Change build scripts
[jvcard.git] / src / be / nikiroo / jvcard / resources / StringUtils.java
1 package be.nikiroo.jvcard.resources;
2
3 import java.awt.Image;
4 import java.awt.geom.AffineTransform;
5 import java.awt.image.AffineTransformOp;
6 import java.awt.image.BufferedImage;
7 import java.io.ByteArrayInputStream;
8 import java.io.ByteArrayOutputStream;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.security.MessageDigest;
14 import java.security.NoSuchAlgorithmException;
15 import java.text.Normalizer;
16 import java.text.Normalizer.Form;
17 import java.text.ParseException;
18 import java.text.SimpleDateFormat;
19 import java.util.Date;
20 import java.util.regex.Pattern;
21
22 import javax.imageio.ImageIO;
23 import javax.xml.bind.DatatypeConverter;
24
25 import com.googlecode.lanterna.gui2.LinearLayout.Alignment;
26
27 public class StringUtils {
28 static private Pattern marks = Pattern
29 .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
30
31 /**
32 * Fix the size of the given {@link String} either with space-padding or by
33 * shortening it.
34 *
35 * @param text
36 * the {@link String} to fix
37 * @param width
38 * the size of the resulting {@link String} or -1 for a noop
39 *
40 * @return the resulting {@link String} of size <i>size</i>
41 */
42 static public String padString(String text, int width) {
43 return padString(text, width, true, Alignment.Beginning);
44 }
45
46 /**
47 * Fix the size of the given {@link String} either with space-padding or by
48 * optionally shortening it.
49 *
50 * @param text
51 * the {@link String} to fix
52 * @param width
53 * the size of the resulting {@link String} if the text fits or
54 * if cut is TRUE or -1 for a noop
55 * @param cut
56 * cut the {@link String} shorter if needed
57 * @param align
58 * align the {@link String} in this position if we have enough
59 * space
60 *
61 * @return the resulting {@link String} of size <i>size</i> minimum
62 */
63 static public String padString(String text, int width, boolean cut,
64 Alignment align) {
65
66 if (width >= 0) {
67 if (text == null)
68 text = "";
69
70 int diff = width - text.length();
71
72 if (diff < 0) {
73 if (cut)
74 text = text.substring(0, width);
75 } else if (diff > 0) {
76 if (diff < 2 && align != Alignment.End)
77 align = Alignment.Beginning;
78
79 switch (align) {
80 case Beginning:
81 text = text + new String(new char[diff]).replace('\0', ' ');
82 break;
83 case End:
84 text = new String(new char[diff]).replace('\0', ' ') + text;
85 break;
86 case Center:
87 case Fill:
88 default:
89 int pad1 = (diff) / 2;
90 int pad2 = (diff + 1) / 2;
91 text = new String(new char[pad1]).replace('\0', ' ') + text
92 + new String(new char[pad2]).replace('\0', ' ');
93 break;
94 }
95 }
96 }
97
98 return text;
99 }
100
101 /**
102 * Sanitise the given input to make it more Terminal-friendly by removing
103 * combining characters.
104 *
105 * @param input
106 * the input to sanitise
107 * @param allowUnicode
108 * allow Unicode or only allow ASCII Latin characters
109 *
110 * @return the sanitised {@link String}
111 */
112 static public String sanitize(String input, boolean allowUnicode) {
113 return sanitize(input, allowUnicode, !allowUnicode);
114 }
115
116 /**
117 * Sanitise the given input to make it more Terminal-friendly by removing
118 * combining characters.
119 *
120 * @param input
121 * the input to sanitise
122 * @param allowUnicode
123 * allow Unicode or only allow ASCII Latin characters
124 * @param removeAllAccents
125 * TRUE to replace all accentuated characters by their non
126 * accentuated counter-parts
127 *
128 * @return the sanitised {@link String}
129 */
130 static public String sanitize(String input, boolean allowUnicode,
131 boolean removeAllAccents) {
132
133 if (removeAllAccents) {
134 input = Normalizer.normalize(input, Form.NFKD);
135 input = marks.matcher(input).replaceAll("");
136 }
137
138 input = Normalizer.normalize(input, Form.NFKC);
139
140 if (!allowUnicode) {
141 StringBuilder builder = new StringBuilder();
142 for (int index = 0; index < input.length(); index++) {
143 char car = input.charAt(index);
144 // displayable chars in ASCII are in the range 32<->255,
145 // except DEL (127)
146 if (car >= 32 && car <= 255 && car != 127) {
147 builder.append(car);
148 }
149 }
150 input = builder.toString();
151 }
152
153 return input;
154 }
155
156 /**
157 * Convert between time in milliseconds to {@link String} in a "static" way
158 * (to exchange data over the wire, for instance).
159 *
160 * @param time
161 * the time in milliseconds
162 *
163 * @return the time as a {@link String}
164 */
165 static public String fromTime(long time) {
166 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
167 return sdf.format(new Date(time));
168 }
169
170 /**
171 * Convert between time as a {@link String} to milliseconds in a "static"
172 * way (to exchange data over the wire, for instance).
173 *
174 * @param time
175 * the time as a {@link String}
176 *
177 * @return the time in milliseconds
178 */
179 static public long toTime(String display) {
180 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
181 try {
182 return sdf.parse(display).getTime();
183 } catch (ParseException e) {
184 return -1;
185 }
186 }
187
188 /**
189 * Convert the given {@link Image} object into a Base64 representation of
190 * the same {@link Image}. object.
191 *
192 * @param image
193 * the {@link Image} object to convert
194 *
195 * @return the Base64 representation
196 *
197 * @throws IOException
198 * in case of IO error
199 */
200 static public String fromImage(BufferedImage image) throws IOException {
201 String imageString = null;
202 ByteArrayOutputStream out = new ByteArrayOutputStream();
203
204 ImageIO.write(image, "jpeg", out);
205 byte[] imageBytes = out.toByteArray();
206
207 imageString = DatatypeConverter.printBase64Binary(imageBytes);
208
209 out.close();
210
211 return imageString;
212 }
213
214 /**
215 * Convert the given {@link File} image into a Base64 representation of the
216 * same {@link File}.
217 *
218 * @param file
219 * the {@link File} image to convert
220 *
221 * @return the Base64 representation
222 *
223 * @throws IOException
224 * in case of IO error
225 */
226 static public String fromImage(File file) throws IOException {
227 String fileString = null;
228 ByteArrayOutputStream out = new ByteArrayOutputStream();
229
230 byte[] buf = new byte[8192];
231 InputStream in = new FileInputStream(file);
232
233 int c = 0;
234 while ((c = in.read(buf, 0, buf.length)) > 0) {
235 out.write(buf, 0, c);
236 }
237 out.flush();
238 in.close();
239
240 fileString = DatatypeConverter.printBase64Binary(out.toByteArray());
241 out.close();
242
243 return fileString;
244 }
245
246 /**
247 * Convert the given Base64 representation of an image into an {@link Image}
248 * object.
249 *
250 * @param b64data
251 * the {@link Image} in Base64 format
252 *
253 * @return the {@link Image} object
254 *
255 * @throws IOException
256 * in case of IO error
257 */
258 static public BufferedImage toImage(String b64data) throws IOException {
259 ByteArrayInputStream in = new ByteArrayInputStream(
260 DatatypeConverter.parseBase64Binary(b64data));
261
262 int orientation;
263 try {
264 orientation = getExifTransorm(in);
265 } catch (Exception e) {
266 // no EXIF transform, ok
267 orientation = -1;
268 }
269
270 in.reset();
271 BufferedImage image = ImageIO.read(in);
272
273 // Note: this code has been found on internet;
274 // thank you anonymous coder.
275 int width = image.getWidth();
276 int height = image.getHeight();
277 AffineTransform affineTransform = new AffineTransform();
278
279 switch (orientation) {
280 case 1:
281 break;
282 case 2: // Flip X
283 affineTransform.scale(-1.0, 1.0);
284 affineTransform.translate(-width, 0);
285 break;
286 case 3: // PI rotation
287 affineTransform.translate(width, height);
288 affineTransform.rotate(Math.PI);
289 break;
290 case 4: // Flip Y
291 affineTransform.scale(1.0, -1.0);
292 affineTransform.translate(0, -height);
293 break;
294 case 5: // - PI/2 and Flip X
295 affineTransform.rotate(-Math.PI / 2);
296 affineTransform.scale(-1.0, 1.0);
297 break;
298 case 6: // -PI/2 and -width
299 affineTransform.translate(height, 0);
300 affineTransform.rotate(Math.PI / 2);
301 break;
302 case 7: // PI/2 and Flip
303 affineTransform.scale(-1.0, 1.0);
304 affineTransform.translate(-height, 0);
305 affineTransform.translate(0, width);
306 affineTransform.rotate(3 * Math.PI / 2);
307 break;
308 case 8: // PI / 2
309 affineTransform.translate(0, width);
310 affineTransform.rotate(3 * Math.PI / 2);
311 break;
312 default:
313 affineTransform = null;
314 break;
315 }
316
317 if (affineTransform != null) {
318 AffineTransformOp affineTransformOp = new AffineTransformOp(
319 affineTransform, AffineTransformOp.TYPE_BILINEAR);
320
321 BufferedImage transformedImage = new BufferedImage(height, width,
322 image.getType());
323 transformedImage = affineTransformOp
324 .filter(image, transformedImage);
325
326 image = transformedImage;
327 }
328 //
329
330 return image;
331 }
332
333 /**
334 * Return a hash of the given {@link String}.
335 *
336 * @param input
337 * the input data
338 *
339 * @return the hash
340 */
341 static public String getHash(String input) {
342 try {
343 MessageDigest md = MessageDigest.getInstance("MD5");
344 md.update(input.getBytes());
345 byte byteData[] = md.digest();
346
347 StringBuffer hexString = new StringBuffer();
348 for (int i = 0; i < byteData.length; i++) {
349 String hex = Integer.toHexString(0xff & byteData[i]);
350 if (hex.length() == 1)
351 hexString.append('0');
352 hexString.append(hex);
353 }
354
355 return hexString.toString();
356 } catch (NoSuchAlgorithmException e) {
357 // all JVM most probably have an MD5 implementation, but even if
358 // not, returning the input is "correct", if inefficient and
359 // unsecure
360 return input;
361 }
362 }
363
364 /**
365 * Return the EXIF transformation flag of this image if any.
366 *
367 * <p>
368 * Note: this code has been found on internet; thank you anonymous coder.
369 * </p>
370 *
371 * @param in
372 * the data {@link InputStream}
373 *
374 * @return the transformation flag if any
375 *
376 * @throws IOException
377 * in case of IO error
378 */
379 static private int getExifTransorm(InputStream in) throws IOException {
380 int[] exif_data = new int[100];
381 int set_flag = 0;
382 int is_motorola = 0;
383
384 /* Read File head, check for JPEG SOI + Exif APP1 */
385 for (int i = 0; i < 4; i++)
386 exif_data[i] = in.read();
387
388 if (exif_data[0] != 0xFF || exif_data[1] != 0xD8
389 || exif_data[2] != 0xFF || exif_data[3] != 0xE1)
390 return -2;
391
392 /* Get the marker parameter length count */
393 int length = (in.read() << 8 | in.read());
394
395 /* Length includes itself, so must be at least 2 */
396 /* Following Exif data length must be at least 6 */
397 if (length < 8)
398 return -1;
399 length -= 8;
400 /* Read Exif head, check for "Exif" */
401 for (int i = 0; i < 6; i++)
402 exif_data[i] = in.read();
403
404 if (exif_data[0] != 0x45 || exif_data[1] != 0x78
405 || exif_data[2] != 0x69 || exif_data[3] != 0x66
406 || exif_data[4] != 0 || exif_data[5] != 0)
407 return -1;
408
409 /* Read Exif body */
410 length = length > exif_data.length ? exif_data.length : length;
411 for (int i = 0; i < length; i++)
412 exif_data[i] = in.read();
413
414 if (length < 12)
415 return -1; /* Length of an IFD entry */
416
417 /* Discover byte order */
418 if (exif_data[0] == 0x49 && exif_data[1] == 0x49)
419 is_motorola = 0;
420 else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D)
421 is_motorola = 1;
422 else
423 return -1;
424
425 /* Check Tag Mark */
426 if (is_motorola == 1) {
427 if (exif_data[2] != 0)
428 return -1;
429 if (exif_data[3] != 0x2A)
430 return -1;
431 } else {
432 if (exif_data[3] != 0)
433 return -1;
434 if (exif_data[2] != 0x2A)
435 return -1;
436 }
437
438 /* Get first IFD offset (offset to IFD0) */
439 int offset;
440 if (is_motorola == 1) {
441 if (exif_data[4] != 0)
442 return -1;
443 if (exif_data[5] != 0)
444 return -1;
445 offset = exif_data[6];
446 offset <<= 8;
447 offset += exif_data[7];
448 } else {
449 if (exif_data[7] != 0)
450 return -1;
451 if (exif_data[6] != 0)
452 return -1;
453 offset = exif_data[5];
454 offset <<= 8;
455 offset += exif_data[4];
456 }
457 if (offset > length - 2)
458 return -1; /* check end of data segment */
459
460 /* Get the number of directory entries contained in this IFD */
461 int number_of_tags;
462 if (is_motorola == 1) {
463 number_of_tags = exif_data[offset];
464 number_of_tags <<= 8;
465 number_of_tags += exif_data[offset + 1];
466 } else {
467 number_of_tags = exif_data[offset + 1];
468 number_of_tags <<= 8;
469 number_of_tags += exif_data[offset];
470 }
471 if (number_of_tags == 0)
472 return -1;
473 offset += 2;
474
475 /* Search for Orientation Tag in IFD0 */
476 for (;;) {
477 if (offset > length - 12)
478 return -1; /* check end of data segment */
479 /* Get Tag number */
480 int tagnum;
481 if (is_motorola == 1) {
482 tagnum = exif_data[offset];
483 tagnum <<= 8;
484 tagnum += exif_data[offset + 1];
485 } else {
486 tagnum = exif_data[offset + 1];
487 tagnum <<= 8;
488 tagnum += exif_data[offset];
489 }
490 if (tagnum == 0x0112)
491 break; /* found Orientation Tag */
492 if (--number_of_tags == 0)
493 return -1;
494 offset += 12;
495 }
496
497 /* Get the Orientation value */
498 if (is_motorola == 1) {
499 if (exif_data[offset + 8] != 0)
500 return -1;
501 set_flag = exif_data[offset + 9];
502 } else {
503 if (exif_data[offset + 9] != 0)
504 return -1;
505 set_flag = exif_data[offset + 8];
506 }
507 if (set_flag > 8)
508 return -1;
509
510 return set_flag;
511 }
512 }