blob: df8062b91748180f83ad5e29d09452b583140946 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 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
*
*******************************************************************************/
package org.eclipse.dltk.internal.core.builder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.dltk.compiler.problem.DefaultProblemFactory;
import org.eclipse.dltk.compiler.problem.IProblemFactory;
import org.eclipse.dltk.compiler.problem.IProblemReporter;
import org.eclipse.dltk.compiler.problem.IProblemSeverityTranslator;
import org.eclipse.dltk.compiler.problem.ProblemCategory;
import org.eclipse.dltk.compiler.util.Util;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.builder.IBuildChange;
import org.eclipse.dltk.core.builder.IBuildContext;
import org.eclipse.dltk.core.builder.IBuildParticipant;
import org.eclipse.dltk.core.builder.IBuildParticipantExtension;
import org.eclipse.dltk.core.builder.IBuildParticipantExtension2;
import org.eclipse.dltk.core.builder.IBuildParticipantExtension3;
import org.eclipse.dltk.core.builder.IBuildParticipantExtension4;
import org.eclipse.dltk.core.builder.IBuildParticipantFilter;
import org.eclipse.dltk.core.builder.IBuildState;
import org.eclipse.dltk.core.builder.IProjectChange;
import org.eclipse.dltk.core.builder.IScriptBuilder;
import org.eclipse.dltk.internal.core.builder.BuildParticipantManager.BuildParticipantResult;
import org.eclipse.osgi.util.NLS;
public class StandardScriptBuilder implements IScriptBuilder {
private static final boolean DEBUG = false;
private static final int WORK_BUILD = 100;
private static final int PARALLEL_THRESHOLD = 10;
private class BuildModulesJob extends Job {
private Queue<ISourceModule> modules;
private int buildType;
private IBuildState state;
public BuildModulesJob(Queue<ISourceModule> modules, int buildType,
IBuildState state) {
super("Build Modules"); //$NON-NLS-1$
this.setSystem(true);
this.modules = modules;
this.buildType = buildType;
this.state = state;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (ISourceModule module; (module = modules.poll()) != null;) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
processModule(module, buildType, state);
}
return Status.OK_STATUS;
}
}
private List<IProblemReporter> fReporters = null;
@Override
public void prepare(IBuildChange change, IBuildState state,
IProgressMonitor monitor) throws CoreException {
if (participants != null) {
for (int i = 0; i < participants.length; ++i) {
final IBuildParticipant participant = participants[i];
if (participant instanceof IBuildParticipantExtension2) {
((IBuildParticipantExtension2) participant).prepare(change,
state);
}
}
}
}
@Override
public void build(IBuildChange change, IBuildState state,
IProgressMonitor monitor) throws CoreException {
// TODO progress reporting
buildExternalElements(change, monitor);
if (toolkit != null) {
buildNatureModules(change.getScriptProject(), change.getBuildType(),
change.getSourceModules(IProjectChange.DEFAULT), state,
SubMonitor.convert(monitor, WORK_BUILD));
}
final List<IFile> resourceChanges = change
.getResources(IProjectChange.DEFAULT);
if (!resourceChanges.isEmpty()) {
buildResources(resourceChanges,
new SubProgressMonitor(monitor, 10));
}
}
private void buildExternalElements(IBuildChange change,
IProgressMonitor monitor) throws CoreException {
final int buildType = change.getBuildType();
beginBuild(buildType, monitor);
final List<IBuildParticipantExtension2> extensions = selectExtension(
IBuildParticipantExtension2.class);
if (extensions != null) {
final List<ISourceModule> externalElements = change
.getExternalModules(IProjectChange.DEFAULT);
int remainingWork = externalElements.size();
for (final ISourceModule module : externalElements) {
if (monitor.isCanceled())
return;
monitor.subTask(NLS.bind(
Messages.ValidatorBuilder_buildExternalModuleSubTask,
String.valueOf(remainingWork),
module.getElementName()));
final ExternalModuleBuildContext context = new ExternalModuleBuildContext(
module, buildType);
try {
for (int i = 0; i < extensions.size(); ++i) {
if (monitor.isCanceled()) {
return;
}
extensions.get(i).buildExternalModule(context);
}
} catch (CoreException e) {
DLTKCore.error(NLS.bind(
Messages.StandardScriptBuilder_errorBuildingExternalModule,
module.getElementName()), e);
}
--remainingWork;
}
}
}
/**
* @return
*/
private <T> List<T> selectExtension(Class<T> clazz) {
if (participants != null) {
int count = 0;
for (int i = 0; i < participants.length; ++i) {
final IBuildParticipant participant = participants[i];
if (clazz.isInstance(participant)) {
++count;
}
}
if (count != 0) {
final List<T> result = new ArrayList<>(count);
for (int i = 0; i < participants.length; ++i) {
final IBuildParticipant participant = participants[i];
if (clazz.isInstance(participant)) {
@SuppressWarnings("unchecked")
final T casted = (T) participant;
result.add(casted);
}
}
return result;
}
}
return null;
}
private void buildNatureModules(final IScriptProject project, int buildType,
final List<ISourceModule> modules, IBuildState state,
final IProgressMonitor monitor) {
final long startTime = DEBUG ? System.currentTimeMillis() : 0;
beginBuild(buildType, monitor);
monitor.beginTask(Messages.ValidatorBuilder_buildingModules,
modules.size());
try {
if (participants.length == 0 || modules.isEmpty()) {
return;
}
if (fReporters == null) {
fReporters = Collections.synchronizedList(
new ArrayList<IProblemReporter>(modules.size()));
}
int availableProcessors = Runtime.getRuntime()
.availableProcessors();
if (modules.size() >= PARALLEL_THRESHOLD
&& availableProcessors > 2) {
processInParallel(modules, buildType, state, project, monitor);
} else {
processInSingleThread(modules, buildType, state, monitor);
}
} finally {
monitor.done();
if (DEBUG) {
System.out.println("Build " + project.getElementName() + "(" //$NON-NLS-1$ //$NON-NLS-2$
+ modules.size() + ") in " //$NON-NLS-1$
+ (System.currentTimeMillis() - startTime) + " ms"); //$NON-NLS-1$
}
}
}
private void processInSingleThread(List<ISourceModule> modules,
int buildType, IBuildState state, IProgressMonitor monitor) {
int numberOfScannedFiles = 0;
for (Iterator<ISourceModule> j = modules.iterator(); j.hasNext();) {
if (monitor.isCanceled()) {
return;
}
final ISourceModule module = j.next();
monitor.subTask(NLS.bind(
Messages.ValidatorBuilder_buildModuleSubTask,
(int) ((numberOfScannedFiles * 100f) / modules.size()),
module.getElementName()));
processModule(module, buildType, state);
monitor.worked(1);
++numberOfScannedFiles;
}
}
private void processInParallel(final List<ISourceModule> modules,
int buildType, IBuildState state, final IScriptProject project,
final IProgressMonitor monitor) {
final Queue<ISourceModule> queue = new ArrayBlockingQueue<>(
modules.size(), true, modules);
int maxThreads = Math.min(modules.size() / 2,
Runtime.getRuntime().availableProcessors());
List<Job> jobs = new ArrayList<>(maxThreads);
try {
for (int i = 0; i < maxThreads; i++) {
Job job = new BuildModulesJob(queue, buildType, state);
job.schedule();
jobs.add(job);
}
int lastNumberOfScannedFiles = 0;
for (Job job : jobs) {
while (!job.join(100, null)) {
if (monitor.isCanceled()) {
for (Job tmpJob : jobs) {
tmpJob.cancel();
}
continue;
}
int numberOfScannedFiles = modules.size() - queue.size();
monitor.subTask(NLS.bind(
Messages.ValidatorBuilder_buildModuleSubTask,
(int) ((numberOfScannedFiles * 100f)
/ modules.size()),
project.getElementName()));
int steps = numberOfScannedFiles - lastNumberOfScannedFiles;
monitor.worked(steps);
lastNumberOfScannedFiles += steps;
}
}
} catch (OperationCanceledException e) {
DLTKCore.error(e);
} catch (InterruptedException e) {
DLTKCore.error(e);
}
}
private void processModule(ISourceModule module, int buildType,
IBuildState state) {
final SourceModuleBuildContext context = new SourceModuleBuildContext(
problemFactory, module, buildType, state);
if (context.reporter != null) {
buildModule(context);
fReporters.add(context.reporter);
}
}
/**
* Calls {@link IBuildParticipantExtension#beginBuild(int)} for all
* {@link #participants}. Returns <code>true</code> if it was called for
* some of them so it is required to call
* {@link IBuildParticipantExtension#endBuild()}. If called multiple times
* only first time actual work are performed, so subsequent calls returns
* result of the first call.
*
* @param buildType
* @param monitor
* @return
*/
private void beginBuild(int buildType, IProgressMonitor monitor) {
if (!beginBuildDone) {
monitor.subTask(Messages.ValidatorBuilder_InitializeBuilders);
endBuildNeeded = false;
int count = 0;
for (int j = 0; j < participants.length; ++j) {
final IBuildParticipant participant = participants[j];
final boolean useParticipant;
if (participant instanceof IBuildParticipantExtension) {
useParticipant = ((IBuildParticipantExtension) participant)
.beginBuild(buildType);
endBuildNeeded = true;
} else {
useParticipant = true;
}
if (useParticipant) {
if (count < j) {
participants[count] = participants[j];
}
++count;
}
}
participants = BuildParticipantManager.copyFirst(participants,
count);
BuildParticipantManager.notifyDependents(participants,
participantDependencies);
beginBuildDone = true;
}
}
private void buildModule(IBuildContext context) {
IBuildParticipant[] selected = participants;
for (IBuildParticipantFilter filter : filters) {
selected = filter.filter(selected, context);
if (selected == null || selected.length == 0) {
return;
}
}
try {
for (int k = 0; k < selected.length; ++k) {
selected[k].build(context);
}
} catch (CoreException e) {
DLTKCore.error(Messages.StandardScriptBuilder_errorBuildingModule,
e);
} finally {
for (IBuildParticipant participant : participants) {
if (participant instanceof IBuildParticipantExtension4) {
((IBuildParticipantExtension4) participant)
.afterBuild(context);
}
}
}
}
protected IStatus buildResources(List<IFile> resources,
IProgressMonitor monitor) {
try {
monitor.beginTask(Util.EMPTY_STRING, resources.size());
try {
for (final IFile resource : resources) {
monitor.subTask(NLS.bind(
Messages.ValidatorBuilder_clearingResourceMarkers,
resource.getName()));
problemFactory.deleteMarkers(resource);
monitor.worked(1);
}
} catch (CoreException e) {
final String msg = Messages.ValidatorBuilder_errorDeleteResourceMarkers;
DLTKCore.error(msg, e);
}
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
@Override
public void clean(IScriptProject project, IProgressMonitor monitor) {
final List<IBuildParticipantExtension3> extensions = selectExtension(
IBuildParticipantExtension3.class);
if (extensions != null) {
for (IBuildParticipantExtension3 extension : extensions) {
extension.clean();
}
}
final IProject p = project.getProject();
try {
problemFactory.deleteMarkers(p);
} catch (CoreException e) {
DLTKCore.error(
NLS.bind(Messages.StandardScriptBuilder_errorCleaning,
p.getName()),
e);
}
}
private boolean beginBuildDone = false;
private boolean endBuildNeeded = false;
private IBuildParticipant[] participants = null;
private Map<IBuildParticipant, List<IBuildParticipant>> participantDependencies = null;
private IBuildParticipantFilter[] filters = null;
private IDLTKLanguageToolkit toolkit = null;
private IProblemFactory problemFactory = null;
protected IDLTKLanguageToolkit getLanguageToolkit() {
return toolkit;
}
@Override
public boolean initialize(IScriptProject project) {
toolkit = project.getLanguageToolkit();
if (toolkit != null) {
final BuildParticipantResult result = BuildParticipantManager
.getBuildParticipants(project, toolkit.getNatureId());
participants = result.participants;
participantDependencies = result.dependencies;
}
if (participants == null || participants.length == 0) {
return false;
}
filters = BuildParticipantManager.getFilters(project,
toolkit.getNatureId(), this);
problemFactory = createProblemFactory();
beginBuildDone = false;
endBuildNeeded = false;
return true;
}
protected IProblemFactory createProblemFactory() {
if (toolkit != null) {
return DLTKLanguageManager.getProblemFactory(toolkit.getNatureId());
} else {
return new DefaultProblemFactory();
}
}
@Override
public void endBuild(IScriptProject project, IBuildState state,
IProgressMonitor monitor) {
if (endBuildNeeded) {
monitor.subTask(Messages.ValidatorBuilder_finalizeBuild);
final IProgressMonitor finalizeMonitor = new SubTaskProgressMonitor(
monitor, Messages.ValidatorBuilder_finalizeBuild);
for (int j = 0; j < participants.length; ++j) {
final IBuildParticipant participant = participants[j];
if (participant instanceof IBuildParticipantExtension) {
((IBuildParticipantExtension) participant)
.endBuild(finalizeMonitor);
}
}
endBuildNeeded = false;
}
if (fReporters != null) {
final IProblemSeverityTranslator severityTranslator = problemFactory
.createSeverityTranslator(project);
for (IProblemReporter reporter : fReporters) {
final BuildProblemReporter buildReporter = (BuildProblemReporter) reporter;
if (buildReporter.hasCategory(ProblemCategory.IMPORT)) {
state.recordImportProblem(
buildReporter.resource.getFullPath());
}
buildReporter.flush(severityTranslator);
}
fReporters = null;
}
participants = null;
participantDependencies = null;
filters = null;
toolkit = null;
problemFactory = null;
beginBuildDone = false;
}
}