| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2007, 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.builder; |
| 12 | |
| 13 | import java.util.ArrayList; |
| 14 | import java.util.Date; |
| 15 | import java.util.HashMap; |
| 16 | import java.util.HashSet; |
| 17 | import java.util.Map; |
| 18 | import java.util.Set; |
| 19 | import java.util.jar.JarFile; |
| 20 | |
| 21 | import org.eclipse.core.resources.IMarker; |
| 22 | import org.eclipse.core.resources.IProject; |
| 23 | import org.eclipse.core.resources.IResource; |
| 24 | import org.eclipse.core.resources.IResourceDelta; |
| 25 | import org.eclipse.core.resources.IWorkspaceRoot; |
| 26 | import org.eclipse.core.resources.IncrementalProjectBuilder; |
| 27 | import org.eclipse.core.resources.ResourcesPlugin; |
| 28 | import org.eclipse.core.runtime.CoreException; |
| 29 | import org.eclipse.core.runtime.IPath; |
| 30 | import org.eclipse.core.runtime.IProgressMonitor; |
| 31 | import org.eclipse.core.runtime.IStatus; |
| 32 | import org.eclipse.core.runtime.OperationCanceledException; |
| 33 | import org.eclipse.core.runtime.Path; |
| 34 | import org.eclipse.core.runtime.SubMonitor; |
| 35 | import org.eclipse.jdt.core.IClasspathAttribute; |
| 36 | import org.eclipse.jdt.core.IClasspathEntry; |
| 37 | import org.eclipse.jdt.core.IJavaProject; |
| 38 | import org.eclipse.jdt.core.IPackageFragmentRoot; |
| 39 | import org.eclipse.jdt.core.JavaCore; |
| 40 | import org.eclipse.jdt.core.JavaModelException; |
| 41 | import org.eclipse.jdt.internal.core.JavaModelManager; |
| 42 | import org.eclipse.jdt.internal.core.builder.State; |
| 43 | import org.eclipse.osgi.service.resolver.BundleDescription; |
| 44 | import org.eclipse.osgi.util.NLS; |
| 45 | import org.eclipse.pde.api.tools.internal.ApiDescriptionManager; |
| 46 | import org.eclipse.pde.api.tools.internal.IApiCoreConstants; |
| 47 | import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; |
| 48 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
| 49 | import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; |
| 50 | import org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer; |
| 51 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; |
| 52 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
| 53 | import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; |
| 54 | import org.eclipse.pde.api.tools.internal.util.Util; |
| 55 | import org.eclipse.pde.core.plugin.IPluginModelBase; |
| 56 | import org.eclipse.pde.core.plugin.PluginRegistry; |
| 57 | |
| 58 | import com.ibm.icu.text.MessageFormat; |
| 59 | |
| 60 | /** |
| 61 | * Builder for creating API tooling resource markers |
| 62 | * @since 1.0.0 |
| 63 | */ |
| 64 | public class ApiAnalysisBuilder extends IncrementalProjectBuilder { |
| 65 | /** |
| 66 | * Constant used for controlling tracing in the API tool builder |
| 67 | */ |
| 68 | static boolean DEBUG = Util.DEBUG; |
| 69 | |
| 70 | /** |
| 71 | * Project relative path to the .settings folder |
| 72 | * @since 1.0.1 |
| 73 | */ |
| 74 | static final IPath SETTINGS_PATH = new Path(".settings"); //$NON-NLS-1$ |
| 75 | |
| 76 | /** |
| 77 | * Project relative path to the manifest file. |
| 78 | */ |
| 79 | static final IPath MANIFEST_PATH = new Path(JarFile.MANIFEST_NAME); |
| 80 | |
| 81 | /** |
| 82 | * Project relative path to the .api_filters file |
| 83 | */ |
| 84 | static final IPath FILTER_PATH = SETTINGS_PATH.append(IApiCoreConstants.API_FILTERS_XML_NAME); |
| 85 | |
| 86 | /** |
| 87 | * Empty listing of projects to be returned by the builder if there is nothing to do |
| 88 | */ |
| 89 | static final IProject[] NO_PROJECTS = new IProject[0]; |
| 90 | |
| 91 | /** |
| 92 | * Constant representing the name of the 'source' attribute on API tooling markers. |
| 93 | * Value is <code>Api Tooling</code> |
| 94 | */ |
| 95 | static final String SOURCE = "Api Tooling"; //$NON-NLS-1$ |
| 96 | |
| 97 | /** |
| 98 | * Method used for initializing tracing in the API tool builder |
| 99 | */ |
| 100 | public static void setDebug(boolean debugValue) { |
| 101 | DEBUG = debugValue || Util.DEBUG; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * The current project for which this builder was defined |
| 106 | */ |
| 107 | private IProject currentproject = null; |
| 108 | |
| 109 | /** |
| 110 | * The API analyzer for this builder |
| 111 | */ |
| 112 | private IApiAnalyzer analyzer = null; |
| 113 | |
| 114 | /** |
| 115 | * Maps prerequisite projects to their output location(s) |
| 116 | */ |
| 117 | HashMap output_locs = new HashMap(); |
| 118 | |
| 119 | /** |
| 120 | * Maps pre-requisite projects to their source locations |
| 121 | */ |
| 122 | HashMap src_locs = new HashMap(); |
| 123 | |
| 124 | /** |
| 125 | * Current build state |
| 126 | */ |
| 127 | private BuildState buildstate = null; |
| 128 | |
| 129 | /** |
| 130 | * Cleans up markers associated with API tooling on the given resource. |
| 131 | * |
| 132 | * @param resource |
| 133 | */ |
| 134 | void cleanupMarkers(IResource resource) { |
| 135 | cleanUnusedFilterMarkers(resource); |
| 136 | cleanupUsageMarkers(resource); |
| 137 | cleanupCompatibilityMarkers(resource); |
| 138 | cleanupUnsupportedTagMarkers(resource); |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * Cleans up unsupported Javadoc tag markers on the specified resource |
| 143 | * @param resource |
| 144 | */ |
| 145 | void cleanupUnsupportedTagMarkers(IResource resource) { |
| 146 | try { |
| 147 | if(DEBUG) { |
| 148 | System.out.println("cleaning unsupported tag problems"); //$NON-NLS-1$ |
| 149 | } |
| 150 | resource.deleteMarkers(IApiMarkerConstants.UNSUPPORTED_TAG_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 151 | } catch (CoreException e) { |
| 152 | ApiPlugin.log(e.getStatus()); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Cleans up only API compatibility markers on the given {@link IResource} |
| 158 | * @param resource the given resource |
| 159 | */ |
| 160 | void cleanupCompatibilityMarkers(IResource resource) { |
| 161 | try { |
| 162 | if (resource != null && resource.isAccessible()) { |
| 163 | resource.deleteMarkers(IApiMarkerConstants.COMPATIBILITY_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 164 | resource.deleteMarkers(IApiMarkerConstants.SINCE_TAGS_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 165 | if (resource.getType() == IResource.PROJECT) { |
| 166 | // on full builds |
| 167 | resource.deleteMarkers(IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 168 | resource.deleteMarkers(IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); |
| 169 | resource.deleteMarkers(IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); |
| 170 | } |
| 171 | } |
| 172 | } catch(CoreException e) { |
| 173 | ApiPlugin.log(e.getStatus()); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * cleans up only API usage markers from the given {@link IResource} |
| 179 | * @param resource |
| 180 | */ |
| 181 | void cleanupUsageMarkers(IResource resource) { |
| 182 | try { |
| 183 | if (resource != null && resource.isAccessible()) { |
| 184 | resource.deleteMarkers(IApiMarkerConstants.API_USAGE_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 185 | } |
| 186 | } catch(CoreException e) { |
| 187 | ApiPlugin.log(e.getStatus()); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Cleans up the unused API filter problems from the given resource |
| 193 | * @param resource |
| 194 | */ |
| 195 | void cleanUnusedFilterMarkers(IResource resource) { |
| 196 | try { |
| 197 | if(resource != null && resource.isAccessible()) { |
| 198 | resource.deleteMarkers(IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 199 | } |
| 200 | } |
| 201 | catch(CoreException ce) { |
| 202 | ApiPlugin.log(ce.getStatus()); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /* (non-Javadoc) |
| 207 | * @see org.eclipse.core.resources.IncrementalProjectBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor) |
| 208 | */ |
| 209 | protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { |
| 210 | this.currentproject = getProject(); |
| 211 | if (!this.currentproject.isAccessible() || !this.currentproject.hasNature(ApiPlugin.NATURE_ID) || hasBeenBuilt(this.currentproject)) { |
| 212 | return NO_PROJECTS; |
| 213 | } |
| 214 | if (DEBUG) { |
| 215 | System.out.println("\nApiAnalysis builder - Starting build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$ |
| 216 | } |
| 217 | SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_builder, 8); |
| 218 | IApiBaseline wbaseline = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline(); |
| 219 | if (wbaseline == null) { |
| 220 | if (DEBUG) { |
| 221 | System.err.println("Could not retrieve a workspace profile"); //$NON-NLS-1$ |
| 222 | } |
| 223 | return NO_PROJECTS; |
| 224 | } |
| 225 | final IProject[] projects = getRequiredProjects(true); |
| 226 | IApiBaseline baseline = ApiPlugin.getDefault().getApiBaselineManager().getDefaultApiBaseline(); |
| 227 | try { |
| 228 | switch(kind) { |
| 229 | case FULL_BUILD : { |
| 230 | if (DEBUG) { |
| 231 | System.out.println("Performing full build as requested by user"); //$NON-NLS-1$ |
| 232 | } |
| 233 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 234 | break; |
| 235 | } |
| 236 | case AUTO_BUILD : |
| 237 | case INCREMENTAL_BUILD : { |
| 238 | this.buildstate = BuildState.getLastBuiltState(currentproject); |
| 239 | if (this.buildstate == null) { |
| 240 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 241 | break; |
| 242 | } |
| 243 | else if(worthDoingFullBuild(projects)) { |
| 244 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 245 | break; |
| 246 | } |
| 247 | else { |
| 248 | IResourceDelta[] deltas = getDeltas(projects); |
| 249 | if(deltas.length < 1) { |
| 250 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 251 | } |
| 252 | else { |
| 253 | IResourceDelta manifest = null; |
| 254 | IResourceDelta filters = null; |
| 255 | boolean filterbuild = false; |
| 256 | for (int i = 0; i < deltas.length; i++) { |
| 257 | manifest = deltas[i].findMember(MANIFEST_PATH); |
| 258 | if(manifest != null) { |
| 259 | break; |
| 260 | } |
| 261 | filters = deltas[i].findMember(FILTER_PATH); |
| 262 | if(filters != null){ |
| 263 | switch(filters.getKind()) { |
| 264 | case IResourceDelta.ADDED: |
| 265 | case IResourceDelta.REMOVED: { |
| 266 | filterbuild = true; |
| 267 | break; |
| 268 | } |
| 269 | case IResourceDelta.CHANGED: { |
| 270 | filterbuild = (filters.getFlags() & IResourceDelta.REPLACED) > 0; |
| 271 | break; |
| 272 | } |
| 273 | } |
| 274 | if(filterbuild) { |
| 275 | break; |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | if (manifest != null || filterbuild) { |
| 280 | if (DEBUG) { |
| 281 | System.out.println("Performing full build since MANIFEST.MF or .api_filters was modified"); //$NON-NLS-1$ |
| 282 | } |
| 283 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 284 | } |
| 285 | else { |
| 286 | State state = (State)JavaModelManager.getJavaModelManager().getLastBuiltState(this.currentproject, localMonitor.newChild(1)); |
| 287 | if(state == null) { |
| 288 | buildAll(baseline, wbaseline, localMonitor.newChild(1)); |
| 289 | break; |
| 290 | } |
| 291 | BuildState.setLastBuiltState(this.currentproject, null); |
| 292 | IncrementalApiBuilder builder = new IncrementalApiBuilder(this); |
| 293 | builder.build(baseline, wbaseline, deltas, state, this.buildstate, localMonitor.newChild(1)); |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | Util.updateMonitor(localMonitor, 0); |
| 300 | |
| 301 | } |
| 302 | catch (OperationCanceledException oce) { |
| 303 | //do nothing, just end and clean up |
| 304 | } |
| 305 | catch(CoreException e) { |
| 306 | IStatus status = e.getStatus(); |
| 307 | if (status == null || status.getCode() != ApiPlugin.REPORT_BASELINE_IS_DISPOSED) { |
| 308 | throw e; |
| 309 | } |
| 310 | ApiPlugin.log(e); |
| 311 | } finally { |
| 312 | Util.updateMonitor(localMonitor, 0); |
| 313 | if(this.analyzer != null) { |
| 314 | this.analyzer.dispose(); |
| 315 | this.analyzer = null; |
| 316 | } |
| 317 | if(projects.length < 1) { |
| 318 | //if this build cycle indicates that more projects need to be built do not close |
| 319 | //the baselines yet, they might be re-read by another build cycle |
| 320 | if(baseline != null) { |
| 321 | baseline.close(); |
| 322 | } |
| 323 | } |
| 324 | Util.updateMonitor(localMonitor, 0); |
| 325 | if (this.buildstate != null) { |
| 326 | for(int i = 0, max = projects.length; i < max; i++) { |
| 327 | IProject project = projects[i]; |
| 328 | if (Util.isApiProject(project)) { |
| 329 | this.buildstate.addApiToolingDependentProject(project.getName()); |
| 330 | } |
| 331 | } |
| 332 | BuildState.saveBuiltState(this.currentproject, this.buildstate); |
| 333 | this.buildstate = null; |
| 334 | Util.updateMonitor(monitor, 0); |
| 335 | } |
| 336 | if(localMonitor != null) { |
| 337 | localMonitor.done(); |
| 338 | } |
| 339 | } |
| 340 | if (DEBUG) { |
| 341 | System.out.println("Finished build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$ |
| 342 | } |
| 343 | return projects; |
| 344 | } |
| 345 | |
| 346 | /** |
| 347 | * if its worth doing a full build considering the given set if projects |
| 348 | * @param projects projects to check the build state for |
| 349 | * @return true if a full build should take place, false otherwise |
| 350 | */ |
| 351 | boolean worthDoingFullBuild(IProject[] projects) { |
| 352 | Set apiToolingDependentProjects = this.buildstate.getApiToolingDependentProjects(); |
| 353 | for (int i = 0, max = projects.length; i < max; i++) { |
| 354 | IProject currentProject = projects[i]; |
| 355 | if (Util.isApiProject(currentProject)) { |
| 356 | if (apiToolingDependentProjects.contains(currentProject.getName())) { |
| 357 | continue; |
| 358 | } |
| 359 | return true; |
| 360 | } else if (apiToolingDependentProjects.contains(currentProject.getName())) { |
| 361 | return true; |
| 362 | } |
| 363 | } |
| 364 | return false; |
| 365 | } |
| 366 | |
| 367 | /** |
| 368 | * Performs a full build for the project |
| 369 | * @param baseline the default baseline |
| 370 | * @param wbaseline the workspace baseline |
| 371 | * @param monitor |
| 372 | */ |
| 373 | void buildAll(IApiBaseline baseline, IApiBaseline wbaseline, IProgressMonitor monitor) throws CoreException { |
| 374 | SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_on_0, 4); |
| 375 | try { |
| 376 | BuildState.setLastBuiltState(this.currentproject, null); |
| 377 | this.buildstate = new BuildState(); |
| 378 | localMonitor.subTask(NLS.bind(BuilderMessages.ApiAnalysisBuilder_initializing_analyzer, currentproject.getName())); |
| 379 | cleanupMarkers(this.currentproject); |
| 380 | IPluginModelBase currentModel = getCurrentModel(); |
| 381 | if (currentModel != null) { |
| 382 | localMonitor.subTask(BuilderMessages.building_workspace_profile); |
| 383 | Util.updateMonitor(localMonitor, 1); |
| 384 | String id = currentModel.getBundleDescription().getSymbolicName(); |
| 385 | // Compatibility checks |
| 386 | IApiComponent apiComponent = wbaseline.getApiComponent(id); |
| 387 | if(apiComponent != null) { |
| 388 | getAnalyzer().analyzeComponent(this.buildstate, null, null, baseline, apiComponent, new BuildContext(), localMonitor.newChild(1)); |
| 389 | Util.updateMonitor(localMonitor, 1); |
| 390 | createMarkers(); |
| 391 | Util.updateMonitor(localMonitor, 1); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | finally { |
| 396 | if(localMonitor != null) { |
| 397 | localMonitor.done(); |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | /** |
| 403 | * Creates new markers are for the listing of problems added to this reporter. |
| 404 | * If no problems have been added to this reporter, or we are not running in the framework, |
| 405 | * no work is done. |
| 406 | */ |
| 407 | protected void createMarkers() { |
| 408 | try { |
| 409 | IResource manifest = Util.getManifestFile(this.currentproject); |
| 410 | if(manifest != null) { |
| 411 | manifest.deleteMarkers(IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| 412 | } |
| 413 | this.currentproject.deleteMarkers(IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| 414 | this.currentproject.deleteMarkers(IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| 415 | } catch (CoreException e) { |
| 416 | ApiPlugin.log(e); |
| 417 | } |
| 418 | IApiProblem[] problems = getAnalyzer().getProblems(); |
| 419 | String type = null; |
| 420 | for(int i = 0; i < problems.length; i++) { |
| 421 | int category = problems[i].getCategory(); |
| 422 | type = getProblemTypeFromCategory(category, problems[i].getKind()); |
| 423 | if(type == null) { |
| 424 | continue; |
| 425 | } |
| 426 | if(DEBUG) { |
| 427 | System.out.println("creating marker for: "+problems[i].toString()); //$NON-NLS-1$ |
| 428 | } |
| 429 | createMarkerForProblem(category, type, problems[i]); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * Returns the {@link IApiMarkerConstants} problem type given the |
| 435 | * problem category |
| 436 | * @param category the problem category - see {@link IApiProblem} for problem categories |
| 437 | * @param kind the kind of the problem - see {@link IApiProblem} for problem kinds |
| 438 | * @return the problem type or <code>null</code> |
| 439 | */ |
| 440 | String getProblemTypeFromCategory(int category, int kind) { |
| 441 | switch(category) { |
| 442 | case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION : { |
| 443 | return IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER; |
| 444 | } |
| 445 | case IApiProblem.CATEGORY_API_BASELINE: { |
| 446 | return IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER; |
| 447 | } |
| 448 | case IApiProblem.CATEGORY_COMPATIBILITY: { |
| 449 | return IApiMarkerConstants.COMPATIBILITY_PROBLEM_MARKER; |
| 450 | } |
| 451 | case IApiProblem.CATEGORY_SINCETAGS: { |
| 452 | return IApiMarkerConstants.SINCE_TAGS_PROBLEM_MARKER; |
| 453 | } |
| 454 | case IApiProblem.CATEGORY_USAGE: { |
| 455 | if(kind == IApiProblem.UNSUPPORTED_TAG_USE) { |
| 456 | return IApiMarkerConstants.UNSUPPORTED_TAG_PROBLEM_MARKER; |
| 457 | } |
| 458 | if(kind == IApiProblem.UNUSED_PROBLEM_FILTERS) { |
| 459 | return IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER; |
| 460 | } |
| 461 | return IApiMarkerConstants.API_USAGE_PROBLEM_MARKER; |
| 462 | } |
| 463 | case IApiProblem.CATEGORY_VERSION: { |
| 464 | return IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER; |
| 465 | } |
| 466 | } |
| 467 | return null; |
| 468 | } |
| 469 | |
| 470 | /** |
| 471 | * Creates an {@link IMarker} on the resource specified |
| 472 | * in the problem (via its path) with the given problem |
| 473 | * attributes |
| 474 | * @param category the category of the problem - see {@link IApiProblem} for categories |
| 475 | * @param type the marker type to create - see {@link IApiMarkerConstants} for types |
| 476 | * @param problem the problem to create a marker from |
| 477 | */ |
| 478 | void createMarkerForProblem(int category, String type, IApiProblem problem) { |
| 479 | IResource resource = resolveResource(problem); |
| 480 | if(resource == null) { |
| 481 | return; |
| 482 | } |
| 483 | try { |
| 484 | IMarker marker = resource.createMarker(type); |
| 485 | int line = problem.getLineNumber(); |
| 486 | switch(category) { |
| 487 | case IApiProblem.CATEGORY_VERSION : |
| 488 | case IApiProblem.CATEGORY_API_BASELINE : |
| 489 | case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION : { |
| 490 | break; |
| 491 | } |
| 492 | default : { |
| 493 | line++; |
| 494 | } |
| 495 | } |
| 496 | marker.setAttributes( |
| 497 | new String[] { |
| 498 | IMarker.MESSAGE, |
| 499 | IMarker.SEVERITY, |
| 500 | IMarker.LINE_NUMBER, |
| 501 | IMarker.CHAR_START, |
| 502 | IMarker.CHAR_END, |
| 503 | IMarker.SOURCE_ID, |
| 504 | IApiMarkerConstants.MARKER_ATTR_PROBLEM_ID}, |
| 505 | new Object[] { |
| 506 | problem.getMessage(), |
| 507 | new Integer(ApiPlugin.getDefault().getSeverityLevel(ApiProblemFactory.getProblemSeverityId(problem), this.currentproject)), |
| 508 | new Integer(line), |
| 509 | new Integer(problem.getCharStart()), |
| 510 | new Integer(problem.getCharEnd()), |
| 511 | ApiAnalysisBuilder.SOURCE, |
| 512 | new Integer(problem.getId()) |
| 513 | } |
| 514 | ); |
| 515 | //add message arguments, if any |
| 516 | String[] args = problem.getMessageArguments(); |
| 517 | if(args.length > 0) { |
| 518 | marker.setAttribute(IApiMarkerConstants.MARKER_ATTR_MESSAGE_ARGUMENTS, createArgAttribute(args)); |
| 519 | } |
| 520 | String typeName = problem.getTypeName(); |
| 521 | if (typeName != null) { |
| 522 | marker.setAttribute(IApiMarkerConstants.MARKER_ATTR_PROBLEM_TYPE_NAME, typeName); |
| 523 | } |
| 524 | //add all other extra arguments, if any |
| 525 | if(problem.getExtraMarkerAttributeIds().length > 0) { |
| 526 | marker.setAttributes(problem.getExtraMarkerAttributeIds(), problem.getExtraMarkerAttributeValues()); |
| 527 | } |
| 528 | } catch (CoreException e) { |
| 529 | //ignore and continue |
| 530 | return; |
| 531 | } |
| 532 | } |
| 533 | |
| 534 | /** |
| 535 | * Resolves the resource from the path in the problem, returns <code>null</code> in |
| 536 | * the following cases: |
| 537 | * <ul> |
| 538 | * <li>The resource is not found in the parent project (findMember() returns null)</li> |
| 539 | * <li>The resource is not accessible (isAccessible() returns false</li> |
| 540 | * </ul> |
| 541 | * @param problem the problem to get the resource for |
| 542 | * @return the resource or <code>null</code> |
| 543 | */ |
| 544 | IResource resolveResource(IApiProblem problem) { |
| 545 | String resourcePath = problem.getResourcePath(); |
| 546 | if (resourcePath == null) { |
| 547 | return null; |
| 548 | } |
| 549 | IResource resource = currentproject.findMember(new Path(resourcePath)); |
| 550 | if(resource == null) { |
| 551 | return null; |
| 552 | } |
| 553 | if(!resource.isAccessible()) { |
| 554 | return null; |
| 555 | } |
| 556 | return resource; |
| 557 | } |
| 558 | |
| 559 | /** |
| 560 | * Creates a single string attribute from an array of strings. Uses the '#' char as |
| 561 | * a delimiter |
| 562 | * @param args |
| 563 | * @return a single string attribute from an array or arguments |
| 564 | */ |
| 565 | String createArgAttribute(String[] args) { |
| 566 | StringBuffer buff = new StringBuffer(); |
| 567 | for(int i = 0; i < args.length; i++) { |
| 568 | buff.append(args[i]); |
| 569 | if(i < args.length-1) { |
| 570 | buff.append("#"); //$NON-NLS-1$ |
| 571 | } |
| 572 | } |
| 573 | return buff.toString(); |
| 574 | } |
| 575 | |
| 576 | /* (non-Javadoc) |
| 577 | * @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor) |
| 578 | */ |
| 579 | protected void clean(IProgressMonitor monitor) throws CoreException { |
| 580 | this.currentproject = getProject(); |
| 581 | SubMonitor localmonitor = SubMonitor.convert(monitor, MessageFormat.format(BuilderMessages.CleaningAPIDescription, new String[] {this.currentproject.getName()}), 2); |
| 582 | try { |
| 583 | // clean up all existing markers |
| 584 | cleanupUsageMarkers(this.currentproject); |
| 585 | cleanupCompatibilityMarkers(this.currentproject); |
| 586 | cleanupUnsupportedTagMarkers(this.currentproject); |
| 587 | this.currentproject.deleteMarkers(IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); |
| 588 | Util.updateMonitor(localmonitor, 1); |
| 589 | //clean up the .api_settings |
| 590 | cleanupApiDescription(this.currentproject); |
| 591 | Util.updateMonitor(localmonitor, 1); |
| 592 | } |
| 593 | finally { |
| 594 | BuildState.setLastBuiltState(this.currentproject, null); |
| 595 | localmonitor.done(); |
| 596 | } |
| 597 | } |
| 598 | |
| 599 | /** |
| 600 | * Cleans the .api_settings file for the given project |
| 601 | * @param project |
| 602 | */ |
| 603 | void cleanupApiDescription(IProject project) { |
| 604 | if(project != null && project.exists()) { |
| 605 | ApiDescriptionManager.getDefault().clean(JavaCore.create(project), true, false); |
| 606 | } |
| 607 | } |
| 608 | |
| 609 | /** |
| 610 | * @return the current {@link IPluginModelBase} based on the current project for this builder |
| 611 | */ |
| 612 | IPluginModelBase getCurrentModel() { |
| 613 | IPluginModelBase[] workspaceModels = PluginRegistry.getWorkspaceModels(); |
| 614 | IPath location = this.currentproject.getLocation(); |
| 615 | IPluginModelBase currentModel = null; |
| 616 | BundleDescription desc = null; |
| 617 | loop: for (int i = 0, max = workspaceModels.length; i < max; i++) { |
| 618 | desc = workspaceModels[i].getBundleDescription(); |
| 619 | if(desc != null) { |
| 620 | Path path = new Path(desc.getLocation()); |
| 621 | if (path.equals(location)) { |
| 622 | currentModel = workspaceModels[i]; |
| 623 | break loop; |
| 624 | } |
| 625 | } |
| 626 | else if(DEBUG) { |
| 627 | System.out.println("Tried to look up bundle description for: " + workspaceModels[i].toString()); //$NON-NLS-1$ |
| 628 | } |
| 629 | } |
| 630 | return currentModel; |
| 631 | } |
| 632 | |
| 633 | /** |
| 634 | * Returns a listing of deltas for this project and for dependent projects |
| 635 | * @param projects |
| 636 | * @return |
| 637 | */ |
| 638 | IResourceDelta[] getDeltas(IProject[] projects) { |
| 639 | if(DEBUG) { |
| 640 | System.out.println("Searching for deltas for build of project: "+this.currentproject.getName()); //$NON-NLS-1$ |
| 641 | } |
| 642 | ArrayList deltas = new ArrayList(); |
| 643 | IResourceDelta delta = getDelta(this.currentproject); |
| 644 | if(delta != null) { |
| 645 | if (DEBUG) { |
| 646 | System.out.println("Found a delta: " + delta); //$NON-NLS-1$ |
| 647 | } |
| 648 | deltas.add(delta); |
| 649 | } |
| 650 | for(int i = 0; i < projects.length; i++) { |
| 651 | delta = getDelta(projects[i]); |
| 652 | if(delta != null) { |
| 653 | if (DEBUG) { |
| 654 | System.out.println("Found a delta: " + delta); //$NON-NLS-1$ |
| 655 | } |
| 656 | deltas.add(delta); |
| 657 | } |
| 658 | } |
| 659 | return (IResourceDelta[]) deltas.toArray(new IResourceDelta[deltas.size()]); |
| 660 | } |
| 661 | |
| 662 | /** |
| 663 | * Returns the API analyzer to use with this instance of the builder |
| 664 | * @return the API analyzer to use |
| 665 | */ |
| 666 | protected synchronized IApiAnalyzer getAnalyzer() { |
| 667 | if(this.analyzer == null) { |
| 668 | this.analyzer = new BaseApiAnalyzer(); |
| 669 | } |
| 670 | return this.analyzer; |
| 671 | } |
| 672 | |
| 673 | /** |
| 674 | * Returns the complete listing of required projects from the classpath of the backing project |
| 675 | * @param includeBinaryPrerequisites |
| 676 | * @return the list of projects required |
| 677 | * @throws CoreException |
| 678 | */ |
| 679 | IProject[] getRequiredProjects(boolean includebinaries) throws CoreException { |
| 680 | IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); |
| 681 | if (this.currentproject == null || workspaceRoot == null) { |
| 682 | return new IProject[0]; |
| 683 | } |
| 684 | ArrayList projects = new ArrayList(); |
| 685 | try { |
| 686 | IJavaProject javaProject = JavaCore.create(this.currentproject); |
| 687 | HashSet blocations = new HashSet(); |
| 688 | blocations.add(javaProject.getOutputLocation()); |
| 689 | this.output_locs.put(this.currentproject, blocations); |
| 690 | HashSet slocations = new HashSet(); |
| 691 | IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); |
| 692 | for (int i = 0; i < roots.length; i++) { |
| 693 | if(roots[i].isArchive()) { |
| 694 | continue; |
| 695 | } |
| 696 | slocations.add(roots[i].getPath()); |
| 697 | } |
| 698 | this.src_locs.put(this.currentproject, slocations); |
| 699 | IClasspathEntry[] entries = javaProject.getResolvedClasspath(true); |
| 700 | for (int i = 0, l = entries.length; i < l; i++) { |
| 701 | IClasspathEntry entry = entries[i]; |
| 702 | IPath path = entry.getPath(); |
| 703 | IProject p = null; |
| 704 | switch (entry.getEntryKind()) { |
| 705 | case IClasspathEntry.CPE_PROJECT : { |
| 706 | p = workspaceRoot.getProject(path.lastSegment()); // missing projects are considered too |
| 707 | if (isOptional(entry) && !p.hasNature(ApiPlugin.NATURE_ID)) {// except if entry is optional |
| 708 | p = null; |
| 709 | } |
| 710 | break; |
| 711 | } |
| 712 | case IClasspathEntry.CPE_LIBRARY : { |
| 713 | if (includebinaries && path.segmentCount() > 1) { |
| 714 | // some binary resources on the class path can come from projects that are not included in the project references |
| 715 | IResource resource = workspaceRoot.findMember(path.segment(0)); |
| 716 | if (resource instanceof IProject) { |
| 717 | p = (IProject) resource; |
| 718 | } |
| 719 | } |
| 720 | break; |
| 721 | } |
| 722 | case IClasspathEntry.CPE_SOURCE: { |
| 723 | IPath entrypath = entry.getOutputLocation(); |
| 724 | if(entrypath != null) { |
| 725 | blocations.add(entrypath); |
| 726 | } |
| 727 | } |
| 728 | } |
| 729 | if (p != null && !projects.contains(p)) { |
| 730 | projects.add(p); |
| 731 | //try to derive all of the output locations for each of the projects |
| 732 | javaProject = JavaCore.create(p); |
| 733 | HashSet bins = new HashSet(); |
| 734 | HashSet srcs = new HashSet(); |
| 735 | if(javaProject.exists()) { |
| 736 | bins.add(javaProject.getOutputLocation()); |
| 737 | IClasspathEntry[] source = javaProject.getRawClasspath(); |
| 738 | IPath entrypath = null; |
| 739 | for(int j = 0; j < source.length; j++) { |
| 740 | if(source[j].getEntryKind() == IClasspathEntry.CPE_SOURCE) { |
| 741 | srcs.add(source[j].getPath()); |
| 742 | entrypath = source[j].getOutputLocation(); |
| 743 | if(entrypath != null) { |
| 744 | bins.add(entrypath); |
| 745 | } |
| 746 | } |
| 747 | } |
| 748 | this.output_locs.put(p, bins); |
| 749 | this.src_locs.put(p, srcs); |
| 750 | } |
| 751 | } |
| 752 | } |
| 753 | } |
| 754 | catch(JavaModelException e) { |
| 755 | return new IProject[0]; |
| 756 | } |
| 757 | IProject[] result = new IProject[projects.size()]; |
| 758 | projects.toArray(result); |
| 759 | return result; |
| 760 | } |
| 761 | |
| 762 | /** |
| 763 | * Returns the output paths of the given project or <code>null</code> if none have been computed |
| 764 | * @param project |
| 765 | * @return the output paths for the given project or <code>null</code> |
| 766 | */ |
| 767 | HashSet getProjectOutputPaths(IProject project) { |
| 768 | return (HashSet) this.output_locs.get(project); |
| 769 | } |
| 770 | |
| 771 | /** |
| 772 | * Returns is the given classpath entry is optional or not |
| 773 | * @param entry |
| 774 | * @return true if the specified {@link IClasspathEntry} is optional, false otherwise |
| 775 | */ |
| 776 | boolean isOptional(IClasspathEntry entry) { |
| 777 | IClasspathAttribute[] attribs = entry.getExtraAttributes(); |
| 778 | for (int i = 0, length = attribs.length; i < length; i++) { |
| 779 | IClasspathAttribute attribute = attribs[i]; |
| 780 | if (IClasspathAttribute.OPTIONAL.equals(attribute.getName()) && "true".equals(attribute.getValue())) //$NON-NLS-1$ |
| 781 | return true; |
| 782 | } |
| 783 | return false; |
| 784 | } |
| 785 | |
| 786 | /* (non-Javadoc) |
| 787 | * @see java.lang.Object#toString() |
| 788 | */ |
| 789 | public String toString() { |
| 790 | return NLS.bind(BuilderMessages.ApiAnalysisBuilder_builder_for_project, this.currentproject.getName()); |
| 791 | } |
| 792 | } |