View Javadoc

1   package org.sourceforge.jemm.client;
2   
3   import java.lang.reflect.Method;
4   import java.util.ArrayList;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.regex.Matcher;
9   import java.util.regex.Pattern;
10  
11  /**
12   * A descriptor is a method signature that has the following format: <code>
13   *   className#methodName<space>(<methodParameters>)<returnValue>
14   *   Where:
15   *   methodName - The name of the originating method.
16   *   <methodParameters> - meets the JVM spec for types, ie D for double, F for float, L<className>; for objects
17   *   <ReturnValues> - meets the JVM spec for types.
18   *   
19   *   The methodParameters and returnValue will work identically to the javap output.
20   * 
21   * @author Paul Keeble
22   * 
23   */
24  public class Descriptor {
25  	private static final Map<String, Class<?>> PRIMITIVE_TYPES = getPrimtiveTypesMap();
26  	private static final Pattern DESCRIPTOR_PATTERN = Pattern.compile("((\\S+)#(\\S+)\\s+\\((.*)\\)(\\S+))");
27  	
28  	private static final Pattern PARTIAL_PATTERN = Pattern.compile("(\\((.*)\\)(\\S+))");
29  
30  	private String className;
31  	private String methodName;
32  	private String[] parameterTypes;
33  	private String returnType;
34  	
35  	public Descriptor(String signature) throws DescriptorParsingException {
36  		parse(signature);
37  	}
38  	
39  	public Descriptor(String className, String methodName, String returnType,String... parameters) {
40  		this.className =className;
41  		this.methodName = methodName;
42  		this.returnType = returnType;
43  		this.parameterTypes = parameters;
44  	}
45  	
46  	public Descriptor(String className,String methodName, String signature) {
47  		this.className =className;
48  		this.methodName = methodName;
49  		parsePartial(signature);
50  	}
51  	
52  	private void parsePartial(String signature) {
53  		try {
54  			Matcher m = PARTIAL_PATTERN.matcher(signature);
55  			if(m.matches()) {
56  				String parameters = m.group(2);
57  				parameterTypes = extractTypesFromParameterString(parameters);
58  				returnType = m.group(3);
59  			} else {
60  				throw new DescriptorParsingException("Descriptor (" + signature +") is invalid");
61  			}
62  		} catch(Exception e) {
63  			throw new DescriptorParsingException("Descriptor (" + signature +") is invalid",e);
64  		}
65  	}
66  	
67  	private void parse(String signature) {
68  		try {
69  			Matcher m = DESCRIPTOR_PATTERN.matcher(signature);
70  			if(m.matches()) {
71  				className =  m.group(2);
72  				methodName = m.group(3);
73  				String parameters = m.group(4);
74  				parameterTypes = extractTypesFromParameterString(parameters);
75  				returnType = m.group(5);
76  			} else {
77  				throw new DescriptorParsingException("Descriptor (" + signature +") is invalid");
78  			}
79  		} catch(Exception e) {
80  			throw new DescriptorParsingException("Descriptor (" + signature +") is invalid",e);
81  		}
82  	}
83  	
84  	public String getClassName() {
85  		return className;
86  	}
87  	
88  	public Class<?> getClassContainingMethod() throws ClassNotFoundException {
89  		return Class.forName(getClassName());
90  	}
91  	
92  	public Method getMethod() throws SecurityException, NoSuchMethodException, ClassNotFoundException {
93  		return getClassContainingMethod().getDeclaredMethod(getMethodName(), getParameterClasses());
94  	}
95  
96  	public String getMethodName() {
97  		return methodName;
98  	}
99  
100 	private String [] extractTypesFromParameterString(String pString) {
101 		List<String> results = new ArrayList<String>();
102 
103 		int i = 0;
104 		
105 		while (i < pString.length()) {
106 			String array = readArrayBraces(pString, i);
107 			String type = readType(pString, i + array.length());
108 
109 			results.add(array + type);
110 			i += array.length() + type.length();
111 		}
112 		return results.toArray(new String[results.size()]);
113 	}
114 	
115 	public String[] getParameterStrings() {
116 		return parameterTypes;
117 	}
118 
119 	/**
120 	 * Reads a single type from the starting index.
121 	 * 
122 	 * @param pString
123 	 * @param index
124 	 * @return
125 	 */
126 	private String readType(String pString, int start) {
127 		if (pString.charAt(start) == 'L') {
128 			int objectNameEndPos = pString.indexOf(";", start);
129 			return convertClassName(pString.substring(start,
130 					objectNameEndPos + 1));
131 		} else {
132 			return pString.substring(start, start + 1);
133 		}
134 	}
135 	
136 	/**
137 	 * If there is a [ at 'start' then all ['s are read until a different
138 	 * character is found and the string of ['s is returned.
139 	 * 
140 	 * @param pString
141 	 *            The string to look in
142 	 * @param start
143 	 *            The index in the String to start with
144 	 * @return The String of ['s or an empty String
145 	 */
146 	private String readArrayBraces(String pString, int start) {
147 		int end = start;
148 		for (int j = start; j < pString.length(); ++j) {
149 			if (pString.charAt(j) != '[') {
150 				end = j;
151 				break;
152 			}
153 		}
154 		return pString.substring(start, end);
155 	}
156 
157 	/**
158 	 * Converts java/lang/Object like class names to a valid class name with
159 	 * full stops such as java.lang.Object.
160 	 * 
161 	 * @param classWithSlashes
162 	 * @return
163 	 */
164 	private String convertClassName(String classWithSlashes) {
165 		return classWithSlashes.replace('/', '.');
166 	}
167 	
168 	public String getReturnString() {
169 		return returnType;
170 	}
171 	
172 	public Class<?> getReturnClass() throws ClassNotFoundException {
173 		return getType(getReturnString());
174 	}
175 	
176 	public Class<?>[] getParameterClasses() throws ClassNotFoundException {
177 		String[] pStrings = getParameterStrings();
178 		List<Class<?>> results = new ArrayList<Class<?>>();
179 		for (int i = 0; i < pStrings.length; ++i) {
180 			results.add(getType(pStrings[i]));
181 		}
182 		if(containsJustVoid(results))
183 			return new Class[0];
184 		
185 		return results.toArray(new Class[results.size()]);
186 	}
187 	
188 	private boolean containsJustVoid(List<Class<?>> results) {
189 		if(results.size()>1 || results.size()==0)
190 			return false;
191 		if(Void.TYPE.equals(results.get(0)))
192 			return true;
193 
194 		return false;
195 	}
196 	
197 	public Class<?> getType(String typeName) throws ClassNotFoundException {
198 		if (PRIMITIVE_TYPES.containsKey(typeName)) {
199 			return PRIMITIVE_TYPES.get(typeName);
200 		} else {
201 			Class<?> clazz = Class.forName(cleanClassName(typeName));
202 			return clazz;
203 		}
204 	}
205 
206 	/**
207 	 * If the passed String starts with L and finishes with ; then the L and the
208 	 * ; is stripped.
209 	 * 
210 	 * @param aClassName The classname to clean.
211 	 * @return The cleaned class name.
212 	 */
213 	protected String cleanClassName(String aClassName) {
214 		if (aClassName.startsWith("L") && aClassName.endsWith(";"))
215 			return aClassName.substring(1, aClassName.length() - 1);
216 		else
217 			return aClassName;
218 	}
219 	
220 	private String getParameterString() {
221 		StringBuffer sb = new StringBuffer();
222 		for(int i=0; i<parameterTypes.length; ++i)
223 			sb.append(parameterTypes[i]);
224 
225 		return sb.toString();
226 	}
227 	
228 	public String getDescriptorString() {
229 		return className + "#" +methodName+" ("+getParameterString()+")"+returnType;
230 	}
231 	
232 	public int hashCode() {
233 		return getDescriptorString().hashCode();
234 	}
235 	
236 	public boolean equals(Object obj) {
237 		if(!(obj instanceof Descriptor))
238 			return false;
239 		
240 		Descriptor rhs = (Descriptor)obj;
241 		return getDescriptorString().equals(rhs.getDescriptorString());
242 	}
243 	
244 	public String toString() {
245 		return getDescriptorString();
246 	}
247 
248 	public static Map<String, Class<?>> getPrimtiveTypesMap() {
249 		Map<String, Class<?>> m = new HashMap<String, Class<?>>();
250 		m.put("B", Byte.TYPE);
251 		m.put("C", Character.TYPE);
252 		m.put("D", Double.TYPE);
253 		m.put("F", Float.TYPE);
254 		m.put("I", Integer.TYPE);
255 		m.put("J", Long.TYPE);
256 		m.put("S", Short.TYPE);
257 		m.put("Z", Boolean.TYPE);
258 		m.put("V", Void.TYPE);
259 		
260 		return m;
261 	}
262 }