1 | /******************************************************************************* |
2 | * Copyright (c) 2009 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.pde.api.tools.internal.provisional.search; |
12 | |
13 | import java.util.ArrayList; |
14 | import java.util.Collections; |
15 | import java.util.Iterator; |
16 | import java.util.List; |
17 | |
18 | import org.eclipse.core.runtime.CoreException; |
19 | import org.eclipse.core.runtime.IProgressMonitor; |
20 | import org.eclipse.core.runtime.IStatus; |
21 | import org.eclipse.core.runtime.MultiStatus; |
22 | import org.eclipse.core.runtime.Status; |
23 | import org.eclipse.core.runtime.SubMonitor; |
24 | import org.eclipse.pde.api.tools.internal.builder.Reference; |
25 | import org.eclipse.pde.api.tools.internal.builder.ReferenceResolver; |
26 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
27 | import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; |
28 | import org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor; |
29 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; |
30 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
31 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement; |
32 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember; |
33 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiScope; |
34 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; |
35 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer; |
36 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; |
37 | import org.eclipse.pde.api.tools.internal.search.SearchMessages; |
38 | import org.eclipse.pde.api.tools.internal.util.Util; |
39 | |
40 | import com.ibm.icu.text.MessageFormat; |
41 | |
42 | /** |
43 | * Engine used to search for API use |
44 | * |
45 | * @since 1.0.0 |
46 | */ |
47 | public final class ApiSearchEngine { |
48 | |
49 | /** |
50 | * Default empty array for no search matches |
51 | */ |
52 | public static final IReference[] NO_REFERENCES = new IReference[0]; |
53 | |
54 | /** |
55 | * Constant used for controlling tracing in the search engine |
56 | */ |
57 | private static boolean DEBUG = Util.DEBUG; |
58 | |
59 | /** |
60 | * Visitor used to extract references from the component is is passed to |
61 | */ |
62 | class ReferenceExtractor extends ApiTypeContainerVisitor { |
63 | static final int COLLECTOR_MAX = 2500; |
64 | private List collector = null; |
65 | private IApiSearchRequestor requestor = null; |
66 | private IApiSearchReporter reporter = null; |
67 | IApiElement element = null; |
68 | private SubMonitor monitor = null; |
69 | |
70 | /** |
71 | * Constructor |
72 | */ |
73 | public ReferenceExtractor(IApiSearchRequestor requestor, IApiSearchReporter reporter, IApiElement element, IProgressMonitor monitor) { |
74 | collector = new ArrayList(); |
75 | this.requestor = requestor; |
76 | this.reporter = reporter; |
77 | this.element = element; |
78 | this.monitor = (SubMonitor) monitor; |
79 | } |
80 | |
81 | /* (non-Javadoc) |
82 | * @see org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor#visit(org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer) |
83 | */ |
84 | public boolean visit(IApiTypeContainer container) { |
85 | return requestor.acceptContainer(container); |
86 | } |
87 | |
88 | /* (non-Javadoc) |
89 | * @see org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor#visit(java.lang.String, org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot) |
90 | */ |
91 | public void visit(String packageName, IApiTypeRoot typeroot) { |
92 | if(monitor.isCanceled()) { |
93 | return; |
94 | } |
95 | try { |
96 | IApiType type = typeroot.getStructure(); |
97 | if(type == null || !requestor.acceptMember(type)) { |
98 | return; |
99 | } |
100 | collector.addAll(acceptReferences(requestor, |
101 | type, |
102 | getResolvedReferences(requestor, type, monitor.newChild(1)), |
103 | monitor.newChild(1))); |
104 | } |
105 | catch(CoreException ce) { |
106 | ApiPlugin.log(ce); |
107 | } |
108 | } |
109 | |
110 | /* (non-Javadoc) |
111 | * @see org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor#end(java.lang.String, org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot) |
112 | */ |
113 | public void end(String packageName, IApiTypeRoot typeroot) { |
114 | if(this.collector.size() >= COLLECTOR_MAX) { |
115 | reportResults(); |
116 | } |
117 | } |
118 | |
119 | /** |
120 | * @see org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor#visit(org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent) |
121 | */ |
122 | public boolean visit(IApiComponent component) { |
123 | return requestor.acceptComponent(component); |
124 | } |
125 | |
126 | /* (non-Javadoc) |
127 | * @see org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor#endVisitPackage(java.lang.String) |
128 | */ |
129 | public void endVisitPackage(String packageName) { |
130 | reportResults(); |
131 | } |
132 | |
133 | private void reportResults() { |
134 | reporter.reportResults(this.element, (IReference[]) collector.toArray(new IReference[collector.size()])); |
135 | collector.clear(); |
136 | } |
137 | } |
138 | |
139 | /** |
140 | * Method used for initializing tracing |
141 | */ |
142 | public static void setDebug(boolean debugValue) { |
143 | DEBUG = debugValue || Util.DEBUG; |
144 | } |
145 | |
146 | /** |
147 | * Simple string used for reporting what is being searched |
148 | */ |
149 | private String fRequestorContext = null; |
150 | |
151 | /** |
152 | * Returns the set of resolved references for the given {@link IApiType} |
153 | * @param requestor |
154 | * @param type |
155 | * @param monitor |
156 | * @return The listing of resolved references from the given {@link IApiType} |
157 | * @throws CoreException |
158 | */ |
159 | List getResolvedReferences(IApiSearchRequestor requestor, IApiType type, IProgressMonitor monitor) throws CoreException { |
160 | String name = type.getSimpleName(); |
161 | SubMonitor localmonitor = SubMonitor.convert(monitor, |
162 | MessageFormat.format(SearchMessages.ApiSearchEngine_extracting_refs_from, new String[] {(name == null ? SearchMessages.ApiSearchEngine_anonymous_type : name)}), 2); |
163 | try { |
164 | List refs = type.extractReferences(requestor.getReferenceKinds(), localmonitor.newChild(1)); |
165 | ReferenceResolver.resolveReferences(refs, localmonitor.newChild(1)); |
166 | return refs; |
167 | } |
168 | finally { |
169 | localmonitor.done(); |
170 | } |
171 | } |
172 | |
173 | /** |
174 | * Runs the given list of references through the search requestor to determine if they should be kept or not |
175 | * @param requestor |
176 | * @param type |
177 | * @param references |
178 | * @param monitor |
179 | * @return |
180 | * @throws CoreException |
181 | */ |
182 | List acceptReferences(IApiSearchRequestor requestor, IApiType type, List references, IProgressMonitor monitor) throws CoreException { |
183 | ArrayList refs = new ArrayList(); |
184 | Reference ref = null; |
185 | SubMonitor localmonitor = SubMonitor.convert(monitor, references.size()); |
186 | IApiMember member = null; |
187 | try { |
188 | for (Iterator iter = references.iterator(); iter.hasNext();) { |
189 | if(localmonitor.isCanceled()) { |
190 | return Collections.EMPTY_LIST; |
191 | } |
192 | ref = (Reference) iter.next(); |
193 | member = ref.getResolvedReference(); |
194 | if(member == null) { |
195 | continue; |
196 | } |
197 | localmonitor.setTaskName(MessageFormat.format(SearchMessages.ApiSearchEngine_searching_for_use_from, new String[] {fRequestorContext, type.getName()})); |
198 | if(requestor.acceptReference(ref)) { |
199 | refs.add(ref); |
200 | } |
201 | localmonitor.worked(1); |
202 | } |
203 | } |
204 | finally { |
205 | localmonitor.done(); |
206 | } |
207 | return refs; |
208 | } |
209 | |
210 | /** |
211 | * Searches for all accepted {@link IReference}s from the given {@link IApiElement} |
212 | * @param requestor |
213 | * @param element |
214 | * @param monitor |
215 | * @return the collection of accepted {@link IReference}s or an empty list, never <code>null</code> |
216 | * @throws CoreException |
217 | */ |
218 | private void searchReferences(IApiSearchRequestor requestor, IApiElement element, IApiSearchReporter reporter, IProgressMonitor monitor) throws CoreException { |
219 | List refs = null; |
220 | SubMonitor localmonitor = SubMonitor.convert(monitor, 3); |
221 | try { |
222 | switch(element.getType()) { |
223 | case IApiElement.TYPE: { |
224 | if(localmonitor.isCanceled()) { |
225 | reporter.reportResults(element, NO_REFERENCES); |
226 | } |
227 | IApiType type = (IApiType) element; |
228 | refs = acceptReferences(requestor, |
229 | type, |
230 | getResolvedReferences(requestor, type, localmonitor.newChild(1)), |
231 | localmonitor.newChild(1)); |
232 | reporter.reportResults(element, (IReference[]) refs.toArray(new IReference[refs.size()])); |
233 | break; |
234 | } |
235 | case IApiElement.COMPONENT: { |
236 | if(localmonitor.isCanceled()) { |
237 | reporter.reportResults(element, NO_REFERENCES); |
238 | } |
239 | ReferenceExtractor visitor = new ReferenceExtractor(requestor, reporter, element, localmonitor.newChild(1)); |
240 | IApiComponent comp = (IApiComponent) element; |
241 | comp.accept(visitor); |
242 | comp.close(); |
243 | Util.updateMonitor(localmonitor, 1); |
244 | break; |
245 | } |
246 | case IApiElement.FIELD: |
247 | case IApiElement.METHOD: { |
248 | if(localmonitor.isCanceled()) { |
249 | reporter.reportResults(element, NO_REFERENCES); |
250 | } |
251 | IApiMember member = (IApiMember) element; |
252 | IApiType type = member.getEnclosingType(); |
253 | if(type != null) { |
254 | refs = acceptReferences(requestor, |
255 | type, |
256 | getResolvedReferences(requestor, type, localmonitor.newChild(1)), |
257 | localmonitor.newChild(1)); |
258 | } |
259 | if (refs != null) { |
260 | reporter.reportResults(element, (IReference[]) refs.toArray(new IReference[refs.size()])); |
261 | } |
262 | break; |
263 | } |
264 | } |
265 | Util.updateMonitor(localmonitor, 1); |
266 | } |
267 | finally { |
268 | localmonitor.done(); |
269 | } |
270 | } |
271 | |
272 | /** |
273 | * Searches for all of the use of API or internal code from the given |
274 | * {@link IApiComponent} within the given {@link IApiBaseline} |
275 | * |
276 | * @param baseline the baseline to search within |
277 | * @param requestor the requestor to use for the search |
278 | * @param reporter the reporter to use when reporting any search results to the user |
279 | * @param monitor the monitor to report progress to |
280 | * @throws CoreException if the search fails |
281 | */ |
282 | public void search(IApiBaseline baseline, IApiSearchRequestor requestor, IApiSearchReporter reporter, IProgressMonitor monitor) throws CoreException { |
283 | if(baseline == null || reporter == null || requestor == null) { |
284 | return; |
285 | } |
286 | IApiScope scope = requestor.getScope(); |
287 | if(scope == null) { |
288 | return; |
289 | } |
290 | fRequestorContext = SearchMessages.ApiSearchEngine_api_internal; |
291 | if(requestor.includesAPI() && !requestor.includesInternal()) { |
292 | fRequestorContext = SearchMessages.ApiSearchEngine_api; |
293 | } |
294 | if(requestor.includesInternal() && !requestor.includesAPI()) { |
295 | fRequestorContext = SearchMessages.ApiSearchEngine_internal; |
296 | } |
297 | IApiElement[] scopeelements = scope.getApiElements(); |
298 | SubMonitor localmonitor = SubMonitor.convert(monitor, |
299 | MessageFormat.format(SearchMessages.ApiSearchEngine_searching_projects, new String[] {fRequestorContext}), scopeelements.length*2+1); |
300 | try { |
301 | long start = System.currentTimeMillis(); |
302 | long loopstart = 0; |
303 | String taskname = null; |
304 | MultiStatus mstatus = null; |
305 | for (int i = 0; i < scopeelements.length; i++) { |
306 | try { |
307 | taskname = MessageFormat.format(SearchMessages.ApiSearchEngine_searching_project, new String[] {scopeelements[i].getApiComponent().getId(), fRequestorContext}); |
308 | localmonitor.setTaskName(taskname); |
309 | if(DEBUG) { |
310 | loopstart = System.currentTimeMillis(); |
311 | System.out.println("Searching "+scopeelements[i].getApiComponent().getId()+"..."); //$NON-NLS-1$ //$NON-NLS-2$ |
312 | } |
313 | searchReferences(requestor, scopeelements[i], reporter, localmonitor.newChild(1)); |
314 | localmonitor.setTaskName(taskname); |
315 | if(localmonitor.isCanceled()) { |
316 | reporter.reportResults(scopeelements[i], NO_REFERENCES); |
317 | return; |
318 | } |
319 | localmonitor.worked(1); |
320 | if(DEBUG) { |
321 | System.out.println(Math.round((((float)(i+1))/scopeelements.length)*100)+"% done in "+(System.currentTimeMillis()-loopstart)+" ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
322 | } |
323 | } |
324 | catch(CoreException ce) { |
325 | if(mstatus == null) { |
326 | mstatus = new MultiStatus(ApiPlugin.PLUGIN_ID, IStatus.ERROR, null, null); |
327 | } |
328 | mstatus.add(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ce.getMessage(), ce)); |
329 | } |
330 | } |
331 | if(DEBUG) { |
332 | System.out.println("Total Search Time: "+((System.currentTimeMillis()-start)/1000)+" seconds"); //$NON-NLS-1$//$NON-NLS-2$ |
333 | } |
334 | if(mstatus != null) { |
335 | throw new CoreException(mstatus); |
336 | } |
337 | } |
338 | finally { |
339 | localmonitor.done(); |
340 | } |
341 | } |
342 | } |