| /*=============================================================================# |
| # 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> 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= 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.updatedSourceUnits.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.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()); |
| |
| 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); |
| } |
| } |
| |
| } |