| /******************************************************************************* |
| * 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><code></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; |
| } |
| } |