blob: 8798aa7aa45787ab9a626fa1286a6d091da0cf89 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.javadoc;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.swt.graphics.Image;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.CompletionRequestor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.TypeFilter;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IJavadocCompletionProcessor;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.ProposalInfo;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
public class JavaDocCompletionEvaluator implements IJavadocCompletionProcessor, IJavaDocTagConstants, IHtmlTagConstants {
private static final String[] fgHTMLProposals= new String[HTML_GENERAL_TAGS.length * 2];
{
String tag= null;
int index= 0;
int offset= 0;
while (index < fgHTMLProposals.length) {
tag= HTML_GENERAL_TAGS[offset];
fgHTMLProposals[index++]= HTML_TAG_PREFIX + tag + HTML_TAG_POSTFIX;
fgHTMLProposals[index++]= HTML_CLOSE_PREFIX + tag + HTML_TAG_POSTFIX;
offset++;
}
}
private ICompilationUnit fCompilationUnit;
private IDocument fDocument;
private int fCurrentPos;
private int fCurrentLength;
private String fErrorMessage;
private JavaElementLabelProvider fLabelProvider;
private List fResult;
private boolean fRestrictToMatchingCase;
public JavaDocCompletionEvaluator() {
fResult= new ArrayList();
}
private static boolean isWordPart(char ch) {
return Character.isJavaIdentifierPart(ch) || (ch == '#') || (ch == '.') || (ch == '/');
}
private static int findCharBeforeWord(IDocument doc, int lineBeginPos, int pos) {
int currPos= pos - 1;
if (currPos > lineBeginPos) {
try {
while (currPos > lineBeginPos && isWordPart(doc.getChar(currPos))) {
currPos--;
}
return currPos;
} catch (BadLocationException e) {
// ignore
}
}
return pos;
}
private static int findLastWhitespace(IDocument doc, int lineBeginPos, int pos) {
try {
int currPos= pos - 1;
while (currPos >= lineBeginPos && Character.isWhitespace(doc.getChar(currPos))) {
currPos--;
}
return currPos + 1;
} catch (BadLocationException e) {
// ignore
}
return pos;
}
private static int findClosingCharacter(IDocument doc, int pos, int end, char endChar) throws BadLocationException {
int curr= pos;
while (curr < end && (doc.getChar(curr) != endChar)) {
curr++;
}
if (curr < end) {
return curr + 1;
}
return pos;
}
private static int findReplaceEndPos(IDocument doc, String newText, String oldText, int pos) {
if (oldText.length() == 0 || oldText.equals(newText)) {
return pos;
}
try {
IRegion lineInfo= doc.getLineInformationOfOffset(pos);
int end= lineInfo.getOffset() + lineInfo.getLength();
if (newText.endsWith(">")) { //$NON-NLS-1$
// for html, search the tag end character
return findClosingCharacter(doc, pos, end, '>');
}
char ch= 0;
int pos1= pos;
while (pos1 < end && Character.isJavaIdentifierPart(ch= doc.getChar(pos1))) {
pos1++;
}
if (pos1 < end) {
// for method references, search the closing bracket
if ((ch == '(') && newText.endsWith(")")) { //$NON-NLS-1$
return findClosingCharacter(doc, pos1, end, ')');
}
}
return pos1;
} catch (BadLocationException e) {
// ignore
}
return pos;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaDocCompletionProcessor#computeCompletionProposals(org.eclipse.jdt.core.ICompilationUnit, int, int, int)
*/
public IJavaCompletionProposal[] computeCompletionProposals(ICompilationUnit cu, int offset, int length, int flags) {
fCompilationUnit= cu;
fCurrentPos= offset;
fCurrentLength= length;
fRestrictToMatchingCase= (flags & RESTRICT_TO_MATCHING_CASE) != 0;
IEditorInput editorInput= new FileEditorInput((IFile) cu.getResource());
fDocument= JavaUI.getDocumentProvider().getDocument(editorInput);
if (fDocument == null) {
return null;
}
fLabelProvider= new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_POST_QUALIFIED | JavaElementLabelProvider.SHOW_PARAMETERS);
try {
evalProposals();
return (JavaCompletionProposal[]) fResult.toArray(new JavaCompletionProposal[fResult.size()]);
} catch (JavaModelException e) {
fErrorMessage= e.getLocalizedMessage();
} finally {
fLabelProvider.dispose();
fLabelProvider= null;
fResult.clear();
}
return null;
}
private void evalProposals() throws JavaModelException {
try {
IRegion info= fDocument.getLineInformationOfOffset(fCurrentPos);
int lineBeginPos= info.getOffset();
int word1Begin= findCharBeforeWord(fDocument, lineBeginPos, fCurrentPos);
if (word1Begin == fCurrentPos) {
return;
}
char firstChar= fDocument.getChar(word1Begin);
if (firstChar == '@') {
String prefix= fDocument.get(word1Begin, fCurrentPos - word1Begin);
addProposals(prefix, JAVADOC_GENERAL_TAGS, JavaPluginImages.IMG_OBJS_JAVADOCTAG);
return;
} else if (firstChar == '<') {
String prefix= fDocument.get(word1Begin, fCurrentPos - word1Begin);
addProposals(prefix, fgHTMLProposals, JavaPluginImages.IMG_OBJS_HTMLTAG);
return;
} else if (!Character.isWhitespace(firstChar)) {
return;
}
String prefix= fDocument.get(word1Begin + 1, fCurrentPos - word1Begin - 1);
// could be a composed java doc construct (@param, @see ...)
int word2End= findLastWhitespace(fDocument, lineBeginPos, word1Begin);
if (word2End != lineBeginPos) {
// find the word before the prefix
int word2Begin= findCharBeforeWord(fDocument, lineBeginPos, word2End);
if (fDocument.getChar(word2Begin) == '@') {
String tag= fDocument.get(word2Begin, word2End - word2Begin);
if (addArgumentProposals(tag, prefix)) {
return;
}
}
}
addAllTags(prefix);
} catch (BadLocationException e) {
// ignore
}
}
private boolean prefixMatches(String prefix, String proposal) {
if (fRestrictToMatchingCase) {
return proposal.startsWith(prefix);
} else if (proposal.length() >= prefix.length()) {
return prefix.equalsIgnoreCase(proposal.substring(0, prefix.length()));
}
return false;
}
private void addAllTags(String prefix) {
String jdocPrefix= "@" + prefix; //$NON-NLS-1$
for (int i= 0; i < JAVADOC_GENERAL_TAGS.length; i++) {
String curr= JAVADOC_GENERAL_TAGS[i];
if (prefixMatches(jdocPrefix, curr)) {
fResult.add(createCompletion(curr, prefix, curr, JavaPluginImages.get(JavaPluginImages.IMG_OBJS_JAVADOCTAG), null, 0));
}
}
String htmlPrefix= "<" + prefix; //$NON-NLS-1$
for (int i= 0; i < fgHTMLProposals.length; i++) {
String curr= fgHTMLProposals[i];
if (prefixMatches(htmlPrefix, curr)) {
fResult.add(createCompletion(curr, prefix, curr, JavaPluginImages.get(JavaPluginImages.IMG_OBJS_HTMLTAG), null, 0));
}
}
}
private void addProposals(String prefix, String[] choices, String imageName) {
for (int i= 0; i < choices.length; i++) {
String curr= choices[i];
if (prefixMatches(prefix, curr)) {
fResult.add(createCompletion(curr, prefix, curr, JavaPluginImages.get(imageName), null, 0));
}
}
}
private void addProposals(String prefix, IJavaElement[] choices) {
for (int i= 0; i < choices.length; i++) {
IJavaElement elem= choices[i];
String curr= getReplaceString(elem);
if (prefixMatches(prefix, curr)) {
ProposalInfo info= (elem instanceof IMember) ? new ProposalInfo((IMember) elem) : null;
fResult.add(createCompletion(curr, prefix, fLabelProvider.getText(elem), fLabelProvider.getImage(elem), info, 0));
}
}
}
private String getReplaceString(IJavaElement elem) {
if (elem instanceof IMethod) {
IMethod meth= (IMethod)elem;
StringBuffer buf= new StringBuffer();
buf.append(meth.getElementName());
buf.append('(');
String[] types= meth.getParameterTypes();
int last= types.length - 1;
for (int i= 0; i <= last; i++) {
buf.append(Signature.toString(Signature.getTypeErasure(types[i])));
if (i != last) {
buf.append(", "); //$NON-NLS-1$
}
}
buf.append(')');
return buf.toString();
}
return elem.getElementName();
}
/*
* Returns true if case is handled
*/
private boolean addArgumentProposals(String tag, String argument) throws JavaModelException {
IJavaElement elem= fCompilationUnit.getElementAt(fCurrentPos);
if ("@see".equals(tag) || "@link".equals(tag) || "@linkplain".equals(tag) || "@value".equals(tag)) { //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-1$
if (elem instanceof IMember) {
evalSeeTag((IMember) elem, argument);
return true;
}
} else if ("@param".equals(tag)) { //$NON-NLS-1$
if (elem instanceof IMethod) {
String[] names= ((IMethod)elem).getParameterNames();
addProposals(argument, names, JavaPluginImages.IMG_MISC_DEFAULT);
ITypeParameter[] typeParameters= ((IMethod)elem).getTypeParameters();
String[] typeParameterNames= new String[typeParameters.length];
for (int i= 0; i < typeParameters.length; i++) {
typeParameterNames[i]= "<"+typeParameters[i].getElementName()+">"; //$NON-NLS-1$ //$NON-NLS-2$
}
addProposals(argument, typeParameterNames, JavaPluginImages.IMG_MISC_DEFAULT);
} else if (elem instanceof IType) {
ITypeParameter[] typeParameters= ((IType) elem).getTypeParameters();
String[] typeParameterNames= new String[typeParameters.length];
for (int i= 0; i < typeParameters.length; i++) {
typeParameterNames[i]= "<"+typeParameters[i].getElementName()+">"; //$NON-NLS-1$ //$NON-NLS-2$
}
addProposals(argument, typeParameterNames, JavaPluginImages.IMG_MISC_DEFAULT);
}
return true;
} else if ("@throws".equals(tag) || "@exception".equals(tag)) { //$NON-NLS-2$ //$NON-NLS-1$
if (elem instanceof IMethod) {
String[] exceptions= ((IMethod)elem).getExceptionTypes();
for (int i= 0; i < exceptions.length; i++) {
String curr= Signature.toString(exceptions[i]);
if (prefixMatches(argument, curr)) {
fResult.add(createCompletion(curr, argument, curr, JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CLASS), null, 100));
}
}
evalTypeNameCompletions((IMethod)elem, fCurrentPos - argument.length(), argument);
}
return true;
} else if ("@serialData".equals(tag)) { //$NON-NLS-1$
if (elem instanceof IField) {
String name= ((IField)elem).getElementName();
fResult.add(createCompletion(name, argument, name, fLabelProvider.getImage(elem), null, 0));
}
return true;
}
return false;
}
private void evalSeeTag(IMember elem, String arg) throws JavaModelException {
int wordStart= fCurrentPos - arg.length();
int pidx= arg.indexOf('#');
if (pidx == -1) {
evalTypeNameCompletions(elem, wordStart, arg);
} else {
IType parent= null;
if (pidx > 0) {
// method or field
parent= getTypeNameResolve(elem, wordStart, wordStart + pidx);
} else {
// '@see #foo'
parent= (IType) elem.getAncestor(IJavaElement.TYPE);
}
if (parent != null) {
int nidx= arg.indexOf('(', pidx);
if (nidx == -1) {
nidx= arg.length();
}
String prefix= arg.substring(pidx + 1, nidx);
addProposals(prefix, parent.getMethods());
addProposals(prefix, parent.getFields());
}
}
}
private void evalTypeNameCompletions(IMember currElem, int wordStart, String arg) throws JavaModelException {
ICompilationUnit preparedCU= createPreparedCU(currElem, wordStart, fCurrentPos);
if (preparedCU != null) {
CompletionRequestor requestor= new CompletionRequestor() {
public void accept(CompletionProposal proposal) {
if (proposal.getKind() == CompletionProposal.TYPE_REF) {
String fullTypeName= new String(Signature.toCharArray(proposal.getSignature()));
if (TypeFilter.isFiltered(fullTypeName)) {
return;
}
int start= proposal.getReplaceStart();
int end= proposal.getReplaceEnd();
char[] completion= proposal.getCompletion();
fResult.add(createSeeTypeCompletion(proposal.getFlags(), start, end, completion, fullTypeName, proposal.getRelevance()));
}
}
};
try {
preparedCU.codeComplete(fCurrentPos, requestor);
if (currElem.getDeclaringType() == null && fCurrentPos > wordStart && currElem.getElementName().startsWith(arg)) {
// for top level types, we use a fake import statement and the current type is never suggested
IType type= (IType) currElem;
char[] name= type.getElementName().toCharArray();
fResult.add(createSeeTypeCompletion(0, wordStart, fCurrentPos, name, JavaModelUtil.getFullyQualifiedName(type), 50));
}
} finally {
preparedCU.discardWorkingCopy();
}
}
}
private IType getTypeNameResolve(IMember elem, int wordStart, int wordEnd) throws JavaModelException {
ICompilationUnit preparedCU= createPreparedCU(elem, wordStart, wordEnd);
if (preparedCU != null) {
try {
IJavaElement[] elements= preparedCU.codeSelect(wordEnd, 0);
if (elements != null && elements.length == 1 && elements[0] instanceof IType) {
IType type= (IType) elements[0];
if (preparedCU.equals(type.getCompilationUnit())) {
IJavaElement[] res= elem.getCompilationUnit().findElements(type);
if (res.length > 0) {
return (IType) res[0];
}
} else {
return type;
}
}
} finally {
preparedCU.getBuffer().setContents(fCompilationUnit.getBuffer().getCharacters());
preparedCU.discardWorkingCopy();
}
}
return null;
}
private ICompilationUnit createPreparedCU(IMember elem, int wordStart, int wordEnd) throws JavaModelException {
int startpos= elem.getSourceRange().getOffset();
char[] content= (char[]) fCompilationUnit.getBuffer().getCharacters().clone();
if ((elem.getDeclaringType() == null) && (wordStart + 6 < content.length)) {
content[startpos++]= 'i'; content[startpos++]= 'm'; content[startpos++]= 'p';
content[startpos++]= 'o'; content[startpos++]= 'r'; content[startpos++]= 't';
}
if (wordStart < content.length) {
for (int i= startpos; i < wordStart; i++) {
content[i]= ' ';
}
}
/*
* Explicitly create a new non-shared working copy.
*/
ICompilationUnit newCU= fCompilationUnit.getWorkingCopy(null);
newCU.getBuffer().setContents(content);
return newCU;
}
private JavaCompletionProposal createCompletion(String newText, String oldText, String labelText, Image image, ProposalInfo proposalInfo, int severity) {
int offset= fCurrentPos - oldText.length();
int length= fCurrentLength + oldText.length();
if (fCurrentLength == 0)
length= findReplaceEndPos(fDocument, newText, oldText, fCurrentPos) - offset;
JavaCompletionProposal proposal= new JavaCompletionProposal(newText, offset, length, image, labelText, severity);
proposal.setProposalInfo(proposalInfo);
proposal.setTriggerCharacters( new char[] { '#' });
return proposal;
}
private JavaCompletionProposal createSeeTypeCompletion(int flags, int start, int end, char[] completion, String fullTypeName, int severity) {
ProposalInfo proposalInfo= new ProposalInfo(fCompilationUnit.getJavaProject(), fullTypeName);
StringBuffer nameBuffer= new StringBuffer();
String simpleName= Signature.getSimpleName(fullTypeName);
nameBuffer.append(simpleName);
if (simpleName.length() != fullTypeName.length()) {
nameBuffer.append(JavaElementLabels.CONCAT_STRING);
nameBuffer.append(Signature.getQualifier(fullTypeName));
}
ImageDescriptor desc= JavaElementImageProvider.getTypeImageDescriptor(false, false, flags, false);
Image image= JavaPlugin.getImageDescriptorRegistry().get(desc);
int compLen= completion.length;
if (compLen > 0 && completion[compLen - 1] == ';') {
compLen--; // remove the semicolon from import proposals
}
JavaCompletionProposal proposal= new JavaTypeCompletionProposal(String.valueOf(completion, 0, compLen), null, start, end - start, image, nameBuffer.toString(), severity);
// JavaCompletionProposal proposal= new JavaCompletionProposal(String.valueOf(completion, 0, compLen), start, end - start, image, nameBuffer.toString(), severity);
proposal.setProposalInfo(proposalInfo);
proposal.setTriggerCharacters( new char[] { '#' });
return proposal;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaDocCompletionProcessor#computeContextInformation(org.eclipse.jdt.core.ICompilationUnit, int)
*/
public IContextInformation[] computeContextInformation(ICompilationUnit cu, int offset) {
fErrorMessage= null;
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaDocCompletionProcessor#getErrorMessage()
*/
public String getErrorMessage() {
return fErrorMessage;
}
}