/*
* 03/21/2010
*
* Copyright (C) 2010 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;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.fife.rsta.ac.java.MemberCompletion.Data;
import org.fife.rsta.ac.java.buildpath.SourceLocation;
import org.fife.rsta.ac.java.classreader.ClassFile;
import org.fife.rsta.ac.java.classreader.MethodInfo;
import org.fife.rsta.ac.java.classreader.Util;
import org.fife.rsta.ac.java.rjc.ast.CompilationUnit;
import org.fife.rsta.ac.java.rjc.ast.FormalParameter;
import org.fife.rsta.ac.java.rjc.ast.Member;
import org.fife.rsta.ac.java.rjc.ast.Method;
import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration;
/**
* Metadata about a method as read from a class file. This class is used by
* instances of {@link MethodCompletion}.
*
* @author Robert Futrell
* @version 1.0
*/
class MethodInfoData implements Data {
/**
* The parent completion provider.
*/
private SourceCompletionProvider provider;
/**
* The actual metadata.
*/
private MethodInfo info;
/**
* Cached method parameter names.
*/
private List paramNames;
/**
* Constructor.
*
* @param info
* @param provider
*/
public MethodInfoData(MethodInfo info, SourceCompletionProvider provider) {
this.info = info;
this.provider = provider;
}
/**
* {@inheritDoc}
*/
public String getEnclosingClassName(boolean fullyQualified) {
return info.getClassFile().getClassName(fullyQualified);
}
/**
* {@inheritDoc}
*/
public String getIcon() {
String key = null;
int flags = info.getAccessFlags();
if (Util.isDefault(flags)) {
key = IconFactory.METHOD_DEFAULT_ICON;
}
else if (Util.isPrivate(flags)) {
key = IconFactory.METHOD_PRIVATE_ICON;
}
else if (Util.isProtected(flags)) {
key = IconFactory.METHOD_PROTECTED_ICON;
}
else if (Util.isPublic(flags)) {
key = IconFactory.METHOD_PUBLIC_ICON;
}
else {
key = IconFactory.METHOD_DEFAULT_ICON;
}
return key;
}
/**
* Scours the source in a location (zip file, directory), looking for a
* particular class's source. If it is found, it is parsed, and the
* {@link Method} for this method (if any) is returned.
*
* @param loc The zip file, jar file, or directory to look in.
* @param cf The {@link ClassFile} representing the class of this method.
* @return The method, or <code>null</code> if it cannot be found, or an
* IO error occurred.
*/
private Method getMethodFromSourceLoc(SourceLocation loc, ClassFile cf) {
Method res = null;
CompilationUnit cu = org.fife.rsta.ac.java.Util.
getCompilationUnitFromDisk(loc, cf);
// If the class's source was found and successfully parsed, look for
// this method.
if (cu!=null) {
for (Iterator i=cu.getTypeDeclarationIterator(); i.hasNext(); ) {
TypeDeclaration td = (TypeDeclaration)i.next();
String typeName = td.getName();
// Avoid inner classes, etc.
if (typeName.equals(cf.getClassName(false))) {
// Get all overloads of this method with the number of
// parameters we're looking for. 99% of the time, there
// will only be 1, the method we're looking for.
List contenders = null;
for (Iterator j=td.getMemberIterator(); j.hasNext(); ) {
Member member = (Member)j.next();
if (member instanceof Method &&
member.getName().equals(info.getName())) {
Method m2 = (Method)member;
if (m2.getParameterCount()==info.getParameterCount()) {
if (contenders==null) {
contenders = new ArrayList(1); // Usually just 1
}
contenders.add(m2);
}
}
}
// We found some matches.
if (contenders!=null) {
// Common case - only 1 overload with the desired
// number of parameters => it must be our method.
if (contenders.size()==1) {
res = (Method)contenders.get(0);
}
// More than 1 overload with the same number of
// parameters... we decide which contender is the one
// we're looking for by checking each of its
// parameters' types and making sure they're correct.
else {
for (int j=0; j<contenders.size(); j++) {
boolean match = true;
Method method = (Method)contenders.get(j);
for (int p=0; p<info.getParameterCount(); p++) {
String type1 = info.getParameterType(p, false);
FormalParameter fp = method.getParameter(p);
String type2 = fp.getType().toString();
if (!type1.equals(type2)) {
match = false;
break;
}
}
if (match) {
res = method;
break;
}
}
}
}
break;
} // if (typeName.equals(cf.getClassName(false)))
} // for (Iterator i=cu.getTypeDeclarationIterator(); i.hasNext(); )
} // if (cu!=null)
return res;
}
/**
* Returns the name of the specified parameter to this method, or
* <code>null</code> if it cannot be determined.
*
* @param index The index of the parameter.
* @return The name of the parameter, or <code>null</code>.
*/
public String getParameterName(int index) {
// First, check whether the debugging attribute was enabled at
// compilation, and the parameter name is embedded in the class file.
// This method takes priority because it *likely* matches a name
// specified in Javadoc, and is much faster for us to fetch (it's
// already parsed).
String name = info.getParameterName(index);
// Otherwise...
if (name==null) {
// Next, check the attached source, if any (lazily parsed).
if (paramNames==null) {
paramNames = new ArrayList(1);
int offs = 0;
String rawSummary = getSummary();
// If there's attached source with Javadoc for this method...
if (rawSummary!=null && rawSummary.startsWith("/**")) {
int nextParam = 0;
int summaryLen = rawSummary.length();
while ((nextParam=rawSummary.indexOf("@param", offs))>-1) {
int temp = nextParam + "@param".length() + 1;
while (temp<summaryLen &&
Character.isWhitespace(rawSummary.charAt(temp))) {
temp++;
}
if (temp<summaryLen) {
int start = temp;
int end = start + 1;
while (end<summaryLen &&
Character.isJavaIdentifierPart(rawSummary.charAt(end))) {
end++;
}
paramNames.add(rawSummary.substring(start, end));
offs = end;
}
else {
break;
}
}
}
}
if (index<paramNames.size()) {
name = (String)paramNames.get(index);
}
}
// Use a default name.
if (name==null) {
name = "arg" + index;
}
return name;
}
public String getSignature() {
// Don't call MethodInfo's implementation, as it is unaware of param
// names.
//return info.getNameAndParameters();
StringBuffer sb = new StringBuffer(info.getName());
sb.append('(');
int paramCount = info.getParameterCount();
for (int i=0; i<paramCount; i++) {
sb.append(info.getParameterType(i, false));
sb.append(' ');
sb.append(getParameterName(i));
if (i<paramCount-1) {
sb.append(", ");
}
}
sb.append(')');
return sb.toString();
}
public String getSummary() {
ClassFile cf = info.getClassFile();
SourceLocation loc = provider.getSourceLocForClass(cf.getClassName(true));
String summary = null;
// First, try to parse the Javadoc for this method from the attached
// source.
if (loc!=null) {
summary = getSummaryFromSourceLoc(loc, cf);
}
// Default to the method signature.
if (summary==null) {
summary = info.getSignature();
}
return summary;
}
/**
* Scours the source in a location (zip file, directory), looking for a
* particular class's source. If it is found, it is parsed, and the
* Javadoc for this method (if any) is returned.
*
* @param loc The zip file, jar file, or directory to look in.
* @param cf The {@link ClassFile} representing the class of this method.
* @return The summary, or <code>null</code> if the method has no javadoc,
* the class's source was not found, or an IO error occurred.
*/
private String getSummaryFromSourceLoc(SourceLocation loc, ClassFile cf) {
Method method = getMethodFromSourceLoc(loc, cf);
return method!=null ? method.getDocComment() : null;
}
/**
* {@inheritDoc}
*/
public String getType() {
return info.getReturnTypeString(false);
}
public boolean isAbstract() {
return info.isAbstract();
}
/**
* {@inheritDoc}
*/
public boolean isConstructor() {
return info.isConstructor();
}
/**
* {@inheritDoc}
*/
public boolean isDeprecated() {
return info.isDeprecated();
}
public boolean isFinal() {
return info.isFinal();
}
public boolean isStatic() {
return info.isStatic();
}
}