| /******************************************************************************* |
| * Copyright (c) 2000, 2011 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 |
| * |
| * This is an implementation of an early-draft specification developed under the Java |
| * Community Process (JCP) and is made available for testing and evaluation purposes |
| * only. The code is not compatible with any specification of the JCP. |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.text.java; |
| |
| 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.jface.text.contentassist.IContextInformation; |
| |
| import org.eclipse.jdt.core.CompletionProposal; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| 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.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; |
| import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| import org.eclipse.jdt.internal.corext.util.QualifiedTypeNameHistory; |
| |
| import org.eclipse.jdt.ui.PreferenceConstants; |
| import org.eclipse.jdt.ui.SharedASTProvider; |
| import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| |
| |
| /** |
| * If passed compilation unit is not null, the replacement string will be seen as a qualified type name. |
| */ |
| public class LazyJavaTypeCompletionProposal extends LazyJavaCompletionProposal { |
| /** Triggers for types. Do not modify. */ |
| protected static final char[] TYPE_TRIGGERS= new char[] { '.', '\t', '[', '(', ' ' }; |
| /** Triggers for types in javadoc. Do not modify. */ |
| protected static final char[] JDOC_TYPE_TRIGGERS= new char[] { '#', '}', ' ', '.' }; |
| |
| /** The compilation unit, or <code>null</code> if none is available. */ |
| protected final ICompilationUnit fCompilationUnit; |
| |
| private String fQualifiedName; |
| private String fSimpleName; |
| private ImportRewrite fImportRewrite; |
| private ContextSensitiveImportRewriteContext fImportContext; |
| |
| public LazyJavaTypeCompletionProposal(CompletionProposal proposal, JavaContentAssistInvocationContext context) { |
| super(proposal, context); |
| fCompilationUnit= context.getCompilationUnit(); |
| fQualifiedName= null; |
| } |
| |
| void setImportRewrite(ImportRewrite importRewrite) { |
| fImportRewrite= importRewrite; |
| } |
| |
| public final String getQualifiedTypeName() { |
| if (fQualifiedName == null) |
| fQualifiedName= String.valueOf(Signature.toCharArray(Signature.getTypeErasure(fProposal.getSignature()))); |
| return fQualifiedName; |
| } |
| |
| protected final String getSimpleTypeName() { |
| if (fSimpleName == null) |
| fSimpleName= Signature.getSimpleName(getQualifiedTypeName()); |
| return fSimpleName; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeReplacementString() |
| */ |
| @Override |
| protected String computeReplacementString() { |
| String replacement= super.computeReplacementString(); |
| |
| /* No import rewriting ever from within the import section. */ |
| if (isImportCompletion()) |
| return replacement; |
| |
| /* Always use the simple name for non-formal javadoc references to types. */ |
| // TODO fix |
| if (fProposal.getKind() == CompletionProposal.TYPE_REF && fInvocationContext.getCoreContext().isInJavadocText()) |
| return getSimpleTypeName(); |
| |
| String qualifiedTypeName= getQualifiedTypeName(); |
| |
| // Type in package info must be fully qualified. |
| if (fCompilationUnit != null && JavaModelUtil.isPackageInfo(fCompilationUnit)) |
| return qualifiedTypeName; |
| |
| if (qualifiedTypeName.indexOf('.') == -1 && replacement.length() > 0) |
| // default package - no imports needed |
| return qualifiedTypeName; |
| |
| /* |
| * If the user types in the qualification, don't force import rewriting on him - insert the |
| * qualified name. |
| */ |
| IDocument document= fInvocationContext.getDocument(); |
| if (document != null) { |
| String prefix= getPrefix(document, getReplacementOffset() + getReplacementLength()); |
| int dotIndex= prefix.lastIndexOf('.'); |
| // match up to the last dot in order to make higher level matching still work (camel case...) |
| if (dotIndex != -1 && qualifiedTypeName.toLowerCase().startsWith(prefix.substring(0, dotIndex + 1).toLowerCase())) |
| return qualifiedTypeName; |
| } |
| |
| /* |
| * The replacement does not contain a qualification (e.g. an inner type qualified by its |
| * parent) - use the replacement directly. |
| */ |
| if (replacement.indexOf('.') == -1) { |
| if (isInJavadoc()) |
| return getSimpleTypeName(); // don't use the braces added for javadoc link proposals |
| return replacement; |
| } |
| |
| /* Add imports if the preference is on. */ |
| if (fImportRewrite == null) |
| fImportRewrite= createImportRewrite(); |
| if (fImportRewrite != null) { |
| return fImportRewrite.addImport(qualifiedTypeName, fImportContext); |
| } |
| |
| // fall back for the case we don't have an import rewrite (see allowAddingImports) |
| |
| /* No imports for implicit imports. */ |
| if (fCompilationUnit != null && JavaModelUtil.isImplicitImport(Signature.getQualifier(qualifiedTypeName), fCompilationUnit)) { |
| return Signature.getSimpleName(qualifiedTypeName); |
| } |
| |
| /* Default: use the fully qualified type name. */ |
| return qualifiedTypeName; |
| } |
| |
| protected final boolean isImportCompletion() { |
| char[] completion= fProposal.getCompletion(); |
| if (completion.length == 0) |
| return false; |
| |
| char last= completion[completion.length - 1]; |
| /* |
| * Proposals end in a semicolon when completing types in normal imports or when completing |
| * static members, in a period when completing types in static imports. |
| */ |
| return last == ';' || last == '.'; |
| } |
| |
| private ImportRewrite createImportRewrite() { |
| if (fCompilationUnit != null && allowAddingImports() && !JavaModelUtil.isPackageInfo(fCompilationUnit)) { |
| 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 SharedASTProvider.getAST(compilationUnit, SharedASTProvider.WAIT_NO, new NullProgressMonitor()); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#apply(org.eclipse.jface.text.IDocument, char, int) |
| */ |
| @Override |
| public void apply(IDocument document, char trigger, int offset) { |
| try { |
| boolean insertClosingParenthesis= trigger == '(' && autocloseBrackets(); |
| if (insertClosingParenthesis) { |
| StringBuffer replacement= new StringBuffer(getReplacementString()); |
| updateReplacementWithParentheses(replacement); |
| setReplacementString(replacement.toString()); |
| trigger= '\0'; |
| } |
| |
| super.apply(document, trigger, offset); |
| |
| if (fImportRewrite != null && fImportRewrite.hasRecordedChanges()) { |
| int oldLen= document.getLength(); |
| fImportRewrite.rewriteImports(new NullProgressMonitor()).apply(document, TextEdit.UPDATE_REGIONS); |
| setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen); |
| } |
| |
| if (insertClosingParenthesis) |
| setUpLinkedMode(document, ')'); |
| |
| rememberSelection(); |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| } catch (BadLocationException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| protected void updateReplacementWithParentheses(StringBuffer replacement) { |
| FormatterPrefs prefs= getFormatterPrefs(); |
| |
| if (prefs.beforeOpeningParen) |
| replacement.append(SPACE); |
| replacement.append(LPAREN); |
| |
| |
| if (prefs.afterOpeningParen) |
| replacement.append(SPACE); |
| |
| setCursorPosition(replacement.length()); |
| |
| if (prefs.afterOpeningParen) |
| replacement.append(SPACE); |
| |
| replacement.append(RPAREN); |
| } |
| |
| /** |
| * Remembers the selection in the content assist history. |
| * |
| * @throws JavaModelException if anything goes wrong |
| * @since 3.2 |
| */ |
| protected final void rememberSelection() throws JavaModelException { |
| IType lhs= fInvocationContext.getExpectedType(); |
| IType rhs= (IType) getJavaElement(); |
| if (lhs != null && rhs != null) |
| JavaPlugin.getDefault().getContentAssistHistory().remember(lhs, rhs); |
| |
| QualifiedTypeNameHistory.remember(getQualifiedTypeName()); |
| } |
| |
| /** |
| * Returns <code>true</code> if imports may 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> |
| * <p> |
| * Subclasses may extend. |
| * </p> |
| * |
| * @return <code>true</code> if imports may be added, <code>false</code> if not |
| */ |
| protected boolean allowAddingImports() { |
| if (isInJavadoc()) { |
| // TODO fix |
| // if (!fContext.isInJavadocFormalReference()) |
| // return false; |
| if (fProposal.getKind() == CompletionProposal.TYPE_REF && fInvocationContext.getCoreContext().isInJavadocText()) |
| return false; |
| |
| if (!isJavadocProcessingEnabled()) |
| return false; |
| } |
| |
| IPreferenceStore preferenceStore= JavaPlugin.getDefault().getPreferenceStore(); |
| return preferenceStore.getBoolean(PreferenceConstants.CODEASSIST_ADDIMPORT); |
| } |
| |
| 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; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#isValidPrefix(java.lang.String) |
| */ |
| @Override |
| protected boolean isValidPrefix(String prefix) { |
| return isPrefix(prefix, getSimpleTypeName()) || isPrefix(prefix, getQualifiedTypeName()); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal#getCompletionText() |
| */ |
| @Override |
| public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) { |
| String prefix= getPrefix(document, completionOffset); |
| |
| String completion; |
| // return the qualified name if the prefix is already qualified |
| if (prefix.indexOf('.') != -1) |
| completion= getQualifiedTypeName(); |
| else |
| completion= getSimpleTypeName(); |
| |
| if (isCamelCaseMatching()) |
| return getCamelCaseCompound(prefix, completion); |
| |
| return completion; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeTriggerCharacters() |
| */ |
| @Override |
| protected char[] computeTriggerCharacters() { |
| return isInJavadoc() ? JDOC_TYPE_TRIGGERS : TYPE_TRIGGERS; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeProposalInfo() |
| */ |
| @Override |
| protected ProposalInfo computeProposalInfo() { |
| if (fCompilationUnit != null) { |
| IJavaProject project= fCompilationUnit.getJavaProject(); |
| if (project != null) |
| return new TypeProposalInfo(project, fProposal); |
| } |
| return super.computeProposalInfo(); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeSortString() |
| */ |
| @Override |
| protected String computeSortString() { |
| // try fast sort string to avoid display string creation |
| return getSimpleTypeName() + Character.MIN_VALUE + getQualifiedTypeName(); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeRelevance() |
| */ |
| @Override |
| protected int computeRelevance() { |
| /* |
| * There are two histories: the RHS history remembers types used for the current expected |
| * type (left hand side), while the type history remembers recently used types in general). |
| * |
| * The presence of an RHS ranking is a much more precise sign for relevance as it proves the |
| * subtype relationship between the proposed type and the expected type. |
| * |
| * The "recently used" factor (of either the RHS or general history) is less important, it should |
| * not override other relevance factors such as if the type is already imported etc. |
| */ |
| float rhsHistoryRank= fInvocationContext.getHistoryRelevance(getQualifiedTypeName()); |
| float typeHistoryRank= QualifiedTypeNameHistory.getDefault().getNormalizedPosition(getQualifiedTypeName()); |
| |
| int recencyBoost= Math.round((rhsHistoryRank + typeHistoryRank) * 5); |
| int rhsBoost= rhsHistoryRank > 0.0f ? 50 : 0; |
| int baseRelevance= super.computeRelevance(); |
| |
| return baseRelevance + rhsBoost + recencyBoost; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeContextInformation() |
| * @since 3.3 |
| */ |
| @Override |
| protected IContextInformation computeContextInformation() { |
| char[] signature= fProposal.getSignature(); |
| char[][] typeParameters= Signature.getTypeArguments(signature); |
| if (typeParameters.length == 0) |
| return super.computeContextInformation(); |
| |
| ProposalContextInformation contextInformation= new ProposalContextInformation(fProposal); |
| if (fContextInformationPosition != 0 && fProposal.getCompletion().length == 0) |
| contextInformation.setContextInformationPosition(fContextInformationPosition); |
| return contextInformation; |
| |
| } |
| } |