1 package be
.nikiroo
.jvcard
.resources
;
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
;
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
;
22 import javax
.imageio
.ImageIO
;
23 import javax
.xml
.bind
.DatatypeConverter
;
25 import com
.googlecode
.lanterna
.gui2
.LinearLayout
.Alignment
;
27 public class StringUtils
{
28 static private Pattern marks
= Pattern
29 .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
32 * Fix the size of the given {@link String} either with space-padding or by
36 * the {@link String} to fix
38 * the size of the resulting {@link String} or -1 for a noop
40 * @return the resulting {@link String} of size <i>size</i>
42 static public String
padString(String text
, int width
) {
43 return padString(text
, width
, true, Alignment
.Beginning
);
47 * Fix the size of the given {@link String} either with space-padding or by
48 * optionally shortening it.
51 * the {@link String} to fix
53 * the size of the resulting {@link String} if the text fits or
54 * if cut is TRUE or -1 for a noop
56 * cut the {@link String} shorter if needed
58 * align the {@link String} in this position if we have enough
61 * @return the resulting {@link String} of size <i>size</i> minimum
63 static public String
padString(String text
, int width
, boolean cut
,
70 int diff
= width
- text
.length();
74 text
= text
.substring(0, width
);
75 } else if (diff
> 0) {
76 if (diff
< 2 && align
!= Alignment
.End
)
77 align
= Alignment
.Beginning
;
81 text
= text
+ new String(new char[diff
]).replace('\0', ' ');
84 text
= new String(new char[diff
]).replace('\0', ' ') + text
;
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', ' ');
102 * Sanitise the given input to make it more Terminal-friendly by removing
103 * combining characters.
106 * the input to sanitise
107 * @param allowUnicode
108 * allow Unicode or only allow ASCII Latin characters
110 * @return the sanitised {@link String}
112 static public String
sanitize(String input
, boolean allowUnicode
) {
113 return sanitize(input
, allowUnicode
, !allowUnicode
);
117 * Sanitise the given input to make it more Terminal-friendly by removing
118 * combining characters.
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
128 * @return the sanitised {@link String}
130 static public String
sanitize(String input
, boolean allowUnicode
,
131 boolean removeAllAccents
) {
133 if (removeAllAccents
) {
134 input
= Normalizer
.normalize(input
, Form
.NFKD
);
135 input
= marks
.matcher(input
).replaceAll("");
138 input
= Normalizer
.normalize(input
, Form
.NFKC
);
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,
146 if (car
>= 32 && car
<= 255 && car
!= 127) {
150 input
= builder
.toString();
157 * Convert between time in milliseconds to {@link String} in a "static" way
158 * (to exchange data over the wire, for instance).
161 * the time in milliseconds
163 * @return the time as a {@link String}
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
));
171 * Convert between time as a {@link String} to milliseconds in a "static"
172 * way (to exchange data over the wire, for instance).
175 * the time as a {@link String}
177 * @return the time in milliseconds
179 static public long toTime(String display
) {
180 SimpleDateFormat sdf
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
182 return sdf
.parse(display
).getTime();
183 } catch (ParseException e
) {
189 * Convert the given {@link Image} object into a Base64 representation of
190 * the same {@link Image}. object.
193 * the {@link Image} object to convert
195 * @return the Base64 representation
197 * @throws IOException
198 * in case of IO error
200 static public String
fromImage(BufferedImage image
) throws IOException
{
201 String imageString
= null;
202 ByteArrayOutputStream out
= new ByteArrayOutputStream();
204 ImageIO
.write(image
, "jpeg", out
);
205 byte[] imageBytes
= out
.toByteArray();
207 imageString
= DatatypeConverter
.printBase64Binary(imageBytes
);
215 * Convert the given {@link File} image into a Base64 representation of the
219 * the {@link File} image to convert
221 * @return the Base64 representation
223 * @throws IOException
224 * in case of IO error
226 static public String
fromImage(File file
) throws IOException
{
227 String fileString
= null;
228 ByteArrayOutputStream out
= new ByteArrayOutputStream();
230 byte[] buf
= new byte[8192];
231 InputStream in
= new FileInputStream(file
);
234 while ((c
= in
.read(buf
, 0, buf
.length
)) > 0) {
235 out
.write(buf
, 0, c
);
240 fileString
= DatatypeConverter
.printBase64Binary(out
.toByteArray());
247 * Convert the given Base64 representation of an image into an {@link Image}
251 * the {@link Image} in Base64 format
253 * @return the {@link Image} object
255 * @throws IOException
256 * in case of IO error
258 static public BufferedImage
toImage(String b64data
) throws IOException
{
259 ByteArrayInputStream in
= new ByteArrayInputStream(
260 DatatypeConverter
.parseBase64Binary(b64data
));
264 orientation
= getExifTransorm(in
);
265 } catch (Exception e
) {
266 // no EXIF transform, ok
271 BufferedImage image
= ImageIO
.read(in
);
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();
279 switch (orientation
) {
283 affineTransform
.scale(-1.0, 1.0);
284 affineTransform
.translate(-width
, 0);
286 case 3: // PI rotation
287 affineTransform
.translate(width
, height
);
288 affineTransform
.rotate(Math
.PI
);
291 affineTransform
.scale(1.0, -1.0);
292 affineTransform
.translate(0, -height
);
294 case 5: // - PI/2 and Flip X
295 affineTransform
.rotate(-Math
.PI
/ 2);
296 affineTransform
.scale(-1.0, 1.0);
298 case 6: // -PI/2 and -width
299 affineTransform
.translate(height
, 0);
300 affineTransform
.rotate(Math
.PI
/ 2);
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);
309 affineTransform
.translate(0, width
);
310 affineTransform
.rotate(3 * Math
.PI
/ 2);
313 affineTransform
= null;
317 if (affineTransform
!= null) {
318 AffineTransformOp affineTransformOp
= new AffineTransformOp(
319 affineTransform
, AffineTransformOp
.TYPE_BILINEAR
);
321 BufferedImage transformedImage
= new BufferedImage(height
, width
,
323 transformedImage
= affineTransformOp
324 .filter(image
, transformedImage
);
326 image
= transformedImage
;
334 * Return a hash of the given {@link String}.
341 static public String
getHash(String input
) {
343 MessageDigest md
= MessageDigest
.getInstance("MD5");
344 md
.update(input
.getBytes());
345 byte byteData
[] = md
.digest();
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
);
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
365 * Return the EXIF transformation flag of this image if any.
368 * Note: this code has been found on internet; thank you anonymous coder.
372 * the data {@link InputStream}
374 * @return the transformation flag if any
376 * @throws IOException
377 * in case of IO error
379 static private int getExifTransorm(InputStream in
) throws IOException
{
380 int[] exif_data
= new int[100];
384 /* Read File head, check for JPEG SOI + Exif APP1 */
385 for (int i
= 0; i
< 4; i
++)
386 exif_data
[i
] = in
.read();
388 if (exif_data
[0] != 0xFF || exif_data
[1] != 0xD8
389 || exif_data
[2] != 0xFF || exif_data
[3] != 0xE1)
392 /* Get the marker parameter length count */
393 int length
= (in
.read() << 8 | in
.read());
395 /* Length includes itself, so must be at least 2 */
396 /* Following Exif data length must be at least 6 */
400 /* Read Exif head, check for "Exif" */
401 for (int i
= 0; i
< 6; i
++)
402 exif_data
[i
] = in
.read();
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)
410 length
= length
> exif_data
.length ? exif_data
.length
: length
;
411 for (int i
= 0; i
< length
; i
++)
412 exif_data
[i
] = in
.read();
415 return -1; /* Length of an IFD entry */
417 /* Discover byte order */
418 if (exif_data
[0] == 0x49 && exif_data
[1] == 0x49)
420 else if (exif_data
[0] == 0x4D && exif_data
[1] == 0x4D)
426 if (is_motorola
== 1) {
427 if (exif_data
[2] != 0)
429 if (exif_data
[3] != 0x2A)
432 if (exif_data
[3] != 0)
434 if (exif_data
[2] != 0x2A)
438 /* Get first IFD offset (offset to IFD0) */
440 if (is_motorola
== 1) {
441 if (exif_data
[4] != 0)
443 if (exif_data
[5] != 0)
445 offset
= exif_data
[6];
447 offset
+= exif_data
[7];
449 if (exif_data
[7] != 0)
451 if (exif_data
[6] != 0)
453 offset
= exif_data
[5];
455 offset
+= exif_data
[4];
457 if (offset
> length
- 2)
458 return -1; /* check end of data segment */
460 /* Get the number of directory entries contained in this IFD */
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];
467 number_of_tags
= exif_data
[offset
+ 1];
468 number_of_tags
<<= 8;
469 number_of_tags
+= exif_data
[offset
];
471 if (number_of_tags
== 0)
475 /* Search for Orientation Tag in IFD0 */
477 if (offset
> length
- 12)
478 return -1; /* check end of data segment */
481 if (is_motorola
== 1) {
482 tagnum
= exif_data
[offset
];
484 tagnum
+= exif_data
[offset
+ 1];
486 tagnum
= exif_data
[offset
+ 1];
488 tagnum
+= exif_data
[offset
];
490 if (tagnum
== 0x0112)
491 break; /* found Orientation Tag */
492 if (--number_of_tags
== 0)
497 /* Get the Orientation value */
498 if (is_motorola
== 1) {
499 if (exif_data
[offset
+ 8] != 0)
501 set_flag
= exif_data
[offset
+ 9];
503 if (exif_data
[offset
+ 9] != 0)
505 set_flag
= exif_data
[offset
+ 8];