blob: 925f2a89f495d87ed18bfd1b41a73c98bafc4197 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Nathan Beyer (Cerner) <nbeyer@cerner.com> - [content assist][5.0] when selected method from favorites is a member of a type with a type variable an invalid static import is added - https://bugs.eclipse.org/bugs/show_bug.cgi?id=202221
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.java;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jdt.internal.ui.JavaPlugin;
/**
* Completion proposal for required imports.
*
* @since 3.3
*/
public class ImportCompletionProposal extends AbstractJavaCompletionProposal {
private final ICompilationUnit fCompilationUnit;
private final int fParentProposalKind;
private ImportRewrite fImportRewrite;
private ContextSensitiveImportRewriteContext fImportContext;
private final CompletionProposal fProposal;
private boolean fReplacementStringComputed;
private int fLengthOfImportsAddedBehindReplacementOffset;
public ImportCompletionProposal(CompletionProposal proposal, JavaContentAssistInvocationContext context, int parentProposalKind) {
super(context);
fProposal= proposal;
fParentProposalKind= parentProposalKind;
fCompilationUnit= context.getCompilationUnit();
}
/*
* @see org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#getReplacementString()
*/
@Override
public final String getReplacementString() {
if (!fReplacementStringComputed)
setReplacementString(computeReplacementString());
return super.getReplacementString();
}
/**
* Computes the replacement string.
*
* @return the replacement string
*/
private String computeReplacementString() {
int proposalKind= fProposal.getKind();
String qualifiedTypeName= null;
char[] qualifiedType= null;
if (proposalKind == CompletionProposal.TYPE_IMPORT) {
qualifiedType= fProposal.getSignature();
qualifiedTypeName= String.valueOf(Signature.toCharArray(qualifiedType));
} else if (proposalKind == CompletionProposal.METHOD_IMPORT || proposalKind == CompletionProposal.FIELD_IMPORT) {
qualifiedType= Signature.getTypeErasure(fProposal.getDeclarationSignature());
qualifiedTypeName= String.valueOf(Signature.toCharArray(qualifiedType));
} else {
/*
* In 3.3 we only support the above import proposals, see
* CompletionProposal#getRequiredProposals()
*/
Assert.isTrue(false);
}
/* Add imports if the preference is on. */
fImportRewrite= createImportRewrite();
if (fImportRewrite != null) {
if (proposalKind == CompletionProposal.TYPE_IMPORT) {
String simpleType= fImportRewrite.addImport(qualifiedTypeName, fImportContext);
if (fParentProposalKind == CompletionProposal.METHOD_REF)
return simpleType + "."; //$NON-NLS-1$
} else {
String res= fImportRewrite.addStaticImport(qualifiedTypeName, String.valueOf(fProposal.getName()), proposalKind == CompletionProposal.FIELD_IMPORT, fImportContext);
int dot= res.lastIndexOf('.');
if (dot != -1) {
String typeName= fImportRewrite.addImport(res.substring(0, dot), fImportContext);
return typeName + '.';
}
}
return ""; //$NON-NLS-1$
}
// Case where we don't have an import rewrite (see allowAddingImports)
if (fCompilationUnit != null && JavaModelUtil.isImplicitImport(Signature.getQualifier(qualifiedTypeName), fCompilationUnit)) {
/* No imports for implicit imports. */
if (fProposal.getKind() == CompletionProposal.TYPE_IMPORT && fParentProposalKind == CompletionProposal.FIELD_REF)
return ""; //$NON-NLS-1$
qualifiedTypeName= String.valueOf(Signature.getSignatureSimpleName(qualifiedType));
}
return qualifiedTypeName + "."; //$NON-NLS-1$
}
/*
* @see org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#apply(org.eclipse.jface.text.IDocument, char, int)
*/
@Override
public void apply(IDocument document, char trigger, int offset) {
try {
super.apply(document, trigger, offset);
if (fImportRewrite != null && fImportRewrite.hasRecordedChanges()) {
int oldLen= document.getLength();
TextEdit textEdit= fImportRewrite.rewriteImports(new NullProgressMonitor());
textEdit.apply(document, TextEdit.UPDATE_REGIONS);
if (textEdit.getOffset() > getReplacementOffset())
fLengthOfImportsAddedBehindReplacementOffset= document.getLength() - oldLen;
else
fLengthOfImportsAddedBehindReplacementOffset= 0;
setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen);
}
} catch (CoreException | BadLocationException e) {
JavaPlugin.log(e);
}
}
/**
* Creates and returns the import rewrite
* if imports should be added at all.
*
* @return the import rewrite or <code>null</code> if no imports can or should be added
*/
private ImportRewrite createImportRewrite() {
if (fCompilationUnit != null && shouldAddImports()) {
try {
CompilationUnit cu= getASTRoot(fCompilationUnit);
if (cu == null) {
ImportRewrite rewrite= StubUtility.createImportRewrite(fCompilationUnit, true);
fImportContext= null;
return rewrite;
} else {
ImportRewrite rewrite= StubUtility.createImportRewrite(cu, true);
fImportContext= new ContextSensitiveImportRewriteContext(cu, fInvocationContext.getInvocationOffset(), rewrite);
return rewrite;
}
} catch (CoreException x) {
JavaPlugin.log(x);
}
}
return null;
}
private CompilationUnit getASTRoot(ICompilationUnit compilationUnit) {
return SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_NO, null);
}
/**
* Returns <code>true</code> if imports should be added. The return value depends on the context
* and preferences only and does not take into account the contents of the compilation unit or
* the kind of proposal. Even if <code>true</code> is returned, there may be cases where no
* imports are added for the proposal. For example:
* <ul>
* <li>when completing within the import section</li>
* <li>when completing informal javadoc references (e.g. within <code>&lt;code&gt;</code>
* tags)</li>
* <li>when completing a type that conflicts with an existing import</li>
* <li>when completing an implicitly imported type (same package, <code>java.lang</code>
* types)</li>
* </ul>
* <p>
* The decision whether a qualified type or the simple type name should be inserted must take
* into account these different scenarios.
* </p>
*
* @return <code>true</code> if imports may be added, <code>false</code> if not
*/
private boolean shouldAddImports() {
if (isInJavadoc() && !isJavadocProcessingEnabled())
return false;
IPreferenceStore preferenceStore= JavaPlugin.getDefault().getPreferenceStore();
return preferenceStore.getBoolean(PreferenceConstants.CODEASSIST_ADDIMPORT);
}
/**
* Returns whether Javadoc processing is enabled.
*
* @return <code>true</code> if Javadoc processing is enabled, <code>false</code> otherwise
*/
private boolean isJavadocProcessingEnabled() {
IJavaProject project= fCompilationUnit.getJavaProject();
boolean processJavadoc;
if (project == null)
processJavadoc= JavaCore.ENABLED.equals(JavaCore.getOption(JavaCore.COMPILER_DOC_COMMENT_SUPPORT));
else
processJavadoc= JavaCore.ENABLED.equals(project.getOption(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, true));
return processJavadoc;
}
/**
* Returns the number of characters inserted behind the replacementOffset by the last invocation of {@link #apply(IDocument, char, int)}
*
* @return the number of characters inserted behind the replacementOffset by the last invocation of {@link #apply(IDocument, char, int)}
*/
public int getLengthOfImportsAddedBehindReplacementOffset() {
return fLengthOfImportsAddedBehindReplacementOffset;
}
}