blob: 90f37743bdf600f63a20dc3cfa8432bdb283acca [file] [log] [blame]
/*=============================================================================#
# 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.build.SourceUnitModelContainer;
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> updatedSourceUnits;
private final List<VirtualSourceUnit> removedFiles;
private SubMonitor visitProgress= nonNullLateInit();
public ProjectBuildTask(final ProjectTaskBuilder<TProject, TSourceUnit, TParticipant> builder) {
super(builder);
this.updatedSourceUnits= new ArrayList<>();
this.removedFiles= new ArrayList<>();
}
private void dispose(final SubMonitor m) {
m.setWorkRemaining(this.updatedSourceUnits.size());
for (final var unit : this.updatedSourceUnits) {
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= nonNullLateInit();
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(),
String.format("An error occurred when checking file '%1$s'",
resource.getProjectRelativePath() ),
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.updatedSourceUnits.add((TSourceUnit)unit);
}
else {
if (unit != null) {
unit.disconnect(m);
}
clear(file);
}
}
}
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.updatedSourceUnits.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.updatedSourceUnits.isEmpty()) {
for (final var sourceUnit : this.updatedSourceUnits) {
m1.setWorkRemaining(workRemaining); workRemaining-= 5;
try {
final var participant= getParticipant(sourceUnit.getModelTypeId());
reconcileSourceUnit(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).",
getBuilderDefinition().getProjectTypeLabel() ),
e ));
}
}
}
}
}
protected void clearSourceUnit(final SourceUnitModelContainer<?, ?> adapter) {
try {
adapter.clearIssues();
}
catch (final CoreException e) {
this.status.add(new Status(IStatus.ERROR, getBuilderDefinition().getBundleId(),
String.format("An error occurred when clearing issue(s) of source unit '%1$s'.",
adapter.getSourceUnit() ),
e ));
}
}
protected abstract void reconcileSourceUnit(TSourceUnit sourceUnit, SubMonitor m);
}