/*******************************************************************************
 * Copyright (c) 2007, 2009 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.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;

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.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
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.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.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;

import com.ibm.icu.text.MessageFormat;

/**
 * Builder for creating API tooling resource markers
 * @since 1.0.0
 */
public class ApiAnalysisBuilder extends IncrementalProjectBuilder {
	/**
	 * Constant used for controlling tracing in the API tool builder
	 */
	static boolean DEBUG = Util.DEBUG;
	
	/**
	 * 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 manifest file.
	 */
	static final IPath MANIFEST_PATH = new Path(JarFile.MANIFEST_NAME);
	
	/**
	 * 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 tooling markers.
	 * Value is <code>Api Tooling</code>
	 */
	static final String SOURCE = "Api Tooling"; //$NON-NLS-1$
	
	/**
	 * Method used for initializing tracing in the API tool builder
	 */
	public static void setDebug(boolean debugValue) {
		DEBUG = debugValue || Util.DEBUG;
	}
	
	/**
	 * 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 output_locs = new HashMap();
	
	/**
	 * Maps pre-requisite projects to their source locations
	 */
	HashMap src_locs = new HashMap();
	
	/**
	 * Current build state
	 */
	private BuildState buildstate = null;
	
	/**
	 * Cleans up markers associated with API tooling on the given resource.
	 * 
	 * @param resource
	 */
	void cleanupMarkers(IResource resource) {
		cleanUnusedFilterMarkers(resource);
		cleanupUsageMarkers(resource);
		cleanupCompatibilityMarkers(resource);
		cleanupUnsupportedTagMarkers(resource);
	}
	
	/**
	 * Cleans up unsupported Javadoc tag markers on the specified resource
	 * @param resource
	 */
	void cleanupUnsupportedTagMarkers(IResource resource) {
		try {
			if(DEBUG) {
				System.out.println("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());
		}
	}
	
	/**
	 * 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);
			}
		} 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)
	 */
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
		this.currentproject = getProject();
		if (!this.currentproject.isAccessible() || !this.currentproject.hasNature(ApiPlugin.NATURE_ID) || hasBeenBuilt(this.currentproject)) {
			return NO_PROJECTS;
		}
		if (DEBUG) {
			System.out.println("\nApiAnalysis builder - Starting build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$
		}
		Util.updateMonitor(monitor, 0);
		SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_builder, 8);
		IApiBaseline wbaseline = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline();
		if (wbaseline == null) {
			if (DEBUG) {
				System.err.println("Could not retrieve a workspace profile");  //$NON-NLS-1$
			}
			return NO_PROJECTS;
		}
		final IProject[] projects = getRequiredProjects(true);
		IApiBaseline baseline = ApiPlugin.getDefault().getApiBaselineManager().getDefaultApiBaseline();
		try {
			switch(kind) {
				case FULL_BUILD : {
					if (DEBUG) {
						System.out.println("Performing full build as requested by user"); //$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 manifest = null;
							IResourceDelta filters = null;
							boolean filterbuild = false;
							for (int i = 0; i < deltas.length; i++) {
								manifest = deltas[i].findMember(MANIFEST_PATH);
								if(manifest != null) {
									break;
								}
								filters = deltas[i].findMember(FILTER_PATH);
								if(filters != null){
									switch(filters.getKind()) {
										case IResourceDelta.ADDED:
										case IResourceDelta.REMOVED: {
											filterbuild = true;
											break;
										}
										case IResourceDelta.CHANGED: {
											filterbuild = (filters.getFlags() & IResourceDelta.REPLACED) > 0;
											break;
										}
									}
									if(filterbuild) {
										break;
									}
								}
							}
							if (manifest != null || filterbuild) {
								if (DEBUG) {
									System.out.println("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));
							}
						}
					}
				}	
			}
			Util.updateMonitor(monitor, 0);
		} catch(CoreException e) {
			IStatus status = e.getStatus();
			if (status == null || status.getCode() != ApiPlugin.REPORT_BASELINE_IS_DISPOSED) {
				throw e;
			}
			ApiPlugin.log(e);
		} finally {
			Util.updateMonitor(monitor, 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(monitor, 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());
					}
				}
				BuildState.saveBuiltState(this.currentproject, this.buildstate);
				this.buildstate = null;
				Util.updateMonitor(monitor, 0);
			}
			if(monitor != null) {
				monitor.done();
			}
		}
		if (DEBUG) {
			System.out.println("Finished build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return projects;
	}
	
	/**
	 * 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 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 {
		try {
			BuildState.setLastBuiltState(this.currentproject, null);
			this.buildstate = new BuildState();
			SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_on_0, 4);
			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(monitor != null) {
				monitor.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(DEBUG) {
				System.out.println("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.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;
			}
		}
		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 {
			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 : {
					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());
			}
		} catch (CoreException e) {
			//ignore and continue
			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) {
			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)
	 */
	protected void clean(IProgressMonitor monitor) throws CoreException {
		this.currentproject = getProject();
		SubMonitor localmonitor = SubMonitor.convert(monitor, MessageFormat.format(BuilderMessages.CleaningAPIDescription, new String[] {this.currentproject.getName()}), 2);
		try {
			// clean up all existing markers
			cleanupUsageMarkers(this.currentproject);
			cleanupCompatibilityMarkers(this.currentproject);
			cleanupUnsupportedTagMarkers(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.getDefault().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(DEBUG) {
				System.out.println("Tried to look up bundle description for: " + workspaceModels[i].toString()); //$NON-NLS-1$
			}
		}
		return currentModel;
	}

	/**
	 * Returns a listing of deltas for this project and for dependent projects
	 * @param projects
	 * @return
	 */
	IResourceDelta[] getDeltas(IProject[] projects) {
		if(DEBUG) {
			System.out.println("Searching for deltas for build of project: "+this.currentproject.getName()); //$NON-NLS-1$
		}
		ArrayList deltas = new ArrayList();
		IResourceDelta delta = getDelta(this.currentproject);
		if(delta != null) {
			if (DEBUG) {
				System.out.println("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 (DEBUG) {
					System.out.println("Found a delta: " + delta); //$NON-NLS-1$
				}
				deltas.add(delta);
			}
		}
		return (IResourceDelta[]) 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 projects = new ArrayList();
		try {
			IJavaProject javaProject = JavaCore.create(this.currentproject);
			HashSet blocations = new HashSet();
			blocations.add(javaProject.getOutputLocation());
			this.output_locs.put(this.currentproject, blocations);
			HashSet slocations = new HashSet();
			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);
						}
					}
				}
				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 bins = new HashSet();
					HashSet srcs = new HashSet();
					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 getProjectOutputPaths(IProject project) {
		return (HashSet) 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()
	 */
	public String toString() {
		return NLS.bind(BuilderMessages.ApiAnalysisBuilder_builder_for_project, this.currentproject.getName());
	}
}
