]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/java/AbstractJavaCompletionProposal.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / text / java / AbstractJavaCompletionProposal.java
1 /*******************************************************************************
2  * Copyright (c) 2005, 2012 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.ui.text.java;
12
13 import java.io.BufferedReader;
14 import java.io.IOException;
15 import java.io.InputStreamReader;
16 import java.net.URL;
17
18 import org.osgi.framework.Bundle;
19
20 import org.eclipse.osgi.util.TextProcessor;
21
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.custom.StyleRange;
24 import org.eclipse.swt.custom.StyledText;
25 import org.eclipse.swt.events.VerifyEvent;
26 import org.eclipse.swt.graphics.Color;
27 import org.eclipse.swt.graphics.FontData;
28 import org.eclipse.swt.graphics.Image;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.graphics.RGB;
31 import org.eclipse.swt.widgets.Shell;
32
33 import org.eclipse.core.runtime.Assert;
34 import org.eclipse.core.runtime.FileLocator;
35 import org.eclipse.core.runtime.IProgressMonitor;
36 import org.eclipse.core.runtime.NullProgressMonitor;
37 import org.eclipse.core.runtime.Platform;
38
39 import org.eclipse.jface.internal.text.html.BrowserInformationControl;
40 import org.eclipse.jface.internal.text.html.HTMLPrinter;
41 import org.eclipse.jface.preference.IPreferenceStore;
42 import org.eclipse.jface.preference.PreferenceConverter;
43 import org.eclipse.jface.resource.JFaceResources;
44 import org.eclipse.jface.viewers.StyledString;
45
46 import org.eclipse.jface.text.BadLocationException;
47 import org.eclipse.jface.text.BadPositionCategoryException;
48 import org.eclipse.jface.text.DefaultPositionUpdater;
49 import org.eclipse.jface.text.DocumentCommand;
50 import org.eclipse.jface.text.DocumentEvent;
51 import org.eclipse.jface.text.IDocument;
52 import org.eclipse.jface.text.IInformationControlCreator;
53 import org.eclipse.jface.text.IPositionUpdater;
54 import org.eclipse.jface.text.IRegion;
55 import org.eclipse.jface.text.ITextPresentationListener;
56 import org.eclipse.jface.text.ITextViewer;
57 import org.eclipse.jface.text.ITextViewerExtension2;
58 import org.eclipse.jface.text.ITextViewerExtension4;
59 import org.eclipse.jface.text.ITextViewerExtension5;
60 import org.eclipse.jface.text.Position;
61 import org.eclipse.jface.text.TextPresentation;
62 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
63 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
64 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
65 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5;
66 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
67 import org.eclipse.jface.text.contentassist.IContextInformation;
68 import org.eclipse.jface.text.link.ILinkedModeListener;
69 import org.eclipse.jface.text.link.LinkedModeModel;
70 import org.eclipse.jface.text.link.LinkedModeUI;
71 import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags;
72 import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy;
73 import org.eclipse.jface.text.link.LinkedPosition;
74 import org.eclipse.jface.text.link.LinkedPositionGroup;
75
76 import org.eclipse.ui.IWorkbenchPage;
77 import org.eclipse.ui.IWorkbenchPart;
78 import org.eclipse.ui.IWorkbenchSite;
79
80 import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
81
82 import org.eclipse.jdt.core.CompletionProposal;
83 import org.eclipse.jdt.core.IJavaElement;
84 import org.eclipse.jdt.core.IMember;
85 import org.eclipse.jdt.core.JavaCore;
86 import org.eclipse.jdt.core.JavaModelException;
87 import org.eclipse.jdt.core.compiler.CharOperation;
88
89 import org.eclipse.jdt.internal.corext.javadoc.JavaDocLocations;
90
91 import org.eclipse.jdt.ui.PreferenceConstants;
92 import org.eclipse.jdt.ui.text.IJavaPartitions;
93 import org.eclipse.jdt.ui.text.JavaTextTools;
94 import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
95 import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
96
97 import org.eclipse.jdt.internal.ui.JavaPlugin;
98 import org.eclipse.jdt.internal.ui.text.java.hover.JavadocBrowserInformationControlInput;
99 import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover;
100
101
102 /**
103  *
104  * @since 3.2
105  */
106 public abstract class AbstractJavaCompletionProposal implements IJavaCompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2, ICompletionProposalExtension3, ICompletionProposalExtension5, ICompletionProposalExtension6 {
107
108
109         /**
110          * A class to simplify tracking a reference position in a document.
111          */
112         static final class ReferenceTracker {
113
114                 /** The reference position category name. */
115                 private static final String CATEGORY= "reference_position"; //$NON-NLS-1$
116                 /** The position updater of the reference position. */
117                 private final IPositionUpdater fPositionUpdater= new DefaultPositionUpdater(CATEGORY);
118                 /** The reference position. */
119                 private final Position fPosition= new Position(0);
120
121                 /**
122                  * Called before document changes occur. It must be followed by a call to postReplace().
123                  *
124                  * @param document the document on which to track the reference position.
125                  * @param offset the offset
126                  * @throws BadLocationException if the offset describes an invalid range in this document
127                  *
128                  */
129                 public void preReplace(IDocument document, int offset) throws BadLocationException {
130                         fPosition.setOffset(offset);
131                         try {
132                                 document.addPositionCategory(CATEGORY);
133                                 document.addPositionUpdater(fPositionUpdater);
134                                 document.addPosition(CATEGORY, fPosition);
135
136                         } catch (BadPositionCategoryException e) {
137                                 // should not happen
138                                 JavaPlugin.log(e);
139                         }
140                 }
141
142                 /**
143                  * Called after the document changed occurred. It must be preceded by a call to preReplace().
144                  *
145                  * @param document the document on which to track the reference position.
146                  * @return offset after the replace
147                  */
148                 public int postReplace(IDocument document) {
149                         try {
150                                 document.removePosition(CATEGORY, fPosition);
151                                 document.removePositionUpdater(fPositionUpdater);
152                                 document.removePositionCategory(CATEGORY);
153
154                         } catch (BadPositionCategoryException e) {
155                                 // should not happen
156                                 JavaPlugin.log(e);
157                         }
158                         return fPosition.getOffset();
159                 }
160         }
161
162         protected static class ExitPolicy implements IExitPolicy {
163
164                 final char fExitCharacter;
165                 private final IDocument fDocument;
166
167                 public ExitPolicy(char exitCharacter, IDocument document) {
168                         fExitCharacter= exitCharacter;
169                         fDocument= document;
170                 }
171
172                 /*
173                  * @see org.eclipse.jdt.internal.ui.text.link.LinkedPositionUI.ExitPolicy#doExit(org.eclipse.jdt.internal.ui.text.link.LinkedPositionManager, org.eclipse.swt.events.VerifyEvent, int, int)
174                  */
175                 public ExitFlags doExit(LinkedModeModel environment, VerifyEvent event, int offset, int length) {
176
177                         if (event.character == fExitCharacter) {
178                                 if (environment.anyPositionContains(offset))
179                                         return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false);
180                                 else
181                                         return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true);
182                         }
183
184                         switch (event.character) {
185                                 case ';':
186                                         return new ExitFlags(ILinkedModeListener.NONE, true);
187                                 case SWT.CR:
188                                         // when entering an anonymous class as a parameter, we don't want
189                                         // to jump after the parenthesis when return is pressed
190                                         if (offset > 0) {
191                                                 try {
192                                                         if (fDocument.getChar(offset - 1) == '{')
193                                                                 return new ExitFlags(ILinkedModeListener.EXIT_ALL, true);
194                                                 } catch (BadLocationException e) {
195                                                 }
196                                         }
197                                         return null;
198                                 default:
199                                         return null;
200                         }
201                 }
202
203         }
204
205         private StyledString fDisplayString;
206         private String fReplacementString;
207         private int fReplacementOffset;
208         private int fReplacementLength;
209         private int fCursorPosition;
210         private Image fImage;
211         private IContextInformation fContextInformation;
212         private ProposalInfo fProposalInfo;
213         private char[] fTriggerCharacters;
214         private String fSortString;
215         private int fRelevance;
216         private boolean fIsInJavadoc;
217
218         private StyleRange fRememberedStyleRange;
219
220         private boolean fToggleEating;
221         private ITextViewer fTextViewer;
222
223
224         /**
225          * The control creator.
226          *
227          * @since 3.2
228          */
229         private IInformationControlCreator fCreator;
230         /**
231          * The CSS used to format javadoc information.
232          * @since 3.3
233          */
234         private static String fgCSSStyles;
235
236         /**
237          * The invocation context of this completion proposal. Can be <code>null</code>.
238          */
239         public final JavaContentAssistInvocationContext fInvocationContext;
240
241         /**
242          * Cache to store last validation state.
243          * @since 3.5
244          */
245         private boolean fIsValidated= true;
246
247         /**
248          * The text presentation listener.
249          * @since 3.6
250          */
251         private ITextPresentationListener fTextPresentationListener;
252
253         protected AbstractJavaCompletionProposal() {
254                 fInvocationContext= null;
255         }
256
257         protected AbstractJavaCompletionProposal(JavaContentAssistInvocationContext context) {
258                 fInvocationContext= context;
259         }
260
261         /*
262          * @see ICompletionProposalExtension#getTriggerCharacters()
263          */
264         public char[] getTriggerCharacters() {
265                 return fTriggerCharacters;
266         }
267
268         /**
269          * Sets the trigger characters.
270          *
271          * @param triggerCharacters The set of characters which can trigger the application of this
272          *        completion proposal
273          */
274         public void setTriggerCharacters(char[] triggerCharacters) {
275                 fTriggerCharacters= triggerCharacters;
276         }
277
278         /**
279          * Sets the proposal info.
280          *
281          * @param proposalInfo The additional information associated with this proposal or
282          *        <code>null</code>
283          */
284         public void setProposalInfo(ProposalInfo proposalInfo) {
285                 fProposalInfo= proposalInfo;
286         }
287
288         /**
289          * Returns the additional proposal info, or <code>null</code> if none exists.
290          *
291          * @return the additional proposal info, or <code>null</code> if none exists
292          */
293         protected ProposalInfo getProposalInfo() {
294                 return fProposalInfo;
295         }
296
297         /**
298          * Sets the cursor position relative to the insertion offset. By default this is the length of
299          * the completion string (Cursor positioned after the completion)
300          *
301          * @param cursorPosition The cursorPosition to set
302          */
303         public void setCursorPosition(int cursorPosition) {
304                 Assert.isTrue(cursorPosition >= 0);
305                 fCursorPosition= cursorPosition;
306         }
307
308         protected int getCursorPosition() {
309                 return fCursorPosition;
310         }
311
312         /*
313          * @see ICompletionProposal#apply
314          */
315         public final void apply(IDocument document) {
316                 // not used any longer
317                 apply(document, (char) 0, getReplacementOffset() + getReplacementLength());
318         }
319
320         /*
321          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply(org.eclipse.jface.text.IDocument, char, int)
322          */
323         public void apply(IDocument document, char trigger, int offset) {
324
325                 if (isSupportingRequiredProposals()) {
326                         CompletionProposal coreProposal= ((MemberProposalInfo)getProposalInfo()).fProposal;
327                         CompletionProposal[] requiredProposals= coreProposal.getRequiredProposals();
328                         for (int i= 0; requiredProposals != null &&  i < requiredProposals.length; i++) {
329                                 int oldLen= document.getLength();
330                                 if (requiredProposals[i].getKind() == CompletionProposal.TYPE_REF) {
331                                         LazyJavaCompletionProposal proposal= createRequiredTypeCompletionProposal(requiredProposals[i], fInvocationContext);
332                                         proposal.apply(document);
333                                         setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen);
334                                 } else
335                                         fInvocationContext.generated_3345393073719118376(this, document, coreProposal, requiredProposals, i, oldLen);
336                         }
337                 }
338
339                 try {
340                         boolean isSmartTrigger= isSmartTrigger(trigger);
341
342                         String replacement;
343                         if (isSmartTrigger || trigger == (char) 0) {
344                                 replacement= getReplacementString();
345                         } else {
346                                 StringBuffer buffer= new StringBuffer(getReplacementString());
347
348                                 // fix for PR #5533. Assumes that no eating takes place.
349                                 if ((getCursorPosition() > 0 && getCursorPosition() <= buffer.length() && buffer.charAt(getCursorPosition() - 1) != trigger)) {
350                                         buffer.insert(getCursorPosition(), trigger);
351                                         setCursorPosition(getCursorPosition() + 1);
352                                 }
353
354                                 replacement= buffer.toString();
355                                 setReplacementString(replacement);
356                         }
357
358                         // reference position just at the end of the document change.
359                         int referenceOffset= getReplacementOffset() + getReplacementLength();
360                         final ReferenceTracker referenceTracker= new ReferenceTracker();
361                         referenceTracker.preReplace(document, referenceOffset);
362
363                         replace(document, getReplacementOffset(), getReplacementLength(), replacement);
364
365                         referenceOffset= referenceTracker.postReplace(document);
366                         setReplacementOffset(referenceOffset - (replacement == null ? 0 : replacement.length()));
367
368                         // PR 47097
369                         if (isSmartTrigger)
370                                 handleSmartTrigger(document, trigger, referenceOffset);
371
372                 } catch (BadLocationException x) {
373                         // ignore
374                 }
375         }
376
377         /**
378          * Creates the required type proposal.
379          * 
380          * @param completionProposal the core completion proposal
381          * @param invocationContext invocation context
382          * @return the required type completion proposal
383          * @since 3.5
384          */
385         protected LazyJavaCompletionProposal createRequiredTypeCompletionProposal(CompletionProposal completionProposal, JavaContentAssistInvocationContext invocationContext) {
386                 if (PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES))
387                         return (LazyJavaCompletionProposal)new FillArgumentNamesCompletionProposalCollector(invocationContext).createJavaCompletionProposal(completionProposal);
388                 else
389                         return new LazyJavaTypeCompletionProposal(completionProposal, invocationContext);
390         }
391
392         private boolean isSmartTrigger(char trigger) {
393                 return trigger == ';' && JavaPlugin.getDefault().getCombinedPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_SEMICOLON)
394                                 || trigger == '{' && JavaPlugin.getDefault().getCombinedPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_OPENING_BRACE);
395         }
396
397         private void handleSmartTrigger(IDocument document, char trigger, int referenceOffset) throws BadLocationException {
398                 DocumentCommand cmd= new DocumentCommand() {
399                 };
400
401                 cmd.offset= referenceOffset;
402                 cmd.length= 0;
403                 cmd.text= Character.toString(trigger);
404                 cmd.doit= true;
405                 cmd.shiftsCaret= true;
406                 cmd.caretOffset= getReplacementOffset() + getCursorPosition();
407
408                 SmartSemicolonAutoEditStrategy strategy= new SmartSemicolonAutoEditStrategy(IJavaPartitions.JAVA_PARTITIONING);
409                 strategy.customizeDocumentCommand(document, cmd);
410
411                 replace(document, cmd.offset, cmd.length, cmd.text);
412                 setCursorPosition(cmd.caretOffset - getReplacementOffset() + cmd.text.length());
413         }
414
415         protected final void replace(IDocument document, int offset, int length, String string) throws BadLocationException {
416                 if (!document.get(offset, length).equals(string))
417                         document.replace(offset, length, string);
418         }
419
420         /*
421          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension1#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
422          */
423         public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
424
425                 IDocument document= viewer.getDocument();
426                 if (fTextViewer == null)
427                         fTextViewer= viewer;
428
429                 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96059
430                 // don't apply the proposal if for some reason we're not valid any longer
431                 if (!isInJavadoc() && !validate(document, offset, null)) {
432                         setCursorPosition(offset);
433                         if (trigger != '\0') {
434                                 try {
435                                         document.replace(offset, 0, String.valueOf(trigger));
436                                         setCursorPosition(getCursorPosition() + 1);
437                                         if (trigger == '(' && autocloseBrackets()) {
438                                                 document.replace(getReplacementOffset() + getCursorPosition(), 0, ")"); //$NON-NLS-1$
439                                                 setUpLinkedMode(document, ')');
440                                         }
441                                 } catch (BadLocationException x) {
442                                         // ignore
443                                 }
444                         }
445                         return;
446                 }
447
448                 // don't eat if not in preferences, XOR with Ctrl
449                 // but: if there is a selection, replace it!
450                 Point selection= viewer.getSelectedRange();
451                 fToggleEating= (stateMask & SWT.CTRL) != 0;
452                 int newLength= selection.x + selection.y - getReplacementOffset();
453                 if ((insertCompletion() ^ fToggleEating) && newLength >= 0)
454                         setReplacementLength(newLength);
455
456                 apply(document, trigger, offset);
457                 fToggleEating= false;
458         }
459
460         /**
461          * Tells whether the user toggled the insert mode by pressing the 'Ctrl' key.
462          * 
463          * @return <code>true</code> if the insert mode is toggled, <code>false</code> otherwise
464          * @since 3.5
465          */
466         protected boolean isInsertModeToggled() {
467                 return fToggleEating;
468         }
469
470         /**
471          * Returns <code>true</code> if the proposal is within javadoc, <code>false</code> otherwise.
472          * 
473          * @return <code>true</code> if the proposal is within javadoc, <code>false</code> otherwise
474          */
475         protected boolean isInJavadoc() {
476                 return fIsInJavadoc;
477         }
478
479         /**
480          * Sets the javadoc attribute.
481          *
482          * @param isInJavadoc <code>true</code> if the proposal is within javadoc
483          */
484         protected void setInJavadoc(boolean isInJavadoc) {
485                 fIsInJavadoc= isInJavadoc;
486         }
487
488         /*
489          * @see ICompletionProposal#getSelection
490          */
491         public Point getSelection(IDocument document) {
492                 if (!fIsValidated)
493                         return null;
494                 return new Point(getReplacementOffset() + getCursorPosition(), 0);
495         }
496
497         /*
498          * @see ICompletionProposal#getContextInformation()
499          */
500         public IContextInformation getContextInformation() {
501                 return fContextInformation;
502         }
503
504         /**
505          * Sets the context information.
506          * @param contextInformation The context information associated with this proposal
507          */
508         public void setContextInformation(IContextInformation contextInformation) {
509                 fContextInformation= contextInformation;
510         }
511
512         /*
513          * @see ICompletionProposal#getDisplayString()
514          */
515         public String getDisplayString() {
516                 if (fDisplayString != null)
517                         return fDisplayString.getString();
518                 return ""; //$NON-NLS-1$
519         }
520
521         /*
522          * @see ICompletionProposal#getAdditionalProposalInfo()
523          */
524         public String getAdditionalProposalInfo() {
525                 Object info= getAdditionalProposalInfo(new NullProgressMonitor());
526                 return info == null ? null : info.toString();
527         }
528
529         /*
530          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension5#getAdditionalProposalInfo(org.eclipse.core.runtime.IProgressMonitor)
531          */
532         public Object getAdditionalProposalInfo(IProgressMonitor monitor) {
533                 if (getProposalInfo() != null) {
534                         String info= getProposalInfo().getInfo(monitor);
535                         if (info != null && info.length() > 0) {
536                                 StringBuffer buffer= new StringBuffer();
537                                 HTMLPrinter.insertPageProlog(buffer, 0, getCSSStyles());
538
539                                 buffer.append(info);
540
541                                 IJavaElement element= null;
542                                 try {
543                                         element= getProposalInfo().getJavaElement();
544                                         if (element instanceof IMember) {
545                                                 String base= JavaDocLocations.getBaseURL((IMember) element);
546                                                 if (base != null) {
547                                                         int endHeadIdx= buffer.indexOf("</head>"); //$NON-NLS-1$
548                                                         buffer.insert(endHeadIdx, "\n<base href='" + base + "'>\n"); //$NON-NLS-1$ //$NON-NLS-2$
549                                                 }
550                                         }
551                                 } catch (JavaModelException e) {
552                                         JavaPlugin.log(e);
553                                 }
554
555                                 HTMLPrinter.addPageEpilog(buffer);
556                                 info= buffer.toString();
557
558                                 return new JavadocBrowserInformationControlInput(null, element, info, 0);
559                         }
560                 }
561                 return null;
562         }
563
564         /**
565          * Returns the style information for displaying HTML (Javadoc) content.
566          *
567          * @return the CSS styles
568          * @since 3.3
569          */
570         protected String getCSSStyles() {
571                 if (fgCSSStyles == null) {
572                         Bundle bundle= Platform.getBundle(JavaPlugin.getPluginId());
573                         URL url= bundle.getEntry("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$
574                         if (url != null) {
575                                 BufferedReader reader= null;
576                                 try {
577                                         url= FileLocator.toFileURL(url);
578                                         reader= new BufferedReader(new InputStreamReader(url.openStream()));
579                                         StringBuffer buffer= new StringBuffer(200);
580                                         String line= reader.readLine();
581                                         while (line != null) {
582                                                 buffer.append(line);
583                                                 buffer.append('\n');
584                                                 line= reader.readLine();
585                                         }
586                                         fgCSSStyles= buffer.toString();
587                                 } catch (IOException ex) {
588                                         JavaPlugin.log(ex);
589                                 } finally {
590                                         try {
591                                                 if (reader != null)
592                                                         reader.close();
593                                         } catch (IOException e) {
594                                         }
595                                 }
596
597                         }
598                 }
599                 String css= fgCSSStyles;
600                 if (css != null) {
601                         FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
602                         css= HTMLPrinter.convertTopLevelFont(css, fontData);
603                 }
604                 return css;
605         }
606
607         /*
608          * @see ICompletionProposalExtension#getContextInformationPosition()
609          */
610         public int getContextInformationPosition() {
611                 if (getContextInformation() == null)
612                         return getReplacementOffset() - 1;
613                 return getReplacementOffset() + getCursorPosition();
614         }
615
616         /**
617          * Gets the replacement offset.
618          * @return Returns a int
619          */
620         public int getReplacementOffset() {
621                 return fReplacementOffset;
622         }
623
624         /**
625          * Sets the replacement offset.
626          * @param replacementOffset The replacement offset to set
627          */
628         public void setReplacementOffset(int replacementOffset) {
629                 Assert.isTrue(replacementOffset >= 0);
630                 fReplacementOffset= replacementOffset;
631         }
632
633         /*
634          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getCompletionOffset()
635          */
636         public int getPrefixCompletionStart(IDocument document, int completionOffset) {
637                 return getReplacementOffset();
638         }
639
640         /**
641          * Gets the replacement length.
642          * @return Returns a int
643          */
644         public int getReplacementLength() {
645                 return fReplacementLength;
646         }
647
648         /**
649          * Sets the replacement length.
650          * @param replacementLength The replacementLength to set
651          */
652         public void setReplacementLength(int replacementLength) {
653                 Assert.isTrue(replacementLength >= 0);
654                 fReplacementLength= replacementLength;
655         }
656
657         /**
658          * Gets the replacement string.
659          * @return Returns a String
660          */
661         public String getReplacementString() {
662                 return fReplacementString;
663         }
664
665         /**
666          * Sets the replacement string.
667          * @param replacementString The replacement string to set
668          */
669         public void setReplacementString(String replacementString) {
670                 Assert.isNotNull(replacementString);
671                 fReplacementString= replacementString;
672         }
673
674         /*
675          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementText()
676          */
677         public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
678                 if (!isCamelCaseMatching())
679                         return getReplacementString();
680
681                 String prefix= getPrefix(document, completionOffset);
682                 return getCamelCaseCompound(prefix, getReplacementString());
683         }
684
685         /*
686          * @see ICompletionProposal#getImage()
687          */
688         public Image getImage() {
689                 return fImage;
690         }
691
692         /**
693          * Sets the image.
694          * @param image The image to set
695          */
696         public void setImage(Image image) {
697                 fImage= image;
698         }
699
700         /*
701          * @see ICompletionProposalExtension#isValidFor(IDocument, int)
702          */
703         public boolean isValidFor(IDocument document, int offset) {
704                 return validate(document, offset, null);
705         }
706
707         /*
708          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
709          */
710         public boolean validate(IDocument document, int offset, DocumentEvent event) {
711
712                 if (!isOffsetValid(offset))
713                         return fIsValidated= false;
714
715                 fIsValidated= isValidPrefix(getPrefix(document, offset));
716
717                 if (fIsValidated && event != null) {
718                         // adapt replacement range to document change
719                         int delta= (event.fText == null ? 0 : event.fText.length()) - event.fLength;
720                         final int newLength= Math.max(getReplacementLength() + delta, 0);
721                         setReplacementLength(newLength);
722                 }
723
724                 return fIsValidated;
725         }
726
727         /**
728          * Checks whether the given offset is valid for this proposal.
729          * 
730          * @param offset the caret offset
731          * @return <code>true</code> if the offset is valid for this proposal
732          * @since 3.5
733          */
734         protected boolean isOffsetValid(int offset) {
735                 return getReplacementOffset() <= offset;
736         }
737
738         /**
739          * Checks whether <code>prefix</code> is a valid prefix for this proposal. Usually, while code
740          * completion is in progress, the user types and edits the prefix in the document in order to
741          * filter the proposal list. From {@link #validate(IDocument, int, DocumentEvent) }, the
742          * current prefix in the document is extracted and this method is called to find out whether the
743          * proposal is still valid.
744          * <p>
745          * The default implementation checks if <code>prefix</code> is a prefix of the proposal's
746          * {@link #getDisplayString() display string} using the {@link #isPrefix(String, String) }
747          * method.
748          * </p>
749          *
750          * @param prefix the current prefix in the document
751          * @return <code>true</code> if <code>prefix</code> is a valid prefix of this proposal
752          */
753         protected boolean isValidPrefix(String prefix) {
754                 /*
755                  * See http://dev.eclipse.org/bugs/show_bug.cgi?id=17667
756                  * why we do not use the replacement string.
757                  * String word= fReplacementString;
758                  *
759                  * Besides that bug we also use the display string
760                  * for performance reasons, as computing the
761                  * replacement string can be expensive.
762                  */
763                 return isPrefix(prefix, TextProcessor.deprocess(getDisplayString()));
764         }
765
766         /**
767          * Gets the proposal's relevance.
768          * @return Returns a int
769          */
770         public int getRelevance() {
771                 return fRelevance;
772         }
773
774         /**
775          * Sets the proposal's relevance.
776          * @param relevance The relevance to set
777          */
778         public void setRelevance(int relevance) {
779                 fRelevance= relevance;
780         }
781
782         /**
783          * Returns the text in <code>document</code> from {@link #getReplacementOffset()} to
784          * <code>offset</code>. Returns the empty string if <code>offset</code> is before the
785          * replacement offset or if an exception occurs when accessing the document.
786          *
787          * @param document the document
788          * @param offset the offset
789          * @return the prefix
790          * @since 3.2
791          */
792         protected String getPrefix(IDocument document, int offset) {
793                 try {
794                         int length= offset - getReplacementOffset();
795                         if (length > 0)
796                                 return document.get(getReplacementOffset(), length);
797                 } catch (BadLocationException x) {
798                 }
799                 return ""; //$NON-NLS-1$
800         }
801
802         /**
803          * Case insensitive comparison of <code>prefix</code> with the start of <code>string</code>.
804          *
805          * @param prefix the prefix
806          * @param string  the string to look for the prefix
807          * @return <code>true</code> if the string begins with the given prefix and
808          *                      <code>false</code> if <code>prefix</code> is longer than <code>string</code>
809          *                      or the string doesn't start with the given prefix
810          * @since 3.2
811          */
812         protected boolean isPrefix(String prefix, String string) {
813                 if (prefix == null || string ==null || prefix.length() > string.length())
814                         return false;
815                 String start= string.substring(0, prefix.length());
816                 return start.equalsIgnoreCase(prefix) || isCamelCaseMatching() && CharOperation.camelCaseMatch(prefix.toCharArray(), string.toCharArray());
817         }
818
819         /**
820          * Matches <code>prefix</code> against <code>string</code> and replaces the matched region
821          * by prefix. Case is preserved as much as possible. This method returns <code>string</code> if camel case completion
822          * is disabled. Examples when camel case completion is enabled:
823          * <ul>
824          * <li>getCamelCompound("NuPo", "NullPointerException") -> "NuPointerException"</li>
825          * <li>getCamelCompound("NuPoE", "NullPointerException") -> "NuPoException"</li>
826          * <li>getCamelCompound("hasCod", "hashCode") -> "hasCode"</li>
827          * </ul>
828          *
829          * @param prefix the prefix to match against
830          * @param string the string to match
831          * @return a compound of prefix and any postfix taken from <code>string</code>
832          * @since 3.2
833          */
834         protected final String getCamelCaseCompound(String prefix, String string) {
835                 if (prefix.length() > string.length())
836                         return string;
837
838                 // a normal prefix - no camel case logic at all
839                 String start= string.substring(0, prefix.length());
840                 if (start.equalsIgnoreCase(prefix))
841                         return string;
842
843                 final char[] patternChars= prefix.toCharArray();
844                 final char[] stringChars= string.toCharArray();
845
846                 for (int i= 1; i <= stringChars.length; i++)
847                         if (CharOperation.camelCaseMatch(patternChars, 0, patternChars.length, stringChars, 0, i))
848                                 return prefix + string.substring(i);
849
850                 // Not a camel case match at all.
851                 // This should not happen -> stay with the default behavior
852                 return string;
853         }
854
855         /**
856          * Returns true if camel case matching is enabled.
857          *
858          * @return <code>true</code> if camel case matching is enabled
859          * @since 3.2
860          */
861         protected boolean isCamelCaseMatching() {
862                 String value= JavaCore.getOption(JavaCore.CODEASSIST_CAMEL_CASE_MATCH);
863                 return JavaCore.ENABLED.equals(value);
864         }
865
866         protected static boolean insertCompletion() {
867                 IPreferenceStore preference= JavaPlugin.getDefault().getPreferenceStore();
868                 return preference.getBoolean(PreferenceConstants.CODEASSIST_INSERT_COMPLETION);
869         }
870
871         private static Color getForegroundColor() {
872                 IPreferenceStore preference= JavaPlugin.getDefault().getPreferenceStore();
873                 RGB rgb= PreferenceConverter.getColor(preference, PreferenceConstants.CODEASSIST_REPLACEMENT_FOREGROUND);
874                 JavaTextTools textTools= JavaPlugin.getDefault().getJavaTextTools();
875                 return textTools.getColorManager().getColor(rgb);
876         }
877
878         private static Color getBackgroundColor() {
879                 IPreferenceStore preference= JavaPlugin.getDefault().getPreferenceStore();
880                 RGB rgb= PreferenceConverter.getColor(preference, PreferenceConstants.CODEASSIST_REPLACEMENT_BACKGROUND);
881                 JavaTextTools textTools= JavaPlugin.getDefault().getJavaTextTools();
882                 return textTools.getColorManager().getColor(rgb);
883         }
884
885         private void repairPresentation(ITextViewer viewer) {
886                 if (fRememberedStyleRange != null) {
887                         if (viewer instanceof ITextViewerExtension2) {
888                                 // attempts to reduce the redraw area
889                                 ITextViewerExtension2 viewer2= (ITextViewerExtension2)viewer;
890                                 viewer2.invalidateTextPresentation(fRememberedStyleRange.start, fRememberedStyleRange.length);
891                         } else
892                                 viewer.invalidateTextPresentation();
893                 }
894         }
895
896         private void updateStyle(ITextViewer viewer) {
897                 StyledText text= viewer.getTextWidget();
898                 int widgetOffset= getWidgetOffset(viewer, fRememberedStyleRange.start);
899                 StyleRange range= new StyleRange(fRememberedStyleRange);
900                 range.start= widgetOffset;
901                 range.length= fRememberedStyleRange.length;
902                 StyleRange currentRange= text.getStyleRangeAtOffset(widgetOffset);
903                 if (currentRange != null) {
904                         range.strikeout= currentRange.strikeout;
905                         range.underline= currentRange.underline;
906                         range.fontStyle= currentRange.fontStyle;
907                 }
908
909                 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
910                 try {
911                         text.setStyleRange(range);
912                 } catch (IllegalArgumentException x) {
913                         // catching exception as offset + length might be outside of the text widget
914                         fRememberedStyleRange= null;
915                 }
916         }
917
918         /**
919          * Convert a document offset to the corresponding widget offset.
920          * 
921          * @param viewer the text viewer
922          * @param documentOffset the document offset
923          * @return widget offset
924          * @since 3.6
925          */
926         private int getWidgetOffset(ITextViewer viewer, int documentOffset) {
927                 if (viewer instanceof ITextViewerExtension5) {
928                         ITextViewerExtension5 extension= (ITextViewerExtension5)viewer;
929                         return extension.modelOffset2WidgetOffset(documentOffset);
930                 }
931                 IRegion visible= viewer.getVisibleRegion();
932                 int widgetOffset= documentOffset - visible.getOffset();
933                 if (widgetOffset > visible.getLength()) {
934                         return -1;
935                 }
936                 return widgetOffset;
937         }
938
939
940         /**
941          * Creates a style range for the text viewer.
942          * 
943          * @param viewer the text viewer
944          * @return the new style range for the text viewer or <code>null</code>
945          * @since 3.6
946          */
947         private StyleRange createStyleRange(ITextViewer viewer) {
948                 StyledText text= viewer.getTextWidget();
949                 if (text == null || text.isDisposed())
950                         return null;
951
952                 int widgetCaret= text.getCaretOffset();
953
954                 int modelCaret= 0;
955                 if (viewer instanceof ITextViewerExtension5) {
956                         ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
957                         modelCaret= extension.widgetOffset2ModelOffset(widgetCaret);
958                 } else {
959                         IRegion visibleRegion= viewer.getVisibleRegion();
960                         modelCaret= widgetCaret + visibleRegion.getOffset();
961                 }
962
963                 if (modelCaret >= getReplacementOffset() + getReplacementLength())
964                         return null;
965
966                 int length= getReplacementOffset() + getReplacementLength() - modelCaret;
967
968                 Color foreground= getForegroundColor();
969                 Color background= getBackgroundColor();
970
971                 return new StyleRange(modelCaret, length, foreground, background);
972         }
973
974         /*
975          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(ITextViewer, boolean)
976          */
977         public void selected(final ITextViewer viewer, boolean smartToggle) {
978                 repairPresentation(viewer);
979                 fRememberedStyleRange= null;
980
981                 if (!insertCompletion() ^ smartToggle) {
982                         StyleRange range= createStyleRange(viewer);
983                         if (range == null)
984                                 return;
985                         
986                         fRememberedStyleRange= range;
987
988                         if (viewer instanceof ITextViewerExtension4) {
989                                 if (fTextPresentationListener == null) {
990                                         fTextPresentationListener= new ITextPresentationListener() {
991                                                 /* (non-Javadoc)
992                                                  * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
993                                                  */
994                                                 public void applyTextPresentation(TextPresentation textPresentation) {
995                                                         fRememberedStyleRange= createStyleRange(viewer);
996                                                         if (fRememberedStyleRange != null)
997                                                                 textPresentation.mergeStyleRange(fRememberedStyleRange);
998                                                 }
999                                         };
1000                                         ((ITextViewerExtension4)viewer).addTextPresentationListener(fTextPresentationListener);
1001                                 }
1002                                 repairPresentation(viewer);
1003                         } else
1004                                 updateStyle(viewer);
1005                 }
1006         }
1007
1008         /*
1009          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(ITextViewer)
1010          */
1011         public void unselected(ITextViewer viewer) {
1012                 if (fTextPresentationListener != null) {
1013                         ((ITextViewerExtension4)viewer).removeTextPresentationListener(fTextPresentationListener);
1014                         fTextPresentationListener= null;
1015                 }
1016                 repairPresentation(viewer);
1017                 fRememberedStyleRange= null;
1018         }
1019
1020         /*
1021          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
1022          */
1023         public IInformationControlCreator getInformationControlCreator() {
1024                 Shell shell= JavaPlugin.getActiveWorkbenchShell();
1025                 if (shell == null || !BrowserInformationControl.isAvailable(shell))
1026                         return null;
1027
1028                 if (fCreator == null) {
1029                         /*
1030                          * FIXME: Take control creators (and link handling) out of JavadocHover,
1031                          * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=232024
1032                          */
1033                         JavadocHover.PresenterControlCreator presenterControlCreator= new JavadocHover.PresenterControlCreator(getSite());
1034                         fCreator= new JavadocHover.HoverControlCreator(presenterControlCreator, true);
1035                 }
1036                 return fCreator;
1037         }
1038
1039         private IWorkbenchSite getSite() {
1040                 IWorkbenchPage page= JavaPlugin.getActivePage();
1041                 if (page != null) {
1042                         IWorkbenchPart part= page.getActivePart();
1043                         if (part != null)
1044                                 return part.getSite();
1045                 }
1046                 return null;
1047         }
1048
1049         public String getSortString() {
1050                 return fSortString;
1051         }
1052
1053         protected void setSortString(String string) {
1054                 fSortString= string;
1055         }
1056
1057         protected ITextViewer getTextViewer() {
1058                 return fTextViewer;
1059         }
1060
1061         protected boolean isToggleEating() {
1062                 return fToggleEating;
1063         }
1064
1065         /**
1066          * Sets up a simple linked mode at {@link #getCursorPosition()} and an exit policy that will
1067          * exit the mode when <code>closingCharacter</code> is typed and an exit position at
1068          * <code>getCursorPosition() + 1</code>.
1069          *
1070          * @param document the document
1071          * @param closingCharacter the exit character
1072          */
1073         protected void setUpLinkedMode(IDocument document, char closingCharacter) {
1074                 if (getTextViewer() != null && autocloseBrackets()) {
1075                         int offset= getReplacementOffset() + getCursorPosition();
1076                         int exit= getReplacementOffset() + getReplacementString().length();
1077                         try {
1078                                 LinkedPositionGroup group= new LinkedPositionGroup();
1079                                 group.addPosition(new LinkedPosition(document, offset, 0, LinkedPositionGroup.NO_STOP));
1080
1081                                 LinkedModeModel model= new LinkedModeModel();
1082                                 model.addGroup(group);
1083                                 model.forceInstall();
1084
1085                                 LinkedModeUI ui= new EditorLinkedModeUI(model, getTextViewer());
1086                                 ui.setSimpleMode(true);
1087                                 ui.setExitPolicy(new ExitPolicy(closingCharacter, document));
1088                                 ui.setExitPosition(getTextViewer(), exit, 0, Integer.MAX_VALUE);
1089                                 ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER);
1090                                 ui.enter();
1091                         } catch (BadLocationException x) {
1092                                 JavaPlugin.log(x);
1093                         }
1094                 }
1095         }
1096
1097         protected boolean autocloseBrackets() {
1098                 IPreferenceStore preferenceStore= JavaPlugin.getDefault().getPreferenceStore();
1099                 return preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACKETS);
1100         }
1101
1102         protected void setDisplayString(String string) {
1103                 fDisplayString= new StyledString(string);
1104         }
1105
1106         /*
1107          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension6#getStyledDisplayString()
1108          * @since 3.4
1109          */
1110         public StyledString getStyledDisplayString() {
1111                 return fDisplayString;
1112         }
1113
1114         public void setStyledDisplayString(StyledString text) {
1115                 fDisplayString= text;
1116         }
1117
1118         /*
1119          * @see java.lang.Object#toString()
1120          */
1121         @Override
1122         public String toString() {
1123                 return getDisplayString();
1124         }
1125
1126         /**
1127          * Returns the java element proposed by the receiver, possibly <code>null</code>.
1128          *
1129          * @return the java element proposed by the receiver, possibly <code>null</code>
1130          */
1131         public IJavaElement getJavaElement() {
1132                 if (getProposalInfo() != null)
1133                         try {
1134                                 return getProposalInfo().getJavaElement();
1135                         } catch (JavaModelException x) {
1136                                 JavaPlugin.log(x);
1137                         }
1138                 return null;
1139         }
1140
1141         /**
1142          * Tells whether required proposals are supported by this proposal.
1143          *
1144          * @return <code>true</code> if required proposals are supported by this proposal
1145          * @see CompletionProposal#getRequiredProposals()
1146          * @since 3.3
1147          */
1148         protected boolean isSupportingRequiredProposals() {
1149                 if (fInvocationContext == null)
1150                         return false;
1151
1152                 ProposalInfo proposalInfo= getProposalInfo();
1153                 return proposalInfo.generated_5860057895832307195(this);
1154         }
1155
1156 }