/*
* 01/16/2011
*
* 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 java.util.List;
import java.util.Map;
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
* structures.<p>
* WARNING: This code is a complete mess.
*
* @author Robert Futrell
* @version 1.0
*/
public class Signature extends AttributeInfo {
private String signature;
public Signature(ClassFile cf, String signature) {
super(cf);
this.signature = signature;
}
public List getClassParamTypes() {
List types = null;
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 offs = 0;
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);
if (semicolon>-1) {
//String type = temp.substring(colon+2, semicolon);
// TODO: ...
types.add(ident);
offs = semicolon + 1;
colon = temp.indexOf(':', offs);
}
else {
System.err.println("WARN: Can't parse signature (1): " + signature);
break;
}
}
else {
System.err.println("WARN: Can't parse signature (2): " + signature);
break;
}
}
}
return types;
}
private int skipLtGt(String str, int start) {
int ltCount = 1;
int offs = start;
while (offs<str.length() && ltCount>0) {
char ch = str.charAt(offs++);
switch (ch) {
case '<':
ltCount++;
break;
case '>':
ltCount--;
break;
}
}
return offs;
}
public List getMethodParamTypes(MethodInfo mi, ClassFile cf, boolean qualified) {
List paramTypeList = null;
String signature = this.signature; // Since we modify it
if (signature!=null) {
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);
} else {
break;
}
}
}
else {
System.out.println("TODO: Unhandled method signature for " +
mi.getName() + ": " + signature);
}
}
return paramTypeList;
}
public String getMethodReturnType(MethodInfo mi, ClassFile cf, boolean qualified) {
String signature = this.signature; // Since we modify it
String sig = null;
if (signature!=null) {
// 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);
sig = res.type;
}
}
else {
System.out.println("TODO: Unhandled method signature for " +
mi.getName() + ": " + signature);
}
}
return sig;
}
public String getSignature() {
return signature;
}
/**
* 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
* to it).
* @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);
type = typeVar;
}
return type;
}
private Map parseAdditionalTypeArgs(String typeParams) {
Map additionalTypeArgs = new HashMap();
int offs = 0;
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);
offs = semicolon + 1;
}
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
// in the descriptors.
// int braceCount = str.lastIndexOf('[') + 1;
int braceCount = -1;
while (str.charAt(++braceCount) == '[');
int pos = braceCount;
String type = null;
boolean extendingGenericType = false;
switch (str.charAt(pos)) {
// BaseType
case 'B':
type = "byte";
pos++;
break;
case 'C':
type = "char";
pos++;
break;
case 'D':
type = "double";
pos++;
break;
case 'F':
type = "float";
pos++;
break;
case 'I':
type = "int";
pos++;
break;
case 'J':
type = "long";
pos++;
break;
case 'S':
type = "short";
pos++;
break;
case 'Z':
type = "boolean";
pos++;
break;
// ObjectType
case 'L':
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";
}
else {
// 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);
// Get type parameters
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);
} else {
break;
}
}
StringBuffer sb = new StringBuffer(type).append('<');
for (int i=0; i<paramTypeList.size(); i++) {
sb.append(paramTypeList.get(i));
if (i<paramTypeList.size()-1) {
sb.append(", ");
}
}
type = sb.append('>').toString();
pos = offs+1;//semicolon + 1; Skip semicolon that came AFTER "<...>"
}
}
else {
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);
type = clazz;
pos += semicolon + 1;
}
break;
case '+': // "super extends T"
extendingGenericType = true;
pos++;
// Fall through
case 'T': // Generic type
semicolon = str.indexOf(';', pos+1);
String typeVar = str.substring(pos+1, semicolon);
type = getTypeArgument(typeVar, cf, additionalTypeArgs);
if (type==null) {
type = "UNKNOWN_GENERIC_TYPE_" + typeVar;
}
else if (extendingGenericType) {
type = "? extends " + type;
}
pos = semicolon + 1;
break;
case '*':
type = "?";
pos++;
break;
// Invalid method descriptor
default:
String temp = "INVALID_TYPE_" + str;
type = temp;
pos += str.length();
break;
}
for (int i = 0; i < braceCount; i++) {
type += "[]";
}
return res.set(type, pos);
}
public String toString() {
return "[Signature: signature=" + getSignature() + "]";
}
private static class ParamDescriptorResult {
public String type;
public int pos;
public ParamDescriptorResult set(String type, int pos) {
this.type = type;
this.pos = pos;
return this;
}
}
}