001: /* ***** BEGIN LICENSE BLOCK *****
002: * Version: MPL 1.1
003: * The contents of this file are subject to the Mozilla Public License Version
004: * 1.1 (the "License"); you may not use this file except in compliance with
005: * the License. You may obtain a copy of the License at
006: * http://www.mozilla.org/MPL/
007: *
008: * Software distributed under the License is distributed on an "AS IS" basis,
009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
010: * for the specific language governing rights and limitations under the
011: * License.
012: *
013: * The Original Code is Riot.
014: *
015: * The Initial Developer of the Original Code is
016: * Neteye GmbH.
017: * Portions created by the Initial Developer are Copyright (C) 2006
018: * the Initial Developer. All Rights Reserved.
019: *
020: * Contributor(s):
021: * Felix Gnass [fgnass at neteye dot de]
022: *
023: * ***** END LICENSE BLOCK ***** */
024: package org.riotfamily.common.util;
025:
026: import java.io.IOException;
027: import java.io.StringReader;
028: import java.io.UnsupportedEncodingException;
029: import java.net.URLDecoder;
030: import java.net.URLEncoder;
031: import java.text.DecimalFormat;
032: import java.text.NumberFormat;
033: import java.text.SimpleDateFormat;
034: import java.util.Calendar;
035: import java.util.Date;
036: import java.util.regex.Matcher;
037: import java.util.regex.Pattern;
038:
039: import org.apache.commons.logging.Log;
040: import org.apache.commons.logging.LogFactory;
041: import org.springframework.util.ClassUtils;
042:
043: /**
044: * Utility class that provides some simple text formatting methods.
045: */
046: public final class FormatUtils {
047:
048: private static final Log log = LogFactory.getLog(FormatUtils.class);
049:
050: private static NumberFormat numberFormat = new DecimalFormat("0.#");
051:
052: private static SimpleDateFormat dateFormat = new SimpleDateFormat(
053: "yyyy-MM-dd HH:mm");
054:
055: private static final String OP_ADDITION = "+";
056:
057: private static final String OP_SUBTRACTION = "-";
058:
059: private static final String ENTITY_LT = "<";
060:
061: private static final String ENTITY_GT = ">";
062:
063: private static final String ENTITY_AMP = "&";
064:
065: private static final String ENTITY_QUOT = """;
066:
067: private static final String BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
068: + "abcdefghijklmnopqrstuvwxyz0123456789+/=";
069:
070: private FormatUtils() {
071: }
072:
073: /**
074: * Returns a formatted string using an appropriate unit (Bytes, KB or MB).
075: */
076: public static String formatByteSize(long bytes) {
077: if (bytes < 1024) {
078: return numberFormat.format(bytes) + " Bytes";
079: }
080: float kb = (float) bytes / 1024;
081: if (kb < 1024) {
082: return numberFormat.format(kb) + " KB";
083: }
084: float mb = kb / 1024;
085: return numberFormat.format(mb) + " MB";
086: }
087:
088: /**
089: * <pre>
090: * camelCase -> Camel Case
091: * CamelCASE -> Camel CASE
092: * Cam31Case -> Cam 31 Case
093: * </pre>
094: */
095: public static String camelToTitleCase(String s) {
096: if (s == null) {
097: return null;
098: }
099: StringBuffer sb = new StringBuffer();
100: char last = 0;
101: for (int i = 0; i < s.length(); i++) {
102: char c = s.charAt(i);
103: if (Character.isUpperCase(c) && last > 0
104: && !Character.isUpperCase(last)) {
105: sb.append(' ');
106: } else if (Character.isDigit(c) && last > 0
107: && !Character.isDigit(last)) {
108: sb.append(' ');
109: }
110: if (i == 0) {
111: c = Character.toUpperCase(c);
112: }
113: sb.append(c);
114: last = c;
115: }
116: return sb.toString();
117: }
118:
119: /**
120: * <pre>
121: * foo-bar -> fooBar
122: * Foo-bAR -> FooBAR
123: * </pre>
124: */
125: public static String xmlToCamelCase(String s) {
126: StringBuffer sb = new StringBuffer(s);
127: int offset = 0;
128: int i;
129: while ((i = sb.indexOf("-", offset)) >= 0) {
130: sb.deleteCharAt(i);
131: sb.setCharAt(i, Character.toUpperCase(sb.charAt(i)));
132: offset = i;
133: }
134: return sb.toString();
135: }
136:
137: /**
138: * <pre>
139: * foo-bar -> Foo Bar
140: * fooBar -> Foo Bar
141: * </pre>
142: */
143: public static String xmlToTitleCase(String s) {
144: return camelToTitleCase(xmlToCamelCase(s));
145: }
146:
147: /**
148: * <pre>
149: * foo.bar -> Foo Bar
150: * foo.barBar -> Foo Bar Bar
151: * </pre>
152: */
153: public static String propertyToTitleCase(String s) {
154: if (s == null) {
155: return null;
156: }
157: return xmlToTitleCase(s.replace('.', '-'));
158: }
159:
160: /**
161: * <pre>
162: * foo.bar -> Foo Bar
163: * foo-foo_bar -> Foo Foo Bar
164: * foo.barBar -> Foo Bar Bar
165: * </pre>
166: */
167: public static String fileNameToTitleCase(String s) {
168: if (s == null) {
169: return null;
170: }
171: return propertyToTitleCase(s.replace('_', '-'));
172: }
173:
174: /**
175: * <pre>
176: * CamelCase -> camel-case
177: * camelCASE -> camel-case
178: * </pre>
179: */
180: public static String camelToXmlCase(String s) {
181: if (s == null) {
182: return null;
183: }
184: StringBuffer sb = new StringBuffer();
185: boolean lastWasLower = false;
186: for (int i = 0; i < s.length(); i++) {
187: char c = s.charAt(i);
188: if (Character.isUpperCase(c)) {
189: if (lastWasLower) {
190: sb.append('-');
191: }
192: c = Character.toLowerCase(c);
193: } else {
194: lastWasLower = true;
195: }
196: sb.append(c);
197: }
198: return sb.toString();
199: }
200:
201: /**
202: * "a", "b", "c" -> "a b c a-b a-b-c"
203: * "a", "b", null -> "a b a-b"
204: */
205: public static String combine(String[] s) {
206: StringBuffer sb = new StringBuffer();
207: for (int i = 0; i < s.length; i++) {
208: if (s[i] != null) {
209: sb.append(s[i]);
210: if (i < s.length - 1 && s[i + 1] != null) {
211: sb.append(' ');
212: }
213: }
214: }
215: for (int j = 1; j < s.length; j++) {
216: if (s[j] != null) {
217: sb.append(' ');
218: for (int i = 0; i <= j; i++) {
219: sb.append(s[i]);
220: if (i < j && s[i + 1] != null) {
221: sb.append('-');
222: }
223: }
224: }
225: }
226: return sb.toString();
227: }
228:
229: /**
230: * Truncates the given String if its length exceeds the specified value.
231: * @since 6.4
232: */
233: public static String truncate(String s, int length) {
234: if (s == null || s.length() <= length) {
235: return s;
236: }
237: return s.substring(0, length);
238: }
239:
240: /**
241: * Converts the given String into a valid CSS class name.
242: */
243: public static String toCssClass(String s) {
244: if (s == null) {
245: return null;
246: }
247: s = s.replaceAll("[.\\s/]", "-");
248: s = s.replaceAll("[^\\w-_]", "");
249: return s;
250: }
251:
252: /**
253: * Parses a formatted String and returns the value in milliseconds. You can
254: * use one of the following suffixes:
255: *
256: * <pre>
257: * s - seconds
258: * m - minutes
259: * h - hours
260: * D - days
261: * W - weeks
262: * M - months
263: * Y - years
264: * </pre>
265: */
266: public static long parseMillis(String s) {
267: if (s == null) {
268: return 0;
269: }
270: long millis = 0;
271: int i = 0;
272: int length = s.length();
273: while (i < length) {
274: long delta = 0;
275: char ch = 0;
276: for (; i < length; i++) {
277: ch = s.charAt(i);
278: if (!Character.isDigit(ch)) {
279: i++;
280: break;
281: }
282: delta *= 10;
283: delta += Character.getNumericValue(ch);
284: }
285: switch (ch) {
286: case 's':
287: case 'S':
288: default:
289: millis += 1000 * delta;
290: break;
291:
292: case 'm':
293: millis += 60 * 1000 * delta;
294: break;
295:
296: case 'h':
297: case 'H':
298: millis += 60L * 60 * 1000 * delta;
299: break;
300:
301: case 'd':
302: case 'D':
303: millis += 24L * 60 * 60 * 1000 * delta;
304: break;
305:
306: case 'w':
307: case 'W':
308: millis += 7L * 24 * 60 * 60 * 1000 * delta;
309: break;
310:
311: case 'M':
312: millis += 30L * 24 * 60 * 60 * 1000 * delta;
313: break;
314:
315: case 'y':
316: case 'Y':
317: millis += 365L * 24 * 60 * 60 * 1000 * delta;
318: break;
319: }
320: }
321: return millis;
322: }
323:
324: /**
325: * Returns a formatted string using the pattern hh:mm:ss. The hours are
326: * omitted if they are zero, the minutes are padded with a '0' character
327: * if they are less than 10.
328: */
329: public static String formatMillis(long millis) {
330: int hours = (int) (millis / (1000 * 60 * 60));
331: int minutes = (int) (millis / (1000 * 60)) % 60;
332: int seconds = (int) (millis / 1000) % 60;
333: StringBuffer sb = new StringBuffer();
334: if (hours > 0) {
335: sb.append(hours);
336: sb.append(':');
337: }
338: if (minutes < 10 && hours > 0) {
339: sb.append(0);
340: }
341: sb.append(minutes);
342: sb.append(':');
343: if (seconds < 10) {
344: sb.append(0);
345: }
346: sb.append(seconds);
347: return sb.toString();
348: }
349:
350: /**
351: * Returns the extension of the given filename. Examples:
352: *
353: * <pre>
354: * $quot;foo.bar" - "bar"
355: * "/some/file.name.foo" - "foo"
356: * </pre>
357: *
358: * The following examples will return an empty String:
359: *
360: * <pre>
361: * "foo"
362: * "foo."
363: * "/dir.with.dots/file"
364: * ".bar"
365: * "/foo/.bar"
366: * </pre>
367: */
368: public static String getExtension(String filename) {
369: if (filename == null) {
370: return "";
371: }
372: int i = filename.lastIndexOf('.');
373: if (i <= 0 || i == filename.length() - 1
374: || filename.indexOf('/', i) != -1
375: || filename.indexOf('\\', i) != -1) {
376:
377: return "";
378: }
379: return filename.substring(i + 1);
380: }
381:
382: /**
383: * Returns the the filename without it's extension.
384: */
385: public static String stripExtension(String filename) {
386: String extension = getExtension(filename);
387: return filename.substring(0, filename.length()
388: - extension.length() - 1);
389: }
390:
391: /**
392: * Parses a formatted String and returns the date. The date to parse starts
393: * with today. You can use one of the following sufixes:
394: *
395: * <pre>
396: *
397: * D - days
398: * W - weeks
399: * M - months
400: * Y - years
401: * </pre>
402: *
403: * Days is option, any number without a suffix is treated as a number of
404: * days
405: */
406: public static Date parseDate(String s) {
407: if (s.startsWith("today")) {
408: String op = null;
409: int days = 0;
410: int months = 0;
411: int years = 0;
412:
413: s = s.substring(5);
414: int i = 0;
415: int length = s.length();
416: long delta = 0;
417: while (i < length) {
418:
419: char ch = 0;
420: for (; i < length; i++) {
421: ch = s.charAt(i);
422: if (!Character.isDigit(ch)) {
423: i++;
424: break;
425: }
426: delta *= 10;
427: delta += Character.getNumericValue(ch);
428: }
429: switch (ch) {
430: case '+':
431: op = OP_ADDITION;
432: break;
433: case '-':
434: op = OP_SUBTRACTION;
435: break;
436: case 'd':
437: case 'D':
438: if (OP_ADDITION.equals(op)) {
439: days += delta;
440: } else if (OP_SUBTRACTION.equals(op)) {
441: days -= delta;
442: }
443: op = null;
444: delta = 0;
445: break;
446:
447: case 'w':
448: case 'W':
449: if (OP_ADDITION.equals(op)) {
450: days += 7 * delta;
451: } else if (OP_SUBTRACTION.equals(op)) {
452: days -= 7 * delta;
453: }
454: op = null;
455: delta = 0;
456: break;
457:
458: case 'M':
459: if (OP_ADDITION.equals(op)) {
460: months += delta;
461: } else if (OP_SUBTRACTION.equals(op)) {
462: months -= delta;
463: }
464: op = null;
465: delta = 0;
466: break;
467:
468: case 'y':
469: case 'Y':
470: if (OP_ADDITION.equals(op)) {
471: years += delta;
472: } else if (OP_SUBTRACTION.equals(op)) {
473: years -= delta;
474: }
475: op = null;
476: delta = 0;
477: break;
478: }
479: if (delta > 0) {
480: if (OP_ADDITION.equals(op)) {
481: days += delta;
482: } else if (OP_SUBTRACTION.equals(op)) {
483: days -= delta;
484: }
485: }
486:
487: }
488: Calendar c = Calendar.getInstance();
489: c.setTime(new Date());
490: c.add(Calendar.DATE, days);
491: c.add(Calendar.MONTH, months);
492: c.add(Calendar.YEAR, years);
493: return c.getTime();
494: }
495: return null;
496: }
497:
498: public static String formatIsoDate(Date date) {
499: return dateFormat.format(date);
500: }
501:
502: /**
503: * Invokes toLowerCase(), converts all whitespaces to underscores and
504: * removes all characters other than a-z, 0-9, dot, underscore or minus.
505: */
506: public static String toFilename(String s) {
507: s = s.toLowerCase();
508: s = s.replaceAll("\\s+", "_");
509: s = s.replaceAll("[^a-z_0-9.-]", "");
510: return s;
511: }
512:
513: /**
514: * Turn special characters into escaped characters conforming to XML.
515: *
516: * @param input the input string
517: * @return the escaped string
518: */
519: public static String xmlEscape(String input) {
520: if (input == null) {
521: return input;
522: }
523: StringBuffer filtered = new StringBuffer(input.length() + 42);
524: char c;
525: for (int i = 0; i < input.length(); i++) {
526: c = input.charAt(i);
527: if (c == '<') {
528: filtered.append(ENTITY_LT);
529: } else if (c == '>') {
530: filtered.append(ENTITY_GT);
531: } else if (c == '&') {
532: filtered.append(ENTITY_AMP);
533: } else if (c == '"') {
534: filtered.append(ENTITY_QUOT);
535: } else {
536: filtered.append(c);
537: }
538: }
539: return filtered.toString();
540: }
541:
542: public static String escapeChars(String s, String chars, char escape) {
543: StringBuffer sb = new StringBuffer(s);
544: for (int i = 0; i < sb.length(); i++) {
545: if (chars.indexOf(sb.charAt(i)) != -1) {
546: sb.insert(i, escape);
547: i++;
548: }
549: }
550: return sb.toString();
551: }
552:
553: public static String regexEscape(String s) {
554: return escapeChars(s, ".+*?{[^$", '\\');
555: }
556:
557: /**
558: * Translates the given string into application/x-www-form-urlencoded
559: * format using UTF-8 as encoding scheme.
560: */
561: public static String uriEscape(String input) {
562: try {
563: return URLEncoder.encode(input, "UTF-8");
564: } catch (UnsupportedEncodingException e) {
565: throw new IllegalStateException(e.getMessage());
566: }
567: }
568:
569: /**
570: * Translates the given path into application/x-www-form-urlencoded
571: * format using UTF-8 as encoding scheme. This method differs from
572: * {@link #uriEscape(String)}} that path component separators (/) are
573: * not encoded.
574: *
575: * @see #uriEscape(String)
576: */
577: public static String uriEscapePath(String input) {
578: StringBuffer result = new StringBuffer();
579:
580: int p = 0;
581: int q = input.indexOf('/');
582:
583: while (q >= 0) {
584: result.append(uriEscape(input.substring(p, q)));
585: p = q + 1;
586: result.append('/');
587:
588: q = input.indexOf('/', p);
589: }
590:
591: result.append(uriEscape(input.substring(p, input.length())));
592: return result.toString();
593: }
594:
595: /**
596: * Decodes the given application/x-www-form-urlencoded string using
597: * UTF-8 as encoding scheme.
598: */
599: public static String uriUnescape(String input) {
600: try {
601: return URLDecoder.decode(input, "UTF-8");
602: } catch (UnsupportedEncodingException e) {
603: throw new IllegalStateException(e.getMessage());
604: }
605: }
606:
607: /**
608: * Escapes all XML special characters in the given array. Dates and
609: * primitive-wrappers are left as-is, all other objects are converted to
610: * their String representation and escaped using
611: * {@link #xmlEscape(String)}.
612: * @since 6.4
613: */
614: public static Object[] htmlEscapeArgs(Object[] args) {
615: Object[] escapedArgs = new Object[args.length];
616: escapedArgs = new Object[args.length];
617: for (int i = 0; i < args.length; i++) {
618: Object arg = args[i];
619: if (arg instanceof String) {
620: escapedArgs[i] = xmlEscape((String) arg);
621: } else if (ClassUtils.isPrimitiveWrapper(arg.getClass())
622: || arg instanceof Date) {
623:
624: escapedArgs[i] = arg;
625: } else {
626: escapedArgs[i] = xmlEscape(arg.toString());
627: }
628: }
629: return escapedArgs;
630: }
631:
632: /**
633: * Extracts an integer from a String using the first capture group of the
634: * given regular expression.
635: * @since 6.4
636: */
637: public static int extractInt(String s, String regex) {
638: Pattern pattern = Pattern.compile(regex);
639: Matcher matcher = pattern.matcher(s);
640: if (matcher.find()) {
641: String group = matcher.group(1);
642: try {
643: return Integer.parseInt(group);
644: } catch (NumberFormatException e) {
645: log.error("Not a valid number: " + group);
646: }
647: }
648: return -1;
649: }
650:
651: public static String stripWhitespaces(String s) {
652: return stripWhitespaces(s, false);
653: }
654:
655: public static String stripWhitespaces(String s,
656: boolean preserveBreaks) {
657: StringReader in = new StringReader(s);
658: StringBuffer sb = new StringBuffer();
659: try {
660: boolean lineBreak = false;
661: boolean charsWritten = false;
662: int count = 0;
663: int i;
664: while ((i = in.read()) != -1) {
665: char c = (char) i;
666: if (Character.isWhitespace(c)) {
667: if (charsWritten) {
668: count++;
669: if (preserveBreaks && c == '\n') {
670: lineBreak = true;
671: }
672: }
673: } else {
674: if (count > 0) {
675: sb.append(lineBreak ? '\n' : ' ');
676: count = 0;
677: lineBreak = false;
678: }
679: sb.append(c);
680: charsWritten = true;
681: }
682: }
683: } catch (IOException e) {
684: // Should never happen since we are using a StringReader
685: } finally {
686: in.close();
687: }
688: return sb.toString();
689: }
690:
691: /**
692: * Decodes the given Base64 encoded String.
693: * @since 6.5
694: */
695: public static String decodeBase64(String s) {
696: if (s == null) {
697: return null;
698: }
699: StringBuffer sb = new StringBuffer();
700: int c1, c2, c3;
701: int e1, e2, e3, e4;
702: for (int i = 0; i < s.length();) {
703: e1 = BASE64.indexOf(s.charAt(i++));
704: e2 = BASE64.indexOf(s.charAt(i++));
705: e3 = BASE64.indexOf(s.charAt(i++));
706: e4 = BASE64.indexOf(s.charAt(i++));
707:
708: c1 = (e1 << 2) | (e2 >> 4);
709: c2 = ((e2 & 15) << 4) | (e3 >> 2);
710: c3 = ((e3 & 3) << 6) | e4;
711:
712: sb.append((char) c1);
713: if (e3 != 64) {
714: sb.append((char) c2);
715: }
716: if (e4 != 64) {
717: sb.append((char) c3);
718: }
719: }
720: return sb.toString();
721: }
722:
723: }
|