* Copyright (C) 2011 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
* This library is distributed under a modified BSD license. See the included
* RSTALanguageSupport.License.txt file for details.
package org.fife.rsta.ac.java.classreader.attributes;
import java.util.ArrayList;
import java.util.HashMap;
import org.fife.rsta.ac.java.classreader.ClassFile;
import org.fife.rsta.ac.java.classreader.MethodInfo;
* The Signature attribute is an optional fixed-length attribute in the
* attribute table of the ClassFile, field_info and method_info
* WARNING: This code is a complete mess.
public class Signature extends AttributeInfo {
private String signature;
public Signature(ClassFile cf, String signature) {
this.signature = signature;
public List getClassParamTypes() {
if (signature!=null && signature.startsWith("<")) {
types = new ArrayList(1); // Usually a small number
int afterMatchingGT = skipLtGt(signature, 1);
// We're assuming we don't come across corrupt signatures...
String temp = signature.substring(1, afterMatchingGT-1);
int colon = temp.indexOf(':', offs);
while (offs<temp.length() && colon>-1) {
String ident = temp.substring(offs, colon);
char ch = temp.charAt(colon+1);
if (ch=='L') { // A ClassTypeSignature
int semicolon = temp.indexOf(';', colon+2);
//String type = temp.substring(colon+2, semicolon);
colon = temp.indexOf(':', offs);
System.err.println("WARN: Can't parse signature (1): " + signature);
System.err.println("WARN: Can't parse signature (2): " + signature);
private int skipLtGt(String str, int start) {
while (offs<str.length() && ltCount>0) {
char ch = str.charAt(offs++);
public List getMethodParamTypes(MethodInfo mi, ClassFile cf, boolean qualified) {
List paramTypeList = null;
String signature = this.signature; // Since we modify it
paramTypeList = new ArrayList();
// Handle "<...>", which essentially defines extra type args
Map additionalTypeArgs = null;
if (signature.charAt(0)=='<') {
int afterMatchingGT = skipLtGt(signature, 1);
String typeParams = signature.substring(1, afterMatchingGT-1);
additionalTypeArgs = parseAdditionalTypeArgs(typeParams);
signature = signature.substring(afterMatchingGT);
if (signature.charAt(0)=='(') {
int rparen = signature.indexOf(')', 1);
String paramDescriptors = signature.substring(1, rparen);
ParamDescriptorResult res = new ParamDescriptorResult();
while (paramDescriptors.length()>0) {
parseParamDescriptor(paramDescriptors, cf, additionalTypeArgs,
mi, "Error parsing method signature for ", res, qualified);
paramTypeList.add(res.type);
if(paramDescriptors.length()>res.pos) {
paramDescriptors = paramDescriptors.substring(res.pos);
System.out.println("TODO: Unhandled method signature for " +
mi.getName() + ": " + signature);
public String getMethodReturnType(MethodInfo mi, ClassFile cf, boolean qualified) {
String signature = this.signature; // Since we modify it
// Handle "<...>", which essentially defines extra type args
Map additionalTypeArgs = null;
if (signature.charAt(0)=='<') {
int afterMatchingGT = skipLtGt(signature, 1);
String typeParams = signature.substring(1, afterMatchingGT-1);
additionalTypeArgs = parseAdditionalTypeArgs(typeParams);
signature = signature.substring(afterMatchingGT);
if (signature.charAt(0)=='(') {
int rparen = signature.indexOf(')', 1);
if (rparen>-1 && rparen<signature.length()-3) { // Should always be true
String afterRParen = signature.substring(rparen+1);
ParamDescriptorResult res = new ParamDescriptorResult();
parseParamDescriptor(afterRParen, cf, additionalTypeArgs, mi,
"Can't parse return type from method sig for ", res, qualified);
System.out.println("TODO: Unhandled method signature for " +
mi.getName() + ": " + signature);
public String getSignature() {
* Returns the type argument specified for a given type parameter.
* @param typeVar The type parameter name.
* @param cf The class file with generic methods.
* @param additionalTypeArgs Additional type arguments for a method (such
* as for "<code><T> T[] toArray(T[] a)</code>", where the
* "<code>T</code>" type parameter is the type of an argument passed
* @return The type argument, or <code>null</code> if the given type
* parameter isn't defined.
private String getTypeArgument(String typeVar, ClassFile cf,
Map additionalTypeArgs) {
String type = cf.getTypeArgument(typeVar);
if (type==null && additionalTypeArgs!=null) {
//type = (String)additionalTypeArgs.get(typeVar);
private Map parseAdditionalTypeArgs(String typeParams) {
Map additionalTypeArgs = new HashMap();
int colon = typeParams.indexOf(':', offs);
while (offs<typeParams.length()) {
String param = typeParams.substring(offs, colon);
int semicolon = typeParams.indexOf(';', offs+1);
int lt = typeParams.indexOf('<', offs+1);
if (lt>-1 && lt<semicolon) { // Type parameters in class
int afterMatchingGT = skipLtGt(typeParams, lt+1);
String typeArg = typeParams.substring(colon+1, afterMatchingGT);
additionalTypeArgs.put(param, typeArg);
offs = afterMatchingGT + 1; // Skip trailing ';' also
else { // No type parameters, just a class name
String typeArg = typeParams.substring(colon+1, semicolon);
additionalTypeArgs.put(param, typeArg);
colon = typeParams.indexOf(':', offs);
return additionalTypeArgs;
private ParamDescriptorResult parseParamDescriptor(String str,
ClassFile cf, Map additionalTypeArgs,
MethodInfo mi, String errorDesc,
ParamDescriptorResult res, boolean qualified) {
// Can't do lastIndexOf() as there may be > 1 array parameter
// int braceCount = str.lastIndexOf('[') + 1;
while (str.charAt(++braceCount) == '[');
boolean extendingGenericType = false;
switch (str.charAt(pos)) {
int semicolon = str.indexOf(';', pos+1);
int lt = str.indexOf('<', pos+1);
if (lt>-1 && lt<semicolon) { // Type parameters in type class
int offs = skipLtGt(str, lt+1);
// There should be a ';' after type parameters
if (offs==str.length() || str.charAt(offs)!=';') {
System.out.println("TODO: " + errorDesc +
mi.getName() + ": " + signature);
type = "ERROR_PARSING_METHOD_SIG";
// Set "type" to class name, without type params
type = str.substring(pos+1, lt);
//type = org.fife.rsta.ac.java.Util.replaceChar(type, '/', '.');
//type = type.substring(type.lastIndexOf('/')+1);
type = qualified ? type.replace('/', '.') : type.substring(type.lastIndexOf('/')+1);
String paramDescriptors = str.substring(lt+1, offs-1);
ParamDescriptorResult res2 = new ParamDescriptorResult();
List paramTypeList = new ArrayList();
// Recursively parse type parameters of this parameter
while (paramDescriptors.length()>0) {
parseParamDescriptor(paramDescriptors, cf, additionalTypeArgs,
mi, "Error parsing method signature for ", res2, qualified);
paramTypeList.add(res2.type);
if(paramDescriptors.length()>res2.pos) {
paramDescriptors = paramDescriptors.substring(res2.pos);
StringBuffer sb = new StringBuffer(type).append('<');
for (int i=0; i<paramTypeList.size(); i++) {
sb.append(paramTypeList.get(i));
if (i<paramTypeList.size()-1) {
type = sb.append('>').toString();
pos = offs+1;//semicolon + 1; Skip semicolon that came AFTER "<...>"
String clazz = str.substring(pos + 1, semicolon);
//clazz = org.fife.rsta.ac.java.Util.replaceChar(clazz, '/', '.');
//clazz = clazz.substring(clazz.lastIndexOf('/')+1);
clazz = qualified ? clazz.replace('/', '.') : clazz.substring(clazz.lastIndexOf('/')+1);
case '+': // "super extends T"
extendingGenericType = true;
case 'T': // Generic type
semicolon = str.indexOf(';', pos+1);
String typeVar = str.substring(pos+1, semicolon);
type = getTypeArgument(typeVar, cf, additionalTypeArgs);
type = "UNKNOWN_GENERIC_TYPE_" + typeVar;
else if (extendingGenericType) {
type = "? extends " + type;
// Invalid method descriptor
String temp = "INVALID_TYPE_" + str;
for (int i = 0; i < braceCount; i++) {
return res.set(type, pos);
public String toString() {
return "[Signature: signature=" + getSignature() + "]";
private static class ParamDescriptorResult {
public ParamDescriptorResult set(String type, int pos) {