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 }