1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.pde.api.tools.ui.internal.use; 15 16 import java.io.File; 17 import java.text.DateFormat; 18 import java.text.MessageFormat; 19 import java.util.ArrayList; 20 import java.util.Calendar; 21 import java.util.HashSet; 22 import java.util.List; 23 import java.util.Set; 24 import java.util.TreeSet; 25 import java.util.regex.Pattern; 26 27 import org.eclipse.core.runtime.CoreException; 28 import org.eclipse.core.runtime.IPath; 29 import org.eclipse.core.runtime.IProgressMonitor; 30 import org.eclipse.core.runtime.IStatus; 31 import org.eclipse.core.runtime.OperationCanceledException; 32 import org.eclipse.core.runtime.Path; 33 import org.eclipse.core.runtime.Status; 34 import org.eclipse.core.runtime.SubMonitor; 35 import org.eclipse.core.runtime.URIUtil; 36 import org.eclipse.core.runtime.jobs.Job; 37 import org.eclipse.debug.core.ILaunchConfiguration; 38 import org.eclipse.osgi.service.resolver.ResolverError; 39 import org.eclipse.osgi.util.NLS; 40 import org.eclipse.pde.api.tools.internal.ApiBaselineManager; 41 import org.eclipse.pde.api.tools.internal.model.ApiModelFactory; 42 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 43 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; 44 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; 45 import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement; 46 import org.eclipse.pde.api.tools.internal.provisional.search.ApiSearchEngine; 47 import org.eclipse.pde.api.tools.internal.provisional.search.IApiSearchReporter; 48 import org.eclipse.pde.api.tools.internal.provisional.search.IApiSearchRequestor; 49 import org.eclipse.pde.api.tools.internal.search.ApiDescriptionModifier; 50 import org.eclipse.pde.api.tools.internal.search.ConsumerReportConvertor; 51 import org.eclipse.pde.api.tools.internal.search.SkippedComponent; 52 import org.eclipse.pde.api.tools.internal.search.UseMetadata; 53 import org.eclipse.pde.api.tools.internal.search.UseReportConverter; 54 import org.eclipse.pde.api.tools.internal.search.UseSearchRequestor; 55 import org.eclipse.pde.api.tools.internal.search.XmlSearchReporter; 56 import org.eclipse.pde.api.tools.internal.util.Util; 57 import org.eclipse.pde.api.tools.ui.internal.ApiUIPlugin; 58 import org.eclipse.pde.core.target.ITargetDefinition; 59 import org.eclipse.pde.core.target.ITargetHandle; 60 import org.eclipse.pde.core.target.ITargetPlatformService; 61 import org.eclipse.pde.core.target.TargetBundle; 62 import org.eclipse.ui.IEditorDescriptor; 63 import org.eclipse.ui.IWorkbenchWindow; 64 import org.eclipse.ui.PartInitException; 65 import org.eclipse.ui.PlatformUI; 66 import org.eclipse.ui.ide.IDE; 67 import org.eclipse.ui.progress.UIJob; 68 69 /** 70 * Job that performs a API use scan. 71 */ 72 public class ApiUseScanJob extends Job { 73 74 /** 75 * Associated launch configuration 76 */ 77 private ILaunchConfiguration configuration = null; 78 79 /** 80 * List of components that were not searched 81 */ 82 Set<SkippedComponent> notsearched = null; 83 84 /** 85 * @param name 86 */ ApiUseScanJob(ILaunchConfiguration configuration)87 public ApiUseScanJob(ILaunchConfiguration configuration) { 88 super(MessageFormat.format(Messages.ApiUseScanJob_api_use_report, configuration.getName())); 89 this.configuration = configuration; 90 } 91 92 @Override run(IProgressMonitor monitor)93 protected IStatus run(IProgressMonitor monitor) { 94 // Build API baseline 95 SubMonitor localmonitor = SubMonitor.convert(monitor); 96 try { 97 IPath rootpath = null; 98 String xmlPath = this.configuration.getAttribute(ApiUseLaunchDelegate.REPORT_PATH, (String) null); 99 if (xmlPath == null) { 100 abort(Messages.ApiUseScanJob_missing_xml_loc); 101 } 102 rootpath = new Path(xmlPath); 103 int kind = this.configuration.getAttribute(ApiUseLaunchDelegate.TARGET_KIND, 0); 104 if (kind != ApiUseLaunchDelegate.KIND_HTML_ONLY) { 105 localmonitor.setTaskName(Messages.ApiUseScanJob_preparing_for_scan); 106 localmonitor.setWorkRemaining((isSpecified(ApiUseLaunchDelegate.CREATE_HTML) ? 14 : 13)); 107 // create baseline 108 IApiBaseline baseline = createApiBaseline(kind, localmonitor.split(1)); 109 Set<String> ids = new HashSet<>(); 110 TreeSet<IApiComponent> scope = new TreeSet<>(Util.componentsorter); 111 getContext(baseline, ids, scope, localmonitor.split(2)); 112 int kinds = 0; 113 if (isSpecified(ApiUseLaunchDelegate.MOD_API_REFERENCES)) { 114 kinds |= IApiSearchRequestor.INCLUDE_API; 115 } 116 if (isSpecified(ApiUseLaunchDelegate.MOD_INTERNAL_REFERENCES)) { 117 kinds |= IApiSearchRequestor.INCLUDE_INTERNAL; 118 } 119 if (isSpecified(ApiUseLaunchDelegate.MOD_ILLEGAL_USE)) { 120 kinds |= IApiSearchRequestor.INCLUDE_ILLEGAL_USE; 121 } 122 UseSearchRequestor requestor = new UseSearchRequestor(ids, scope.toArray(new IApiElement[scope.size()]), kinds); 123 List<String> jars = this.configuration.getAttribute(ApiUseLaunchDelegate.JAR_PATTERNS_LIST, (List<String>) null); 124 String[] sjars = getStrings(jars); 125 requestor.setJarPatterns(sjars); 126 List<String> api = this.configuration.getAttribute(ApiUseLaunchDelegate.API_PATTERNS_LIST, (List<String>) null); 127 String[] sapi = getStrings(api); 128 String filterRoot = this.configuration.getAttribute(ApiUseLaunchDelegate.FILTER_ROOT, (String) null); 129 requestor.setFilterRoot(filterRoot); 130 List<String> internal = this.configuration.getAttribute(ApiUseLaunchDelegate.INTERNAL_PATTERNS_LIST, (List<String>) null); 131 String[] sinternal = getStrings(internal); 132 if (sapi != null || sinternal != null) { 133 // modify API descriptions 134 IApiComponent[] components = baseline.getApiComponents(); 135 ApiDescriptionModifier visitor = new ApiDescriptionModifier(sinternal, sapi); 136 for (IApiComponent component : components) { 137 if (!component.isSystemComponent() && !component.isSourceComponent()) { 138 visitor.setApiDescription(component.getApiDescription()); 139 component.getApiDescription().accept(visitor, null); 140 } 141 } 142 } 143 xmlPath = rootpath.append("xml").toOSString(); //$NON-NLS-1$ 144 if (isSpecified(ApiUseLaunchDelegate.CLEAN_XML)) { 145 localmonitor.setTaskName(Messages.ApiUseScanJob_cleaning_xml_loc); 146 scrubReportLocation(new File(xmlPath), localmonitor.split(1)); 147 } 148 UseMetadata data = new UseMetadata(kinds, this.configuration.getAttribute(ApiUseLaunchDelegate.TARGET_SCOPE, (String) null), this.configuration.getAttribute(ApiUseLaunchDelegate.SEARCH_SCOPE, (String) null), baseline.getLocation(), xmlPath, sapi, sinternal, sjars, this.configuration.getAttribute(ApiUseLaunchDelegate.FILTER_ROOT, (String) null), DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime()), this.configuration.getAttribute(ApiUseLaunchDelegate.DESCRIPTION, (String) null)); 149 IApiSearchReporter reporter = new XmlSearchReporter(xmlPath, false); 150 try { 151 ApiSearchEngine engine = new ApiSearchEngine(); 152 engine.search(baseline, requestor, reporter, localmonitor.split(6)); 153 } finally { 154 reporter.reportNotSearched(ApiUseScanJob.this.notsearched.toArray(new IApiElement[ApiUseScanJob.this.notsearched.size()])); 155 reporter.reportMetadata(data); 156 reporter.reportCounts(); 157 // Dispose the baseline if it's not managed (it's temporary) 158 ApiBaselineManager apiManager = ApiBaselineManager.getManager(); 159 IApiBaseline[] baselines = apiManager.getApiBaselines(); 160 boolean dispose = true; 161 for (IApiBaseline baseline2 : baselines) { 162 if (baseline.equals(baseline2)) { 163 dispose = false; 164 break; 165 } 166 } 167 if (dispose) { 168 baseline.dispose(); 169 } 170 } 171 } else { 172 localmonitor.setWorkRemaining(10); 173 } 174 if (isSpecified(ApiUseLaunchDelegate.CREATE_HTML)) { 175 localmonitor.setTaskName(Messages.ApiUseScanJob_generating_html_reports); 176 String htmlPath = rootpath.append("html").toOSString(); //$NON-NLS-1$ 177 178 int reportType = ApiUseLaunchDelegate.REPORT_KIND_PRODUCER; 179 if (this.configuration.getAttribute(ApiUseLaunchDelegate.REPORT_TYPE, ApiUseLaunchDelegate.REPORT_KIND_PRODUCER) == ApiUseLaunchDelegate.REPORT_KIND_CONSUMER) { 180 reportType = ApiUseLaunchDelegate.REPORT_KIND_CONSUMER; 181 } 182 183 performReportCreation(reportType, isSpecified(ApiUseLaunchDelegate.CLEAN_HTML), htmlPath, xmlPath, isSpecified(ApiUseLaunchDelegate.DISPLAY_REPORT), getStrings(this.configuration.getAttribute(ApiUseLaunchDelegate.REPORT_TO_PATTERNS_LIST, (List<String>) null)), getStrings(this.configuration.getAttribute(ApiUseLaunchDelegate.REPORT_PATTERNS_LIST, (List<String>) null)), localmonitor.split(10)); 184 } 185 186 } catch (CoreException e) { 187 return e.getStatus(); 188 } finally { 189 localmonitor.done(); 190 } 191 return Status.OK_STATUS; 192 } 193 getStrings(List<String> list)194 private String[] getStrings(List<String> list) { 195 if (list == null || list.isEmpty()) { 196 return null; 197 } 198 return list.toArray(new String[list.size()]); 199 } 200 201 /** 202 * Throws a new {@link CoreException} with the given message 203 * 204 * @param message 205 * @throws CoreException 206 */ abort(String message)207 void abort(String message) throws CoreException { 208 throw new CoreException(new Status(IStatus.ERROR, ApiUIPlugin.PLUGIN_ID, message)); 209 } 210 211 /** 212 * Creates a new {@link IApiBaseline} from the location set in the backing 213 * launch configuration 214 * 215 * @param kind 216 * @param monitor 217 * @return the new {@link IApiBaseline} 218 * @throws CoreException 219 */ createApiBaseline(int kind, IProgressMonitor monitor)220 private IApiBaseline createApiBaseline(int kind, IProgressMonitor monitor) throws CoreException { 221 ApiBaselineManager bmanager = ApiBaselineManager.getManager(); 222 switch (kind) { 223 case ApiUseLaunchDelegate.KIND_API_BASELINE: 224 String name = this.configuration.getAttribute(ApiUseLaunchDelegate.BASELINE_NAME, (String) null); 225 if (name == null) { 226 abort(Messages.ApiUseScanJob_baseline_name_missing); 227 } 228 IApiBaseline baseline = bmanager.getApiBaseline(name); 229 if (baseline == null) { 230 abort(MessageFormat.format(Messages.ApiUseScanJob_baseline_does_not_exist, name)); 231 } 232 return baseline; 233 case ApiUseLaunchDelegate.KIND_INSTALL_PATH: 234 String path = this.configuration.getAttribute(ApiUseLaunchDelegate.INSTALL_PATH, (String) null); 235 if (path == null) { 236 abort(Messages.ApiUseScanJob_unspecified_install_path); 237 } 238 File file = new File(path); 239 if (!file.exists() || !file.isDirectory()) { 240 abort(MessageFormat.format(Messages.ApiUseScanJob_intall_dir_does_no_exist, path)); 241 } 242 return createBaseline(path, monitor); 243 case ApiUseLaunchDelegate.KIND_TARGET_DEFINITION: 244 String memento = this.configuration.getAttribute(ApiUseLaunchDelegate.TARGET_HANDLE, (String) null); 245 if (memento == null) { 246 abort(Messages.ApiUseScanJob_target_unspecified); 247 } 248 ITargetPlatformService service = getTargetService(); 249 ITargetHandle handle = service.getTarget(memento); 250 ITargetDefinition definition = handle.getTargetDefinition(); 251 return createBaseline(definition, monitor); 252 default: 253 abort(Messages.ApiUseScanJob_target_api_unspecified); 254 } 255 return null; 256 } 257 258 /** 259 * Returns the target service or <code>null</code> 260 * 261 * @return service or <code>null</code> 262 */ getTargetService()263 private ITargetPlatformService getTargetService() { 264 return ApiPlugin.getDefault().acquireService(ITargetPlatformService.class); 265 } 266 267 /** 268 * Collects the context of reference ids and scope elements in one pass 269 * 270 * @param baseline the baseline to check components from 271 * @param ids the reference ids to consider 272 * @param scope the scope of elements to search 273 * @param monitor 274 * @throws CoreException 275 */ getContext(IApiBaseline baseline, Set<String> ids, Set<IApiComponent> scope, IProgressMonitor monitor)276 private void getContext(IApiBaseline baseline, Set<String> ids, Set<IApiComponent> scope, IProgressMonitor monitor) throws CoreException { 277 SubMonitor localmonitor = SubMonitor.convert(monitor, Messages.ApiUseScanJob_collecting_target_components, 10); 278 this.notsearched = new TreeSet<>(Util.componentsorter); 279 String regex = this.configuration.getAttribute(ApiUseLaunchDelegate.TARGET_SCOPE, (String) null); 280 Pattern pattern = getPattern(regex); 281 regex = this.configuration.getAttribute(ApiUseLaunchDelegate.SEARCH_SCOPE, (String) null); 282 Pattern pattern2 = getPattern(regex); 283 IApiComponent[] components = baseline.getApiComponents(); 284 localmonitor.setWorkRemaining(components.length); 285 for (IApiComponent component : components) { 286 localmonitor.subTask(NLS.bind(Messages.ApiUseScanJob_checking_component, component.getSymbolicName())); 287 localmonitor.split(1); 288 if (acceptComponent(component, pattern, true)) { 289 ids.add(component.getSymbolicName()); 290 } 291 if (acceptComponent(component, pattern2, false)) { 292 scope.add(component); 293 } else { 294 localmonitor.subTask(NLS.bind(Messages.ApiUseScanJob_skipping_component, component.getSymbolicName())); 295 this.notsearched.add(new SkippedComponent(component.getSymbolicName(), component.getVersion(), null)); 296 } 297 } 298 } 299 300 /** 301 * Returns a pattern for the given regular expression or <code>null</code> 302 * if none. 303 * 304 * @param regex expression, <code>null</code> or empty 305 * @return associated pattern or <code>null</code> 306 */ getPattern(String regex)307 private Pattern getPattern(String regex) { 308 if (regex == null) { 309 return null; 310 } 311 if (regex.trim().length() == 0) { 312 return null; 313 } 314 return Pattern.compile(regex); 315 } 316 317 /** 318 * Returns if we should add the given component to our search scope 319 * 320 * @param component 321 * @param pattern 322 * @param allowresolve 323 * @return 324 * @throws CoreException 325 */ acceptComponent(IApiComponent component, Pattern pattern, boolean allowresolve)326 boolean acceptComponent(IApiComponent component, Pattern pattern, boolean allowresolve) throws CoreException { 327 if (!allowresolve) { 328 ResolverError[] errors = component.getErrors(); 329 if (errors != null) { 330 this.notsearched.add(new SkippedComponent(component.getSymbolicName(), component.getVersion(), errors)); 331 return false; 332 } 333 } 334 if (component.isSystemComponent()) { 335 return false; 336 } 337 if (pattern != null) { 338 return pattern.matcher(component.getSymbolicName()).matches(); 339 } 340 return true; 341 } 342 343 /** 344 * Returns if the given search modifier is set in the backing 345 * {@link ILaunchConfiguration} 346 * 347 * @param modifier 348 * @return true if the modifier is set, false otherwise 349 * @throws CoreException 350 */ isSpecified(int modifier)351 private boolean isSpecified(int modifier) throws CoreException { 352 int modifiers = configuration.getAttribute(ApiUseLaunchDelegate.SEARCH_MODIFIERS, 0); 353 return (modifiers & modifier) > 0; 354 } 355 356 /** 357 * Performs the report creation 358 * 359 * @param reportType what report converter to use, either 360 * {@link ApiUseLaunchDelegate#REPORT_KIND_PRODUCER} or 361 * {@link ApiUseLaunchDelegate#REPORT_KIND_CONSUMER} 362 * @param cleanh 363 * @param hlocation 364 * @param rlocation 365 * @param openhtml 366 * @param monitor 367 * @throws OperationCanceledException 368 */ performReportCreation(int reportType, boolean cleanh, String hlocation, String rlocation, boolean openhtml, String[] topatterns, String[] frompatterns, IProgressMonitor monitor)369 void performReportCreation(int reportType, boolean cleanh, String hlocation, String rlocation, boolean openhtml, String[] topatterns, String[] frompatterns, IProgressMonitor monitor) { 370 SubMonitor localmonitor = SubMonitor.convert(monitor, Messages.ApiUseScanJob_creating_html_reports, 10); 371 if (cleanh) { 372 cleanReportLocation(hlocation, localmonitor.split(5)); 373 } 374 try { 375 UseReportConverter converter = null; 376 if (reportType == ApiUseLaunchDelegate.REPORT_KIND_CONSUMER) { 377 converter = new ConsumerReportConvertor(hlocation, rlocation, topatterns, frompatterns); 378 } else { 379 converter = new UseReportConverter(hlocation, rlocation, topatterns, frompatterns); 380 } 381 382 converter.convert(null, localmonitor.split(5)); 383 if (openhtml) { 384 final File index = converter.getReportIndex(); 385 if (index != null) { 386 UIJob ujob = new UIJob(Util.EMPTY_STRING) { 387 @Override 388 public IStatus runInUIThread(IProgressMonitor monitor) { 389 IEditorDescriptor edesc = null; 390 try { 391 edesc = IDE.getEditorDescriptor(index.getName()); 392 IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 393 IDE.openEditor(window.getActivePage(), index.toURI(), edesc.getId(), true); 394 } catch (PartInitException e) { 395 e.printStackTrace(); 396 } 397 return Status.OK_STATUS; 398 } 399 }; 400 ujob.setPriority(Job.INTERACTIVE); 401 ujob.setSystem(true); 402 ujob.schedule(); 403 } 404 } 405 } catch (OperationCanceledException oce) { 406 // re-throw 407 throw oce; 408 } catch (Exception e) { 409 ApiPlugin.log(e); 410 } 411 } 412 413 /** 414 * Cleans the report location specified by the parameter reportLocation 415 * 416 * @param monitor 417 */ cleanReportLocation(String location, IProgressMonitor monitor)418 void cleanReportLocation(String location, IProgressMonitor monitor) { 419 File file = new File(location); 420 SubMonitor localmonitor = SubMonitor.convert(monitor, Messages.ApiUseScanJob_deleting_old_reports, 1); 421 if (file.exists()) { 422 scrubReportLocation(file, localmonitor.split(1)); 423 localmonitor.subTask(NLS.bind(Messages.ApiUseScanJob_deleting_root_folder, file.getName())); 424 } 425 } 426 427 /** 428 * Cleans the location if it exists 429 * 430 * @param file 431 * @param monitor 432 */ scrubReportLocation(File file, IProgressMonitor monitor)433 void scrubReportLocation(File file, IProgressMonitor monitor) { 434 SubMonitor subMonitor = SubMonitor.convert(monitor); 435 if (file.exists() && file.isDirectory()) { 436 File[] files = file.listFiles(); 437 if (files != null) { 438 subMonitor.setWorkRemaining(files.length); 439 for (File file2 : files) { 440 SubMonitor iterationMonitor = subMonitor.split(1); 441 iterationMonitor.subTask(NLS.bind(Messages.ApiUseScanJob_deleteing_file, file2.getPath())); 442 if (file2.isDirectory()) { 443 scrubReportLocation(file2, iterationMonitor); 444 } else { 445 file2.delete(); 446 } 447 } 448 } 449 file.delete(); 450 } 451 } 452 453 /** 454 * Creates an API baseline from a target definition. 455 * 456 * @param definition 457 * @param monitor progress monitor 458 */ createBaseline(ITargetDefinition definition, IProgressMonitor monitor)459 private IApiBaseline createBaseline(ITargetDefinition definition, IProgressMonitor monitor) throws CoreException { 460 SubMonitor localmonitor = SubMonitor.convert(monitor, Messages.ApiUseScanJob_reading_target, 10); 461 definition.resolve(localmonitor.split(2)); 462 TargetBundle[] bundles = definition.getBundles(); 463 List<IApiComponent> components = new ArrayList<>(); 464 IApiBaseline profile = ApiModelFactory.newApiBaseline(definition.getName()); 465 localmonitor.setWorkRemaining(bundles.length); 466 for (int i = 0; i < bundles.length; i++) { 467 localmonitor.split(1); 468 if (bundles[i].getStatus().isOK() && !bundles[i].isSourceBundle()) { 469 IApiComponent component = ApiModelFactory.newApiComponent(profile, URIUtil.toFile(bundles[i].getBundleInfo().getLocation()).getAbsolutePath()); 470 if (component != null) { 471 components.add(component); 472 } 473 } 474 } 475 profile.addApiComponents(components.toArray(new IApiComponent[components.size()])); 476 return profile; 477 } 478 479 /** 480 * Creates a baseline at an install location 481 * 482 * @param path 483 * @param monitor 484 */ createBaseline(String installLocation, IProgressMonitor monitor)485 private IApiBaseline createBaseline(String installLocation, IProgressMonitor monitor) throws CoreException { 486 SubMonitor localmonitor = SubMonitor.convert(monitor, Messages.ApiUseScanJob_scanning, 10); 487 IApiBaseline baseline = ApiModelFactory.newApiBaseline(this.configuration.getName()); 488 IApiComponent[] components = ApiModelFactory.addComponents(baseline, installLocation, localmonitor); 489 if (components.length == 0) { 490 abort(MessageFormat.format(Messages.ApiUseScanJob_no_bundles, installLocation)); 491 } 492 return baseline; 493 } 494 495 } 496