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 67 public final class GenericJavaTypeProposal extends LazyJavaTypeCompletionProposal { 68 69 private final static char[] GENERIC_TYPE_TRIGGERS= new char[] { '.', '\t', '[', '(', '<', ' ' }; 70 71 76 private static class ContextInformation implements IContextInformation, IContextInformationExtension { 77 private final String fInformationDisplayString; 78 private final String fContextDisplayString; 79 private final Image fImage; 80 private final int fPosition; 81 82 ContextInformation(GenericJavaTypeProposal proposal) { 83 fContextDisplayString= proposal.getDisplayString(); 86 fInformationDisplayString= computeContextString(proposal); 87 fImage= proposal.getImage(); 88 fPosition= proposal.getReplacementOffset() + proposal.getReplacementString().indexOf('<') + 1; 89 } 90 91 94 public String getContextDisplayString() { 95 return fContextDisplayString; 96 } 97 98 101 public Image getImage() { 102 return fImage; 103 } 104 105 108 public String getInformationDisplayString() { 109 return fInformationDisplayString; 110 } 111 112 private String computeContextString(GenericJavaTypeProposal proposal) { 113 try { 114 TypeArgumentProposal[] proposals= proposal.computeTypeArgumentProposals(); 115 if (proposals.length == 0) 116 return null; 117 118 StringBuffer buf= new StringBuffer (); 119 for (int i= 0; i < proposals.length; i++) { 120 buf.append(proposals[i].getDisplayName()); 121 if (i < proposals.length - 1) 122 buf.append(", "); } 124 return buf.toString(); 125 126 } catch (JavaModelException e) { 127 return null; 128 } 129 } 130 131 134 public int getContextInformationPosition() { 135 return fPosition; 136 } 137 138 141 public boolean equals(Object obj) { 142 if (obj instanceof ContextInformation) { 143 ContextInformation ci= (ContextInformation) obj; 144 return getContextInformationPosition() == ci.getContextInformationPosition() && getInformationDisplayString().equals(ci.getInformationDisplayString()); 145 } 146 return false; 147 } 148 } 149 150 private static final class TypeArgumentProposal { 151 private final boolean fIsAmbiguous; 152 private final String fProposal; 153 private final String fTypeDisplayName; 154 155 TypeArgumentProposal(String proposal, boolean ambiguous, String typeDisplayName) { 156 fIsAmbiguous= ambiguous; 157 fProposal= proposal; 158 fTypeDisplayName= typeDisplayName; 159 } 160 161 public String getDisplayName() { 162 return fTypeDisplayName; 163 } 164 165 boolean isAmbiguous() { 166 return fIsAmbiguous; 167 } 168 169 String getProposals() { 170 return fProposal; 171 } 172 173 public String toString() { 174 return fProposal; 175 } 176 } 177 178 private IRegion fSelectedRegion; private TypeArgumentProposal[] fTypeArgumentProposals; 180 181 public GenericJavaTypeProposal(CompletionProposal typeProposal, JavaContentAssistInvocationContext context) { 182 super(typeProposal, context); 183 } 184 185 188 public void apply(IDocument document, char trigger, int offset) { 189 190 if (shouldAppendArguments(document, offset, trigger)) { 191 try { 192 TypeArgumentProposal[] typeArgumentProposals= computeTypeArgumentProposals(); 193 if (typeArgumentProposals.length > 0) { 194 195 int[] offsets= new int[typeArgumentProposals.length]; 196 int[] lengths= new int[typeArgumentProposals.length]; 197 StringBuffer buffer= createParameterList(typeArgumentProposals, offsets, lengths); 198 199 super.setReplacementString(buffer.toString()); 201 super.apply(document, '\0', offset); 203 204 if (getTextViewer() != null) { 205 if (hasAmbiguousProposals(typeArgumentProposals)) { 206 adaptOffsets(offsets, buffer); 207 installLinkedMode(document, offsets, lengths, typeArgumentProposals); 208 } else { 209 fSelectedRegion= new Region(getReplacementOffset() + getReplacementString().length(), 0); 210 } 211 } 212 213 return; 214 } 215 } catch (JavaModelException e) { 216 JavaPlugin.log(e); 218 } 219 } 220 221 super.apply(document, trigger, offset); 227 } 228 229 232 protected char[] computeTriggerCharacters() { 233 return GENERIC_TYPE_TRIGGERS; 234 } 235 236 248 private void adaptOffsets(int[] offsets, StringBuffer buffer) { 249 String replacementString= getReplacementString(); 250 int delta= buffer.length() - replacementString.length(); for (int i= 0; i < offsets.length; i++) { 252 offsets[i]-= delta; 253 } 254 } 255 256 275 private TypeArgumentProposal[] computeTypeArgumentProposals() throws JavaModelException { 276 if (fTypeArgumentProposals == null) { 277 278 IType type= (IType) getJavaElement(); 279 if (type == null) 280 return new TypeArgumentProposal[0]; 281 282 ITypeParameter[] parameters= type.getTypeParameters(); 283 if (parameters.length == 0) 284 return new TypeArgumentProposal[0]; 285 286 TypeArgumentProposal[] arguments= new TypeArgumentProposal[parameters.length]; 287 288 ITypeBinding expectedTypeBinding= getExpectedType(); 289 if (expectedTypeBinding != null && expectedTypeBinding.isParameterizedType()) { 290 293 IType expectedType= (IType) expectedTypeBinding.getJavaElement(); 294 295 IType[] path= computeInheritancePath(type, expectedType); 296 if (path == null) 297 return new TypeArgumentProposal[0]; 301 302 int[] indices= new int[parameters.length]; 303 for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) { 304 indices[paramIdx]= mapTypeParameterIndex(path, path.length - 1, paramIdx); 305 } 306 307 ITypeBinding[] typeArguments= expectedTypeBinding.getTypeArguments(); 310 for (int paramIdx= 0; paramIdx < parameters.length; paramIdx++) { 311 if (indices[paramIdx] != -1) { 312 ITypeBinding binding= typeArguments[indices[paramIdx]]; 314 arguments[paramIdx]= computeTypeProposal(binding, parameters[paramIdx]); 315 } 316 } 317 } 318 319 for (int i= 0; i < arguments.length; i++) { 322 if (arguments[i] == null) { 323 arguments[i]= computeTypeProposal(parameters[i]); 324 } 325 } 326 fTypeArgumentProposals= arguments; 327 } 328 return fTypeArgumentProposals; 329 } 330 331 343 private TypeArgumentProposal computeTypeProposal(ITypeParameter parameter) throws JavaModelException { 344 String [] bounds= parameter.getBounds(); 345 String elementName= parameter.getElementName(); 346 String displayName= computeTypeParameterDisplayName(parameter, bounds); 347 if (bounds.length == 1 && !"java.lang.Object".equals(bounds[0])) return new TypeArgumentProposal(Signature.getSimpleName(bounds[0]), true, displayName); 349 else 350 return new TypeArgumentProposal(elementName, true, displayName); 351 } 352 353 private String computeTypeParameterDisplayName(ITypeParameter parameter, String [] bounds) { 354 if (bounds.length == 0 || bounds.length == 1 && "java.lang.Object".equals(bounds[0])) return parameter.getElementName(); 356 StringBuffer buf= new StringBuffer (parameter.getElementName()); 357 buf.append(" extends "); for (int i= 0; i < bounds.length; i++) { 359 buf.append(Signature.getSimpleName(bounds[i])); 360 if (i < bounds.length - 1) 361 buf.append(" & "); } 363 return buf.toString(); 364 } 365 366 385 private TypeArgumentProposal computeTypeProposal(ITypeBinding binding, ITypeParameter parameter) throws JavaModelException { 386 final String name= binding.getName(); 387 if (binding.isWildcardType()) { 388 389 if (binding.isUpperbound()) { 390 String contextName= name.replaceFirst("\\?", parameter.getElementName()); return new TypeArgumentProposal(binding.getBound().getName(), true, contextName); 394 } 395 396 return computeTypeProposal(parameter); 399 } 400 401 return new TypeArgumentProposal(name, false, name); 403 } 404 405 425 private IType[] computeInheritancePath(IType subType, IType superType) throws JavaModelException { 426 if (superType == null) 427 return null; 428 429 if (superType.equals(subType)) 431 return new IType[] { subType }; 432 433 ITypeHierarchy hierarchy= subType.newSupertypeHierarchy(getProgressMonitor()); 434 if (!hierarchy.contains(superType)) 435 return null; 437 List path= new LinkedList (); 438 path.add(superType); 439 do { 440 superType= hierarchy.getSubtypes(superType)[0]; 442 path.add(superType); 443 } while (!superType.equals(subType)); 445 return (IType[]) path.toArray(new IType[path.size()]); 446 } 447 448 private NullProgressMonitor getProgressMonitor() { 449 return new NullProgressMonitor(); 450 } 451 452 475 private int mapTypeParameterIndex(IType[] path, int pathIndex, int paramIndex) throws JavaModelException, ArrayIndexOutOfBoundsException { 476 if (pathIndex == 0) 477 return paramIndex; 479 480 IType subType= path[pathIndex]; 481 IType superType= path[pathIndex - 1]; 482 483 String superSignature= findMatchingSuperTypeSignature(subType, superType); 484 ITypeParameter param= subType.getTypeParameters()[paramIndex]; 485 int index= findMatchingTypeArgumentIndex(superSignature, param.getElementName()); 486 if (index == -1) { 487 return -1; 489 } 490 491 return mapTypeParameterIndex(path, pathIndex - 1, index); 492 } 493 494 508 private String findMatchingSuperTypeSignature(IType subType, IType superType) throws JavaModelException { 509 String [] signatures= getSuperTypeSignatures(subType, superType); 510 for (int i= 0; i < signatures.length; i++) { 511 String signature= signatures[i]; 512 String qualified= SignatureUtil.qualifySignature(signature, subType); 513 String subFQN= SignatureUtil.stripSignatureToFQN(qualified); 514 515 String superFQN= superType.getFullyQualifiedName(); 516 if (subFQN.equals(superFQN)) { 517 return signature; 518 } 519 520 } 522 523 throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "Illegal hierarchy", null))); } 525 526 540 private int findMatchingTypeArgumentIndex(String signature, String argument) { 541 String [] typeArguments= Signature.getTypeArguments(signature); 542 for (int i= 0; i < typeArguments.length; i++) { 543 if (Signature.getSignatureSimpleName(typeArguments[i]).equals(argument)) 544 return i; 545 } 546 return -1; 547 } 548 549 559 private String [] getSuperTypeSignatures(IType subType, IType superType) throws JavaModelException { 560 if (superType.isInterface()) 561 return subType.getSuperInterfaceTypeSignatures(); 562 else 563 return new String [] {subType.getSuperclassTypeSignature()}; 564 } 565 566 572 private ITypeBinding getExpectedType() { 573 char[][] chKeys= fInvocationContext.getCoreContext().getExpectedTypesKeys(); 574 if (chKeys == null || chKeys.length == 0) 575 return null; 576 577 String [] keys= new String [chKeys.length]; 578 for (int i= 0; i < keys.length; i++) { 579 keys[i]= String.valueOf(chKeys[0]); 580 } 581 582 final ASTParser parser= ASTParser.newParser(AST.JLS3); 583 parser.setProject(fCompilationUnit.getJavaProject()); 584 parser.setResolveBindings(true); 585 586 final Map bindings= new HashMap (); 587 ASTRequestor requestor= new ASTRequestor() { 588 public void acceptBinding(String bindingKey, IBinding binding) { 589 bindings.put(bindingKey, binding); 590 } 591 }; 592 parser.createASTs(new ICompilationUnit[0], keys, requestor, null); 593 594 if (bindings.size() > 0) 595 return (ITypeBinding) bindings.get(keys[0]); 596 597 return null; 598 } 599 600 610 private boolean shouldAppendArguments(IDocument document, int offset, char trigger) { 611 if (trigger != '\0' && trigger != '<') 612 return false; 613 614 try { 615 IRegion region= document.getLineInformationOfOffset(offset); 616 String line= document.get(region.getOffset(), region.getLength()); 617 618 int index= offset - region.getOffset(); 619 while (index != line.length() && Character.isUnicodeIdentifierPart(line.charAt(index))) 620 ++index; 621 622 if (index == line.length()) 623 return true; 624 625 char ch= line.charAt(index); 626 return ch != '<'; 627 628 } catch (BadLocationException e) { 629 return true; 630 } 631 } 632 633 private StringBuffer createParameterList(TypeArgumentProposal[] typeArguments, int[] offsets, int[] lengths) { 634 StringBuffer buffer= new StringBuffer (); 635 buffer.append(getReplacementString()); 636 637 FormatterPrefs prefs= getFormatterPrefs(); 638 final char LESS= '<'; 639 final char GREATER= '>'; 640 if (prefs.beforeOpeningBracket) 641 buffer.append(SPACE); 642 buffer.append(LESS); 643 if (prefs.afterOpeningBracket) 644 buffer.append(SPACE); 645 StringBuffer separator= new StringBuffer (3); 646 if (prefs.beforeTypeArgumentComma) 647 separator.append(SPACE); 648 separator.append(COMMA); 649 if (prefs.afterTypeArgumentComma) 650 separator.append(SPACE); 651 652 for (int i= 0; i != typeArguments.length; i++) { 653 if (i != 0) 654 buffer.append(separator); 655 656 offsets[i]= buffer.length(); 657 buffer.append(typeArguments[i]); 658 lengths[i]= buffer.length() - offsets[i]; 659 } 660 if (prefs.beforeClosingBracket) 661 buffer.append(SPACE); 662 buffer.append(GREATER); 663 664 return buffer; 665 } 666 667 private void installLinkedMode(IDocument document, int[] offsets, int[] lengths, TypeArgumentProposal[] typeArgumentProposals) { 668 int replacementOffset= getReplacementOffset(); 669 String replacementString= getReplacementString(); 670 671 try { 672 LinkedModeModel model= new LinkedModeModel(); 673 for (int i= 0; i != offsets.length; i++) { 674 if (typeArgumentProposals[i].isAmbiguous()) { 675 LinkedPositionGroup group= new LinkedPositionGroup(); 676 group.addPosition(new LinkedPosition(document, replacementOffset + offsets[i], lengths[i], LinkedPositionGroup.NO_STOP)); 677 model.addGroup(group); 678 } 679 } 680 681 model.forceInstall(); 682 JavaEditor editor= getJavaEditor(); 683 if (editor != null) { 684 model.addLinkingListener(new EditorHighlightingSynchronizer(editor)); 685 } 686 687 LinkedModeUI ui= new EditorLinkedModeUI(model, getTextViewer()); 688 ui.setExitPolicy(new ExitPolicy('>', document)); 689 ui.setExitPosition(getTextViewer(), replacementOffset + replacementString.length(), 0, Integer.MAX_VALUE); 690 ui.setDoContextInfo(true); 691 ui.enter(); 692 693 fSelectedRegion= ui.getSelectedRegion(); 694 695 } catch (BadLocationException e) { 696 JavaPlugin.log(e); 697 openErrorDialog(e); 698 } 699 } 700 701 private boolean hasAmbiguousProposals(TypeArgumentProposal[] typeArgumentProposals) { 702 boolean hasAmbiguousProposals= false; 703 for (int i= 0; i < typeArgumentProposals.length; i++) { 704 if (typeArgumentProposals[i].isAmbiguous()) { 705 hasAmbiguousProposals= true; 706 break; 707 } 708 } 709 return hasAmbiguousProposals; 710 } 711 712 718 private JavaEditor getJavaEditor() { 719 IEditorPart part= JavaPlugin.getActivePage().getActiveEditor(); 720 if (part instanceof JavaEditor) 721 return (JavaEditor) part; 722 else 723 return null; 724 } 725 726 729 public Point getSelection(IDocument document) { 730 if (fSelectedRegion == null) 731 return super.getSelection(document); 732 733 return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); 734 } 735 736 private void openErrorDialog(BadLocationException e) { 737 Shell shell= getTextViewer().getTextWidget().getShell(); 738 MessageDialog.openError(shell, JavaTextMessages.ExperimentalProposal_error_msg, e.getMessage()); 739 } 740 741 744 protected IContextInformation computeContextInformation() { 745 if (fTypeArgumentProposals != null) { 748 try { 749 if (hasParameters()) { 750 TypeArgumentProposal[] proposals= computeTypeArgumentProposals(); 751 if (hasAmbiguousProposals(proposals)) 752 return new ContextInformation(this); 753 } 754 } catch (JavaModelException e) { 755 } 756 } 757 return super.computeContextInformation(); 758 } 759 760 protected int computeCursorPosition() { 761 if (fSelectedRegion != null) 762 return fSelectedRegion.getOffset() - getReplacementOffset(); 763 return super.computeCursorPosition(); 764 } 765 766 private boolean hasParameters() { 767 try { 768 IType type= (IType) getJavaElement(); 769 if (type == null) 770 return false; 771 772 return type.getTypeParameters().length > 0; 773 } catch (JavaModelException e) { 774 return false; 775 } 776 } 777 } 778 | Popular Tags |