001    package serp.bytecode;
002    
003    import java.util.*;
004    
005    /**
006     * Caching and conversion of names in both internal and external form.
007     *
008     * @author Abe White
009     */
010    public 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            StringBuffer prefix = new StringBuffer();
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            StringBuffer postfix = new StringBuffer(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            StringBuffer buf = new StringBuffer();
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    }