Bug 575348: [Ltk-Project] Add extensible builder extracted from
Tex/Wikitext
Change-Id: I4ff65967af1a4dc7b1d4f8716efb603fe5546d25
diff --git a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
index 521b666..d468e3f 100644
--- a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
+++ b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
@@ -41,4 +41,6 @@
org.eclipse.statet.ltk.model.core.element,
org.eclipse.statet.ltk.model.core.impl,
org.eclipse.statet.ltk.model.core.util,
+ org.eclipse.statet.ltk.project.core,
+ org.eclipse.statet.ltk.project.core.builder,
org.eclipse.statet.ltk.refactoring.core
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/LtkProject.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/LtkProject.java
new file mode 100644
index 0000000..5da7c67
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/LtkProject.java
@@ -0,0 +1,31 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core;
+
+import org.eclipse.core.resources.IProject;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
+
+
+@NonNullByDefault
+public interface LtkProject extends PreferenceAccess {
+
+
+ IProject getProject();
+
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildParticipant.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildParticipant.java
new file mode 100644
index 0000000..7de7f24
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildParticipant.java
@@ -0,0 +1,92 @@
+/*=============================================================================#
+ # Copyright (c) 2014, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core.builder;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.SubMonitor;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+
+
+@NonNullByDefault
+public class ProjectBuildParticipant<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit> {
+
+
+ TProject ltkProject= nonNullLateInit();
+
+ int buildType;
+
+ boolean enabled;
+
+
+ public ProjectBuildParticipant() {
+ }
+
+
+ public final TProject getLtkProject() {
+ return this.ltkProject;
+ }
+
+ public final int getBuildType() {
+ return this.buildType;
+ }
+
+ public void init() {
+ }
+
+ protected final void setEnabled(final boolean enabled) {
+ this.enabled= enabled;
+ }
+
+ public final boolean isEnabled() {
+ return this.enabled;
+ }
+
+ /**
+ * @param file the file to clear
+ * @throws CoreException
+ */
+ public void clearSourceUnit(final IFile file) throws CoreException {
+ }
+
+ /**
+ * @param sourceUnit the added/changed source unit
+ * @param monitor SubMonitor-recommended
+ */
+ public void handleSourceUnitUpdated(final TSourceUnit sourceUnit,
+ final SubMonitor m) throws CoreException {
+ // update index
+ }
+
+ /**
+ * @param file the removed resource
+ * @param monitor SubMonitor-recommended
+ */
+ public void handleSourceUnitRemoved(final IFile file,
+ final SubMonitor m) throws CoreException {
+ // remove from index
+ }
+
+ public void finish(final SubMonitor m) throws CoreException {
+ }
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildTask.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildTask.java
new file mode 100644
index 0000000..25d0eee
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectBuildTask.java
@@ -0,0 +1,342 @@
+/*=============================================================================#
+ # Copyright (c) 2014, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core.builder;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.osgi.util.NLS;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.model.core.LtkModels;
+import org.eclipse.statet.ltk.model.core.SourceUnitManager;
+import org.eclipse.statet.ltk.model.core.element.SourceUnit;
+import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+
+
+@NonNullByDefault
+public abstract class ProjectBuildTask<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit,
+ TParticipant extends ProjectBuildParticipant<TProject, TSourceUnit>>
+ extends ProjectTask<TProject, TSourceUnit, TParticipant>
+ implements IResourceVisitor, IResourceDeltaVisitor {
+
+
+ private final static class VirtualSourceUnit {
+
+ private final IFile file;
+
+ private final String modelTypeId;
+
+
+ public VirtualSourceUnit(final IFile file, final String modelTypeId) {
+ this.file= file;
+ this.modelTypeId= modelTypeId;
+ }
+
+
+ public IFile getResource() {
+ return this.file;
+ }
+
+ public String getModelTypeId() {
+ return this.modelTypeId;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return this.file.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.file.toString();
+ }
+
+ }
+
+
+ private final SourceUnitManager suManager= LtkModels.getSourceUnitManager();
+
+ private final List<TSourceUnit> updatetSourceUnits;
+ private final List<VirtualSourceUnit> removedFiles;
+
+ private SubMonitor visitProgress= nonNullLateInit();
+
+
+ public ProjectBuildTask(final ProjectTaskBuilder<TProject, TSourceUnit, TParticipant> builder) {
+ super(builder);
+
+ this.updatetSourceUnits= new ArrayList<>();
+ this.removedFiles= new ArrayList<>();
+ }
+
+ private void dispose(final SubMonitor m) {
+ m.setWorkRemaining(this.updatetSourceUnits.size());
+ for (final var unit : this.updatetSourceUnits) {
+ unit.disconnect(m.newChild(1));
+ }
+ }
+
+
+ public void build(final int kind,
+ final SubMonitor m) throws CoreException {
+ try {
+ m.beginTask(NLS.bind("Preparing TeX build for ''{0}''", getProject().getName()),
+ 1 + 2 + 8 + 1 );
+
+ final IResourceDelta delta;
+ switch (kind) {
+ case IncrementalProjectBuilder.AUTO_BUILD:
+ case IncrementalProjectBuilder.INCREMENTAL_BUILD:
+ delta= getBuilder().getDelta(getProject());
+ m.worked(1);
+ break;
+ default:
+ delta= null;
+ }
+
+ if (m.isCanceled()) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ m.setWorkRemaining(2 + 8 + 1);
+
+ this.visitProgress= m.newChild(2);
+ if (delta != null) {
+ setBuildType(IncrementalProjectBuilder.INCREMENTAL_BUILD);
+ delta.accept(this);
+ }
+ else {
+ setBuildType(IncrementalProjectBuilder.FULL_BUILD);
+ getProject().accept(this);
+ }
+ this.visitProgress= null;
+
+ if (m.isCanceled()) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ processChanges(m.newChild(8, SubMonitor.SUPPRESS_NONE));
+ }
+ finally {
+ m.setWorkRemaining(1);
+ dispose(m.newChild(1));
+
+ finish();
+ }
+ }
+
+
+ @Override
+ public boolean visit(final IResourceDelta delta) throws CoreException {
+ final IResource resource= delta.getResource();
+ if (resource.getType() == IResource.FILE) {
+ if (this.visitProgress.isCanceled()) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ this.visitProgress.setWorkRemaining(100);
+
+ try {
+ switch (delta.getKind()) {
+ case IResourceDelta.ADDED:
+ case IResourceDelta.CHANGED:
+ visitFileAdded((IFile)resource, delta, this.visitProgress.newChild(1));
+ break;
+ case IResourceDelta.REMOVED:
+ visitFileRemove((IFile)resource, delta, this.visitProgress.newChild(1));
+ break;
+ default:
+ break;
+ }
+ }
+ catch (final Exception e) {
+ this.status.add(new Status(IStatus.ERROR, getBuilderDefinition().getBundleId(),
+ NLS.bind("An error occurred when checking file ''{0}''", resource.getFullPath()),
+ e ));
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visit(final IResource resource) throws CoreException {
+ if (resource.getType() == IResource.FILE) {
+ this.visitProgress.setWorkRemaining(100);
+
+ visitFileAdded((IFile)resource, null, this.visitProgress.newChild(1));
+ }
+ return true;
+ }
+
+ private void visitFileAdded(final IFile file, final @Nullable IResourceDelta delta,
+ final SubMonitor m) throws CoreException {
+ final IContentDescription contentDescription= file.getContentDescription();
+ if (contentDescription == null) {
+ return;
+ }
+ final IContentType contentType= contentDescription.getContentType();
+ if (contentType == null) {
+ return;
+ }
+ final var definition= getBuilderDefinition();
+ final int contentId= definition.checkSourceUnitContent(contentType);
+ if (contentId == 0) {
+ final SourceUnit unit= this.suManager.getSourceUnit(
+ Ltk.PERSISTENCE_CONTEXT, file, contentType, true, m );
+ if (definition.getSourceUnitType().isInstance(unit)) {
+ this.updatetSourceUnits.add((TSourceUnit)unit);
+ }
+ else {
+ if (unit != null) {
+ unit.disconnect(m);
+ }
+ clear(file, null);
+ }
+ }
+ }
+
+ private void visitFileRemove(final IFile file, final IResourceDelta delta,
+ final SubMonitor m) throws CoreException {
+ // There is no contentDescription for removed files
+// final IContentDescription contentDescription= file.getContentDescription();
+
+// if (contentType.isKindOf(LTX_CONTENT_TYPE)) {
+// final ModelTypeDescriptor modelType= this.modelRegistry.getModelTypeForContentType(contentType.getId());
+// final VirtualSourceUnit unit= new VirtualSourceUnit(file, (modelType != null) ? modelType.getId() : null);
+// this.removedLtxFiles.add(unit);
+//
+// if ((delta != null && (delta.getFlags() & IResourceDelta.MOVED_TO) != 0)) {
+// final IResource movedTo= file.getWorkspace().getRoot().findMember(delta.getMovedToPath());
+// if (movedTo instanceof IFile) {
+// final DocProject movedToProject= DocProject.getDocProject(movedTo.getProject());
+// if (modelType == null
+// || movedToProject == null || movedToProject == getDocProject()
+// || !getDocProjectBuilder().hasBeenBuilt(movedToProject.getProject()) ) {
+// clearLtx((IFile) movedTo, getParticipant(unit.getModelTypeId()));
+// }
+// }
+// }
+// }
+ }
+
+ private void processChanges(final SubMonitor m) throws CoreException {
+ m.beginTask(String.format("Analyzing %1$s file(s) of '%2$s'",
+ getBuilderDefinition().getProjectTypeLabel(),
+ getProject().getName()),
+ 10 + 10 );
+
+ { final SubMonitor m1= m.newChild(10);
+ int workRemaining= this.removedFiles.size() + this.updatetSourceUnits.size() * 5;
+ for (final VirtualSourceUnit unit : this.removedFiles) {
+ m1.setWorkRemaining(workRemaining--);
+ try {
+ final var participant= getParticipant(unit.getModelTypeId());
+
+ if (participant != null) {
+ participant.handleSourceUnitRemoved(unit.getResource(), m1.newChild(1));
+ }
+ }
+ catch (final Exception e) {
+ this.status.add(new Status(IStatus.ERROR, getBuilderDefinition().getBundleId(),
+ NLS.bind("An error occurred when processing removed file ''{0}''.",
+ unit.getResource() ),
+ e ));
+ }
+ if (m1.isCanceled()) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ }
+
+ if (!this.updatetSourceUnits.isEmpty()) {
+ for (final var sourceUnit : this.updatetSourceUnits) {
+ m1.setWorkRemaining(workRemaining); workRemaining-= 5;
+ try {
+ final var participant= getParticipant(sourceUnit.getModelTypeId());
+
+ clear((IFile)sourceUnit.getResource(), participant);
+
+ reconcile(sourceUnit, m1.newChild(3));
+
+ if (participant != null && participant.isEnabled()) {
+ participant.handleSourceUnitUpdated(sourceUnit, m1.newChild(2));
+ }
+ }
+ catch (final CancellationException e) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ catch (final Exception e) {
+ this.status.add(new Status(IStatus.ERROR, getBuilderDefinition().getBundleId(),
+ NLS.bind("An error occurred when processing file ''{0}''.",
+ sourceUnit.getResource() ),
+ e ));
+ }
+ if (m1.isCanceled()) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ }
+ }
+ }
+ }
+ { final SubMonitor m1= m.newChild(10);
+ final var participants= getParticipants();
+ int workRemaining= participants.size();
+ for (final var participant : participants) {
+ m1.setWorkRemaining(workRemaining--);
+ if (participant.isEnabled()) {
+ try {
+ participant.finish(m1.newChild(1));
+ }
+ catch (final Exception e) {
+ this.status.add(new Status(IStatus.ERROR, getBuilderDefinition().getBundleId(),
+ String.format("An error occurred when processing %1$s file(s) in '%2$s'.",
+ getBuilderDefinition().getProjectTypeLabel(),
+ getProject().getName() ),
+ e ));
+ }
+ }
+ }
+ }
+ }
+
+ protected abstract void reconcile(TSourceUnit sourceUnit, SubMonitor m);
+
+
+ private void clear(final IFile file, final @Nullable TParticipant partitipant)
+ throws CoreException {
+ if (partitipant != null) {
+ partitipant.clearSourceUnit(file);
+ }
+ }
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectCleanTask.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectCleanTask.java
new file mode 100644
index 0000000..77b23ab
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectCleanTask.java
@@ -0,0 +1,40 @@
+/*=============================================================================#
+ # Copyright (c) 2014, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core.builder;
+
+import org.eclipse.core.runtime.SubMonitor;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+
+
+@NonNullByDefault
+public class ProjectCleanTask<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit,
+ TBuildParticipant extends ProjectBuildParticipant<TProject, TSourceUnit>>
+ extends ProjectTask<TProject, TSourceUnit, TBuildParticipant> {
+
+
+ public ProjectCleanTask(final ProjectTaskBuilder<TProject, TSourceUnit, TBuildParticipant> builder) {
+ super(builder);
+ }
+
+
+ public void clean(final SubMonitor m) {
+ }
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTask.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTask.java
new file mode 100644
index 0000000..fd1a082
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTask.java
@@ -0,0 +1,127 @@
+/*=============================================================================#
+ # Copyright (c) 2014, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core.builder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.MultiStatus;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.model.core.LtkModels;
+import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+import org.eclipse.statet.ltk.project.core.builder.ProjectTaskBuilder.BuilderDefinition;
+
+
+@NonNullByDefault
+public abstract class ProjectTask<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit,
+ TParticipant extends ProjectBuildParticipant<TProject, TSourceUnit>> {
+
+
+ @SuppressWarnings("rawtypes")
+ private static final ProjectBuildParticipant NO_PARTICIPANT= new ProjectBuildParticipant<>();
+
+
+ private final Map<String, TParticipant> participants= new HashMap<>();
+
+ private final ProjectTaskBuilder<TProject, TSourceUnit, TParticipant> projectBuilder;
+ private final IProject project;
+
+ private int buildType;
+
+ protected final MultiStatus status;
+
+
+ public ProjectTask(final ProjectTaskBuilder<TProject, TSourceUnit, TParticipant> projectBuilder) {
+ this.projectBuilder= projectBuilder;
+ this.project= projectBuilder.getProject();
+
+ this.status= new MultiStatus(getBuilderDefinition().getBundleId(), 0,
+ String.format("%1$s build status for '%2$s'",
+ getBuilderDefinition().getProjectTypeLabel(),
+ getProject().getName() ),
+ null );
+ }
+
+
+ protected final ProjectTaskBuilder<TProject, TSourceUnit, TParticipant> getBuilder() {
+ return this.projectBuilder;
+ }
+
+ protected final BuilderDefinition<TProject, TSourceUnit, TParticipant> getBuilderDefinition() {
+ return this.projectBuilder.getBuilderDefinition();
+ }
+
+ public final IProject getProject() {
+ return this.project;
+ }
+
+ public void setBuildType(final int buildType) {
+ this.buildType= buildType;
+ }
+
+ protected final Collection<TParticipant> getParticipants() {
+ final var values= this.participants.values();
+ final List<TParticipant> list= new ArrayList<>(values.size());
+ for (final var participant : values) {
+ if (participant != null) {
+ list.add(participant);
+ }
+ }
+ return list;
+ }
+
+ protected final @Nullable TParticipant getParticipant(final String modelTypeId) {
+ if (modelTypeId == null) {
+ return null;
+ }
+ @Nullable TParticipant participant= this.participants.get(modelTypeId);
+ if (participant == null) {
+ participant= loadParticipant(modelTypeId);
+ this.participants.put(modelTypeId, participant);
+ }
+ return (participant != NO_PARTICIPANT) ? participant : null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private TParticipant loadParticipant(final String modelTypeId) {
+ final @Nullable TParticipant participant= (TParticipant)LtkModels.getModelAdapter(
+ modelTypeId, getBuilderDefinition().getParticipantType() );
+ if (participant == null) {
+ return (TParticipant)NO_PARTICIPANT;
+ }
+ participant.ltkProject= getBuilder().getLtkProject();
+ participant.buildType= this.buildType;
+ participant.enabled= false;
+ participant.init();
+ return participant;
+ }
+
+
+ protected void finish() {
+ if (!this.status.isOK()) {
+ getBuilder().log(this.status);
+ }
+ }
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTaskBuilder.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTaskBuilder.java
new file mode 100644
index 0000000..7fb7d27
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/project/core/builder/ProjectTaskBuilder.java
@@ -0,0 +1,209 @@
+/*=============================================================================#
+ # Copyright (c) 2014, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.project.core.builder;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.util.Map;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+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.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.osgi.util.NLS;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+
+
+@NonNullByDefault
+public abstract class ProjectTaskBuilder<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit,
+ TParticipant extends ProjectBuildParticipant<TProject, TSourceUnit>>
+ extends IncrementalProjectBuilder {
+
+
+ public static abstract class BuilderDefinition<TProject extends LtkProject,
+ TSourceUnit extends WorkspaceSourceUnit,
+ TBuildParticipant extends ProjectBuildParticipant<TProject, TSourceUnit>> {
+
+
+ private final String bundleId;
+ private final Plugin bundle;
+
+ private final String projectNatureId;
+ private final String projectTypeLabel;
+
+ private final Class<TSourceUnit> sourceUnitType;
+
+ private final Class<TBuildParticipant> participantType;
+
+
+ public BuilderDefinition(final String bundleId, final Plugin bundle,
+ final String projectNatureId, final String projectLabel,
+ final Class<TSourceUnit> sourceUnitType,
+ final Class<TBuildParticipant> participantType) {
+ this.bundleId= bundleId;
+ this.bundle= bundle;
+
+ this.projectNatureId= projectNatureId;
+ this.projectTypeLabel= projectLabel;
+
+ this.sourceUnitType= sourceUnitType;
+
+ this.participantType= participantType;
+ }
+
+
+ public String getBundleId() {
+ return this.bundleId;
+ }
+
+ public Plugin getBundle() {
+ return this.bundle;
+ }
+
+
+ public String getProjectNatureId() {
+ return this.projectNatureId;
+ }
+
+ public String getProjectTypeLabel() {
+ return this.projectTypeLabel;
+ }
+
+
+ public abstract int checkSourceUnitContent(final IContentType contentType);
+
+ public Class<TSourceUnit> getSourceUnitType() {
+ return this.sourceUnitType;
+ }
+
+
+ public Class<TBuildParticipant> getParticipantType() {
+ return this.participantType;
+ }
+
+ }
+
+
+ private final BuilderDefinition<TProject, TSourceUnit, TParticipant> definition;
+
+ private TProject ltkProject= nonNullLateInit();
+
+
+ public ProjectTaskBuilder(final BuilderDefinition<TProject, TSourceUnit, TParticipant> definition) {
+ this.definition= definition;
+ }
+
+
+ public final BuilderDefinition<TProject, TSourceUnit, TParticipant> getBuilderDefinition() {
+ return this.definition;
+ }
+
+ public TProject getLtkProject() {
+ return this.ltkProject;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void startupOnInitialize() {
+ super.startupOnInitialize();
+
+ try {
+ this.ltkProject= (TProject)getProject().getNature(this.definition.getProjectNatureId());
+ }
+ catch (final CoreException e) {
+ log(e.getStatus());
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void check(final SubMonitor progress) throws CoreException {
+ if (this.ltkProject == null) {
+ throw new CoreException(new Status(IStatus.ERROR, this.definition.getBundleId(),
+ NLS.bind("{0} project nature is missing.",
+ this.definition.getProjectTypeLabel() )));
+ }
+ }
+
+
+ @Override
+ protected IProject @Nullable[] build(final int kind,
+ final @Nullable Map<String, @Nullable String> args,
+ final @Nullable IProgressMonitor monitor) throws CoreException {
+ final SubMonitor m= SubMonitor.convert(monitor, 1 + 20);
+ try {
+ check(m.newChild(1, SubMonitor.SUPPRESS_NONE));
+
+ final var projectTask= createBuildTask();
+ projectTask.build(kind, m.newChild(20, SubMonitor.SUPPRESS_NONE));
+
+ return null;
+ }
+ catch (final CoreException e) {
+ if (e.getStatus().getSeverity() == IStatus.CANCEL) {
+ throw new OperationCanceledException();
+ }
+ throw e;
+ }
+ finally {
+ m.done();
+ }
+ }
+
+ @Override
+ protected void clean(final @Nullable IProgressMonitor monitor) throws CoreException {
+ final SubMonitor m= SubMonitor.convert(monitor, 1 + 20);
+ try {
+ check(m.newChild(1, SubMonitor.SUPPRESS_NONE));
+
+ final var projectTask= createCleanTask();
+ projectTask.clean(m.newChild(20, SubMonitor.SUPPRESS_NONE));
+ }
+ catch (final CoreException e) {
+ if (e.getStatus().getSeverity() == IStatus.CANCEL) {
+ throw new OperationCanceledException();
+ }
+ throw e;
+ }
+ finally {
+ m.done();
+ }
+ }
+
+
+ protected abstract ProjectBuildTask<TProject, TSourceUnit, TParticipant> createBuildTask();
+
+ protected abstract ProjectCleanTask<TProject, TSourceUnit, TParticipant> createCleanTask();
+
+
+ protected void log(final IStatus status) {
+ final var log= this.definition.getBundle().getLog();
+ if (log != null) {
+ log.log(status);
+ }
+ }
+
+}