--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 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.java;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.contentassist.IContextInformationExtension;
+import org.eclipse.jface.text.link.LinkedModeModel;
+import org.eclipse.jface.text.link.LinkedModeUI;
+import org.eclipse.jface.text.link.LinkedPosition;
+import org.eclipse.jface.text.link.LinkedPositionGroup;
+
+import org.eclipse.ui.IEditorPart;
+
+import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
+
+import org.eclipse.jdt.core.CompletionProposal;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jdt.core.ITypeParameter;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.ASTRequestor;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+
+import org.eclipse.jdt.internal.corext.dom.Bindings;
+import org.eclipse.jdt.internal.corext.template.java.SignatureUtil;
+import org.eclipse.jdt.internal.corext.util.Strings;
+
+import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
+import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
+
+
+/**
+ * Proposal for generic types.
+ * <p>
+ * Only used when compliance is set to 5.0 or higher.
+ * </p>
+ */
+public class LazyGenericTypeProposal extends LazyJavaTypeCompletionProposal {
+ /** Triggers for types. Do not modify. */
+ private final static char[] GENERIC_TYPE_TRIGGERS= new char[] { '.', '\t', '[', '(', '<', ' ' };
+
+ /**
+ * Short-lived context information object for generic types. Currently, these
+ * are only created after inserting a type proposal, as core doesn't give us
+ * the correct type proposal from within SomeType<|>.
+ */
+ private static class ContextInformation implements IContextInformation, IContextInformationExtension {
+ private final String fInformationDisplayString;
+ private final String fContextDisplayString;
+ private final Image fImage;
+ private final int fPosition;
+
+ ContextInformation(LazyGenericTypeProposal proposal) {
+ // don't cache the proposal as content assistant
+ // might hang on to the context info
+ fContextDisplayString= proposal.getDisplayString();
+ fInformationDisplayString= computeContextString(proposal);
+ fImage= proposal.getImage();
+ fPosition= proposal.getReplacementOffset() + proposal.getReplacementString().indexOf('<') + 1;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.contentassist.IContextInformation#getContextDisplayString()
+ */
+ public String getContextDisplayString() {
+ return fContextDisplayString;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.contentassist.IContextInformation#getImage()
+ */
+ public Image getImage() {
+ return fImage;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.contentassist.IContextInformation#getInformationDisplayString()
+ */
+ public String getInformationDisplayString() {
+ return fInformationDisplayString;
+ }
+
+ private String computeContextString(LazyGenericTypeProposal proposal) {
+ try {
+ TypeArgumentProposal[] proposals= proposal.computeTypeArgumentProposals();
+ if (proposals.length == 0)
+ return null;
+
+ StringBuffer buf= new StringBuffer();
+ for (int i= 0; i < proposals.length; i++) {
+ buf.append(proposals[i].getDisplayName());
+ if (i < proposals.length - 1)
+ buf.append(", "); //$NON-NLS-1$
+ }
+ return Strings.markJavaElementLabelLTR(buf.toString());
+
+ } catch (JavaModelException e) {
+ return null;
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.contentassist.IContextInformationExtension#getContextInformationPosition()
+ */
+ public int getContextInformationPosition() {
+ return fPosition;
+ }
+
+ /*
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ContextInformation) {
+ ContextInformation ci= (ContextInformation) obj;
+ return getContextInformationPosition() == ci.getContextInformationPosition() && getInformationDisplayString().equals(ci.getInformationDisplayString());
+ }
+ return false;
+ }
+
+ /*
+ * @see java.lang.Object#hashCode()
+ * @since 3.1
+ */
+ @Override
+ public int hashCode() {
+ int low= fContextDisplayString != null ? fContextDisplayString.hashCode() : 0;
+ return fPosition << 24 | fInformationDisplayString.hashCode() << 16 | low;
+ }
+
+ }
+
+ private static final class TypeArgumentProposal {
+ private final boolean fIsAmbiguous;
+ private final String fProposal;
+ private final String fTypeDisplayName;
+
+ TypeArgumentProposal(String proposal, boolean ambiguous, String typeDisplayName) {
+ fIsAmbiguous= ambiguous;
+ fProposal= proposal;
+ fTypeDisplayName= typeDisplayName;
+ }
+
+ public String getDisplayName() {
+ return fTypeDisplayName;
+ }
+
+ boolean isAmbiguous() {
+ return fIsAmbiguous;
+ }
+
+ @Override
+ public String toString() {
+ return fProposal;
+ }
+ }
+
+ private IRegion fSelectedRegion; // initialized by apply()
+ private TypeArgumentProposal[] fTypeArgumentProposals;
+ private boolean fCanUseDiamond;
+
+
+ public LazyGenericTypeProposal(CompletionProposal typeProposal, JavaContentAssistInvocationContext context) {
+ super(typeProposal, context);
+ }
+
+ /*
+ * @see ICompletionProposalExtension#apply(IDocument, char)
+ */
+ @Override
+ public void apply(IDocument document, char trigger, int offset) {
+ boolean onlyAppendArguments;
+ try {
+ onlyAppendArguments= fProposal.getCompletion().length == 0 && offset > 0 && document.getChar(offset - 1) == '<';
+ } catch (BadLocationException e) {
+ onlyAppendArguments= false;
+ }
+
+ if (onlyAppendArguments || shouldAppendArguments(document, offset, trigger)) {
+ try {
+ TypeArgumentProposal[] typeArgumentProposals= computeTypeArgumentProposals();
+ if (typeArgumentProposals.length > 0) {
+
+ int[] offsets= new int[typeArgumentProposals.length];
+ int[] lengths= new int[typeArgumentProposals.length];
+ StringBuffer buffer;
+
+ if (canUseDiamond()) {
+ buffer= new StringBuffer(getReplacementString());
+ buffer.append("<>"); //$NON-NLS-1$
+ } else
+ buffer= createParameterList(typeArgumentProposals, offsets, lengths, onlyAppendArguments);
+
+ // set the generic type as replacement string
+ boolean insertClosingParenthesis= trigger == '(' && autocloseBrackets();
+ if (insertClosingParenthesis)
+ updateReplacementWithParentheses(buffer);
+ super.setReplacementString(buffer.toString());
+
+ // add import & remove package, update replacement offset
+ super.apply(document, '\0', offset);
+
+ if (getTextViewer() != null) {
+ if (hasAmbiguousProposals(typeArgumentProposals)) {
+ adaptOffsets(offsets, buffer);
+ installLinkedMode(document, offsets, lengths, typeArgumentProposals, insertClosingParenthesis);
+ } else {
+ if (insertClosingParenthesis)
+ setUpLinkedMode(document, ')');
+ else
+ fSelectedRegion= new Region(getReplacementOffset() + getReplacementString().length(), 0);
+ }
+ }
+
+ return;
+ }
+ } catch (JavaModelException e) {
+ // log and continue
+ JavaPlugin.log(e);
+ }
+ }
+
+ // default is to use the super implementation
+ // reasons:
+ // - not a parameterized type,
+ // - already followed by <type arguments>
+ // - proposal type does not inherit from expected type
+ super.apply(document, trigger, offset);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaTypeCompletionProposal#computeTriggerCharacters()
+ */
+ @Override
+ protected char[] computeTriggerCharacters() {
+ return GENERIC_TYPE_TRIGGERS;
+ }
+
+ /**
+ * Adapt the parameter offsets to any modification of the replacement
+ * string done by <code>apply</code>. For example, applying the proposal
+ * may add an import instead of inserting the fully qualified name.
+ * <p>
+ * This assumes that modifications happen only at the beginning of the
+ * replacement string and do not touch the type arguments list.
+ * </p>
+ *
+ * @param offsets the offsets to modify
+ * @param buffer the original replacement string
+ */
+ private void adaptOffsets(int[] offsets, StringBuffer buffer) {
+ String replacementString= getReplacementString();
+ int delta= buffer.length() - replacementString.length(); // due to using an import instead of package
+ for (int i= 0; i < offsets.length; i++) {
+ offsets[i]-= delta;
+ }
+ }
+
+ /**
+ * Computes the type argument proposals for this type proposals. If there is
+ * an expected type binding that is a super type of the proposed type, the
+ * wildcard type arguments of the proposed type that can be mapped through
+ * to type the arguments of the expected type binding are bound accordingly.
+ * <p>
+ * For type arguments that cannot be mapped to arguments in the expected
+ * type, or if there is no expected type, the upper bound of the type
+ * argument is proposed.
+ * </p>
+ * <p>
+ * The argument proposals have their <code>isAmbiguos</code> flag set to
+ * <code>false</code> if the argument can be mapped to a non-wildcard type
+ * argument in the expected type, otherwise the proposal is ambiguous.
+ * </p>
+ *
+ * @return the type argument proposals for the proposed type
+ * @throws JavaModelException if accessing the java model fails
+ */
+ private TypeArgumentProposal[] computeTypeArgumentProposals() throws JavaModelException {
+ if (fTypeArgumentProposals == null) {
+
+ IType type= (IType) getJavaElement();
+ if (type == null)
+ return new TypeArgumentProposal[0];
+
+ ITypeParameter[] parameters= type.getTypeParameters();
+ if (parameters.length == 0)
+ return new TypeArgumentProposal[0];
+
+ TypeArgumentProposal[] arguments= new TypeArgumentProposal[parameters.length];
+
+ ITypeBinding expectedTypeBinding= getExpectedType();
+ if (expectedTypeBinding != null && expectedTypeBinding.isParameterizedType()) {
+ // in this case, the type arguments we propose need to be compatible
+ // with the corresponding type parameters to declared type
+
+ IType expectedType= (IType) expectedTypeBinding.getJavaElement();
+
+ IType[] path= computeInheritancePath(type, expectedType);
+ if (path == null)
+ // proposed type does not inherit from expected type
+ // the user might be looking for an inner type of proposed type
+ // to instantiate -> do not add any type arguments
+ return new TypeArgumentProposal[0];
+
+ int[] indices= new int[parameters.length];
+ for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) {
+ indices[paramIdx]= mapTypeParameterIndex(path, path.length - 1, paramIdx);
+ }
+
+ // for type arguments that are mapped through to the expected type's
+ // parameters, take the arguments of the expected type
+ ITypeBinding[] typeArguments= expectedTypeBinding.getTypeArguments();
+ for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) {
+ if (indices[paramIdx] != -1) {
+ // type argument is mapped through
+ ITypeBinding binding= typeArguments[indices[paramIdx]];
+ arguments[paramIdx]= computeTypeProposal(binding, parameters[paramIdx]);
+ }
+ }
+ }
+
+ // for type arguments that are not mapped through to the expected type,
+ // take the lower bound of the type parameter
+ for (int i= 0; i < arguments.length; i++) {
+ if (arguments[i] == null) {
+ arguments[i]= computeTypeProposal(parameters[i]);
+ }
+ }
+ fTypeArgumentProposals= arguments;
+ }
+ return fTypeArgumentProposals;
+ }
+
+ /**
+ * Returns a type argument proposal for a given type parameter. The proposal is:
+ * <ul>
+ * <li>the type bound for type parameters with a single bound</li>
+ * <li>the type parameter name for all other (unbounded or more than one bound) type parameters</li>
+ * </ul>
+ * Type argument proposals for type parameters are always ambiguous.
+ *
+ * @param parameter the type parameter of the inserted type
+ * @return a type argument proposal for <code>parameter</code>
+ * @throws JavaModelException if this element does not exist or if an exception occurs while
+ * accessing its corresponding resource
+ */
+ private TypeArgumentProposal computeTypeProposal(ITypeParameter parameter) throws JavaModelException {
+ String[] bounds= parameter.getBounds();
+ String elementName= parameter.getElementName();
+ String displayName= computeTypeParameterDisplayName(parameter, bounds);
+ if (bounds.length == 1 && !"java.lang.Object".equals(bounds[0])) //$NON-NLS-1$
+ return new TypeArgumentProposal(Signature.getSimpleName(bounds[0]), true, displayName);
+ else
+ return new TypeArgumentProposal(elementName, true, displayName);
+ }
+
+ private String computeTypeParameterDisplayName(ITypeParameter parameter, String[] bounds) {
+ if (bounds.length == 0 || bounds.length == 1 && "java.lang.Object".equals(bounds[0])) //$NON-NLS-1$
+ return parameter.getElementName();
+ StringBuffer buf= new StringBuffer(parameter.getElementName());
+ buf.append(" extends "); //$NON-NLS-1$
+ for (int i= 0; i < bounds.length; i++) {
+ buf.append(Signature.getSimpleName(bounds[i]));
+ if (i < bounds.length - 1)
+ buf.append(" & "); //$NON-NLS-1$
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Returns a type argument proposal for a given type binding. The proposal is:
+ * <ul>
+ * <li>the simple type name for normal types or type variables (unambigous proposal)</li>
+ * <li>for wildcard types (ambigous proposals):
+ * <ul>
+ * <li>the upper bound for wildcards with an upper bound</li>
+ * <li>the {@linkplain #computeTypeProposal(ITypeParameter) parameter proposal} for unbounded
+ * wildcards or wildcards with a lower bound</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @param binding the type argument binding in the expected type
+ * @param parameter the type parameter of the inserted type
+ * @return a type argument proposal for <code>binding</code>
+ * @throws JavaModelException if this element does not exist or if an exception occurs while
+ * accessing its corresponding resource
+ * @see #computeTypeProposal(ITypeParameter)
+ */
+ private TypeArgumentProposal computeTypeProposal(ITypeBinding binding, ITypeParameter parameter) throws JavaModelException {
+ final String name= Bindings.getTypeQualifiedName(binding);
+ if (binding.isWildcardType()) {
+
+ if (binding.isUpperbound()) {
+ // replace the wildcard ? with the type parameter name to get "E extends Bound" instead of "? extends Bound"
+ String contextName= name.replaceFirst("\\?", parameter.getElementName()); //$NON-NLS-1$
+ // upper bound - the upper bound is the bound itself
+ return new TypeArgumentProposal(binding.getBound().getName(), true, contextName);
+ }
+
+ // no or upper bound - use the type parameter of the inserted type, as it may be more
+ // restrictive (eg. List<?> list= new SerializableList<Serializable>())
+ return computeTypeProposal(parameter);
+ }
+
+ // not a wildcard but a type or type variable - this is unambigously the right thing to insert
+ return new TypeArgumentProposal(name, false, name);
+ }
+
+ /**
+ * Computes one inheritance path from <code>superType</code> to <code>subType</code> or
+ * <code>null</code> if <code>subType</code> does not inherit from <code>superType</code>. Note
+ * that there may be more than one inheritance path - this method simply returns one.
+ * <p>
+ * The returned array contains <code>superType</code> at its first index, and
+ * <code>subType</code> at its last index. If <code>subType</code> equals <code>superType</code>
+ * , an array of length 1 is returned containing that type.
+ * </p>
+ *
+ * @param subType the sub type
+ * @param superType the super type
+ * @return an inheritance path from <code>superType</code> to <code>subType</code>, or
+ * <code>null</code> if <code>subType</code> does not inherit from
+ * <code>superType</code>
+ * @throws JavaModelException if this element does not exist or if an exception occurs while
+ * accessing its corresponding resource
+ */
+ private IType[] computeInheritancePath(IType subType, IType superType) throws JavaModelException {
+ if (superType == null)
+ return null;
+
+ // optimization: avoid building the type hierarchy for the identity case
+ if (superType.equals(subType))
+ return new IType[] { subType };
+
+ ITypeHierarchy hierarchy= subType.newSupertypeHierarchy(getProgressMonitor());
+ if (!hierarchy.contains(superType))
+ return null; // no path
+
+ List<IType> path= new LinkedList<IType>();
+ path.add(superType);
+ do {
+ // any sub type must be on a hierarchy chain from superType to subType
+ superType= hierarchy.getSubtypes(superType)[0];
+ path.add(superType);
+ } while (!superType.equals(subType)); // since the equality case is handled above, we can spare one check
+
+ return path.toArray(new IType[path.size()]);
+ }
+
+ private NullProgressMonitor getProgressMonitor() {
+ return new NullProgressMonitor();
+ }
+
+ /**
+ * For the type parameter at <code>paramIndex</code> in the type at <code>path[pathIndex]</code>
+ * , this method computes the corresponding type parameter index in the type at
+ * <code>path[0]</code>. If the type parameter does not map to a type parameter of the super
+ * type, <code>-1</code> is returned.
+ *
+ * @param path the type inheritance path, a non-empty array of consecutive sub types
+ * @param pathIndex an index into <code>path</code> specifying the type to start with
+ * @param paramIndex the index of the type parameter to map - <code>path[pathIndex]</code> must
+ * have a type parameter at that index, lest an
+ * <code>ArrayIndexOutOfBoundsException</code> is thrown
+ * @return the index of the type parameter in <code>path[0]</code> corresponding to the type
+ * parameter at <code>paramIndex</code> in <code>path[pathIndex]</code>, or -1 if there
+ * is no corresponding type parameter
+ * @throws JavaModelException if this element does not exist or if an exception occurs while
+ * accessing its corresponding resource
+ * @throws ArrayIndexOutOfBoundsException if <code>path[pathIndex]</code> has <=
+ * <code>paramIndex</code> parameters
+ */
+ private int mapTypeParameterIndex(IType[] path, int pathIndex, int paramIndex) throws JavaModelException, ArrayIndexOutOfBoundsException {
+ if (pathIndex == 0)
+ // break condition: we've reached the top of the hierarchy
+ return paramIndex;
+
+ IType subType= path[pathIndex];
+ IType superType= path[pathIndex - 1];
+
+ String superSignature= findMatchingSuperTypeSignature(subType, superType);
+ ITypeParameter param= subType.getTypeParameters()[paramIndex];
+ int index= findMatchingTypeArgumentIndex(superSignature, param.getElementName());
+ if (index == -1) {
+ // not mapped through
+ return -1;
+ }
+
+ return mapTypeParameterIndex(path, pathIndex - 1, index);
+ }
+
+ /**
+ * Finds and returns the super type signature in the
+ * <code>extends</code> or <code>implements</code> clause of
+ * <code>subType</code> that corresponds to <code>superType</code>.
+ *
+ * @param subType a direct and true sub type of <code>superType</code>
+ * @param superType a direct super type (super class or interface) of
+ * <code>subType</code>
+ * @return the super type signature of <code>subType</code> referring
+ * to <code>superType</code>
+ * @throws JavaModelException if extracting the super type signatures
+ * fails, or if <code>subType</code> contains no super type
+ * signature to <code>superType</code>
+ */
+ private String findMatchingSuperTypeSignature(IType subType, IType superType) throws JavaModelException {
+ String[] signatures= getSuperTypeSignatures(subType, superType);
+ for (int i= 0; i < signatures.length; i++) {
+ String signature= signatures[i];
+ String qualified= SignatureUtil.qualifySignature(signature, subType);
+ String subFQN= SignatureUtil.stripSignatureToFQN(qualified);
+
+ String superFQN= superType.getFullyQualifiedName();
+ if (subFQN.equals(superFQN)) {
+ return signature;
+ }
+
+ // TODO handle local types
+ }
+
+ throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "Illegal hierarchy", null))); //$NON-NLS-1$
+ }
+
+ /**
+ * Finds and returns the index of the type argument named
+ * <code>argument</code> in the given super type signature.
+ * <p>
+ * If <code>signature</code> does not contain a corresponding type
+ * argument, or if <code>signature</code> has no type parameters (i.e. is
+ * a reference to a non-parameterized type or a raw type), -1 is returned.
+ * </p>
+ *
+ * @param signature the super type signature from a type's
+ * <code>extends</code> or <code>implements</code> clause
+ * @param argument the name of the type argument to find
+ * @return the index of the given type argument, or -1 if there is none
+ */
+ private int findMatchingTypeArgumentIndex(String signature, String argument) {
+ String[] typeArguments= Signature.getTypeArguments(signature);
+ for (int i= 0; i < typeArguments.length; i++) {
+ if (Signature.getSignatureSimpleName(typeArguments[i]).equals(argument))
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the super interface signatures of <code>subType</code> if
+ * <code>superType</code> is an interface, otherwise returns the super
+ * type signature.
+ *
+ * @param subType the sub type signature
+ * @param superType the super type signature
+ * @return the super type signatures of <code>subType</code>
+ * @throws JavaModelException if any java model operation fails
+ */
+ private String[] getSuperTypeSignatures(IType subType, IType superType) throws JavaModelException {
+ if (superType.isInterface())
+ return subType.getSuperInterfaceTypeSignatures();
+ else
+ return new String[] {subType.getSuperclassTypeSignature()};
+ }
+
+ /**
+ * Returns the type binding of the expected type as it is contained in the
+ * code completion context.
+ *
+ * @return the binding of the expected type
+ */
+ private ITypeBinding getExpectedType() {
+ char[][] chKeys= fInvocationContext.getCoreContext().getExpectedTypesKeys();
+ if (chKeys == null || chKeys.length == 0)
+ return null;
+
+ String[] keys= new String[chKeys.length];
+ for (int i= 0; i < keys.length; i++) {
+ keys[i]= String.valueOf(chKeys[0]);
+ }
+
+ final ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
+ parser.setProject(fCompilationUnit.getJavaProject());
+ parser.setResolveBindings(true);
+ parser.setStatementsRecovery(true);
+
+ final Map<String, IBinding> bindings= new HashMap<String, IBinding>();
+ ASTRequestor requestor= new ASTRequestor() {
+ @Override
+ public void acceptBinding(String bindingKey, IBinding binding) {
+ bindings.put(bindingKey, binding);
+ }
+ };
+ parser.createASTs(new ICompilationUnit[0], keys, requestor, null);
+
+ if (bindings.size() > 0)
+ return (ITypeBinding) bindings.get(keys[0]);
+
+ return null;
+ }
+
+ /**
+ * Returns <code>true</code> if type arguments should be appended when
+ * applying this proposal, <code>false</code> if not (for example if the
+ * document already contains a type argument list after the insertion point.
+ *
+ * @param document the document
+ * @param offset the insertion offset
+ * @param trigger the trigger character
+ * @return <code>true</code> if arguments should be appended
+ */
+ private boolean shouldAppendArguments(IDocument document, int offset, char trigger) {
+ /*
+ * No argument list if there were any special triggers (for example a period to qualify an
+ * inner type).
+ */
+ if (trigger != '\0' && trigger != '<' && trigger != '(')
+ return false;
+
+ /* No argument list if the completion is empty (already within the argument list). */
+ char[] completion= fProposal.getCompletion();
+ if (completion.length == 0)
+ return false;
+
+ /* No argument list if there already is a generic signature behind the name. */
+ try {
+ IRegion region= document.getLineInformationOfOffset(offset);
+ String line= document.get(region.getOffset(), region.getLength());
+
+ int index= offset - region.getOffset();
+ while (index != line.length() && Character.isUnicodeIdentifierPart(line.charAt(index)))
+ ++index;
+
+ if (index == line.length())
+ return true;
+
+ char ch= line.charAt(index);
+ return ch != '<';
+
+ } catch (BadLocationException e) {
+ return true;
+ }
+ }
+
+ private StringBuffer createParameterList(TypeArgumentProposal[] typeArguments, int[] offsets, int[] lengths, boolean onlyAppendArguments) {
+ StringBuffer buffer= new StringBuffer();
+ buffer.append(getReplacementString());
+
+ FormatterPrefs prefs= getFormatterPrefs();
+ final char LESS= '<';
+ final char GREATER= '>';
+ if (!onlyAppendArguments) {
+ if (prefs.beforeOpeningBracket)
+ buffer.append(SPACE);
+ buffer.append(LESS);
+ }
+ if (prefs.afterOpeningBracket)
+ buffer.append(SPACE);
+ StringBuffer separator= new StringBuffer(3);
+ if (prefs.beforeTypeArgumentComma)
+ separator.append(SPACE);
+ separator.append(COMMA);
+ if (prefs.afterTypeArgumentComma)
+ separator.append(SPACE);
+
+ for (int i= 0; i != typeArguments.length; i++) {
+ if (i != 0)
+ buffer.append(separator);
+
+ offsets[i]= buffer.length();
+ buffer.append(typeArguments[i]);
+ lengths[i]= buffer.length() - offsets[i];
+ }
+ if (prefs.beforeClosingBracket)
+ buffer.append(SPACE);
+
+ if (!onlyAppendArguments)
+ buffer.append(GREATER);
+
+ return buffer;
+ }
+
+ private void installLinkedMode(IDocument document, int[] offsets, int[] lengths, TypeArgumentProposal[] typeArgumentProposals, boolean withParentheses) {
+ int replacementOffset= getReplacementOffset();
+ String replacementString= getReplacementString();
+
+ try {
+ LinkedModeModel model= new LinkedModeModel();
+ for (int i= 0; i != offsets.length; i++) {
+ if (typeArgumentProposals[i].isAmbiguous()) {
+ LinkedPositionGroup group= new LinkedPositionGroup();
+ group.addPosition(new LinkedPosition(document, replacementOffset + offsets[i], lengths[i]));
+ model.addGroup(group);
+ }
+ }
+ if (withParentheses) {
+ LinkedPositionGroup group= new LinkedPositionGroup();
+ group.addPosition(new LinkedPosition(document, replacementOffset + getCursorPosition(), 0));
+ model.addGroup(group);
+ }
+
+ model.forceInstall();
+ JavaEditor editor= getJavaEditor();
+ if (editor != null) {
+ model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
+ }
+
+ LinkedModeUI ui= new EditorLinkedModeUI(model, getTextViewer());
+ ui.setExitPolicy(new ExitPolicy(withParentheses ? ')' : '>', document));
+ ui.setExitPosition(getTextViewer(), replacementOffset + replacementString.length(), 0, Integer.MAX_VALUE);
+ ui.setDoContextInfo(true);
+ ui.enter();
+
+ fSelectedRegion= ui.getSelectedRegion();
+
+ } catch (BadLocationException e) {
+ JavaPlugin.log(e);
+ openErrorDialog(e);
+ }
+ }
+
+ private boolean hasAmbiguousProposals(TypeArgumentProposal[] typeArgumentProposals) {
+ boolean hasAmbiguousProposals= false;
+ for (int i= 0; i < typeArgumentProposals.length; i++) {
+ if (typeArgumentProposals[i].isAmbiguous()) {
+ hasAmbiguousProposals= true;
+ break;
+ }
+ }
+ return hasAmbiguousProposals;
+ }
+
+ /**
+ * Returns the currently active java editor, or <code>null</code> if it
+ * cannot be determined.
+ *
+ * @return the currently active java editor, or <code>null</code>
+ */
+ private JavaEditor getJavaEditor() {
+ IEditorPart part= JavaPlugin.getActivePage().getActiveEditor();
+ if (part instanceof JavaEditor)
+ return (JavaEditor) part;
+ else
+ return null;
+ }
+
+ /*
+ * @see ICompletionProposal#getSelection(IDocument)
+ */
+ @Override
+ public Point getSelection(IDocument document) {
+ if (fSelectedRegion == null)
+ return super.getSelection(document);
+
+ return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
+ }
+
+ private void openErrorDialog(BadLocationException e) {
+ Shell shell= getTextViewer().getTextWidget().getShell();
+ MessageDialog.openError(shell, JavaTextMessages.FilledArgumentNamesMethodProposal_error_msg, e.getMessage());
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal#computeContextInformation()
+ */
+ @Override
+ protected IContextInformation computeContextInformation() {
+ try {
+ if (hasParameters()) {
+ TypeArgumentProposal[] proposals= computeTypeArgumentProposals();
+ if (hasAmbiguousProposals(proposals))
+ return new ContextInformation(this);
+ }
+ } catch (JavaModelException e) {
+ }
+ return super.computeContextInformation();
+ }
+
+ @Override
+ protected int computeCursorPosition() {
+ if (fSelectedRegion != null)
+ return fSelectedRegion.getOffset() - getReplacementOffset();
+ return super.computeCursorPosition();
+ }
+
+ private boolean hasParameters() {
+ try {
+ IType type= (IType) getJavaElement();
+ if (type == null)
+ return false;
+
+ return type.getTypeParameters().length > 0;
+ } catch (JavaModelException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets whether this proposal can use the diamond.
+ *
+ * @param canUseDiamond <code>true</code> if a diamond can be inserted
+ * @see CompletionProposal#canUseDiamond(org.eclipse.jdt.core.CompletionContext)
+ * @since 3.7
+ */
+ void canUseDiamond(boolean canUseDiamond) {
+ fCanUseDiamond= canUseDiamond;
+ }
+
+ /**
+ * Tells whether this proposal can use the diamond.
+ *
+ * @return <code>true</code> if a diamond can be used
+ * @see CompletionProposal#canUseDiamond(org.eclipse.jdt.core.CompletionContext)
+ * @since 3.7
+ */
+ protected boolean canUseDiamond() {
+ return fCanUseDiamond;
+ }
+
+}