001package serp.bytecode;
002
003import java.util.*;
004
005/**
006 * Caching and conversion of names in both internal and external form.
007 *
008 * @author Abe White
009 */
010public class NameCache {
011    static final Object[][] _codes = new Object[][] {
012        { byte.class, "B" },
013        { char.class, "C" },
014        { double.class, "D" },
015        { float.class, "F" },
016        { int.class, "I" },
017        { long.class, "J" },
018        { short.class, "S" },
019        { boolean.class, "Z" },
020        { void.class, "V" },
021    };
022
023    // caches of internal and external forms of strings
024    private final Map _internal = new HashMap();
025    private final Map _internalDescriptor = new HashMap();
026    private final Map _external = new HashMap();
027    private final Map _externalHuman = new HashMap();
028
029    /**
030     * Converts the given class name to its internal form.
031     *
032     * @param className the name to convert
033     * @param descriptor true if the name is to be used for a descriptor
034     * section -- the difference seems to be that for
035     * descriptors, non-primitives are prefixed with 'L' and ended with ';'
036     */
037    public String getInternalForm(String className, boolean descriptor) {
038        if (className == null || className.length() == 0)
039            return className;
040
041        Map cache = (descriptor) ? _internalDescriptor : _internal;
042        String cached = (String) cache.get(className);
043        if (cached != null)
044            return cached;
045
046        String ret = getInternalFormInternal(className, descriptor);
047        cache.put(className, ret);
048        return ret;
049    }
050
051    /**
052     * @see #getInternalForm
053     */
054    private String getInternalFormInternal(String cls, boolean descriptor) {
055        // handle array types, whether already in internal form or not
056        StringBuilder prefix = new StringBuilder();
057        while (true) {
058            if (cls.endsWith("[]")) {
059                prefix.append("[");
060                cls = cls.substring(0, cls.length() - 2);
061            } else if (cls.startsWith("[")) {
062                prefix.append("[");
063                cls = cls.substring(1);
064            } else
065                break;
066        }
067
068        // handle primitive array types
069        for (int i = 0; i < _codes.length; i++)
070            if (cls.equals(_codes[i][1].toString()) 
071                || cls.equals(_codes[i][0].toString()))
072                return prefix.append(_codes[i][1]).toString();
073
074        // if in descriptor form, strip leading 'L' and trailing ';'
075        if (cls.startsWith("L") && cls.endsWith(";"))
076            cls = cls.substring(1, cls.length() - 1);
077
078        // non-primitive; make sure we don't prefix method descriptors with 'L'
079        cls = cls.replace('.', '/');
080        if ((descriptor || prefix.length() > 0) && cls.charAt(0) != '(')
081            return prefix.append("L").append(cls).append(";").toString();
082        return prefix.append(cls).toString();
083    }
084
085    /**
086     * Given the internal name of the class, return the 'normal' java name.
087     *
088     * @param internalName the internal name being used
089     * @param humanReadable if the returned name should be in human-readable
090     * form, rather than a form suitable for a
091     * {@link Class#forName} call -- the difference
092     * lies in the handling of arrays
093     */
094    public String getExternalForm(String internalName, boolean humanReadable) {
095        if (internalName == null || internalName.length() == 0)
096            return internalName;
097
098        Map cache = (humanReadable) ? _externalHuman : _external;
099        String cached = (String) cache.get(internalName);
100        if (cached != null)
101            return cached;
102
103        String ret = getExternalFormInternal(internalName, humanReadable);
104        cache.put(internalName, ret);
105        return ret;
106    }
107
108    /**
109     * @see #getExternalForm
110     */
111    private String getExternalFormInternal(String intern, 
112        boolean humanReadable) {
113        if (!humanReadable) {
114            // check against primitives
115            for (int i = 0; i < _codes.length; i++) {
116                if (intern.equals(_codes[i][1].toString()))
117                    return _codes[i][0].toString();
118                if (intern.equals(_codes[i][0].toString()))
119                    return intern;
120            }
121            intern = getInternalForm(intern, false);
122            return intern.replace('/', '.');
123        }
124
125        // handle arrays
126        StringBuilder postfix = new StringBuilder(2);
127        while (intern.startsWith("[")) {
128            intern = intern.substring(1);
129            postfix.append("[]");
130        }
131
132        // strip off leading 'L' and trailing ';'
133        if (intern.endsWith(";"))
134            intern = intern.substring(1, intern.length() - 1);
135
136        // check primitives
137        for (int i = 0; i < _codes.length; i++)
138            if (intern.equals(_codes[i][1].toString()))
139                return _codes[i][0].toString() + postfix;
140        return intern.replace('/', '.') + postfix;
141    }
142
143    /**
144     * Construct a method descriptor from the given return and parameter
145     * types, which will be converted to internal form.
146     */
147    public String getDescriptor(String returnType, String[] paramTypes) {
148        StringBuilder buf = new StringBuilder();
149        buf.append("(");
150        if (paramTypes != null) {
151            for (int i = 0; i < paramTypes.length; i++) {
152                if (paramTypes[i] == null) 
153                    throw new NullPointerException("paramTypes[" + i 
154                        + "] = null");
155                buf.append(getInternalForm(paramTypes[i], true));
156            }
157        }
158
159        buf.append(")");
160        if (returnType == null)
161            throw new NullPointerException("returnType = null");
162
163        buf.append(getInternalForm(returnType, true));
164        return buf.toString();
165    }
166
167    /**
168     * Return the return type, in internal form, for the given method
169     * descriptor string.
170     */
171    public String getDescriptorReturnName(String descriptor) {
172        int index = descriptor.indexOf(')');
173        if (index == -1)
174            return "";
175        return descriptor.substring(descriptor.indexOf(')') + 1);
176    }
177
178    /**
179     * Return the parameter types, in internal form, for the given method
180     * descriptor string.
181     */
182    public String[] getDescriptorParamNames(String descriptor) {
183        if (descriptor == null || descriptor.length() == 0)
184            return new String[0];
185
186        int index = descriptor.indexOf(')');
187        if (index == -1)
188            return new String[0];
189
190        // get rid of the parens and the return type
191        descriptor = descriptor.substring(1, index);
192
193        // break the param string into individual params
194        List tokens = new LinkedList();
195        while (descriptor.length() > 0) {
196            index = 0;
197
198            // skip the '[' up to the first letter code
199            while (!Character.isLetter(descriptor.charAt(index)))
200                index++;
201
202            // non-primitives always start with 'L' and end with ';'
203            if (descriptor.charAt(index) == 'L')
204                index = descriptor.indexOf(';');
205
206            tokens.add(descriptor.substring(0, index + 1));
207            descriptor = descriptor.substring(index + 1);
208        }
209        return (String[]) tokens.toArray(new String[tokens.size()]);
210    }
211
212    /**
213     * Return the component type name for the given array type, or null
214     * if the given string does not represent an array type name. The name
215     * given should be in proper {@link Class#forName} form.
216     */
217    public String getComponentName(String name) {
218        if (name == null || !name.startsWith("["))
219            return null;
220
221        name = name.substring(1);
222        if (!name.startsWith("[") && name.endsWith(";"))
223            name = name.substring(1, name.length() - 1);
224
225        // will convert primitive type codes to names
226        return getExternalForm(name, false);
227    }
228
229    /**
230     * Clear the cache.
231     */
232    public void clear() {
233        _internal.clear();
234        _internalDescriptor.clear();
235        _external.clear();
236        _externalHuman.clear();
237    }
238}