/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.util;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.oro.text.perl.Perl5Util;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.MatchResult;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.PatternMatcherInput;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.apache.oro.text.regex.Substitution;
import org.apache.oro.text.regex.Util;
import org.apache.log4j.Logger;
/**
* A (static) class of generally-useful string utilities.
*
* @author Bill Schneider
*/
public class StringUtils {
private static final Logger s_log = Logger.getLogger(StringUtils.class);
private static Perl5Util s_re = new Perl5Util();
public static final String NEW_LINE = System.getProperty("line.separator");
private StringUtils() {
// can't instantiate me!
}
/**
* Tests if a string is empty.
* @param s A string to test
* @return true if s is null or empty;
* otherwise false
*/
public final static boolean emptyString(String s) {
boolean expr = (s == null || s.trim().length() == 0);
return expr;
}
/**
* Tests if a string is empty.
* @param o A string to test
* @return true if o is null or empty;
* otherwise false
*/
public final static boolean emptyString(Object o) {
boolean expr =
(o == null || (o instanceof String && ((String)o).length() ==0));
return expr;
}
/**
* If the String is null, returns an empty string. Otherwise,
* returns the string unaltered
*/
public final static String nullToEmptyString(String s) {
return (s == null) ? "" : s;
}
/**
* Escapes some "special" characters in HTML text (ampersand, angle
* brackets, quote).
* @param s The plain-text string to quote
* @return The string with special characters escpaed.
*/
public final static String quoteHtml(String s) {
if (s != null) {
StringBuffer result = new StringBuffer(s.length() + 10);
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
switch (ch) {
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
default:
result.append(ch);
}
}
return result.toString();
} else {
return "";
}
}
/**
* Takes a plaintext string, and returns an HTML string that, when
* rendered by a web browser, will appear as the original input string
*
* @param s The input plaintext string
* @return A HTML string with blank lines coverted to
<p>* and ampersands/angle brackets escaped. */ public final static String textToHtml(String s) { s = quoteHtml(s); s = s_re.substitute("s/\r\n\r\n/
/g", s); s = s_re.substitute("s/\n\n/
/g", s); s = s_re.substitute("s/\r\r/
/g", s);
s = s_re.substitute("s/\r\n/
/g", s);
s = s_re.substitute("s/\n/
/g", s);
s = s_re.substitute("s/\r/
/g", s);
return s;
}
/**
* Removes tags and substitutes P tags with newlines. For much
* more extensive conversion of HTML fragments to plain text
* equivalents, see {@link HtmlToText}.
*/
public final static String htmlToText(String s) {
if (s != null) {
// first take out new-lines
s = s_re.substitute("s/\n//g", s);
s = s_re.substitute("s/\r//g", s);
s = s_re.substitute("s/<[Pp]>/\n\n/g", s);
s = s_re.substitute("s/
/\n/ig", s);
// take out other tags
s = s_re.substitute("s/<([^>]*)>/ /g", s);
return s;
} else {
return "";
}
}
/**
* Converts plain text with simple inline markup
* into HTML. The following constructs are recognised:
*
*
text
* * my item
* * my next item
* * my final item
+ my item
* + my next item
* + my final item
$2|gx", s);
s = s_re.substitute("s|\u0001|=|gx", s);
if (s_log.isDebugEnabled()) {
s_log.debug("After styles {" + s + "}");
}
// Links are next on the list @text(url)
s = s_re.substitute("s|@@|\u0001|gx", s);
s = s_re.substitute("s|@([^\\(@]+)\\(([^\\)]+)\\)|$1|gx", s);
s = s_re.substitute("s|\u0001|@|gx", s);
if (s_log.isDebugEnabled()) {
s_log.debug("After links pass two {" + s + "}");
}
// Finally we can unobscure the hyperlinks
s = smartTextReplace(new UnobscureSubstitution(links),
"\u0002([^\u0002]+)\u0002", s);
s = s_re.substitute("s|\u0001|@|gx", s);
if (s_log.isDebugEnabled()) {
s_log.debug("After links pass three {" + s + "}");
}
// And those entities
s = smartTextReplace(new EntitySubstitution(),
"\u0003([^\u0003]+)\u0003", s);
if (s_log.isDebugEnabled()) {
s_log.debug("After entities (complete) {" + s + "}");
}
return s;
}
private static String smartTextReplace(Substitution subst,
String pattern,
String s) {
Perl5Matcher matcher = new Perl5Matcher();
Perl5Compiler compiler = new Perl5Compiler();
StringBuffer result = new StringBuffer();
PatternMatcherInput input = new PatternMatcherInput(s);
try {
Util.substitute(result,
matcher,
compiler.compile(pattern),
subst,
input,
Util.SUBSTITUTE_ALL);
} catch (MalformedPatternException e) {
throw new UncheckedWrapperException("cannot perform substitution", e);
}
return result.toString();
}
private static class TitledLinkSubstitution implements Substitution {
private Map m_hash;
public TitledLinkSubstitution(Map hash) {
m_hash = hash;
}
public void appendSubstitution(StringBuffer appendBuffer,
MatchResult match,
int substitutionCount,
PatternMatcherInput originalInput,
PatternMatcher matcher,
Pattern pattern) {
String title = match.group(1);
String link = match.group(2);
s_log.debug("Link: " + link);
Integer i = new Integer(m_hash.size());
s_log.debug("Key: " + i);
m_hash.put(i, link);
String dst = "@" + title + "(\u0002" + i.toString() + "\u0002)";
appendBuffer.append(dst);
s_log.debug("Encoded Link: " + dst);
}
}
private static class UntitledLinkSubstitution implements Substitution {
private Map m_hash;
public UntitledLinkSubstitution(Map hash) {
m_hash = hash;
}
public void appendSubstitution(StringBuffer appendBuffer,
MatchResult match,
int substitutionCount,
PatternMatcherInput originalInput,
PatternMatcher matcher,
Pattern pattern) {
String link = match.group(1);
s_log.debug("Link: " + link);
Integer i = new Integer(m_hash.size());
s_log.debug("Key: " + i);
m_hash.put(i, link);
String dst = "@\u0002" + i.toString() + "\u0002(\u0002" +
i.toString() + "\u0002)";
appendBuffer.append(dst);
s_log.debug("Encoded Link: " + dst);
}
}
private static class UnobscureSubstitution implements Substitution {
private Map m_hash;
public UnobscureSubstitution(Map hash) {
m_hash = hash;
}
public void appendSubstitution(StringBuffer appendBuffer,
MatchResult match,
int substitutionCount,
PatternMatcherInput originalInput,
PatternMatcher matcher,
Pattern pattern) {
String s = match.group(1);
s_log.debug("Key: " + s);
Integer i = new Integer(s);
appendBuffer.append((String)m_hash.get(i));
s_log.debug("Link: " + m_hash.get(i));
}
}
private static class EntitySubstitution implements Substitution {
public void appendSubstitution(StringBuffer appendBuffer,
MatchResult match,
int substitutionCount,
PatternMatcherInput originalInput,
PatternMatcher matcher,
Pattern pattern) {
String s = match.group(1);
s_log.debug("Key: " + s);
appendBuffer.append((String)s_entities.get(s));
s_log.debug("Entity: " + s_entities.get(s));
}
}
/**
* Convert a string of items separated by a separator
* character to an (string)array of the items. sep is the separator
* character. Example: Input - s == "cat,house,dog" sep==','
* Output - {"cat", "house", "dog"}
* @param s string contains items separated by a separator character.
* @param sep separator character.
* @return Array of items.
**/
public static String [] split(String s, char sep) {
ArrayList al = new ArrayList();
int start_pos, end_pos;
start_pos = 0;
while (start_pos < s.length()) {
end_pos = s.indexOf(sep, start_pos);
if (end_pos == -1) {
end_pos = s.length();
}
String found_item = s.substring(start_pos, end_pos);
al.add(found_item);
start_pos = end_pos + 1;
}
if (s.length() > 0 && s.charAt(s.length()-1) == sep) {
al.add(""); // In case last character is separator
}
String [] returned_array = new String[al.size()];
al.toArray(returned_array);
return returned_array;
}
/**
* Given a string, split it into substrings matching a regular * expression that you supply. Parts of the original string which * don't match the regular expression also appear as substrings. The * upshot of this is that the final substrings can be concatenated * to get the original string.
* *As an example, let's say the original string is:
* ** s = "/packages/foo/xsl/::vhost::/foo_::locale::.xsl"; ** *
We call the function like this:
* ** output = splitUp (s, "/::\\w+::/"); ** *
The result (output) will be the following list:
* ("/packages/foo/xsl/", "::vhost::", "/foo_", "::locale::", ".xsl")
*
*
* Notice the important property that concatenating all these * strings together will restore the original string.
* *Here is another useful example. To split up HTML into elements * and content, do:
* ** output = splitUp (html, "/<.*?>/"); ** *
You will end up with something like this:
* *
* ("The following text will be ", "", "bold", "", ".")
*
*
* @param s The original string to split.
* @param re The regular expression in the format required by
* {@link org.apache.oro.text.perl.Perl5Util#match(String, String)}.
* @return List of substrings.
*
* @author Richard W.M. Jones
*
* This is equivalent to the Perl "global match in array context",
* specifically: @a = /(RE)|(.+)/g;
Add a possible newline for proper wrapping.
* *Checks the given String to see if it ends with whitspace. If so, it
* assumes this whitespace is intentional formatting and returns a reference
* to the original string. If not, a new String object is
* created containing the original plus a platform-dependent newline
* character obtained from {@link System#getProperty(String)
* System.getProperty("line.separator")}.
Notes: * *
find in str and
* replaces them with them with replace.
*
* @pre find != null
* @pre replace != null
**/
public static String replace(final String str,
final String find,
final String replace) {
Assert.exists(find, String.class);
Assert.exists(replace, String.class);
if ( str == null ) return null;
int cur = str.indexOf(find);
if ( cur < 0 ) return str;
final int findLength = find.length();
// If replace is longer than find, assume the result is going to be
// slightly longer than the original string.
final int bufferLength =
replace.length() > findLength ? (int) (str.length() * 1.1) : str.length();
StringBuffer sb = new StringBuffer(bufferLength);
int last = 0;
if ( cur == 0 ) {
sb.append(replace);
cur = str.indexOf(find, cur+findLength);
last = findLength;
}
while ( cur > 0 ) {
sb.append(str.substring(last, cur));
sb.append(replace);
last = cur + findLength;
cur = str.indexOf(find, cur+findLength);
}
if ( last < str.length()-1) {
sb.append(str.substring(last));
}
return sb.toString();
}
/**
* An interface allowing the value for a placeholder to be
* dynamically generated.
*/
public interface PlaceholderValueGenerator {
/**
* Returns the value corresponding to the supplied key
* placeholder.
*
* @param key the key being substituted
*/
public String generate(String key);
}
private static class HashSubstitution implements Substitution {
private Map m_hash;
public HashSubstitution(Map hash) {
m_hash = hash;
}
public void appendSubstitution(StringBuffer appendBuffer,
MatchResult match,
int substitutionCount,
PatternMatcherInput originalInput,
PatternMatcher matcher,
Pattern pattern) {
String placeholder = match.toString();
String key = placeholder.substring(2, placeholder.length()-2);
Object value = (m_hash.containsKey(key) ?
m_hash.get(key) :
placeholder);
if( s_log.isDebugEnabled() ) {
Object hashValue = m_hash.get( key );
s_log.debug( "Placeholder: " + placeholder );
s_log.debug( "Key: " + key );
if( null != value ) {
s_log.debug( "Value (" + value.getClass().getName() +
"): " + value.toString() );
}
if( null != hashValue ) {
s_log.debug( "Hash Value (" +
hashValue.getClass().getName() + "): " +
hashValue.toString() );
}
}
value = (m_hash.containsKey(key) ? m_hash.get(key) : "");
String val;
if( value instanceof PlaceholderValueGenerator ) {
PlaceholderValueGenerator gen = (PlaceholderValueGenerator)value;
val = gen.generate(key);
} else if( value.getClass().isArray() ) {
Object[] values = (Object[]) value;
StringBuffer buf = new StringBuffer();
for( int i = 0; i < values.length; i++ ) {
buf.append( values[i].toString() );
if( (values.length - 1) != i ) {
buf.append( ", " );
}
}
val = buf.toString();
} else {
val = value.toString();
}
appendBuffer.append(val);
}
}
/**
* @throws NullPointerException if throwable is null
*/
public static String getStackTrace(Throwable throwable) {
if (throwable==null) { throw new NullPointerException("throwable"); }
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
pw.close();
return sw.toString();
}
/**
* Returns a list of lines where each line represents one level
* in the stack trace captured by throwable.
*
* For a stack trace like this:
* *
* java.lang.Throwable
* at Main.level3(Main.java:19)
* at Main.level2(Main.java:15)
* at Main.level1(Main.java:11)
* at Main.main(Main.java:7)
*
*
* the returned list looks like this:
* *
* ["java.lang.Throwable",
* "Main.level3(Main.java:20)",
* "Main.level2(Main.java:15)",
* "Main.level1(Main.java:11)",
* "Main.main(Main.java:7)"]
*
*
* @see #getStackTrace(Throwable)
* @throws NullPointerException if throwable is null
*/
public static List getStackList(Throwable throwable) {
StringTokenizer tkn = new StringTokenizer
(getStackTrace(throwable), System.getProperty("line.separator"));
List list = new LinkedList();
while ( tkn.hasMoreTokens() ) {
String token = tkn.nextToken().trim();
if ( "".equals(token) ) { continue; }
if ( token.startsWith("at ") ) {
list.add(token.substring(3));
} else {
list.add(token);
}
}
return list;
}
/**
* Convert a name into a URL form, the java equivalent of
* "manipulate-input.js"
*
* For example, "Business promotions!" will be converted to
* "business-promotions".
*
* @param name
* the to be converted into a URL.
* @return the converted name, possibly unchanged and null if the input is null.
*/
public static String urlize(String name) {
if (name == null) {
return null;
}
StringBuffer urlizedName = new StringBuffer(name.length());
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (Character.isLetter(ch)) {
urlizedName.append(Character.toLowerCase(ch));
}
else if (Character.isDigit(ch) || ch == '_' || ch == '-') {
urlizedName.append(ch);
}
else if (ch == ' ' || ch == '&' || ch == '/') {
urlizedName.append('-');
}
}
return urlizedName.toString();
}
}