blob: 370e080b053bc0d2acb5cc486d015014037bd3cb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2014 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.pde.api.tools.ui.internal.completion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.CompletionContext;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.pde.api.tools.internal.JavadocTagManager;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.IApiJavadocTag;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.eclipse.pde.api.tools.ui.internal.ApiUIPlugin;
import org.eclipse.swt.graphics.Image;
/**
* This class creates completion proposals to Javadoc header blocks
* for the Javadoc tags contributed via the apiJavadocTags extension point.
*
* @see IApiJavadocTag
* @see JavadocTagManager
* @see APIToolsJavadocCompletionProposal
*
* @since 1.0.0
*/
public class APIToolsJavadocCompletionProposalComputer implements IJavaCompletionProposalComputer {
private String fErrorMessage = null;
private Image fImageHandle = null;
private ASTParser fParser = null;
HashMap/*<String, Boolean>*/ fExistingTags = null;
/**
* Collects all of the existing API Tools Javadoc tags form a given Javadoc node
*/
class TagCollector extends ASTVisitor {
/* (non-Javadoc)
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.Javadoc)
*/
public boolean visit(Javadoc node) {
Set tagnames = ApiPlugin.getJavadocTagManager().getAllTagNames();
List tags = node.tags();
if(fExistingTags == null) {
fExistingTags = new HashMap(tags.size());
}
TagElement tag = null;
String name = null;
for(Iterator iter = tags.iterator(); iter.hasNext();) {
tag = (TagElement) iter.next();
name = tag.getTagName();
if(name == null) {
continue;
}
if(tagnames.contains(name)) {
//only add existing API tools tags
fExistingTags.put(name, Boolean.valueOf(tag.fragments().isEmpty()));
}
}
return false;
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#computeCompletionProposals(org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
*/
public List computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
JavaContentAssistInvocationContext jcontext = null;
if(context instanceof JavaContentAssistInvocationContext) {
jcontext = (JavaContentAssistInvocationContext) context;
}
else {
return Collections.EMPTY_LIST;
}
IJavaProject project = jcontext.getProject();
if(project == null || !Util.isApiProject(project)) {
return Collections.EMPTY_LIST;
}
CompletionContext corecontext = jcontext.getCoreContext();
if(!corecontext.isInJavadoc()) {
return Collections.EMPTY_LIST;
}
ICompilationUnit cunit = jcontext.getCompilationUnit();
if(cunit != null) {
try {
int offset = jcontext.getInvocationOffset();
IJavaElement element = cunit.getElementAt(offset);
if (element == null) {
return Collections.EMPTY_LIST;
}
ImageDescriptor imagedesc = jcontext.getLabelProvider().createImageDescriptor(
org.eclipse.jdt.core.CompletionProposal.create(org.eclipse.jdt.core.CompletionProposal.JAVADOC_BLOCK_TAG,
offset));
fImageHandle = (imagedesc == null ? null : imagedesc.createImage());
int type = getType(element);
int member = IApiJavadocTag.MEMBER_NONE;
int elementtype = element.getElementType();
switch(elementtype) {
case IJavaElement.TYPE: {
IType itype = (IType) element;
if(hasNonVisibleParent(element, itype.isInterface())) {
return Collections.EMPTY_LIST;
}
break;
}
case IJavaElement.METHOD: {
IMethod method = (IMethod) element;
int flags = method.getFlags();
boolean inter = method.getDeclaringType().isInterface();
if(Flags.isPrivate(flags) ||
(!Flags.isDefaultMethod(flags) && inter) ||
(Flags.isPackageDefault(flags) && !inter) ||
hasNonVisibleParent(element, inter)) {
return Collections.EMPTY_LIST;
}
member = IApiJavadocTag.MEMBER_METHOD;
if(method.isConstructor()) {
member = IApiJavadocTag.MEMBER_CONSTRUCTOR;
}
break;
}
case IJavaElement.FIELD: {
IField field = (IField) element;
int flags = field.getFlags();
boolean inter = field.getDeclaringType().isInterface();
if(Flags.isFinal(flags) ||
field.isEnumConstant() ||
Flags.isPrivate(flags) ||
(Flags.isPackageDefault(flags) && !inter) ||
hasNonVisibleParent(element, inter)) {
return Collections.EMPTY_LIST;
}
member = IApiJavadocTag.MEMBER_FIELD;
break;
}
}
IApiJavadocTag[] tags = ApiPlugin.getJavadocTagManager().getTagsForType(type, member);
int tagcount = tags.length;
if(tagcount > 0) {
ArrayList list = null;
collectExistingTags(element, jcontext);
String completiontext = null;
int tokenstart = corecontext.getTokenStart();
int length = offset - tokenstart;
for(int i = 0; i < tagcount; i++) {
if(!acceptTag(tags[i], element)) {
continue;
}
completiontext = tags[i].getCompleteTag(type, member);
if(appliesToContext(jcontext.getDocument(), completiontext, tokenstart, (length > 0 ? length : 1))) {
if(list == null) {
list = new ArrayList(tagcount-i);
}
list.add(new APIToolsJavadocCompletionProposal(corecontext, completiontext, tags[i].getTagName(), fImageHandle));
}
}
if(list != null) {
return list;
}
}
}
catch (JavaModelException e) {
fErrorMessage = e.getMessage();
}
}
return Collections.EMPTY_LIST;
}
/**
* Returns if the given element has a non-visible parent in its parent hierarchy
*
* @param element
* @return <code>true</code> if a parent type is non-visible, <code>false</code> otherwise
* @throws JavaModelException
* @since 1.0.400
*/
boolean hasNonVisibleParent(IJavaElement element, boolean isinterface) throws JavaModelException {
if(element == null) {
return false;
}
boolean isinter = isinterface;
switch(element.getElementType()) {
case IJavaElement.TYPE: {
IType type = (IType) element;
int flags = type.getFlags();
IType ptype = type.getDeclaringType();
if(type.isInterface()) {
if(ptype == null) {
return false;
}
isinter &= true;
if(Flags.isPublic(flags) || isinter) {
return hasNonVisibleParent(ptype, isinter);
}
if(ptype.isClass()) {
if(Flags.isPrivate(flags) || Flags.isPackageDefault(flags)) {
return true;
}
}
}
if(ptype != null) {
if(!Flags.isStatic(flags) && ! Flags.isPublic(flags)) {
return true;
}
}
if(Flags.isPrivate(flags) || Flags.isPackageDefault(flags)) {
return true;
}
break;
}
}
return hasNonVisibleParent(element.getParent(), isinter);
}
/**
* Method to post process returned flags from the {@link org.eclipse.pde.api.tools.internal.JavadocTagManager}
* @param tag the tag to process
* @param element the {@link IJavaElement} the tag will appear on
* @return true if the tag should be included in completion proposals, false otherwise
*/
private boolean acceptTag(IApiJavadocTag tag, IJavaElement element) throws JavaModelException {
if(fExistingTags != null) {
Boolean fragments = (Boolean) fExistingTags.get(tag.getTagName());
//if the tag has a fragment don't overwrite / propose again
if(fragments != null && Boolean.FALSE.equals(fragments)) {
return false;
}
}
switch(element.getElementType()) {
case IJavaElement.TYPE: {
IType type = (IType) element;
int flags = type.getFlags();
String tagname = tag.getTagName();
if(Flags.isAbstract(flags)) {
return !tagname.equals(JavadocTagManager.TAG_NOINSTANTIATE);
}
if(Flags.isFinal(flags)) {
return !tagname.equals(JavadocTagManager.TAG_NOEXTEND);
}
break;
}
case IJavaElement.METHOD: {
IMethod method = (IMethod) element;
if(Flags.isFinal(method.getFlags()) || Flags.isStatic(method.getFlags())) {
return !tag.getTagName().equals(JavadocTagManager.TAG_NOOVERRIDE);
}
IType type = method.getDeclaringType();
if(type != null && Flags.isFinal(type.getFlags())) {
return !tag.getTagName().equals(JavadocTagManager.TAG_NOOVERRIDE);
}
}
}
return true;
}
/**
* Returns the type of the enclosing type.
*
* @param element java element
* @return TYPE_INTERFACE, TYPE_CLASS, TYPE_ENUM, TYPE_ANNOTATION or -1
* @throws JavaModelException
*/
private int getType(IJavaElement element) throws JavaModelException {
IJavaElement lelement = element;
while (lelement != null && lelement.getElementType() != IJavaElement.TYPE) {
lelement = lelement.getParent();
}
if (lelement instanceof IType) {
IType type = (IType) lelement;
if(type.isAnnotation()) {
return IApiJavadocTag.TYPE_ANNOTATION;
}
else if (type.isInterface()) {
return IApiJavadocTag.TYPE_INTERFACE;
}
else if(type.isEnum()) {
return IApiJavadocTag.TYPE_ENUM;
}
}
return IApiJavadocTag.TYPE_CLASS;
}
/**
* Collects the existing tags on the {@link IJavaElement} we have been activated on
* @param element
* @param jcontext
* @throws JavaModelException
* @throws BadLocationException
*/
private void collectExistingTags(IJavaElement element, JavaContentAssistInvocationContext jcontext) throws JavaModelException {
if(element instanceof IMember) {
IMember member = (IMember) element;
ICompilationUnit cunit = jcontext.getCompilationUnit();
if(cunit != null) {
if(cunit.isWorkingCopy()) {
cunit.reconcile(ICompilationUnit.NO_AST, false, false, null, null);
}
fParser.setSource(member.getSource().toCharArray());
fParser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
Map options = element.getJavaProject().getOptions(true);
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
fParser.setCompilerOptions(options);
fParser.setStatementsRecovery(false);
fParser.setResolveBindings(false);
fParser.setBindingsRecovery(false);
ASTNode ast = fParser.createAST(null);
TagCollector collector = new TagCollector();
if (ast.getNodeType() == ASTNode.TYPE_DECLARATION) {
TypeDeclaration typeDeclaration = (TypeDeclaration) ast;
List bodyDeclarations = typeDeclaration.bodyDeclarations();
if (bodyDeclarations.size() == 1) {
// only one element should be there as we are parsing a specific member
BodyDeclaration bodyDeclaration = (BodyDeclaration) bodyDeclarations.iterator().next();
Javadoc javadoc = bodyDeclaration.getJavadoc();
if (javadoc != null) {
javadoc.accept(collector);
}
}
}
}
}
}
/**
* Determines if the specified completion applies to the current offset context in the document
* @param document
* @param completiontext
* @param offset
* @return true if the completion applies, false otherwise
*/
private boolean appliesToContext(IDocument document, String completiontext, int tokenstart, int length) {
if(length > completiontext.length()) {
return false;
}
try {
String prefix = document.get(tokenstart, length);
return prefix.equals(completiontext.substring(0, length));
}
catch (BadLocationException e) {
ApiUIPlugin.log(e);
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#computeContextInformation(org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
*/
public List computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
return Collections.EMPTY_LIST;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#getErrorMessage()
*/
public String getErrorMessage() {
return fErrorMessage;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#sessionEnded()
*/
public void sessionEnded() {
if(fImageHandle != null) {
fImageHandle.dispose();
}
fParser = null;
if(fExistingTags != null) {
fExistingTags.clear();
fExistingTags = null;
}
fErrorMessage = null;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#sessionStarted()
*/
public void sessionStarted() {
fParser = ASTParser.newParser(AST.JLS8);
fErrorMessage = null;
}
}