]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2000, 2011 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.util.ArrayList; | |
14 | import java.util.Arrays; | |
15 | import java.util.Collections; | |
16 | import java.util.Iterator; | |
17 | import java.util.List; | |
18 | ||
19 | import org.eclipse.swt.graphics.Image; | |
20 | import org.eclipse.swt.graphics.Point; | |
21 | import org.eclipse.swt.widgets.Shell; | |
22 | ||
23 | import org.eclipse.core.runtime.IProgressMonitor; | |
24 | import org.eclipse.core.runtime.OperationCanceledException; | |
25 | ||
26 | import org.eclipse.jface.dialogs.ErrorDialog; | |
27 | import org.eclipse.jface.dialogs.MessageDialog; | |
28 | ||
29 | import org.eclipse.jface.text.IDocument; | |
30 | import org.eclipse.jface.text.ITextViewer; | |
31 | import org.eclipse.jface.text.contentassist.ICompletionProposal; | |
32 | import org.eclipse.jface.text.contentassist.IContextInformation; | |
33 | import org.eclipse.jface.text.contentassist.IContextInformationExtension; | |
34 | ||
35 | import org.eclipse.ui.IWorkbenchCommandConstants; | |
36 | import org.eclipse.ui.PlatformUI; | |
37 | import org.eclipse.ui.keys.IBindingService; | |
38 | ||
39 | import org.eclipse.jdt.core.CompletionProposal; | |
40 | import org.eclipse.jdt.core.CompletionRequestor; | |
41 | import org.eclipse.jdt.core.ICompilationUnit; | |
42 | import org.eclipse.jdt.core.JavaModelException; | |
43 | ||
44 | import org.eclipse.jdt.internal.corext.util.Messages; | |
45 | ||
46 | import org.eclipse.jdt.ui.PreferenceConstants; | |
47 | import org.eclipse.jdt.ui.text.java.CompletionProposalCollector; | |
48 | import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; | |
49 | import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer; | |
50 | import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; | |
51 | ||
52 | import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner; | |
53 | import org.eclipse.jdt.internal.ui.text.Symbols; | |
54 | ||
55 | /** | |
56 | * Computes Java completion proposals and context infos. | |
57 | * | |
58 | * @since 3.2 | |
59 | */ | |
60 | public class JavaCompletionProposalComputer implements IJavaCompletionProposalComputer { | |
61 | ||
62 | private static final class ContextInformationWrapper implements IContextInformation, IContextInformationExtension { | |
63 | ||
64 | private final IContextInformation fContextInformation; | |
65 | private int fPosition; | |
66 | ||
67 | public ContextInformationWrapper(IContextInformation contextInformation) { | |
68 | fContextInformation= contextInformation; | |
69 | } | |
70 | ||
71 | /* | |
72 | * @see IContextInformation#getContextDisplayString() | |
73 | */ | |
74 | public String getContextDisplayString() { | |
75 | return fContextInformation.getContextDisplayString(); | |
76 | } | |
77 | ||
78 | /* | |
79 | * @see IContextInformation#getImage() | |
80 | */ | |
81 | public Image getImage() { | |
82 | return fContextInformation.getImage(); | |
83 | } | |
84 | ||
85 | /* | |
86 | * @see IContextInformation#getInformationDisplayString() | |
87 | */ | |
88 | public String getInformationDisplayString() { | |
89 | return fContextInformation.getInformationDisplayString(); | |
90 | } | |
91 | ||
92 | /* | |
93 | * @see IContextInformationExtension#getContextInformationPosition() | |
94 | */ | |
95 | public int getContextInformationPosition() { | |
96 | return fPosition; | |
97 | } | |
98 | ||
99 | public void setContextInformationPosition(int position) { | |
100 | fPosition= position; | |
101 | } | |
102 | ||
103 | /* | |
104 | * @see org.eclipse.jface.text.contentassist.IContextInformation#equals(java.lang.Object) | |
105 | */ | |
106 | @Override | |
107 | public boolean equals(Object object) { | |
108 | if (object instanceof ContextInformationWrapper) | |
109 | return fContextInformation.equals(((ContextInformationWrapper) object).fContextInformation); | |
110 | else | |
111 | return fContextInformation.equals(object); | |
112 | } | |
113 | ||
114 | /* | |
115 | * @see java.lang.Object#hashCode() | |
116 | * @since 3.5 | |
117 | */ | |
118 | @Override | |
119 | public int hashCode() { | |
120 | return fContextInformation.hashCode(); | |
121 | } | |
122 | } | |
123 | ||
124 | private static final long JAVA_CODE_ASSIST_TIMEOUT= Long.getLong("org.eclipse.jdt.ui.codeAssistTimeout", 5000).longValue(); // ms //$NON-NLS-1$ | |
125 | ||
126 | private String fErrorMessage; | |
127 | ||
128 | private final IProgressMonitor fTimeoutProgressMonitor; | |
129 | ||
130 | public JavaCompletionProposalComputer() { | |
131 | fTimeoutProgressMonitor= createTimeoutProgressMonitor(JAVA_CODE_ASSIST_TIMEOUT); | |
132 | } | |
133 | ||
134 | protected int guessContextInformationPosition(ContentAssistInvocationContext context) { | |
135 | return context.getInvocationOffset(); | |
136 | } | |
137 | ||
138 | protected final int guessMethodContextInformationPosition(ContentAssistInvocationContext context) { | |
139 | final int contextPosition= context.getInvocationOffset(); | |
140 | ||
141 | IDocument document= context.getDocument(); | |
142 | JavaHeuristicScanner scanner= new JavaHeuristicScanner(document); | |
143 | int bound= Math.max(-1, contextPosition - 200); | |
144 | ||
145 | // try the innermost scope of parentheses that looks like a method call | |
146 | int pos= contextPosition - 1; | |
147 | do { | |
148 | int paren= scanner.findOpeningPeer(pos, bound, '(', ')'); | |
149 | if (paren == JavaHeuristicScanner.NOT_FOUND) | |
150 | break; | |
151 | int token= scanner.previousToken(paren - 1, bound); | |
152 | // next token must be a method name (identifier) or the closing angle of a | |
153 | // constructor call of a parameterized type. | |
154 | if (token == Symbols.TokenIDENT || token == Symbols.TokenGREATERTHAN) | |
155 | return paren + 1; | |
156 | pos= paren - 1; | |
157 | } while (true); | |
158 | ||
159 | return contextPosition; | |
160 | } | |
161 | ||
162 | private List<IContextInformation> addContextInformations(JavaContentAssistInvocationContext context, int offset) { | |
163 | List<ICompletionProposal> proposals= internalComputeCompletionProposals(offset, context); | |
164 | List<IContextInformation> result= new ArrayList<IContextInformation>(proposals.size()); | |
165 | List<IContextInformation> anonymousResult= new ArrayList<IContextInformation>(proposals.size()); | |
166 | ||
167 | for (Iterator<ICompletionProposal> it= proposals.iterator(); it.hasNext();) { | |
168 | ICompletionProposal proposal= it.next(); | |
169 | IContextInformation contextInformation= proposal.getContextInformation(); | |
170 | if (contextInformation != null) { | |
171 | ContextInformationWrapper wrapper= new ContextInformationWrapper(contextInformation); | |
172 | wrapper.setContextInformationPosition(offset); | |
173 | if (proposal instanceof AnonymousTypeCompletionProposal) | |
174 | anonymousResult.add(wrapper); | |
175 | else | |
176 | result.add(wrapper); | |
177 | } | |
178 | } | |
179 | ||
180 | if (result.size() == 0) | |
181 | return anonymousResult; | |
182 | return result; | |
183 | ||
184 | } | |
185 | ||
186 | /* | |
187 | * @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#computeContextInformation(org.eclipse.jface.text.contentassist.TextContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor) | |
188 | */ | |
189 | public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) { | |
190 | if (context instanceof JavaContentAssistInvocationContext) { | |
191 | JavaContentAssistInvocationContext javaContext= (JavaContentAssistInvocationContext) context; | |
192 | ||
193 | int contextInformationPosition= guessContextInformationPosition(javaContext); | |
194 | List<IContextInformation> result= addContextInformations(javaContext, contextInformationPosition); | |
195 | return result; | |
196 | } | |
197 | return Collections.emptyList(); | |
198 | } | |
199 | ||
200 | /* | |
201 | * @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.jface.text.contentassist.TextContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor) | |
202 | */ | |
203 | public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) { | |
204 | if (context instanceof JavaContentAssistInvocationContext) { | |
205 | JavaContentAssistInvocationContext javaContext= (JavaContentAssistInvocationContext) context; | |
206 | return internalComputeCompletionProposals(context.getInvocationOffset(), javaContext); | |
207 | } | |
208 | return Collections.emptyList(); | |
209 | } | |
210 | ||
211 | private List<ICompletionProposal> internalComputeCompletionProposals(int offset, JavaContentAssistInvocationContext context) { | |
212 | ICompilationUnit unit= context.getCompilationUnit(); | |
213 | if (unit == null) | |
214 | return Collections.emptyList(); | |
215 | ||
216 | ITextViewer viewer= context.getViewer(); | |
217 | ||
218 | CompletionProposalCollector collector= createCollector(context); | |
219 | collector.setInvocationContext(context); | |
220 | ||
221 | // Allow completions for unresolved types - since 3.3 | |
222 | collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_REF, true); | |
223 | collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_IMPORT, true); | |
224 | collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.FIELD_IMPORT, true); | |
225 | ||
226 | collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.TYPE_REF, true); | |
227 | collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.TYPE_IMPORT, true); | |
228 | collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.METHOD_IMPORT, true); | |
229 | ||
230 | collector.setAllowsRequiredProposals(CompletionProposal.CONSTRUCTOR_INVOCATION, CompletionProposal.TYPE_REF, true); | |
231 | ||
232 | collector.setAllowsRequiredProposals(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION, CompletionProposal.TYPE_REF, true); | |
233 | collector.setAllowsRequiredProposals(CompletionProposal.ANONYMOUS_CLASS_DECLARATION, CompletionProposal.TYPE_REF, true); | |
234 | ||
235 | collector.setAllowsRequiredProposals(CompletionProposal.TYPE_REF, CompletionProposal.TYPE_REF, true); | |
236 | ||
237 | // Set the favorite list to propose static members - since 3.3 | |
238 | collector.setFavoriteReferences(getFavoriteStaticMembers()); | |
239 | ||
240 | try { | |
241 | Point selection= viewer.getSelectedRange(); | |
242 | if (selection.y > 0) | |
243 | collector.setReplacementLength(selection.y); | |
244 | unit.codeComplete(offset, collector, fTimeoutProgressMonitor); | |
245 | } catch (OperationCanceledException x) { | |
246 | IBindingService bindingSvc= (IBindingService)PlatformUI.getWorkbench().getAdapter(IBindingService.class); | |
247 | String keyBinding= bindingSvc.getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST); | |
248 | fErrorMessage= Messages.format(JavaTextMessages.CompletionProcessor_error_javaCompletion_took_too_long_message, keyBinding); | |
249 | } catch (JavaModelException x) { | |
250 | Shell shell= viewer.getTextWidget().getShell(); | |
251 | if (x.isDoesNotExist() && !unit.getJavaProject().isOnClasspath(unit)) | |
252 | MessageDialog.openInformation(shell, JavaTextMessages.CompletionProcessor_error_notOnBuildPath_title, JavaTextMessages.CompletionProcessor_error_notOnBuildPath_message); | |
253 | else | |
254 | ErrorDialog.openError(shell, JavaTextMessages.CompletionProcessor_error_accessing_title, JavaTextMessages.CompletionProcessor_error_accessing_message, x.getStatus()); | |
255 | } | |
256 | ||
257 | ICompletionProposal[] javaProposals= collector.getJavaCompletionProposals(); | |
258 | int contextInformationOffset= guessMethodContextInformationPosition(context); | |
259 | if (contextInformationOffset != offset) { | |
260 | for (int i= 0; i < javaProposals.length; i++) { | |
261 | if (javaProposals[i] instanceof JavaMethodCompletionProposal) { | |
262 | JavaMethodCompletionProposal jmcp= (JavaMethodCompletionProposal) javaProposals[i]; | |
263 | jmcp.setContextInformationPosition(contextInformationOffset); | |
264 | } | |
265 | } | |
266 | } | |
267 | ||
268 | List<ICompletionProposal> proposals= new ArrayList<ICompletionProposal>(Arrays.asList(javaProposals)); | |
269 | if (proposals.size() == 0) { | |
270 | String error= collector.getErrorMessage(); | |
271 | if (error.length() > 0) | |
272 | fErrorMessage= error; | |
273 | } | |
274 | return proposals; | |
275 | } | |
276 | ||
277 | /** | |
278 | * Returns a new progress monitor that get cancelled after the given timeout. | |
279 | * | |
280 | * @param timeout the timeout in ms | |
281 | * @return the progress monitor | |
282 | * @since 3.5 | |
283 | */ | |
284 | private IProgressMonitor createTimeoutProgressMonitor(final long timeout) { | |
285 | return new IProgressMonitor() { | |
286 | ||
287 | private long fEndTime; | |
288 | ||
289 | public void beginTask(String name, int totalWork) { | |
290 | fEndTime= System.currentTimeMillis() + timeout; | |
291 | } | |
292 | public boolean isCanceled() { | |
293 | return fEndTime <= System.currentTimeMillis(); | |
294 | } | |
295 | public void done() { | |
296 | } | |
297 | public void internalWorked(double work) { | |
298 | } | |
299 | public void setCanceled(boolean value) { | |
300 | } | |
301 | public void setTaskName(String name) { | |
302 | } | |
303 | public void subTask(String name) { | |
304 | } | |
305 | public void worked(int work) { | |
306 | } | |
307 | }; | |
308 | } | |
309 | ||
310 | /** | |
311 | * Returns the array with favorite static members. | |
312 | * | |
313 | * @return the <code>String</code> array with with favorite static members | |
314 | * @see CompletionRequestor#setFavoriteReferences(String[]) | |
315 | * @since 3.3 | |
316 | */ | |
317 | private String[] getFavoriteStaticMembers() { | |
318 | String serializedFavorites= PreferenceConstants.getPreferenceStore().getString(PreferenceConstants.CODEASSIST_FAVORITE_STATIC_MEMBERS); | |
319 | if (serializedFavorites != null && serializedFavorites.length() > 0) | |
320 | return serializedFavorites.split(";"); //$NON-NLS-1$ | |
321 | return new String[0]; | |
322 | } | |
323 | ||
324 | /** | |
325 | * Creates the collector used to get proposals from core. | |
326 | * | |
327 | * @param context the context | |
328 | * @return the collector | |
329 | */ | |
330 | protected CompletionProposalCollector createCollector(JavaContentAssistInvocationContext context) { | |
331 | if (PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES)) | |
332 | return new FillArgumentNamesCompletionProposalCollector(context); | |
333 | else | |
334 | return new CompletionProposalCollector(context.getCompilationUnit(), true); | |
335 | } | |
336 | ||
337 | /* | |
338 | * @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#getErrorMessage() | |
339 | */ | |
340 | public String getErrorMessage() { | |
341 | return fErrorMessage; | |
342 | } | |
343 | ||
344 | /* | |
345 | * @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#sessionStarted() | |
346 | */ | |
347 | public void sessionStarted() { | |
348 | } | |
349 | ||
350 | /* | |
351 | * @see org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer#sessionEnded() | |
352 | */ | |
353 | public void sessionEnded() { | |
354 | fErrorMessage= null; | |
355 | } | |
356 | } |