001package serp.util;
002
003import java.math.*;
004import java.util.*;
005
006/**
007 * String utiltity methods.
008 *
009 * @author Abe White
010 */
011public 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            StringBuilder buf = new StringBuilder(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        StringBuilder buf = new StringBuilder(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            StringBuilder buf = new StringBuilder(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}