Java tutorial
/******************************************************************************* * Copyright (c) 2007, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.pde.api.tools.internal.builder; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.jar.JarFile; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelMarker; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.builder.State; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.api.tools.internal.ApiDescriptionManager; import org.eclipse.pde.api.tools.internal.IApiCoreConstants; import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; import org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer; import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; import org.eclipse.pde.api.tools.internal.util.Util; import org.eclipse.pde.core.build.IBuild; import org.eclipse.pde.core.build.IBuildEntry; import org.eclipse.pde.core.build.IBuildModel; import org.eclipse.pde.core.plugin.IPluginModelBase; import org.eclipse.pde.core.plugin.PluginRegistry; import org.osgi.framework.Constants; import com.ibm.icu.text.MessageFormat; /** * Builder for creating API Tools resource markers * * @since 1.0.0 */ public class ApiAnalysisBuilder extends IncrementalProjectBuilder { /** * Project relative path to the .settings folder * * @since 1.0.1 */ static final IPath SETTINGS_PATH = new Path(".settings"); //$NON-NLS-1$ /** * Project relative path to the build.properties file */ public static final IPath BUILD_PROPERTIES_PATH = new Path("build.properties"); //$NON-NLS-1$ /** * Project relative path to the manifest file. */ public static final IPath MANIFEST_PATH = new Path(JarFile.MANIFEST_NAME); /** * {@link Comparator} to sort {@link ManifestElement}s * * @since 1.0.3 */ static final Comparator<ManifestElement> fgManifestElementComparator = new Comparator<ManifestElement>() { @Override public int compare(ManifestElement o1, ManifestElement o2) { return o1.getValue().compareTo(o2.getValue()); } }; /** * Array of header names that we care about when a manifest delta is * discovered * * @since 1.0.3 */ public static final HashSet<String> IMPORTANT_HEADERS = new HashSet<String>(7); static { IMPORTANT_HEADERS.add(Constants.SYSTEM_BUNDLE_SYMBOLICNAME); IMPORTANT_HEADERS.add(Constants.BUNDLE_VERSION); IMPORTANT_HEADERS.add(Constants.REQUIRE_BUNDLE); IMPORTANT_HEADERS.add(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT); IMPORTANT_HEADERS.add(Constants.EXPORT_PACKAGE); IMPORTANT_HEADERS.add(Constants.IMPORT_PACKAGE); IMPORTANT_HEADERS.add(Constants.BUNDLE_CLASSPATH); } /** * Project relative path to the .api_filters file */ static final IPath FILTER_PATH = SETTINGS_PATH.append(IApiCoreConstants.API_FILTERS_XML_NAME); /** * Empty listing of projects to be returned by the builder if there is * nothing to do */ static final IProject[] NO_PROJECTS = new IProject[0]; /** * Constant representing the name of the 'source' attribute on API Tools * markers. Value is <code>API Tools</code> */ static final String SOURCE = "API Tools"; //$NON-NLS-1$ /** * Boolean flag to disable the API builder (the builder will always return * {@link #NO_PROJECTS}. Not accessible from the UI by default, but can be * set by other tools. The behaviour is not API and may be changed or * removed in a future release. See bug 221913. */ private static boolean buildDisabled = false; /** * The current project for which this builder was defined */ private IProject currentproject = null; /** * The API analyzer for this builder */ private IApiAnalyzer analyzer = null; /** * Maps prerequisite projects to their output location(s) */ HashMap<IProject, HashSet<IPath>> output_locs = new HashMap<IProject, HashSet<IPath>>(); /** * Maps prerequisite projects to their source locations */ HashMap<IProject, HashSet<IPath>> src_locs = new HashMap<IProject, HashSet<IPath>>(); /** * Current build state */ private BuildState buildstate = null; /** * Cleans up markers associated with API Tools on the given resource. * * @param resource */ void cleanupMarkers(IResource resource) { cleanUnusedFilterMarkers(resource); cleanupUsageMarkers(resource); cleanupCompatibilityMarkers(resource); cleanupUnsupportedTagMarkers(resource); cleanupUnsupportedAnnotationMarkers(resource); cleanApiUseScanMarkers(resource); cleanupFatalMarkers(resource); } /** * Cleans up API use scan breakage related markers on the specified resource * * @param resource */ void cleanApiUseScanMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: cleaning api use problems"); //$NON-NLS-1$ } resource.deleteMarkers(IApiMarkerConstants.API_USESCAN_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); IProject project = resource.getProject(); IMarker[] markers = project.findMarkers(IApiMarkerConstants.API_USESCAN_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); for (int i = 0; i < markers.length; i++) { String typeName = markers[i].getAttribute(IApiMarkerConstants.API_USESCAN_TYPE, null); IJavaElement adaptor = (IJavaElement) resource.getAdapter(IJavaElement.class); if (adaptor != null && adaptor instanceof ICompilationUnit) { IType typeroot = ((ICompilationUnit) adaptor).findPrimaryType(); if (typeroot != null && typeName != null && typeName.startsWith(typeroot.getFullyQualifiedName())) { markers[i].delete(); } } } } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * Cleans up unsupported Javadoc tag markers on the specified resource * * @param resource */ void cleanupUnsupportedTagMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: cleaning unsupported tag problems"); //$NON-NLS-1$ } resource.deleteMarkers(IApiMarkerConstants.UNSUPPORTED_TAG_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * Removes all of the unsupported annotation markers from the given resource * and all of its children * * @param resource * @since 1.0.600 */ void cleanupUnsupportedAnnotationMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: cleaning unsupported annotation problems"); //$NON-NLS-1$ } resource.deleteMarkers(IApiMarkerConstants.UNSUPPORTED_ANNOTATION_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * Cleans up only API compatibility markers on the given {@link IResource} * * @param resource the given resource */ void cleanupCompatibilityMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { resource.deleteMarkers(IApiMarkerConstants.COMPATIBILITY_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(IApiMarkerConstants.SINCE_TAGS_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); if (resource.getType() == IResource.PROJECT) { // on full builds resource.deleteMarkers(IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); resource.deleteMarkers(IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); } } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * cleans up only API usage markers from the given {@link IResource} * * @param resource */ void cleanupUsageMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { resource.deleteMarkers(IApiMarkerConstants.API_USAGE_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); if (resource.getType() != IResource.PROJECT) { IProject pj = resource.getProject(); if (pj != null) { pj.deleteMarkers(IApiMarkerConstants.API_USAGE_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); } } } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * cleans up only fatal problem markers from the given {@link IResource} * * @param resource */ void cleanupFatalMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { resource.deleteMarkers(IApiMarkerConstants.FATAL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); } } catch (CoreException e) { ApiPlugin.log(e.getStatus()); } } /** * Cleans up the unused API filter problems from the given resource * * @param resource */ void cleanUnusedFilterMarkers(IResource resource) { try { if (resource != null && resource.isAccessible()) { resource.deleteMarkers(IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); } } catch (CoreException ce) { ApiPlugin.log(ce.getStatus()); } } /* * (non-Javadoc) * @see org.eclipse.core.resources.IncrementalProjectBuilder#build(int, * java.util.Map, org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { this.currentproject = getProject(); if (buildDisabled || shouldAbort(this.currentproject)) { return NO_PROJECTS; } // update build time stamp BuildStamps.incBuildStamp(this.currentproject); if (ApiPlugin.DEBUG_BUILDER) { System.out.println("\nApiAnalysisBuilder: Starting build of " + this.currentproject.getName() + " @ " //$NON-NLS-1$//$NON-NLS-2$ + new Date(System.currentTimeMillis())); } SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_builder, 8); IApiBaseline wbaseline = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline(); if (wbaseline == null) { if (ApiPlugin.DEBUG_BUILDER) { System.err.println("ApiAnalysisBuilder: Could not retrieve a workspace baseline"); //$NON-NLS-1$ } return NO_PROJECTS; } final IProject[] projects = getRequiredProjects(true); IApiBaseline baseline = ApiPlugin.getDefault().getApiBaselineManager().getDefaultApiBaseline(); try { switch (kind) { case FULL_BUILD: { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Performing full build as requested"); //$NON-NLS-1$ } buildAll(baseline, wbaseline, localMonitor.newChild(1)); break; } case AUTO_BUILD: case INCREMENTAL_BUILD: { this.buildstate = BuildState.getLastBuiltState(currentproject); if (this.buildstate == null) { buildAll(baseline, wbaseline, localMonitor.newChild(1)); break; } else if (worthDoingFullBuild(projects)) { buildAll(baseline, wbaseline, localMonitor.newChild(1)); break; } else { IResourceDelta[] deltas = getDeltas(projects); if (deltas.length < 1) { buildAll(baseline, wbaseline, localMonitor.newChild(1)); } else { IResourceDelta filters = null; boolean full = false; for (int i = 0; i < deltas.length; i++) { full = shouldFullBuild(deltas[i]); if (full) { break; } filters = deltas[i].findMember(FILTER_PATH); if (filters != null) { switch (filters.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.REMOVED: { full = true; break; } case IResourceDelta.CHANGED: { full = (filters.getFlags() & (IResourceDelta.REPLACED | IResourceDelta.CONTENT)) > 0; break; } default: { break; } } if (full) { break; } } } if (full) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println( "ApiAnalysisBuilder: Performing full build since MANIFEST.MF or .api_filters was modified"); //$NON-NLS-1$ } buildAll(baseline, wbaseline, localMonitor.newChild(1)); } else { State state = (State) JavaModelManager.getJavaModelManager() .getLastBuiltState(this.currentproject, localMonitor.newChild(1)); if (state == null) { buildAll(baseline, wbaseline, localMonitor.newChild(1)); break; } BuildState.setLastBuiltState(this.currentproject, null); IncrementalApiBuilder builder = new IncrementalApiBuilder(this); builder.build(baseline, wbaseline, deltas, state, this.buildstate, localMonitor.newChild(1)); } } } break; } default: { break; } } Util.updateMonitor(localMonitor, 0); } catch (OperationCanceledException oce) { // do nothing, but don't forward it // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Trapped OperationCanceledException"); //$NON-NLS-1$ } } catch (CoreException e) { IStatus status = e.getStatus(); if (status == null || status.getCode() != ApiPlugin.REPORT_BASELINE_IS_DISPOSED) { throw e; } ApiPlugin.log(e); } finally { try { Util.updateMonitor(localMonitor, 0); if (this.analyzer != null) { this.analyzer.dispose(); this.analyzer = null; } if (projects.length < 1) { // if this build cycle indicates that more projects need to // be built do not close // the baselines yet, they might be re-read by another build // cycle if (baseline != null) { baseline.close(); } } Util.updateMonitor(localMonitor, 0); if (this.buildstate != null) { for (int i = 0, max = projects.length; i < max; i++) { IProject project = projects[i]; if (Util.isApiProject(project)) { this.buildstate.addApiToolingDependentProject(project.getName()); } } this.buildstate.setBuildPathCRC(BuildState.computeBuildPathCRC(this.currentproject)); IFile manifest = (IFile) currentproject.findMember(MANIFEST_PATH); if (manifest != null && manifest.exists()) { try { this.buildstate.setManifestState( ManifestElement.parseBundleManifest(manifest.getContents(), null)); } catch (Exception e) { ApiPlugin.log(e); } } IPluginModelBase base = PluginRegistry.findModel(currentproject); if (base != null) { try { IBuildModel model = PluginRegistry.createBuildModel(base); if (model != null) { this.buildstate.setBuildPropertiesState(model); } } catch (CoreException ce) { ApiPlugin.log(ce); } } BuildState.saveBuiltState(this.currentproject, this.buildstate); this.buildstate = null; Util.updateMonitor(monitor, 0); } if (localMonitor != null) { localMonitor.done(); } } catch (OperationCanceledException oce) { // do nothing, but don't forward it // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Trapped OperationCanceledException"); //$NON-NLS-1$ } } } if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Finished build of " + this.currentproject.getName() + " @ " //$NON-NLS-1$//$NON-NLS-2$ + new Date(System.currentTimeMillis())); } return projects; } /** * Returns if the backing project should be fully built, based on the delta * * @param delta the {@link IResourceDelta} to examine * @return <code>true</code> if the project should have a full build, * <code>false</code> otherwise * @since 1.0.3 */ boolean shouldFullBuild(IResourceDelta delta) { switch (delta.getKind()) { case IResourceDelta.CHANGED: { IResourceDelta subdelta = delta.findMember(MANIFEST_PATH); if (subdelta != null) { IFile file = (IFile) subdelta.getResource(); return file.getProject().equals(currentproject) && compareManifest(file, buildstate); } subdelta = delta.findMember(BUILD_PROPERTIES_PATH); if (subdelta != null) { IFile file = (IFile) subdelta.getResource(); return file.getProject().equals(currentproject) && compareBuildProperties(buildstate); } break; } default: { break; } } return false; } /** * Compares the current <code>MANIFEST.MF</code> against the saved state. If * the {@link BuildState} is <code>null</code> or there is no saved state * for the current project context a full build is assumed. * * @param manifest the handle to the <code>MANIFEST.MF</code> file * @param state the current {@link BuildState} or <code>null</code> * @return <code>true</code> if there are changes that require a full build, * <code>false</code> otherwise * @since 1.0.3 */ boolean compareManifest(IFile manifest, BuildState state) { if (state != null) { try { Map<String, String> stateheaders = state.getManifestState(); if (stateheaders != null) { Map<String, String> headers = ManifestElement.parseBundleManifest(manifest.getContents(), null); Entry<String, String> entry = null; for (Iterator<Entry<String, String>> i = stateheaders.entrySet().iterator(); i.hasNext();) { entry = i.next(); String key = entry.getKey(); String value = headers.get(key); ManifestElement[] e1 = ManifestElement.parseHeader(key, value); ManifestElement[] e2 = ManifestElement.parseHeader(key, entry.getValue()); if (e1 != null && e2 != null && e1.length == e2.length) { Arrays.sort(e1, fgManifestElementComparator); Arrays.sort(e2, fgManifestElementComparator); for (int j = 0; j < e1.length; j++) { String[] v1 = e1[j].getValueComponents(); String[] v2 = e2[j].getValueComponents(); // compare value bits if (v1.length == v2.length) { Arrays.sort(v1); Arrays.sort(v2); for (int k = 0; k < v2.length; k++) { if (!v1[k].equals(v2[k])) { return true; } } } else { return true; } // compare directives Enumeration<String> e = e1[j].getDirectiveKeys(); if (e != null) { while (e.hasMoreElements()) { String key2 = e.nextElement(); if (!Util.equalsOrNull(e1[j].getDirective(key2), e2[j].getDirective(key2))) { return true; } } } // compare attributes e = e1[j].getKeys(); if (e != null) { while (e.hasMoreElements()) { String key2 = e.nextElement(); if (!Util.equalsOrNull(e1[j].getAttribute(key2), e2[j].getAttribute(key2))) { return true; } } } } } else { return true; } } return false; } } catch (Exception e) { ApiPlugin.log(e); return false; } } return true; } /** * Compares the current <code>build.properties</code> against the saved one. * If the given {@link BuildState} is <code>null</code> or there is no saved * state for the current project context a full build is assumed. * * @param state the current {@link BuildState} or <code>null</code> * @return <code>true</code> if there are changes that require a full build, * <code>false</code> otherwise * @since 1.0.3 */ boolean compareBuildProperties(BuildState state) { if (state != null) { Map<String, String> map = state.getBuildPropertiesState(); if (map != null) { IPluginModelBase base = PluginRegistry.findModel(currentproject); if (base != null) { try { IBuildModel model = PluginRegistry.createBuildModel(base); if (model != null) { IBuild ibuild = model.getBuild(); Entry<String, String> entry; for (Iterator<Entry<String, String>> i = map.entrySet().iterator(); i.hasNext();) { entry = i.next(); IBuildEntry be = ibuild.getEntry(entry.getKey()); if (be != null && !entry.getValue().equals(Util.deepToString(be.getTokens()))) { return true; } } } } catch (CoreException ce) { ApiPlugin.log(ce); return false; } } } return false; } return true; } /** * Returns if the builder should abort the build of the given project. The * build decides to abort if one of the following are true: * <ul> * <li>The project is not accessible</li> * <li>The project does not have the API tools nature</li> * <li>The project has already been built - as decided by the build * framework</li> * <li>The project has fatal JDT errors that prevent the creation of class * files</li> * </ul> * * @param project * @return true if the builder should abort building the given project, * false otherwise * @throws CoreException * @see {@link #hasBeenBuilt(IProject)} * @see {@link #hasFatalProblems(IProject)} * @since 1.1 */ boolean shouldAbort(IProject project) throws CoreException { return !project.isAccessible() || !project.hasNature(ApiPlugin.NATURE_ID) || hasBeenBuilt(project) || hasFatalProblems(project); } /** * Returns if the project we are about to build has fatal JDT problems that * prevent class files from being built * * @param project * @return true if the given project has fatal JDT problems * @see * @throws CoreException * @see {@link org.eclipse.jdt.core.IJavaModelMarker#BUILDPATH_PROBLEM_MARKER} * @since 1.1 */ boolean hasFatalProblems(IProject project) throws CoreException { IMarker[] problems = project.findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_ZERO); if (problems.length > 0) { cleanupMarkers(project); IApiProblem problem = ApiProblemFactory.newFatalProblem(Path.EMPTY.toString(), new String[] { project.getName() }, IApiProblem.FATAL_JDT_BUILDPATH_PROBLEM); createMarkerForProblem(IApiProblem.CATEGORY_FATAL_PROBLEM, IApiMarkerConstants.FATAL_PROBLEM_MARKER, problem); return true; } cleanupFatalMarkers(project); return false; } /** * if its worth doing a full build considering the given set if projects * * @param projects projects to check the build state for * @return true if a full build should take place, false otherwise */ boolean worthDoingFullBuild(IProject[] projects) { Set<String> apiToolingDependentProjects = this.buildstate.getApiToolingDependentProjects(); for (int i = 0, max = projects.length; i < max; i++) { IProject currentProject = projects[i]; if (Util.isApiProject(currentProject)) { if (apiToolingDependentProjects.contains(currentProject.getName())) { continue; } return true; } else if (apiToolingDependentProjects.contains(currentProject.getName())) { return true; } } return false; } /** * Performs a full build for the project * * @param baseline the default baseline * @param wbaseline the workspace baseline * @param monitor */ void buildAll(IApiBaseline baseline, IApiBaseline wbaseline, IProgressMonitor monitor) throws CoreException { SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_on_0, 4); try { BuildState.setLastBuiltState(this.currentproject, null); this.buildstate = new BuildState(); localMonitor.subTask( NLS.bind(BuilderMessages.ApiAnalysisBuilder_initializing_analyzer, currentproject.getName())); cleanupMarkers(this.currentproject); IPluginModelBase currentModel = getCurrentModel(); if (currentModel != null) { localMonitor.subTask(BuilderMessages.building_workspace_profile); Util.updateMonitor(localMonitor, 1); String id = currentModel.getBundleDescription().getSymbolicName(); // Compatibility checks IApiComponent apiComponent = wbaseline.getApiComponent(id); if (apiComponent != null) { getAnalyzer().analyzeComponent(this.buildstate, null, null, baseline, apiComponent, new BuildContext(), localMonitor.newChild(1)); Util.updateMonitor(localMonitor, 1); createMarkers(); Util.updateMonitor(localMonitor, 1); } } } finally { if (localMonitor != null) { localMonitor.done(); } } } /** * Creates new markers are for the listing of problems added to this * reporter. If no problems have been added to this reporter, or we are not * running in the framework, no work is done. */ protected void createMarkers() { try { IResource manifest = Util.getManifestFile(this.currentproject); if (manifest != null) { manifest.deleteMarkers(IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); } this.currentproject.deleteMarkers(IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); this.currentproject.deleteMarkers(IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); } catch (CoreException e) { ApiPlugin.log(e); } IApiProblem[] problems = getAnalyzer().getProblems(); String type = null; for (int i = 0; i < problems.length; i++) { int category = problems[i].getCategory(); type = getProblemTypeFromCategory(category, problems[i].getKind()); if (type == null) { continue; } if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: creating marker for: " + problems[i].toString()); //$NON-NLS-1$ } createMarkerForProblem(category, type, problems[i]); } } /** * Returns the {@link IApiMarkerConstants} problem type given the problem * category * * @param category the problem category - see {@link IApiProblem} for * problem categories * @param kind the kind of the problem - see {@link IApiProblem} for problem * kinds * @return the problem type or <code>null</code> */ String getProblemTypeFromCategory(int category, int kind) { switch (category) { case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION: { return IApiMarkerConstants.API_COMPONENT_RESOLUTION_PROBLEM_MARKER; } case IApiProblem.CATEGORY_API_BASELINE: { return IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER; } case IApiProblem.CATEGORY_COMPATIBILITY: { return IApiMarkerConstants.COMPATIBILITY_PROBLEM_MARKER; } case IApiProblem.CATEGORY_SINCETAGS: { return IApiMarkerConstants.SINCE_TAGS_PROBLEM_MARKER; } case IApiProblem.CATEGORY_USAGE: { if (kind == IApiProblem.UNSUPPORTED_TAG_USE) { return IApiMarkerConstants.UNSUPPORTED_TAG_PROBLEM_MARKER; } if (kind == IApiProblem.UNSUPPORTED_ANNOTATION_USE) { return IApiMarkerConstants.UNSUPPORTED_ANNOTATION_PROBLEM_MARKER; } if (kind == IApiProblem.UNUSED_PROBLEM_FILTERS) { return IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER; } return IApiMarkerConstants.API_USAGE_PROBLEM_MARKER; } case IApiProblem.CATEGORY_VERSION: { return IApiMarkerConstants.VERSION_NUMBERING_PROBLEM_MARKER; } case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM: { return IApiMarkerConstants.API_USESCAN_PROBLEM_MARKER; } default: { return null; } } } /** * Creates an {@link IMarker} on the resource specified in the problem (via * its path) with the given problem attributes * * @param category the category of the problem - see {@link IApiProblem} for * categories * @param type the marker type to create - see {@link IApiMarkerConstants} * for types * @param problem the problem to create a marker from */ void createMarkerForProblem(int category, String type, IApiProblem problem) { IResource resource = resolveResource(problem); if (resource == null) { return; } try { if (category == IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM) { IMarker[] markers = resource.findMarkers(type, true, IResource.DEPTH_ZERO); for (int i = 0; i < markers.length; i++) { String msg = markers[i].getAttribute(IMarker.MESSAGE, null); if (msg == null || msg.equalsIgnoreCase(problem.getMessage())) { int markerSeverity = markers[i].getAttribute(IMarker.SEVERITY, 0); int problemSeverity = ApiPlugin.getDefault().getSeverityLevel( ApiProblemFactory.getProblemSeverityId(problem), this.currentproject); if (markerSeverity == problemSeverity) { return; // Marker already exists } } else { markers[i].delete(); // create the marker afresh } } } IMarker marker = resource.createMarker(type); int line = problem.getLineNumber(); switch (category) { case IApiProblem.CATEGORY_VERSION: case IApiProblem.CATEGORY_API_BASELINE: case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION: case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM: { break; } default: { line++; } } marker.setAttributes( new String[] { IMarker.MESSAGE, IMarker.SEVERITY, IMarker.LINE_NUMBER, IMarker.CHAR_START, IMarker.CHAR_END, IMarker.SOURCE_ID, IApiMarkerConstants.MARKER_ATTR_PROBLEM_ID }, new Object[] { problem.getMessage(), new Integer(ApiPlugin.getDefault().getSeverityLevel( ApiProblemFactory.getProblemSeverityId(problem), this.currentproject)), new Integer(line), new Integer(problem.getCharStart()), new Integer(problem.getCharEnd()), ApiAnalysisBuilder.SOURCE, new Integer(problem.getId()) }); // add message arguments, if any String[] args = problem.getMessageArguments(); if (args.length > 0) { marker.setAttribute(IApiMarkerConstants.MARKER_ATTR_MESSAGE_ARGUMENTS, createArgAttribute(args)); } String typeName = problem.getTypeName(); if (typeName != null) { marker.setAttribute(IApiMarkerConstants.MARKER_ATTR_PROBLEM_TYPE_NAME, typeName); } // add all other extra arguments, if any if (problem.getExtraMarkerAttributeIds().length > 0) { marker.setAttributes(problem.getExtraMarkerAttributeIds(), problem.getExtraMarkerAttributeValues()); } if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Created the marker: " + marker.getId() + " - " //$NON-NLS-1$//$NON-NLS-2$ + marker.getAttributes().entrySet()); } } catch (CoreException e) { ApiPlugin.log(e); return; } } /** * Resolves the resource from the path in the problem, returns * <code>null</code> in the following cases: * <ul> * <li>The resource is not found in the parent project (findMember() returns * null)</li> * <li>The resource is not accessible (isAccessible() returns false</li> * </ul> * * @param problem the problem to get the resource for * @return the resource or <code>null</code> */ IResource resolveResource(IApiProblem problem) { String resourcePath = problem.getResourcePath(); if (resourcePath == null) { return null; } IResource resource = currentproject.findMember(new Path(resourcePath)); if (resource == null) { // might be re-exported try to look it up IJavaProject jp = JavaCore.create(currentproject); try { IType type = jp.findType(problem.getTypeName()); if (type != null) { return type.getResource(); } } catch (JavaModelException jme) { // do nothing } return null; } if (!resource.isAccessible()) { return null; } return resource; } /** * Creates a single string attribute from an array of strings. Uses the '#' * char as a delimiter * * @param args * @return a single string attribute from an array or arguments */ String createArgAttribute(String[] args) { StringBuffer buff = new StringBuffer(); for (int i = 0; i < args.length; i++) { buff.append(args[i]); if (i < args.length - 1) { buff.append("#"); //$NON-NLS-1$ } } return buff.toString(); } /* * (non-Javadoc) * @see * org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse * .core.runtime.IProgressMonitor) */ @Override protected void clean(IProgressMonitor monitor) throws CoreException { this.currentproject = getProject(); SubMonitor localmonitor = SubMonitor.convert(monitor, MessageFormat .format(BuilderMessages.CleaningAPIDescription, new Object[] { this.currentproject.getName() }), 2); try { // clean up all existing markers cleanupUsageMarkers(this.currentproject); cleanupCompatibilityMarkers(this.currentproject); cleanupUnsupportedTagMarkers(this.currentproject); cleanupUnsupportedAnnotationMarkers(this.currentproject); this.currentproject.deleteMarkers(IApiMarkerConstants.UNUSED_FILTER_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); Util.updateMonitor(localmonitor, 1); // clean up the .api_settings cleanupApiDescription(this.currentproject); Util.updateMonitor(localmonitor, 1); } finally { BuildState.setLastBuiltState(this.currentproject, null); localmonitor.done(); } } /** * Cleans the .api_settings file for the given project * * @param project */ void cleanupApiDescription(IProject project) { if (project != null && project.exists()) { ApiDescriptionManager.getManager().clean(JavaCore.create(project), true, false); } } /** * @return the current {@link IPluginModelBase} based on the current project * for this builder */ IPluginModelBase getCurrentModel() { IPluginModelBase[] workspaceModels = PluginRegistry.getWorkspaceModels(); IPath location = this.currentproject.getLocation(); IPluginModelBase currentModel = null; BundleDescription desc = null; loop: for (int i = 0, max = workspaceModels.length; i < max; i++) { desc = workspaceModels[i].getBundleDescription(); if (desc != null) { Path path = new Path(desc.getLocation()); if (path.equals(location)) { currentModel = workspaceModels[i]; break loop; } } else if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Tried to look up bundle description for: " //$NON-NLS-1$ + workspaceModels[i].toString()); } } return currentModel; } /** * Returns a listing of deltas for this project and for dependent projects * * @param projects * @return */ IResourceDelta[] getDeltas(IProject[] projects) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Searching for deltas for build of project: " //$NON-NLS-1$ + this.currentproject.getName()); } ArrayList<IResourceDelta> deltas = new ArrayList<IResourceDelta>(); IResourceDelta delta = getDelta(this.currentproject); if (delta != null) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Found a delta: " + delta); //$NON-NLS-1$ } deltas.add(delta); } for (int i = 0; i < projects.length; i++) { delta = getDelta(projects[i]); if (delta != null) { if (ApiPlugin.DEBUG_BUILDER) { System.out.println("ApiAnalysisBuilder: Found a delta: " + delta); //$NON-NLS-1$ } deltas.add(delta); } } return deltas.toArray(new IResourceDelta[deltas.size()]); } /** * Returns the API analyzer to use with this instance of the builder * * @return the API analyzer to use */ protected synchronized IApiAnalyzer getAnalyzer() { if (this.analyzer == null) { this.analyzer = new BaseApiAnalyzer(); } return this.analyzer; } /** * Returns the complete listing of required projects from the classpath of * the backing project * * @param includeBinaryPrerequisites * @return the list of projects required * @throws CoreException */ IProject[] getRequiredProjects(boolean includebinaries) throws CoreException { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); if (this.currentproject == null || workspaceRoot == null) { return new IProject[0]; } ArrayList<IProject> projects = new ArrayList<IProject>(); try { IJavaProject javaProject = JavaCore.create(this.currentproject); HashSet<IPath> blocations = new HashSet<IPath>(); blocations.add(javaProject.getOutputLocation()); this.output_locs.put(this.currentproject, blocations); HashSet<IPath> slocations = new HashSet<IPath>(); IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); for (int i = 0; i < roots.length; i++) { if (roots[i].isArchive()) { continue; } slocations.add(roots[i].getPath()); } this.src_locs.put(this.currentproject, slocations); IClasspathEntry[] entries = javaProject.getResolvedClasspath(true); for (int i = 0, l = entries.length; i < l; i++) { IClasspathEntry entry = entries[i]; IPath path = entry.getPath(); IProject p = null; switch (entry.getEntryKind()) { case IClasspathEntry.CPE_PROJECT: { p = workspaceRoot.getProject(path.lastSegment()); // missing // projects // are // considered // too if (isOptional(entry) && !p.hasNature(ApiPlugin.NATURE_ID)) {// except // if // entry // is // optional p = null; } break; } case IClasspathEntry.CPE_LIBRARY: { if (includebinaries && path.segmentCount() > 1) { // some binary resources on the class path can come // from projects that are not included in the // project references IResource resource = workspaceRoot.findMember(path.segment(0)); if (resource instanceof IProject) { p = (IProject) resource; } } break; } case IClasspathEntry.CPE_SOURCE: { IPath entrypath = entry.getOutputLocation(); if (entrypath != null) { blocations.add(entrypath); } break; } default: { break; } } if (p != null && !projects.contains(p)) { projects.add(p); // try to derive all of the output locations for each of the // projects javaProject = JavaCore.create(p); HashSet<IPath> bins = new HashSet<IPath>(); HashSet<IPath> srcs = new HashSet<IPath>(); if (javaProject.exists()) { bins.add(javaProject.getOutputLocation()); IClasspathEntry[] source = javaProject.getRawClasspath(); IPath entrypath = null; for (int j = 0; j < source.length; j++) { if (source[j].getEntryKind() == IClasspathEntry.CPE_SOURCE) { srcs.add(source[j].getPath()); entrypath = source[j].getOutputLocation(); if (entrypath != null) { bins.add(entrypath); } } } this.output_locs.put(p, bins); this.src_locs.put(p, srcs); } } } } catch (JavaModelException e) { return new IProject[0]; } IProject[] result = new IProject[projects.size()]; projects.toArray(result); return result; } /** * Returns the output paths of the given project or <code>null</code> if * none have been computed * * @param project * @return the output paths for the given project or <code>null</code> */ HashSet<IPath> getProjectOutputPaths(IProject project) { return this.output_locs.get(project); } /** * Returns is the given classpath entry is optional or not * * @param entry * @return true if the specified {@link IClasspathEntry} is optional, false * otherwise */ boolean isOptional(IClasspathEntry entry) { IClasspathAttribute[] attribs = entry.getExtraAttributes(); for (int i = 0, length = attribs.length; i < length; i++) { IClasspathAttribute attribute = attribs[i]; if (IClasspathAttribute.OPTIONAL.equals(attribute.getName()) && "true".equals(attribute.getValue())) { //$NON-NLS-1$ return true; } } return false; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return NLS.bind(BuilderMessages.ApiAnalysisBuilder_builder_for_project, this.currentproject != null ? this.currentproject.getName() : null); } }