0001: /**
0002: * $Revision: 6032 $
0003: * $Date: 2006-11-09 17:16:09 -0800 (Thu, 09 Nov 2006) $
0004: *
0005: * Copyright (C) 2004-2006 Jive Software. All rights reserved.
0006: *
0007: * This software is published under the terms of the GNU Public License (GPL),
0008: * a copy of which is included in this distribution.
0009: */package org.jivesoftware.util;
0010:
0011: import java.io.UnsupportedEncodingException;
0012: import java.security.MessageDigest;
0013: import java.security.NoSuchAlgorithmException;
0014: import java.text.BreakIterator;
0015: import java.util.*;
0016: import java.util.concurrent.ConcurrentHashMap;
0017:
0018: /**
0019: * Utility class to peform common String manipulation algorithms.
0020: */
0021: public class StringUtils {
0022:
0023: // Constants used by escapeHTMLTags
0024: private static final char[] QUOTE_ENCODE = """.toCharArray();
0025: private static final char[] AMP_ENCODE = "&".toCharArray();
0026: private static final char[] LT_ENCODE = "<".toCharArray();
0027: private static final char[] GT_ENCODE = ">".toCharArray();
0028:
0029: private StringUtils() {
0030: // Not instantiable.
0031: }
0032:
0033: /**
0034: * Replaces all instances of oldString with newString in string.
0035: *
0036: * @param string the String to search to perform replacements on.
0037: * @param oldString the String that should be replaced by newString.
0038: * @param newString the String that will replace all instances of oldString.
0039: * @return a String will all instances of oldString replaced by newString.
0040: */
0041: public static String replace(String string, String oldString,
0042: String newString) {
0043: if (string == null) {
0044: return null;
0045: }
0046: int i = 0;
0047: // Make sure that oldString appears at least once before doing any processing.
0048: if ((i = string.indexOf(oldString, i)) >= 0) {
0049: // Use char []'s, as they are more efficient to deal with.
0050: char[] string2 = string.toCharArray();
0051: char[] newString2 = newString.toCharArray();
0052: int oLength = oldString.length();
0053: StringBuilder buf = new StringBuilder(string2.length);
0054: buf.append(string2, 0, i).append(newString2);
0055: i += oLength;
0056: int j = i;
0057: // Replace all remaining instances of oldString with newString.
0058: while ((i = string.indexOf(oldString, i)) > 0) {
0059: buf.append(string2, j, i - j).append(newString2);
0060: i += oLength;
0061: j = i;
0062: }
0063: buf.append(string2, j, string2.length - j);
0064: return buf.toString();
0065: }
0066: return string;
0067: }
0068:
0069: /**
0070: * Replaces all instances of oldString with newString in line with the
0071: * added feature that matches of newString in oldString ignore case.
0072: *
0073: * @param line the String to search to perform replacements on
0074: * @param oldString the String that should be replaced by newString
0075: * @param newString the String that will replace all instances of oldString
0076: * @return a String will all instances of oldString replaced by newString
0077: */
0078: public static String replaceIgnoreCase(String line,
0079: String oldString, String newString) {
0080: if (line == null) {
0081: return null;
0082: }
0083: String lcLine = line.toLowerCase();
0084: String lcOldString = oldString.toLowerCase();
0085: int i = 0;
0086: if ((i = lcLine.indexOf(lcOldString, i)) >= 0) {
0087: char[] line2 = line.toCharArray();
0088: char[] newString2 = newString.toCharArray();
0089: int oLength = oldString.length();
0090: StringBuilder buf = new StringBuilder(line2.length);
0091: buf.append(line2, 0, i).append(newString2);
0092: i += oLength;
0093: int j = i;
0094: while ((i = lcLine.indexOf(lcOldString, i)) > 0) {
0095: buf.append(line2, j, i - j).append(newString2);
0096: i += oLength;
0097: j = i;
0098: }
0099: buf.append(line2, j, line2.length - j);
0100: return buf.toString();
0101: }
0102: return line;
0103: }
0104:
0105: /**
0106: * Replaces all instances of oldString with newString in line with the
0107: * added feature that matches of newString in oldString ignore case.
0108: * The count paramater is set to the number of replaces performed.
0109: *
0110: * @param line the String to search to perform replacements on
0111: * @param oldString the String that should be replaced by newString
0112: * @param newString the String that will replace all instances of oldString
0113: * @param count a value that will be updated with the number of replaces
0114: * performed.
0115: * @return a String will all instances of oldString replaced by newString
0116: */
0117: public static String replaceIgnoreCase(String line,
0118: String oldString, String newString, int[] count) {
0119: if (line == null) {
0120: return null;
0121: }
0122: String lcLine = line.toLowerCase();
0123: String lcOldString = oldString.toLowerCase();
0124: int i = 0;
0125: if ((i = lcLine.indexOf(lcOldString, i)) >= 0) {
0126: int counter = 1;
0127: char[] line2 = line.toCharArray();
0128: char[] newString2 = newString.toCharArray();
0129: int oLength = oldString.length();
0130: StringBuilder buf = new StringBuilder(line2.length);
0131: buf.append(line2, 0, i).append(newString2);
0132: i += oLength;
0133: int j = i;
0134: while ((i = lcLine.indexOf(lcOldString, i)) > 0) {
0135: counter++;
0136: buf.append(line2, j, i - j).append(newString2);
0137: i += oLength;
0138: j = i;
0139: }
0140: buf.append(line2, j, line2.length - j);
0141: count[0] = counter;
0142: return buf.toString();
0143: }
0144: return line;
0145: }
0146:
0147: /**
0148: * Replaces all instances of oldString with newString in line.
0149: * The count Integer is updated with number of replaces.
0150: *
0151: * @param line the String to search to perform replacements on.
0152: * @param oldString the String that should be replaced by newString.
0153: * @param newString the String that will replace all instances of oldString.
0154: * @return a String will all instances of oldString replaced by newString.
0155: */
0156: public static String replace(String line, String oldString,
0157: String newString, int[] count) {
0158: if (line == null) {
0159: return null;
0160: }
0161: int i = 0;
0162: if ((i = line.indexOf(oldString, i)) >= 0) {
0163: int counter = 1;
0164: char[] line2 = line.toCharArray();
0165: char[] newString2 = newString.toCharArray();
0166: int oLength = oldString.length();
0167: StringBuilder buf = new StringBuilder(line2.length);
0168: buf.append(line2, 0, i).append(newString2);
0169: i += oLength;
0170: int j = i;
0171: while ((i = line.indexOf(oldString, i)) > 0) {
0172: counter++;
0173: buf.append(line2, j, i - j).append(newString2);
0174: i += oLength;
0175: j = i;
0176: }
0177: buf.append(line2, j, line2.length - j);
0178: count[0] = counter;
0179: return buf.toString();
0180: }
0181: return line;
0182: }
0183:
0184: /**
0185: * This method takes a string and strips out all tags except <br> tags while still leaving
0186: * the tag body intact.
0187: *
0188: * @param in the text to be converted.
0189: * @return the input string with all tags removed.
0190: */
0191: public static String stripTags(String in) {
0192: if (in == null) {
0193: return null;
0194: }
0195: char ch;
0196: int i = 0;
0197: int last = 0;
0198: char[] input = in.toCharArray();
0199: int len = input.length;
0200: StringBuilder out = new StringBuilder((int) (len * 1.3));
0201: for (; i < len; i++) {
0202: ch = input[i];
0203: if (ch > '>') {
0204: } else if (ch == '<') {
0205: if (i + 3 < len && input[i + 1] == 'b'
0206: && input[i + 2] == 'r' && input[i + 3] == '>') {
0207: i += 3;
0208: continue;
0209: }
0210: if (i > last) {
0211: out.append(input, last, i - last);
0212: }
0213: last = i + 1;
0214: } else if (ch == '>') {
0215: last = i + 1;
0216: }
0217: }
0218: if (last == 0) {
0219: return in;
0220: }
0221: if (i > last) {
0222: out.append(input, last, i - last);
0223: }
0224: return out.toString();
0225: }
0226:
0227: /**
0228: * This method takes a string which may contain HTML tags (ie, <b>,
0229: * <table>, etc) and converts the '<'' and '>' characters to
0230: * their HTML escape sequences. It will also replace LF with <br>.
0231: *
0232: * @param in the text to be converted.
0233: * @return the input string with the characters '<' and '>' replaced
0234: * with their HTML escape sequences.
0235: */
0236: public static String escapeHTMLTags(String in) {
0237: if (in == null) {
0238: return null;
0239: }
0240: char ch;
0241: int i = 0;
0242: int last = 0;
0243: char[] input = in.toCharArray();
0244: int len = input.length;
0245: StringBuilder out = new StringBuilder((int) (len * 1.3));
0246: for (; i < len; i++) {
0247: ch = input[i];
0248: if (ch > '>') {
0249: } else if (ch == '<') {
0250: if (i > last) {
0251: out.append(input, last, i - last);
0252: }
0253: last = i + 1;
0254: out.append(LT_ENCODE);
0255: } else if (ch == '>') {
0256: if (i > last) {
0257: out.append(input, last, i - last);
0258: }
0259: last = i + 1;
0260: out.append(GT_ENCODE);
0261: } else if (ch == '\n') {
0262: if (i > last) {
0263: out.append(input, last, i - last);
0264: }
0265: last = i + 1;
0266: out.append("<br>");
0267: }
0268: }
0269: if (last == 0) {
0270: return in;
0271: }
0272: if (i > last) {
0273: out.append(input, last, i - last);
0274: }
0275: return out.toString();
0276: }
0277:
0278: /**
0279: * Used by the hash method.
0280: */
0281: private static Map<String, MessageDigest> digests = new ConcurrentHashMap<String, MessageDigest>();
0282:
0283: /**
0284: * Hashes a String using the Md5 algorithm and returns the result as a
0285: * String of hexadecimal numbers. This method is synchronized to avoid
0286: * excessive MessageDigest object creation. If calling this method becomes
0287: * a bottleneck in your code, you may wish to maintain a pool of
0288: * MessageDigest objects instead of using this method.
0289: * <p/>
0290: * A hash is a one-way function -- that is, given an
0291: * input, an output is easily computed. However, given the output, the
0292: * input is almost impossible to compute. This is useful for passwords
0293: * since we can store the hash and a hacker will then have a very hard time
0294: * determining the original password.
0295: * <p/>
0296: * In Jive, every time a user logs in, we simply
0297: * take their plain text password, compute the hash, and compare the
0298: * generated hash to the stored hash. Since it is almost impossible that
0299: * two passwords will generate the same hash, we know if the user gave us
0300: * the correct password or not. The only negative to this system is that
0301: * password recovery is basically impossible. Therefore, a reset password
0302: * method is used instead.
0303: *
0304: * @param data the String to compute the hash of.
0305: * @return a hashed version of the passed-in String
0306: */
0307: public static String hash(String data) {
0308: return hash(data, "MD5");
0309: }
0310:
0311: /**
0312: * Hashes a String using the specified algorithm and returns the result as a
0313: * String of hexadecimal numbers. This method is synchronized to avoid
0314: * excessive MessageDigest object creation. If calling this method becomes
0315: * a bottleneck in your code, you may wish to maintain a pool of
0316: * MessageDigest objects instead of using this method.
0317: * <p/>
0318: * A hash is a one-way function -- that is, given an
0319: * input, an output is easily computed. However, given the output, the
0320: * input is almost impossible to compute. This is useful for passwords
0321: * since we can store the hash and a hacker will then have a very hard time
0322: * determining the original password.
0323: * <p/>
0324: * In Jive, every time a user logs in, we simply
0325: * take their plain text password, compute the hash, and compare the
0326: * generated hash to the stored hash. Since it is almost impossible that
0327: * two passwords will generate the same hash, we know if the user gave us
0328: * the correct password or not. The only negative to this system is that
0329: * password recovery is basically impossible. Therefore, a reset password
0330: * method is used instead.
0331: *
0332: * @param data the String to compute the hash of.
0333: * @param algorithm the name of the algorithm requested.
0334: * @return a hashed version of the passed-in String
0335: */
0336: public static String hash(String data, String algorithm) {
0337: try {
0338: return hash(data.getBytes("utf-8"), algorithm);
0339: } catch (UnsupportedEncodingException e) {
0340: Log.error(e);
0341: }
0342: return data;
0343: }
0344:
0345: /**
0346: * Hashes a byte array using the specified algorithm and returns the result as a
0347: * String of hexadecimal numbers. This method is synchronized to avoid
0348: * excessive MessageDigest object creation. If calling this method becomes
0349: * a bottleneck in your code, you may wish to maintain a pool of
0350: * MessageDigest objects instead of using this method.
0351: * <p/>
0352: * A hash is a one-way function -- that is, given an
0353: * input, an output is easily computed. However, given the output, the
0354: * input is almost impossible to compute. This is useful for passwords
0355: * since we can store the hash and a hacker will then have a very hard time
0356: * determining the original password.
0357: * <p/>
0358: * In Jive, every time a user logs in, we simply
0359: * take their plain text password, compute the hash, and compare the
0360: * generated hash to the stored hash. Since it is almost impossible that
0361: * two passwords will generate the same hash, we know if the user gave us
0362: * the correct password or not. The only negative to this system is that
0363: * password recovery is basically impossible. Therefore, a reset password
0364: * method is used instead.
0365: *
0366: * @param bytes the byte array to compute the hash of.
0367: * @param algorithm the name of the algorithm requested.
0368: * @return a hashed version of the passed-in String
0369: */
0370: public static String hash(byte[] bytes, String algorithm) {
0371: synchronized (algorithm.intern()) {
0372: MessageDigest digest = digests.get(algorithm);
0373: if (digest == null) {
0374: try {
0375: digest = MessageDigest.getInstance(algorithm);
0376: digests.put(algorithm, digest);
0377: } catch (NoSuchAlgorithmException nsae) {
0378: Log
0379: .error(
0380: "Failed to load the "
0381: + algorithm
0382: + " MessageDigest. "
0383: + "Jive will be unable to function normally.",
0384: nsae);
0385: return null;
0386: }
0387: }
0388: // Now, compute hash.
0389: digest.update(bytes);
0390: return encodeHex(digest.digest());
0391: }
0392: }
0393:
0394: /**
0395: * Turns an array of bytes into a String representing each byte as an
0396: * unsigned hex number.
0397: * <p/>
0398: * Method by Santeri Paavolainen, Helsinki Finland 1996<br>
0399: * (c) Santeri Paavolainen, Helsinki Finland 1996<br>
0400: * Distributed under LGPL.
0401: *
0402: * @param bytes an array of bytes to convert to a hex-string
0403: * @return generated hex string
0404: */
0405: public static String encodeHex(byte[] bytes) {
0406: StringBuilder buf = new StringBuilder(bytes.length * 2);
0407: int i;
0408:
0409: for (i = 0; i < bytes.length; i++) {
0410: if (((int) bytes[i] & 0xff) < 0x10) {
0411: buf.append("0");
0412: }
0413: buf.append(Long.toString((int) bytes[i] & 0xff, 16));
0414: }
0415: return buf.toString();
0416: }
0417:
0418: /**
0419: * Turns a hex encoded string into a byte array. It is specifically meant
0420: * to "reverse" the toHex(byte[]) method.
0421: *
0422: * @param hex a hex encoded String to transform into a byte array.
0423: * @return a byte array representing the hex String[
0424: */
0425: public static byte[] decodeHex(String hex) {
0426: char[] chars = hex.toCharArray();
0427: byte[] bytes = new byte[chars.length / 2];
0428: int byteCount = 0;
0429: for (int i = 0; i < chars.length; i += 2) {
0430: int newByte = 0x00;
0431: newByte |= hexCharToByte(chars[i]);
0432: newByte <<= 4;
0433: newByte |= hexCharToByte(chars[i + 1]);
0434: bytes[byteCount] = (byte) newByte;
0435: byteCount++;
0436: }
0437: return bytes;
0438: }
0439:
0440: /**
0441: * Returns the the byte value of a hexadecmical char (0-f). It's assumed
0442: * that the hexidecimal chars are lower case as appropriate.
0443: *
0444: * @param ch a hexedicmal character (0-f)
0445: * @return the byte value of the character (0x00-0x0F)
0446: */
0447: private static byte hexCharToByte(char ch) {
0448: switch (ch) {
0449: case '0':
0450: return 0x00;
0451: case '1':
0452: return 0x01;
0453: case '2':
0454: return 0x02;
0455: case '3':
0456: return 0x03;
0457: case '4':
0458: return 0x04;
0459: case '5':
0460: return 0x05;
0461: case '6':
0462: return 0x06;
0463: case '7':
0464: return 0x07;
0465: case '8':
0466: return 0x08;
0467: case '9':
0468: return 0x09;
0469: case 'a':
0470: return 0x0A;
0471: case 'b':
0472: return 0x0B;
0473: case 'c':
0474: return 0x0C;
0475: case 'd':
0476: return 0x0D;
0477: case 'e':
0478: return 0x0E;
0479: case 'f':
0480: return 0x0F;
0481: }
0482: return 0x00;
0483: }
0484:
0485: /**
0486: * Encodes a String as a base64 String.
0487: *
0488: * @param data a String to encode.
0489: * @return a base64 encoded String.
0490: */
0491: public static String encodeBase64(String data) {
0492: byte[] bytes = null;
0493: try {
0494: bytes = data.getBytes("UTF-8");
0495: } catch (UnsupportedEncodingException uee) {
0496: Log.error(uee);
0497: }
0498: return encodeBase64(bytes);
0499: }
0500:
0501: /**
0502: * Encodes a byte array into a base64 String.
0503: *
0504: * @param data a byte array to encode.
0505: * @return a base64 encode String.
0506: */
0507: public static String encodeBase64(byte[] data) {
0508: // Encode the String. We pass in a flag to specify that line
0509: // breaks not be added. This is consistent with our previous base64
0510: // implementation. Section 2.1 of 3548 (base64 spec) also specifies
0511: // no line breaks by default.
0512: return Base64.encodeBytes(data, Base64.DONT_BREAK_LINES);
0513: }
0514:
0515: /**
0516: * Decodes a base64 String.
0517: *
0518: * @param data a base64 encoded String to decode.
0519: * @return the decoded String.
0520: */
0521: public static byte[] decodeBase64(String data) {
0522: return Base64.decode(data);
0523: }
0524:
0525: /**
0526: * Converts a line of text into an array of lower case words using a
0527: * BreakIterator.wordInstance().<p>
0528: *
0529: * This method is under the Jive Open Source Software License and was
0530: * written by Mark Imbriaco.
0531: *
0532: * @param text a String of text to convert into an array of words
0533: * @return text broken up into an array of words.
0534: */
0535: public static String[] toLowerCaseWordArray(String text) {
0536: if (text == null || text.length() == 0) {
0537: return new String[0];
0538: }
0539:
0540: List<String> wordList = new ArrayList<String>();
0541: BreakIterator boundary = BreakIterator.getWordInstance();
0542: boundary.setText(text);
0543: int start = 0;
0544:
0545: for (int end = boundary.next(); end != BreakIterator.DONE; start = end, end = boundary
0546: .next()) {
0547: String tmp = text.substring(start, end).trim();
0548: // Remove characters that are not needed.
0549: tmp = replace(tmp, "+", "");
0550: tmp = replace(tmp, "/", "");
0551: tmp = replace(tmp, "\\", "");
0552: tmp = replace(tmp, "#", "");
0553: tmp = replace(tmp, "*", "");
0554: tmp = replace(tmp, ")", "");
0555: tmp = replace(tmp, "(", "");
0556: tmp = replace(tmp, "&", "");
0557: if (tmp.length() > 0) {
0558: wordList.add(tmp);
0559: }
0560: }
0561: return wordList.toArray(new String[wordList.size()]);
0562: }
0563:
0564: /**
0565: * Pseudo-random number generator object for use with randomString().
0566: * The Random class is not considered to be cryptographically secure, so
0567: * only use these random Strings for low to medium security applications.
0568: */
0569: private static Random randGen = new Random();
0570:
0571: /**
0572: * Array of numbers and letters of mixed case. Numbers appear in the list
0573: * twice so that there is a more equal chance that a number will be picked.
0574: * We can use the array to get a random number or letter by picking a random
0575: * array index.
0576: */
0577: private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz"
0578: + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
0579:
0580: /**
0581: * Returns a random String of numbers and letters (lower and upper case)
0582: * of the specified length. The method uses the Random class that is
0583: * built-in to Java which is suitable for low to medium grade security uses.
0584: * This means that the output is only pseudo random, i.e., each number is
0585: * mathematically generated so is not truly random.<p>
0586: * <p/>
0587: * The specified length must be at least one. If not, the method will return
0588: * null.
0589: *
0590: * @param length the desired length of the random String to return.
0591: * @return a random String of numbers and letters of the specified length.
0592: */
0593: public static String randomString(int length) {
0594: if (length < 1) {
0595: return null;
0596: }
0597: // Create a char buffer to put random letters and numbers in.
0598: char[] randBuffer = new char[length];
0599: for (int i = 0; i < randBuffer.length; i++) {
0600: randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
0601: }
0602: return new String(randBuffer);
0603: }
0604:
0605: /**
0606: * Intelligently chops a String at a word boundary (whitespace) that occurs
0607: * at the specified index in the argument or before. However, if there is a
0608: * newline character before <code>length</code>, the String will be chopped
0609: * there. If no newline or whitespace is found in <code>string</code> up to
0610: * the index <code>length</code>, the String will chopped at <code>length</code>.
0611: * <p/>
0612: * For example, chopAtWord("This is a nice String", 10) will return
0613: * "This is a" which is the first word boundary less than or equal to 10
0614: * characters into the original String.
0615: *
0616: * @param string the String to chop.
0617: * @param length the index in <code>string</code> to start looking for a
0618: * whitespace boundary at.
0619: * @return a substring of <code>string</code> whose length is less than or
0620: * equal to <code>length</code>, and that is chopped at whitespace.
0621: */
0622: public static String chopAtWord(String string, int length) {
0623: if (string == null || string.length() == 0) {
0624: return string;
0625: }
0626:
0627: char[] charArray = string.toCharArray();
0628: int sLength = string.length();
0629: if (length < sLength) {
0630: sLength = length;
0631: }
0632:
0633: // First check if there is a newline character before length; if so,
0634: // chop word there.
0635: for (int i = 0; i < sLength - 1; i++) {
0636: // Windows
0637: if (charArray[i] == '\r' && charArray[i + 1] == '\n') {
0638: return string.substring(0, i + 1);
0639: }
0640: // Unix
0641: else if (charArray[i] == '\n') {
0642: return string.substring(0, i);
0643: }
0644: }
0645: // Also check boundary case of Unix newline
0646: if (charArray[sLength - 1] == '\n') {
0647: return string.substring(0, sLength - 1);
0648: }
0649:
0650: // Done checking for newline, now see if the total string is less than
0651: // the specified chop point.
0652: if (string.length() < length) {
0653: return string;
0654: }
0655:
0656: // No newline, so chop at the first whitespace.
0657: for (int i = length - 1; i > 0; i--) {
0658: if (charArray[i] == ' ') {
0659: return string.substring(0, i).trim();
0660: }
0661: }
0662:
0663: // Did not find word boundary so return original String chopped at
0664: // specified length.
0665: return string.substring(0, length);
0666: }
0667:
0668: /**
0669: * Reformats a string where lines that are longer than <tt>width</tt>
0670: * are split apart at the earliest wordbreak or at maxLength, whichever is
0671: * sooner. If the width specified is less than 5 or greater than the input
0672: * Strings length the string will be returned as is.
0673: * <p/>
0674: * Please note that this method can be lossy - trailing spaces on wrapped
0675: * lines may be trimmed.
0676: *
0677: * @param input the String to reformat.
0678: * @param width the maximum length of any one line.
0679: * @return a new String with reformatted as needed.
0680: */
0681: public static String wordWrap(String input, int width, Locale locale) {
0682: // protect ourselves
0683: if (input == null) {
0684: return "";
0685: } else if (width < 5) {
0686: return input;
0687: } else if (width >= input.length()) {
0688: return input;
0689: }
0690:
0691: // default locale
0692: if (locale == null) {
0693: locale = JiveGlobals.getLocale();
0694: }
0695:
0696: StringBuilder buf = new StringBuilder(input);
0697: boolean endOfLine = false;
0698: int lineStart = 0;
0699:
0700: for (int i = 0; i < buf.length(); i++) {
0701: if (buf.charAt(i) == '\n') {
0702: lineStart = i + 1;
0703: endOfLine = true;
0704: }
0705:
0706: // handle splitting at width character
0707: if (i > lineStart + width - 1) {
0708: if (!endOfLine) {
0709: int limit = i - lineStart - 1;
0710: BreakIterator breaks = BreakIterator
0711: .getLineInstance(locale);
0712: breaks.setText(buf.substring(lineStart, i));
0713: int end = breaks.last();
0714:
0715: // if the last character in the search string isn't a space,
0716: // we can't split on it (looks bad). Search for a previous
0717: // break character
0718: if (end == limit + 1) {
0719: if (!Character.isWhitespace(buf
0720: .charAt(lineStart + end))) {
0721: end = breaks.preceding(end - 1);
0722: }
0723: }
0724:
0725: // if the last character is a space, replace it with a \n
0726: if (end != BreakIterator.DONE && end == limit + 1) {
0727: buf.replace(lineStart + end, lineStart + end
0728: + 1, "\n");
0729: lineStart = lineStart + end;
0730: }
0731: // otherwise, just insert a \n
0732: else if (end != BreakIterator.DONE && end != 0) {
0733: buf.insert(lineStart + end, '\n');
0734: lineStart = lineStart + end + 1;
0735: } else {
0736: buf.insert(i, '\n');
0737: lineStart = i + 1;
0738: }
0739: } else {
0740: buf.insert(i, '\n');
0741: lineStart = i + 1;
0742: endOfLine = false;
0743: }
0744: }
0745: }
0746:
0747: return buf.toString();
0748: }
0749:
0750: /**
0751: * Escapes all necessary characters in the String so that it can be used in SQL
0752: *
0753: * @param string the string to escape.
0754: * @return the string with appropriate characters escaped.
0755: */
0756: public static String escapeForSQL(String string) {
0757: if (string == null) {
0758: return null;
0759: } else if (string.length() == 0) {
0760: return string;
0761: }
0762:
0763: char ch;
0764: char[] input = string.toCharArray();
0765: int i = 0;
0766: int last = 0;
0767: int len = input.length;
0768: StringBuilder out = null;
0769: for (; i < len; i++) {
0770: ch = input[i];
0771:
0772: if (ch == '\'') {
0773: if (out == null) {
0774: out = new StringBuilder(len + 2);
0775: }
0776: if (i > last) {
0777: out.append(input, last, i - last);
0778: }
0779: last = i + 1;
0780: out.append('\'').append('\'');
0781: }
0782: }
0783:
0784: if (out == null) {
0785: return string;
0786: } else if (i > last) {
0787: out.append(input, last, i - last);
0788: }
0789:
0790: return out.toString();
0791: }
0792:
0793: /**
0794: * Escapes all necessary characters in the String so that it can be used
0795: * in an XML doc.
0796: *
0797: * @param string the string to escape.
0798: * @return the string with appropriate characters escaped.
0799: */
0800: public static String escapeForXML(String string) {
0801: if (string == null) {
0802: return null;
0803: }
0804: char ch;
0805: int i = 0;
0806: int last = 0;
0807: char[] input = string.toCharArray();
0808: int len = input.length;
0809: StringBuilder out = new StringBuilder((int) (len * 1.3));
0810: for (; i < len; i++) {
0811: ch = input[i];
0812: if (ch > '>') {
0813: } else if (ch == '<') {
0814: if (i > last) {
0815: out.append(input, last, i - last);
0816: }
0817: last = i + 1;
0818: out.append(LT_ENCODE);
0819: } else if (ch == '&') {
0820: if (i > last) {
0821: out.append(input, last, i - last);
0822: }
0823: last = i + 1;
0824: out.append(AMP_ENCODE);
0825: } else if (ch == '"') {
0826: if (i > last) {
0827: out.append(input, last, i - last);
0828: }
0829: last = i + 1;
0830: out.append(QUOTE_ENCODE);
0831: }
0832: }
0833: if (last == 0) {
0834: return string;
0835: }
0836: if (i > last) {
0837: out.append(input, last, i - last);
0838: }
0839: return out.toString();
0840: }
0841:
0842: /**
0843: * Unescapes the String by converting XML escape sequences back into normal
0844: * characters.
0845: *
0846: * @param string the string to unescape.
0847: * @return the string with appropriate characters unescaped.
0848: */
0849: public static String unescapeFromXML(String string) {
0850: string = replace(string, "<", "<");
0851: string = replace(string, ">", ">");
0852: string = replace(string, """, "\"");
0853: return replace(string, "&", "&");
0854: }
0855:
0856: private static final char[] zeroArray = "0000000000000000000000000000000000000000000000000000000000000000"
0857: .toCharArray();
0858:
0859: /**
0860: * Pads the supplied String with 0's to the specified length and returns
0861: * the result as a new String. For example, if the initial String is
0862: * "9999" and the desired length is 8, the result would be "00009999".
0863: * This type of padding is useful for creating numerical values that need
0864: * to be stored and sorted as character data. Note: the current
0865: * implementation of this method allows for a maximum <tt>length</tt> of
0866: * 64.
0867: *
0868: * @param string the original String to pad.
0869: * @param length the desired length of the new padded String.
0870: * @return a new String padded with the required number of 0's.
0871: */
0872: public static String zeroPadString(String string, int length) {
0873: if (string == null || string.length() > length) {
0874: return string;
0875: }
0876: StringBuilder buf = new StringBuilder(length);
0877: buf.append(zeroArray, 0, length - string.length()).append(
0878: string);
0879: return buf.toString();
0880: }
0881:
0882: /**
0883: * Formats a Date as a fifteen character long String made up of the Date's
0884: * padded millisecond value.
0885: *
0886: * @return a Date encoded as a String.
0887: */
0888: public static String dateToMillis(Date date) {
0889: return zeroPadString(Long.toString(date.getTime()), 15);
0890: }
0891:
0892: /**
0893: * Returns a textual representation for the time that has elapsed.
0894: *
0895: * @param delta the elapsed time.
0896: * @return textual representation for the time that has elapsed.
0897: */
0898: public static String getElapsedTime(long delta) {
0899: if (delta < JiveConstants.MINUTE) {
0900: return LocaleUtils.getLocalizedString("global.less-minute");
0901: } else if (delta < JiveConstants.HOUR) {
0902: long mins = delta / JiveConstants.MINUTE;
0903: StringBuilder sb = new StringBuilder();
0904: sb.append(mins).append(" ");
0905: sb.append((mins == 1) ? LocaleUtils
0906: .getLocalizedString("global.minute") : LocaleUtils
0907: .getLocalizedString("global.minutes"));
0908: return sb.toString();
0909: } else if (delta < JiveConstants.DAY) {
0910: long hours = delta / JiveConstants.HOUR;
0911: delta -= hours * JiveConstants.HOUR;
0912: long mins = delta / JiveConstants.MINUTE;
0913: StringBuilder sb = new StringBuilder();
0914: sb.append(hours).append(" ");
0915: sb.append((hours == 1) ? LocaleUtils
0916: .getLocalizedString("global.hour") : LocaleUtils
0917: .getLocalizedString("global.hours"));
0918: sb.append(", ");
0919: sb.append(mins).append(" ");
0920: sb.append((mins == 1) ? LocaleUtils
0921: .getLocalizedString("global.minute") : LocaleUtils
0922: .getLocalizedString("global.minutes"));
0923: return sb.toString();
0924: } else {
0925: long days = delta / JiveConstants.DAY;
0926: delta -= days * JiveConstants.DAY;
0927: long hours = delta / JiveConstants.HOUR;
0928: delta -= hours * JiveConstants.HOUR;
0929: long mins = delta / JiveConstants.MINUTE;
0930: StringBuilder sb = new StringBuilder();
0931: sb.append(days).append(" ");
0932: sb.append((days == 1) ? LocaleUtils
0933: .getLocalizedString("global.day") : LocaleUtils
0934: .getLocalizedString("global.days"));
0935: sb.append(", ");
0936: sb.append(hours).append(" ");
0937: sb.append((hours == 1) ? LocaleUtils
0938: .getLocalizedString("global.hour") : LocaleUtils
0939: .getLocalizedString("global.hours"));
0940: sb.append(", ");
0941: sb.append(mins).append(" ");
0942: sb.append((mins == 1) ? LocaleUtils
0943: .getLocalizedString("global.minute") : LocaleUtils
0944: .getLocalizedString("global.minutes"));
0945: return sb.toString();
0946: }
0947: }
0948:
0949: /**
0950: * Returns a collection of Strings as a comma-delimitted list of strings.
0951: *
0952: * @return a String representing the Collection.
0953: */
0954: public static String collectionToString(
0955: Collection<String> collection) {
0956: if (collection == null || collection.isEmpty()) {
0957: return "";
0958: }
0959: StringBuilder buf = new StringBuilder();
0960: String delim = "";
0961: for (String element : collection) {
0962: buf.append(delim);
0963: buf.append(element);
0964: delim = ",";
0965: }
0966: return buf.toString();
0967: }
0968:
0969: /**
0970: * Returns a comma-delimitted list of Strings as a Collection.
0971: *
0972: * @return a Collection representing the String.
0973: */
0974: public static Collection<String> stringToCollection(String string) {
0975: if (string == null || string.trim().length() == 0) {
0976: return Collections.emptyList();
0977: }
0978: Collection<String> collection = new ArrayList<String>();
0979: StringTokenizer tokens = new StringTokenizer(string, ",");
0980: while (tokens.hasMoreTokens()) {
0981: collection.add(tokens.nextToken().trim());
0982: }
0983: return collection;
0984: }
0985:
0986: /**
0987: * Abbreviates a string to a specified length and then adds an ellipsis
0988: * if the input is greater than the maxWidth. Example input:
0989: * <pre>
0990: * user1@jivesoftware.com/home
0991: * </pre>
0992: * and a maximum length of 20 characters, the abbreviate method will return:
0993: * <pre>
0994: * user1@jivesoftware.c...
0995: * </pre>
0996: * @param str the String to abbreviate.
0997: * @param maxWidth the maximum size of the string, minus the ellipsis.
0998: * @return the abbreviated String, or <tt>null</tt> if the string was <tt>null</tt>.
0999: */
1000: public static String abbreviate(String str, int maxWidth) {
1001: if (null == str) {
1002: return null;
1003: }
1004:
1005: if (str.length() <= maxWidth) {
1006: return str;
1007: }
1008:
1009: return str.substring(0, maxWidth) + "...";
1010: }
1011: }
|