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 }