1. /* ====================================================================
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowledgement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowledgement may appear in the software itself,
  24. * if and wherever such third-party acknowledgements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Software Foundation.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.commons.lang;
  55. /**
  56. * <p>Operations on Strings that contain words.</p>
  57. *
  58. * <p>This class tries to handle <code>null</code> input gracefully.
  59. * An exception will not be thrown for a <code>null</code> input.
  60. * Each method documents its behaviour in more detail.</p>
  61. *
  62. * @author Apache Jakarta Velocity
  63. * @author Henri Yandell
  64. * @author Stephen Colebourne
  65. * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
  66. * @author Gary Gregory
  67. * @since 2.0
  68. * @version $Id: WordUtils.java,v 1.8 2003/08/23 10:39:20 scolebourne Exp $
  69. */
  70. public class WordUtils {
  71. /**
  72. * <p><code>WordWrapUtils</code> instances should NOT be constructed in
  73. * standard programming. Instead, the class should be used as
  74. * <code>WordWrapUtils.wrap("foo bar", 20);</code>.</p>
  75. *
  76. * <p>This constructor is public to permit tools that require a JavaBean
  77. * instance to operate.</p>
  78. */
  79. public WordUtils() {
  80. }
  81. // Wrapping
  82. //--------------------------------------------------------------------------
  83. // /**
  84. // * <p>Wraps a block of text to a specified line length using '\n' as
  85. // * a newline.</p>
  86. // *
  87. // * <p>This method takes a block of text, which might have long lines in it
  88. // * and wraps the long lines based on the supplied lineLength parameter.</p>
  89. // *
  90. // * <p>If a single word is longer than the line length (eg. a URL), it will
  91. // * not be broken, and will display beyond the expected width.</p>
  92. // *
  93. // * <p>If there are tabs in inString, you are going to get results that are
  94. // * a bit strange. Tabs are a single character but are displayed as 4 or 8
  95. // * spaces. Remove the tabs.</p>
  96. // *
  97. // * @param str text which is in need of word-wrapping, may be null
  98. // * @param lineLength the column to wrap the words at
  99. // * @return the text with all the long lines word-wrapped
  100. // * <code>null</code> if null string input
  101. // */
  102. // public static String wrapText(String str, int lineLength) {
  103. // return wrap(str, null, lineLength);
  104. // }
  105. // /**
  106. // * <p>Wraps a block of text to a specified line length.</p>
  107. // *
  108. // * <p>This method takes a block of text, which might have long lines in it
  109. // * and wraps the long lines based on the supplied lineLength parameter.</p>
  110. // *
  111. // * <p>If a single word is longer than the wrapColumn (eg. a URL), it will
  112. // * not be broken, and will display beyond the expected width.</p>
  113. // *
  114. // * <p>If there are tabs in inString, you are going to get results that are
  115. // * a bit strange. Tabs are a single character but are displayed as 4 or 8
  116. // * spaces. Remove the tabs.</p>
  117. // *
  118. // * @param str text which is in need of word-wrapping, may be null
  119. // * @param newLineChars the characters that define a newline, null treated as \n
  120. // * @param lineLength the column to wrap the words at
  121. // * @return the text with all the long lines word-wrapped
  122. // * <code>null</code> if null string input
  123. // */
  124. // public static String wrapText(String str, String newLineChars, int lineLength) {
  125. // if (str == null) {
  126. // return null;
  127. // }
  128. // if (newLineChars == null) {
  129. // newLineChars = "\n";
  130. // }
  131. // StringTokenizer lineTokenizer = new StringTokenizer(str, newLineChars, true);
  132. // StringBuffer stringBuffer = new StringBuffer();
  133. //
  134. // while (lineTokenizer.hasMoreTokens()) {
  135. // try {
  136. // String nextLine = lineTokenizer.nextToken();
  137. //
  138. // if (nextLine.length() > lineLength) {
  139. // // This line is long enough to be wrapped.
  140. // nextLine = wrapLine(nextLine, null, lineLength, false);
  141. // }
  142. //
  143. // stringBuffer.append(nextLine);
  144. //
  145. // } catch (NoSuchElementException nsee) {
  146. // // thrown by nextToken(), but I don't know why it would
  147. // break;
  148. // }
  149. // }
  150. //
  151. // return (stringBuffer.toString());
  152. // }
  153. // Wrapping
  154. //-----------------------------------------------------------------------
  155. /**
  156. * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
  157. *
  158. * <p>New lines will be separated by the system property line separator.
  159. * Very long words, such as URLs will <i>not</i> be wrapped.</p>
  160. *
  161. * <p>Leading spaces on a new line are stripped.
  162. * Trailing spaces are not stripped.</p>
  163. *
  164. * <pre>
  165. * WordUtils.wrap(null, *) = null
  166. * WordUtils.wrap("", *) = ""
  167. * </pre>
  168. *
  169. * @param str the String to be word wrapped, may be null
  170. * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
  171. * @return a line with newlines inserted, <code>null</code> if null input
  172. */
  173. public static String wrap(String str, int wrapLength) {
  174. return wrap(str, wrapLength, null, false);
  175. }
  176. /**
  177. * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
  178. *
  179. * <p>Leading spaces on a new line are stripped.
  180. * Trailing spaces are not stripped.</p>
  181. *
  182. * <pre>
  183. * WordUtils.wrap(null, *, *, *) = null
  184. * WordUtils.wrap("", *, *, *) = ""
  185. * </pre>
  186. *
  187. * @param str the String to be word wrapped, may be null
  188. * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
  189. * @param newLineStr the string to insert for a new line,
  190. * <code>null</code> uses the system property line separator
  191. * @param wrapLongWords true if long words (such as URLs) should be wrapped
  192. * @return a line with newlines inserted, <code>null</code> if null input
  193. */
  194. public static String wrap(String str, int wrapLength, String newLineStr, boolean wrapLongWords) {
  195. if (str == null) {
  196. return null;
  197. }
  198. if (newLineStr == null) {
  199. newLineStr = SystemUtils.LINE_SEPARATOR;
  200. }
  201. if (wrapLength < 1) {
  202. wrapLength = 1;
  203. }
  204. int inputLineLength = str.length();
  205. int offset = 0;
  206. StringBuffer wrappedLine = new StringBuffer(inputLineLength + 32);
  207. while ((inputLineLength - offset) > wrapLength) {
  208. if (str.charAt(offset) == ' ') {
  209. offset++;
  210. continue;
  211. }
  212. int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset);
  213. if (spaceToWrapAt >= offset) {
  214. // normal case
  215. wrappedLine.append(str.substring(offset, spaceToWrapAt));
  216. wrappedLine.append(newLineStr);
  217. offset = spaceToWrapAt + 1;
  218. } else {
  219. // really long word or URL
  220. if (wrapLongWords) {
  221. // wrap really long word one line at a time
  222. wrappedLine.append(str.substring(offset, wrapLength + offset));
  223. wrappedLine.append(newLineStr);
  224. offset += wrapLength;
  225. } else {
  226. // do not wrap really long word, just extend beyond limit
  227. spaceToWrapAt = str.indexOf(' ', wrapLength + offset);
  228. if (spaceToWrapAt >= 0) {
  229. wrappedLine.append(str.substring(offset, spaceToWrapAt));
  230. wrappedLine.append(newLineStr);
  231. offset = spaceToWrapAt + 1;
  232. } else {
  233. wrappedLine.append(str.substring(offset));
  234. offset = inputLineLength;
  235. }
  236. }
  237. }
  238. }
  239. // Whatever is left in line is short enough to just pass through
  240. wrappedLine.append(str.substring(offset));
  241. return wrappedLine.toString();
  242. }
  243. // Capitalizing
  244. //-----------------------------------------------------------------------
  245. /**
  246. * <p>Capitalizes all the whitespace separated words in a String.
  247. * Only the first letter of each word is changed. To change all letters to
  248. * the capitalized case, use {@link #capitalizeFully(String)}.</p>
  249. *
  250. * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
  251. * A <code>null</code> input String returns <code>null</code>.
  252. * Capitalization uses the unicode title case, normally equivalent to
  253. * upper case.</p>
  254. *
  255. * <pre>
  256. * WordUtils.capitalize(null) = null
  257. * WordUtils.capitalize("") = ""
  258. * WordUtils.capitalize("i am FINE") = "I Am FINE"
  259. * </pre>
  260. *
  261. * @param str the String to capitalize, may be null
  262. * @return capitalized String, <code>null</code> if null String input
  263. * @see #uncapitalize(String)
  264. * @see #capitalizeFully(String)
  265. */
  266. public static String capitalize(String str) {
  267. int strLen;
  268. if (str == null || (strLen = str.length()) == 0) {
  269. return str;
  270. }
  271. StringBuffer buffer = new StringBuffer(strLen);
  272. boolean whitespace = true;
  273. for (int i = 0; i < strLen; i++) {
  274. char ch = str.charAt(i);
  275. if (Character.isWhitespace(ch)) {
  276. buffer.append(ch);
  277. whitespace = true;
  278. } else if (whitespace) {
  279. buffer.append(Character.toTitleCase(ch));
  280. whitespace = false;
  281. } else {
  282. buffer.append(ch);
  283. }
  284. }
  285. return buffer.toString();
  286. }
  287. /**
  288. * <p>Capitalizes all the whitespace separated words in a String.
  289. * All letters are changed, so the resulting string will be fully changed.</p>
  290. *
  291. * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
  292. * A <code>null</code> input String returns <code>null</code>.
  293. * Capitalization uses the unicode title case, normally equivalent to
  294. * upper case.</p>
  295. *
  296. * <pre>
  297. * WordUtils.capitalize(null) = null
  298. * WordUtils.capitalize("") = ""
  299. * WordUtils.capitalize("i am FINE") = "I Am Fine"
  300. * </pre>
  301. *
  302. * @param str the String to capitalize, may be null
  303. * @return capitalized String, <code>null</code> if null String input
  304. */
  305. public static String capitalizeFully(String str) {
  306. int strLen;
  307. if (str == null || (strLen = str.length()) == 0) {
  308. return str;
  309. }
  310. StringBuffer buffer = new StringBuffer(strLen);
  311. boolean whitespace = true;
  312. for (int i = 0; i < strLen; i++) {
  313. char ch = str.charAt(i);
  314. if (Character.isWhitespace(ch)) {
  315. buffer.append(ch);
  316. whitespace = true;
  317. } else if (whitespace) {
  318. buffer.append(Character.toTitleCase(ch));
  319. whitespace = false;
  320. } else {
  321. buffer.append(Character.toLowerCase(ch));
  322. }
  323. }
  324. return buffer.toString();
  325. }
  326. /**
  327. * <p>Uncapitalizes all the whitespace separated words in a String.
  328. * Only the first letter of each word is changed.</p>
  329. *
  330. * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
  331. * A <code>null</code> input String returns <code>null</code>.</p>
  332. *
  333. * <pre>
  334. * WordUtils.uncapitalize(null) = null
  335. * WordUtils.uncapitalize("") = ""
  336. * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
  337. * </pre>
  338. *
  339. * @param str the String to uncapitalize, may be null
  340. * @return uncapitalized String, <code>null</code> if null String input
  341. * @see #capitalize(String)
  342. */
  343. public static String uncapitalize(String str) {
  344. int strLen;
  345. if (str == null || (strLen = str.length()) == 0) {
  346. return str;
  347. }
  348. StringBuffer buffer = new StringBuffer(strLen);
  349. boolean whitespace = true;
  350. for (int i = 0; i < strLen; i++) {
  351. char ch = str.charAt(i);
  352. if (Character.isWhitespace(ch)) {
  353. buffer.append(ch);
  354. whitespace = true;
  355. } else if (whitespace) {
  356. buffer.append(Character.toLowerCase(ch));
  357. whitespace = false;
  358. } else {
  359. buffer.append(ch);
  360. }
  361. }
  362. return buffer.toString();
  363. }
  364. /**
  365. * <p>Swaps the case of a String using a word based algorithm.</p>
  366. *
  367. * <ul>
  368. * <li>Upper case character converts to Lower case</li>
  369. * <li>Title case character converts to Lower case</li>
  370. * <li>Lower case character after Whitespace or at start converts to Title case</li>
  371. * <li>Other Lower case character converts to Upper case</li>
  372. * </ul>
  373. *
  374. * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
  375. * A <code>null</code> input String returns <code>null</code>.</p>
  376. *
  377. * <pre>
  378. * StringUtils.swapCase(null) = null
  379. * StringUtils.swapCase("") = ""
  380. * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
  381. * </pre>
  382. *
  383. * @param str the String to swap case, may be null
  384. * @return the changed String, <code>null</code> if null String input
  385. */
  386. public static String swapCase(String str) {
  387. int strLen;
  388. if (str == null || (strLen = str.length()) == 0) {
  389. return str;
  390. }
  391. StringBuffer buffer = new StringBuffer(strLen);
  392. boolean whitespace = true;
  393. char ch = 0;
  394. char tmp = 0;
  395. for (int i = 0; i < strLen; i++) {
  396. ch = str.charAt(i);
  397. if (Character.isUpperCase(ch)) {
  398. tmp = Character.toLowerCase(ch);
  399. } else if (Character.isTitleCase(ch)) {
  400. tmp = Character.toLowerCase(ch);
  401. } else if (Character.isLowerCase(ch)) {
  402. if (whitespace) {
  403. tmp = Character.toTitleCase(ch);
  404. } else {
  405. tmp = Character.toUpperCase(ch);
  406. }
  407. } else {
  408. tmp = ch;
  409. }
  410. buffer.append(tmp);
  411. whitespace = Character.isWhitespace(ch);
  412. }
  413. return buffer.toString();
  414. }
  415. }