1 11 package org.eclipse.jdt.internal.ui.text.java; 12 13 import java.util.HashMap ; 14 import java.util.LinkedList ; 15 import java.util.List ; 16 import java.util.Map ; 17 18 import org.eclipse.core.runtime.CoreException; 19 import org.eclipse.core.runtime.IStatus; 20 import org.eclipse.core.runtime.NullProgressMonitor; 21 import org.eclipse.core.runtime.Status; 22 23 import org.eclipse.swt.graphics.Image; 24 import org.eclipse.swt.graphics.Point; 25 import org.eclipse.swt.widgets.Shell; 26 27 import org.eclipse.jface.dialogs.MessageDialog; 28 29 import org.eclipse.jface.text.BadLocationException; 30 import org.eclipse.jface.text.IDocument; 31 import org.eclipse.jface.text.IRegion; 32 import org.eclipse.jface.text.Region; 33 import org.eclipse.jface.text.contentassist.IContextInformation; 34 import org.eclipse.jface.text.contentassist.IContextInformationExtension; 35 import org.eclipse.jface.text.link.LinkedModeModel; 36 import org.eclipse.jface.text.link.LinkedModeUI; 37 import org.eclipse.jface.text.link.LinkedPosition; 38 import org.eclipse.jface.text.link.LinkedPositionGroup; 39 40 import org.eclipse.ui.IEditorPart; 41 import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; 42 43 import org.eclipse.jdt.core.CompletionProposal; 44 import org.eclipse.jdt.core.ICompilationUnit; 45 import org.eclipse.jdt.core.IType; 46 import org.eclipse.jdt.core.ITypeHierarchy; 47 import org.eclipse.jdt.core.ITypeParameter; 48 import org.eclipse.jdt.core.JavaModelException; 49 import org.eclipse.jdt.core.Signature; 50 import org.eclipse.jdt.core.dom.AST; 51 import org.eclipse.jdt.core.dom.ASTParser; 52 import org.eclipse.jdt.core.dom.ASTRequestor; 53 import org.eclipse.jdt.core.dom.IBinding; 54 import org.eclipse.jdt.core.dom.ITypeBinding; 55 56 import org.eclipse.jdt.internal.corext.template.java.SignatureUtil; 57 58 import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; 59 60 import org.eclipse.jdt.internal.ui.JavaPlugin; 61 import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer; 62 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; 63 64 70 public final class LazyGenericTypeProposal extends LazyJavaTypeCompletionProposal { 71 72 private final static char[] GENERIC_TYPE_TRIGGERS= new char[] { '.', '\t', '[', '(', '<', ' ' }; 73 74 79 private static class ContextInformation implements IContextInformation, IContextInformationExtension { 80 private final String fInformationDisplayString; 81 private final String fContextDisplayString; 82 private final Image fImage; 83 private final int fPosition; 84 85 ContextInformation(LazyGenericTypeProposal proposal) { 86 fContextDisplayString= proposal.getDisplayString(); 89 fInformationDisplayString= computeContextString(proposal); 90 fImage= proposal.getImage(); 91 fPosition= proposal.getReplacementOffset() + proposal.getReplacementString().indexOf('<') + 1; 92 } 93 94 97 public String getContextDisplayString() { 98 return fContextDisplayString; 99 } 100 101 104 public Image getImage() { 105 return fImage; 106 } 107 108 111 public String getInformationDisplayString() { 112 return fInformationDisplayString; 113 } 114 115 private String computeContextString(LazyGenericTypeProposal proposal) { 116 try { 117 TypeArgumentProposal[] proposals= proposal.computeTypeArgumentProposals(); 118 if (proposals.length == 0) 119 return null; 120 121 StringBuffer buf= new StringBuffer (); 122 for (int i= 0; i < proposals.length; i++) { 123 buf.append(proposals[i].getDisplayName()); 124 if (i < proposals.length - 1) 125 buf.append(", "); } 127 return buf.toString(); 128 129 } catch (JavaModelException e) { 130 return null; 131 } 132 } 133 134 137 public int getContextInformationPosition() { 138 return fPosition; 139 } 140 141 144 public boolean equals(Object obj) { 145 if (obj instanceof ContextInformation) { 146 ContextInformation ci= (ContextInformation) obj; 147 return getContextInformationPosition() == ci.getContextInformationPosition() && getInformationDisplayString().equals(ci.getInformationDisplayString()); 148 } 149 return false; 150 } 151 } 152 153 private static final class TypeArgumentProposal { 154 private final boolean fIsAmbiguous; 155 private final String fProposal; 156 private final String fTypeDisplayName; 157 158 TypeArgumentProposal(String proposal, boolean ambiguous, String typeDisplayName) { 159 fIsAmbiguous= ambiguous; 160 fProposal= proposal; 161 fTypeDisplayName= typeDisplayName; 162 } 163 164 public String getDisplayName() { 165 return fTypeDisplayName; 166 } 167 168 boolean isAmbiguous() { 169 return fIsAmbiguous; 170 } 171 172 String getProposals() { 173 return fProposal; 174 } 175 176 public String toString() { 177 return fProposal; 178 } 179 } 180 181 private IRegion fSelectedRegion; private TypeArgumentProposal[] fTypeArgumentProposals; 183 184 public LazyGenericTypeProposal(CompletionProposal typeProposal, JavaContentAssistInvocationContext context) { 185 super(typeProposal, context); 186 } 187 188 191 public void apply(IDocument document, char trigger, int offset) { 192 193 if (shouldAppendArguments(document, offset, trigger)) { 194 try { 195 TypeArgumentProposal[] typeArgumentProposals= computeTypeArgumentProposals(); 196 if (typeArgumentProposals.length > 0) { 197 198 int[] offsets= new int[typeArgumentProposals.length]; 199 int[] lengths= new int[typeArgumentProposals.length]; 200 StringBuffer buffer= createParameterList(typeArgumentProposals, offsets, lengths); 201 202 boolean insertClosingParenthesis= trigger == '(' && autocloseBrackets(); 204 if (insertClosingParenthesis) 205 updateReplacementWithParentheses(buffer); 206 super.setReplacementString(buffer.toString()); 207 208 super.apply(document, '\0', offset); 210 211 if (getTextViewer() != null) { 212 if (hasAmbiguousProposals(typeArgumentProposals)) { 213 adaptOffsets(offsets, buffer); 214 installLinkedMode(document, offsets, lengths, typeArgumentProposals, insertClosingParenthesis); 215 } else { 216 if (insertClosingParenthesis) 217 setUpLinkedMode(document, ')'); 218 else 219 fSelectedRegion= new Region(getReplacementOffset() + getReplacementString().length(), 0); 220 } 221 } 222 223 return; 224 } 225 } catch (JavaModelException e) { 226 JavaPlugin.log(e); 228 } 229 } 230 231 super.apply(document, trigger, offset); 237 } 238 239 242 protected char[] computeTriggerCharacters() { 243 return GENERIC_TYPE_TRIGGERS; 244 } 245 246 258 private void adaptOffsets(int[] offsets, StringBuffer buffer) { 259 String replacementString= getReplacementString(); 260 int delta= buffer.length() - replacementString.length(); for (int i= 0; i < offsets.length; i++) { 262 offsets[i]-= delta; 263 } 264 } 265 266 285 private TypeArgumentProposal[] computeTypeArgumentProposals() throws JavaModelException { 286 if (fTypeArgumentProposals == null) { 287 288 IType type= (IType) getJavaElement(); 289 if (type == null) 290 return new TypeArgumentProposal[0]; 291 292 ITypeParameter[] parameters= type.getTypeParameters(); 293 if (parameters.length == 0) 294 return new TypeArgumentProposal[0]; 295 296 TypeArgumentProposal[] arguments= new TypeArgumentProposal[parameters.length]; 297 298 ITypeBinding expectedTypeBinding= getExpectedType(); 299 if (expectedTypeBinding != null && expectedTypeBinding.isParameterizedType()) { 300 303 IType expectedType= (IType) expectedTypeBinding.getJavaElement(); 304 305 IType[] path= computeInheritancePath(type, expectedType); 306 if (path == null) 307 return new TypeArgumentProposal[0]; 311 312 int[] indices= new int[parameters.length]; 313 for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) { 314 indices[paramIdx]= mapTypeParameterIndex(path, path.length - 1, paramIdx); 315 } 316 317 ITypeBinding[] typeArguments= expectedTypeBinding.getTypeArguments(); 320 for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) { 321 if (indices[paramIdx] != -1) { 322 ITypeBinding binding= typeArguments[indices[paramIdx]]; 324 arguments[paramIdx]= computeTypeProposal(binding, parameters[paramIdx]); 325 } 326 } 327 } 328 329 for (int i= 0; i < arguments.length; i++) { 332 if (arguments[i] == null) { 333 arguments[i]= computeTypeProposal(parameters[i]); 334 } 335 } 336 fTypeArgumentProposals= arguments; 337 } 338 return fTypeArgumentProposals; 339 } 340 341 353 private TypeArgumentProposal computeTypeProposal(ITypeParameter parameter) throws JavaModelException { 354 String [] bounds= parameter.getBounds(); 355 String elementName= parameter.getElementName(); 356 String displayName= computeTypeParameterDisplayName(parameter, bounds); 357 if (bounds.length == 1 && !"java.lang.Object".equals(bounds[0])) return new TypeArgumentProposal(Signature.getSimpleName(bounds[0]), true, displayName); 359 else 360 return new TypeArgumentProposal(elementName, true, displayName); 361 } 362 363 private String computeTypeParameterDisplayName(ITypeParameter parameter, String [] bounds) { 364 if (bounds.length == 0 || bounds.length == 1 && "java.lang.Object".equals(bounds[0])) return parameter.getElementName(); 366 StringBuffer buf= new StringBuffer (parameter.getElementName()); 367 buf.append(" extends "); for (int i= 0; i < bounds.length; i++) { 369 buf.append(Signature.getSimpleName(bounds[i])); 370 if (i < bounds.length - 1) 371 buf.append(" & "); } 373 return buf.toString(); 374 } 375 376 395 private TypeArgumentProposal computeTypeProposal(ITypeBinding binding, ITypeParameter parameter) throws JavaModelException { 396 final String name= binding.getName(); 397 if (binding.isWildcardType()) { 398 399 if (binding.isUpperbound()) { 400 String contextName= name.replaceFirst("\\?", parameter.getElementName()); return new TypeArgumentProposal(binding.getBound().getName(), true, contextName); 404 } 405 406 return computeTypeProposal(parameter); 409 } 410 411 return new TypeArgumentProposal(name, false, name); 413 } 414 415 435 private IType[] computeInheritancePath(IType subType, IType superType) throws JavaModelException { 436 if (superType == null) 437 return null; 438 439 if (superType.equals(subType)) 441 return new IType[] { subType }; 442 443 ITypeHierarchy hierarchy= subType.newSupertypeHierarchy(getProgressMonitor()); 444 if (!hierarchy.contains(superType)) 445 return null; 447 List path= new LinkedList (); 448 path.add(superType); 449 do { 450 superType= hierarchy.getSubtypes(superType)[0]; 452 path.add(superType); 453 } while (!superType.equals(subType)); 455 return (IType[]) path.toArray(new IType[path.size()]); 456 } 457 458 private NullProgressMonitor getProgressMonitor() { 459 return new NullProgressMonitor(); 460 } 461 462 485 private int mapTypeParameterIndex(IType[] path, int pathIndex, int paramIndex) throws JavaModelException, ArrayIndexOutOfBoundsException { 486 if (pathIndex == 0) 487 return paramIndex; 489 490 IType subType= path[pathIndex]; 491 IType superType= path[pathIndex - 1]; 492 493 String superSignature= findMatchingSuperTypeSignature(subType, superType); 494 ITypeParameter param= subType.getTypeParameters()[paramIndex]; 495 int index= findMatchingTypeArgumentIndex(superSignature, param.getElementName()); 496 if (index == -1) { 497 return -1; 499 } 500 501 return mapTypeParameterIndex(path, pathIndex - 1, index); 502 } 503 504 518 private String findMatchingSuperTypeSignature(IType subType, IType superType) throws JavaModelException { 519 String [] signatures= getSuperTypeSignatures(subType, superType); 520 for (int i= 0; i < signatures.length; i++) { 521 String signature= signatures[i]; 522 String qualified= SignatureUtil.qualifySignature(signature, subType); 523 String subFQN= SignatureUtil.stripSignatureToFQN(qualified); 524 525 String superFQN= superType.getFullyQualifiedName(); 526 if (subFQN.equals(superFQN)) { 527 return signature; 528 } 529 530 } 532 533 throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "Illegal hierarchy", null))); } 535 536 550 private int findMatchingTypeArgumentIndex(String signature, String argument) { 551 String [] typeArguments= Signature.getTypeArguments(signature); 552 for (int i= 0; i < typeArguments.length; i++) { 553 if (Signature.getSignatureSimpleName(typeArguments[i]).equals(argument)) 554 return i; 555 } 556 return -1; 557 } 558 559 569 private String [] getSuperTypeSignatures(IType subType, IType superType) throws JavaModelException { 570 if (superType.isInterface()) 571 return subType.getSuperInterfaceTypeSignatures(); 572 else 573 return new String [] {subType.getSuperclassTypeSignature()}; 574 } 575 576 582 private ITypeBinding getExpectedType() { 583 char[][] chKeys= fInvocationContext.getCoreContext().getExpectedTypesKeys(); 584 if (chKeys == null || chKeys.length == 0) 585 return null; 586 587 String [] keys= new String [chKeys.length]; 588 for (int i= 0; i < keys.length; i++) { 589 keys[i]= String.valueOf(chKeys[0]); 590 } 591 592 final ASTParser parser= ASTParser.newParser(AST.JLS3); 593 parser.setProject(fCompilationUnit.getJavaProject()); 594 parser.setResolveBindings(true); 595 596 final Map bindings= new HashMap (); 597 ASTRequestor requestor= new ASTRequestor() { 598 public void acceptBinding(String bindingKey, IBinding binding) { 599 bindings.put(bindingKey, binding); 600 } 601 }; 602 parser.createASTs(new ICompilationUnit[0], keys, requestor, null); 603 604 if (bindings.size() > 0) 605 return (ITypeBinding) bindings.get(keys[0]); 606 607 return null; 608 } 609 610 620 private boolean shouldAppendArguments(IDocument document, int offset, char trigger) { 621 625 if (trigger != '\0' && trigger != '<' && trigger != '(') 626 return false; 627 628 629 char[] completion= fProposal.getCompletion(); 630 if (completion.length == 0) 631 return false; 632 633 634 try { 635 IRegion region= document.getLineInformationOfOffset(offset); 636 String line= document.get(region.getOffset(), region.getLength()); 637 638 int index= offset - region.getOffset(); 639 while (index != line.length() && Character.isUnicodeIdentifierPart(line.charAt(index))) 640 ++index; 641 642 if (index == line.length()) 643 return true; 644 645 char ch= line.charAt(index); 646 return ch != '<'; 647 648 } catch (BadLocationException e) { 649 return true; 650 } 651 } 652 653 private StringBuffer createParameterList(TypeArgumentProposal[] typeArguments, int[] offsets, int[] lengths) { 654 StringBuffer buffer= new StringBuffer (); 655 buffer.append(getReplacementString()); 656 657 FormatterPrefs prefs= getFormatterPrefs(); 658 final char LESS= '<'; 659 final char GREATER= '>'; 660 if (prefs.beforeOpeningBracket) 661 buffer.append(SPACE); 662 buffer.append(LESS); 663 if (prefs.afterOpeningBracket) 664 buffer.append(SPACE); 665 StringBuffer separator= new StringBuffer (3); 666 if (prefs.beforeTypeArgumentComma) 667 separator.append(SPACE); 668 separator.append(COMMA); 669 if (prefs.afterTypeArgumentComma) 670 separator.append(SPACE); 671 672 for (int i= 0; i != typeArguments.length; i++) { 673 if (i != 0) 674 buffer.append(separator); 675 676 offsets[i]= buffer.length(); 677 buffer.append(typeArguments[i]); 678 lengths[i]= buffer.length() - offsets[i]; 679 } 680 if (prefs.beforeClosingBracket) 681 buffer.append(SPACE); 682 buffer.append(GREATER); 683 684 return buffer; 685 } 686 687 private void installLinkedMode(IDocument document, int[] offsets, int[] lengths, TypeArgumentProposal[] typeArgumentProposals, boolean withParentheses) { 688 int replacementOffset= getReplacementOffset(); 689 String replacementString= getReplacementString(); 690 691 try { 692 LinkedModeModel model= new LinkedModeModel(); 693 for (int i= 0; i != offsets.length; i++) { 694 if (typeArgumentProposals[i].isAmbiguous()) { 695 LinkedPositionGroup group= new LinkedPositionGroup(); 696 group.addPosition(new LinkedPosition(document, replacementOffset + offsets[i], lengths[i])); 697 model.addGroup(group); 698 } 699 } 700 if (withParentheses) { 701 LinkedPositionGroup group= new LinkedPositionGroup(); 702 group.addPosition(new LinkedPosition(document, replacementOffset + getCursorPosition(), 0)); 703 model.addGroup(group); 704 } 705 706 model.forceInstall(); 707 JavaEditor editor= getJavaEditor(); 708 if (editor != null) { 709 model.addLinkingListener(new EditorHighlightingSynchronizer(editor)); 710 } 711 712 LinkedModeUI ui= new EditorLinkedModeUI(model, getTextViewer()); 713 ui.setExitPolicy(new ExitPolicy(withParentheses ? ')' : '>', document)); 714 ui.setExitPosition(getTextViewer(), replacementOffset + replacementString.length(), 0, Integer.MAX_VALUE); 715 ui.setDoContextInfo(true); 716 ui.enter(); 717 718 fSelectedRegion= ui.getSelectedRegion(); 719 720 } catch (BadLocationException e) { 721 JavaPlugin.log(e); 722 openErrorDialog(e); 723 } 724 } 725 726 private boolean hasAmbiguousProposals(TypeArgumentProposal[] typeArgumentProposals) { 727 boolean hasAmbiguousProposals= false; 728 for (int i= 0; i < typeArgumentProposals.length; i++) { 729 if (typeArgumentProposals[i].isAmbiguous()) { 730 hasAmbiguousProposals= true; 731 break; 732 } 733 } 734 return hasAmbiguousProposals; 735 } 736 737 743 private JavaEditor getJavaEditor() { 744 IEditorPart part= JavaPlugin.getActivePage().getActiveEditor(); 745 if (part instanceof JavaEditor) 746 return (JavaEditor) part; 747 else 748 return null; 749 } 750 751 754 public Point getSelection(IDocument document) { 755 if (fSelectedRegion == null) 756 return super.getSelection(document); 757 758 return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); 759 } 760 761 private void openErrorDialog(BadLocationException e) { 762 Shell shell= getTextViewer().getTextWidget().getShell(); 763 MessageDialog.openError(shell, JavaTextMessages.FilledArgumentNamesMethodProposal_error_msg, e.getMessage()); 764 } 765 766 769 protected IContextInformation computeContextInformation() { 770 try { 771 if (hasParameters()) { 772 TypeArgumentProposal[] proposals= computeTypeArgumentProposals(); 773 if (hasAmbiguousProposals(proposals)) 774 return new ContextInformation(this); 775 } 776 } catch (JavaModelException e) { 777 } 778 return super.computeContextInformation(); 779 } 780 781 protected int computeCursorPosition() { 782 if (fSelectedRegion != null) 783 return fSelectedRegion.getOffset() - getReplacementOffset(); 784 return super.computeCursorPosition(); 785 } 786 787 private boolean hasParameters() { 788 try { 789 IType type= (IType) getJavaElement(); 790 if (type == null) 791 return false; 792 793 return type.getTypeParameters().length > 0; 794 } catch (JavaModelException e) { 795 return false; 796 } 797 } 798 } 799 | Popular Tags |