001 package serp.util;
002
003 import java.math.*;
004 import java.util.*;
005
006 /**
007 * String utiltity methods.
008 *
009 * @author Abe White
010 */
011 public class Strings {
012 private static final Object[][] _codes = new Object[][] {
013 { byte.class, "byte", "B" },
014 { char.class, "char", "C" },
015 { double.class, "double", "D" },
016 { float.class, "float", "F" },
017 { int.class, "int", "I" },
018 { long.class, "long", "J" },
019 { short.class, "short", "S" },
020 { boolean.class, "boolean", "Z" },
021 { void.class, "void", "V" }
022 };
023
024 /**
025 * Replace all instances of <code>from</code> in <code>str</code>
026 * with <code>to</code>.
027 *
028 * @param str the candidate string to replace
029 * @param from the token to replace
030 * @param to the new token
031 * @return the string with all the replacements made
032 */
033 public static String replace(String str, String from, String to) {
034 String[] split = split(str, from, Integer.MAX_VALUE);
035 return join(split, to);
036 }
037
038 /**
039 * Splits the given string on the given token. Follows the semantics
040 * of the Java 1.4 {@link String#split(String,int)} method, but does
041 * not treat the given token as a regular expression.
042 */
043 public static String[] split(String str, String token, int max) {
044 if (str == null || str.length() == 0)
045 return new String[0];
046 if (token == null || token.length() == 0)
047 throw new IllegalArgumentException("token: [" + token + "]");
048
049 // split on token
050 LinkedList ret = new LinkedList();
051 int start = 0;
052 for (int split = 0; split != -1;) {
053 split = str.indexOf(token, start);
054 if (split == -1 && start >= str.length())
055 ret.add("");
056 else if (split == -1)
057 ret.add(str.substring(start));
058 else {
059 ret.add(str.substring(start, split));
060 start = split + token.length();
061 }
062 }
063
064 // now take max into account; this isn't the most efficient way
065 // of doing things since we split the maximum number of times
066 // regardless of the given parameters, but it makes things easy
067 if (max == 0) {
068 // discard any trailing empty splits
069 while (ret.getLast().equals(""))
070 ret.removeLast();
071 } else if (max > 0 && ret.size() > max) {
072 // move all splits over max into the last split
073 StringBuffer buf = new StringBuffer(ret.removeLast().toString());
074 while (ret.size() >= max) {
075 buf.insert(0, token);
076 buf.insert(0, ret.removeLast());
077 }
078 ret.add(buf.toString());
079 }
080 return (String[]) ret.toArray(new String[ret.size()]);
081 }
082
083 /**
084 * Joins the given strings, placing the given token between them.
085 */
086 public static String join(Object[] strings, String token) {
087 if (strings == null)
088 return null;
089
090 StringBuffer buf = new StringBuffer(20 * strings.length);
091 for (int i = 0; i < strings.length; i++) {
092 if (i > 0)
093 buf.append(token);
094 if (strings[i] != null)
095 buf.append(strings[i]);
096 }
097 return buf.toString();
098 }
099
100 /**
101 * Return the class for the given string, correctly handling
102 * primitive types. If the given class loader is null, the context
103 * loader of the current thread will be used.
104 *
105 * @throws RuntimeException on load error
106 */
107 public static Class toClass(String str, ClassLoader loader) {
108 return toClass(str, false, loader);
109 }
110
111 /**
112 * Return the class for the given string, correctly handling
113 * primitive types. If the given class loader is null, the context
114 * loader of the current thread will be used.
115 *
116 * @throws RuntimeException on load error
117 */
118 public static Class toClass(String str, boolean resolve,
119 ClassLoader loader) {
120 if (str == null)
121 throw new NullPointerException("str == null");
122
123 // array handling
124 int dims = 0;
125 while (str.endsWith("[]")) {
126 dims++;
127 str = str.substring(0, str.length() - 2);
128 }
129
130 // check against primitive types
131 boolean primitive = false;
132 if (str.indexOf('.') == -1) {
133 for (int i = 0; !primitive && (i < _codes.length); i++) {
134 if (_codes[i][1].equals(str)) {
135 if (dims == 0)
136 return (Class) _codes[i][0];
137 str = (String) _codes[i][2];
138 primitive = true;
139 }
140 }
141 }
142
143 if (dims > 0) {
144 int size = str.length() + dims;
145 if (!primitive)
146 size += 2;
147
148 StringBuffer buf = new StringBuffer(size);
149 for (int i = 0; i < dims; i++)
150 buf.append('[');
151 if (!primitive)
152 buf.append('L');
153 buf.append(str);
154 if (!primitive)
155 buf.append(';');
156 str = buf.toString();
157 }
158
159 if (loader == null)
160 loader = Thread.currentThread().getContextClassLoader();
161 try {
162 return Class.forName(str, resolve, loader);
163 } catch (Throwable t) {
164 throw new IllegalArgumentException(t.toString());
165 }
166 }
167
168 /**
169 * Return only the class name, without package.
170 */
171 public static String getClassName(Class cls) {
172 return (cls == null) ? null : getClassName(cls.getName());
173 }
174
175 /**
176 * Return only the class name.
177 */
178 public static String getClassName(String fullName) {
179 if (fullName == null)
180 return null;
181
182 // special case for arrays
183 int dims = 0;
184 for (int i = 0; i < fullName.length(); i++) {
185 if (fullName.charAt(i) != '[') {
186 dims = i;
187 break;
188 }
189 }
190 if (dims > 0)
191 fullName = fullName.substring(dims);
192
193 // check for primitives
194 for (int i = 0; i < _codes.length; i++) {
195 if (_codes[i][2].equals(fullName)) {
196 fullName = (String) _codes[i][1];
197 break;
198 }
199 }
200
201 fullName = fullName.substring(fullName.lastIndexOf('.') + 1);
202 for (int i = 0; i < dims; i++)
203 fullName = fullName + "[]";
204 return fullName;
205 }
206
207 /**
208 * Return only the package, or empty string if none.
209 */
210 public static String getPackageName(Class cls) {
211 return (cls == null) ? null : getPackageName(cls.getName());
212 }
213
214 /**
215 * Return only the package, or empty string if none.
216 */
217 public static String getPackageName(String fullName) {
218 if (fullName == null)
219 return null;
220 int dotIdx = fullName.lastIndexOf('.');
221 return (dotIdx == -1) ? "" : fullName.substring(0, dotIdx);
222 }
223
224 /**
225 * Return <code>val</code> as the type specified by
226 * <code>type</code>. If <code>type</code> is a primitive, the
227 * primitive wrapper type is created and returned, and
228 * <code>null</code>s are converted to the Java default for the
229 * primitive type.
230 *
231 * @param val The string value to parse
232 * @param type The type to parse. This must be a primitive or a
233 * primitive wrapper, or one of {@link BigDecimal},
234 * {@link BigInteger}, {@link String}, {@link Date}.
235 * @throws IllegalArgumentException if <code>type</code> is not a
236 * supported type, or if <code>val</code> cannot be
237 * converted into an instance of type <code>type</code>.
238 */
239 public static Object parse(String val, Class type) {
240 if (!canParse(type))
241 throw new IllegalArgumentException("invalid type: " +
242 type.getName());
243
244 // deal with null value
245 if (val == null) {
246 if (!type.isPrimitive())
247 return null;
248 if (type == boolean.class)
249 return Boolean.FALSE;
250 if (type == byte.class)
251 return new Byte((byte) 0);
252 if (type == char.class)
253 return new Character((char) 0);
254 if (type == double.class)
255 return new Double(0);
256 if (type == float.class)
257 return new Float(0);
258 if (type == int.class)
259 return Numbers.valueOf(0);
260 if (type == long.class)
261 return Numbers.valueOf(0L);
262 if (type == short.class)
263 return new Short((short) 0);
264 throw new IllegalStateException("invalid type: " + type);
265 }
266
267 // deal with non-null value
268 if (type == boolean.class || type == Boolean.class)
269 return Boolean.valueOf(val);
270 if (type == byte.class || type == Byte.class)
271 return Byte.valueOf(val);
272 if (type == char.class || type == Character.class) {
273 if (val.length() == 0)
274 return new Character((char) 0);
275 if (val.length() == 1)
276 return new Character(val.charAt(0));
277 throw new IllegalArgumentException("'" + val + "' is longer than "
278 + "one character.");
279 }
280 if (type == double.class || type == Double.class)
281 return Double.valueOf(val);
282 if (type == float.class || type == Float.class)
283 return Float.valueOf(val);
284 if (type == int.class || type == Integer.class)
285 return Integer.valueOf(val);
286 if (type == long.class || type == Long.class)
287 return Long.valueOf(val);
288 if (type == short.class || type == Short.class)
289 return Short.valueOf(val);
290 if (type == String.class)
291 return val;
292 if (type == Date.class)
293 return new Date(val);
294 if (type == BigInteger.class)
295 return new BigInteger(val);
296 if (type == BigDecimal.class)
297 return new BigDecimal(val);
298 throw new IllegalArgumentException("Invalid type: " + type);
299 }
300
301 /**
302 * Whether the given type is parsable via {@link #parse}.
303 */
304 public static boolean canParse(Class type) {
305 return type.isPrimitive() || type == Boolean.class
306 || type == Byte.class || type == Character.class
307 || type == Short.class || type == Integer.class
308 || type == Long.class || type == Float.class
309 || type == Double.class || type == String.class
310 || type == Date.class || type == BigInteger.class
311 || type == BigDecimal.class;
312 }
313 }