| 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 | } |