| /*=============================================================================# |
| # Copyright (c) 2009, 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.internal.r.core.builder; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| 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.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.MultiStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| 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.collections.ImList; |
| |
| import org.eclipse.statet.internal.r.core.Messages; |
| import org.eclipse.statet.internal.r.core.RCorePlugin; |
| import org.eclipse.statet.internal.r.core.RProjectNature; |
| import org.eclipse.statet.internal.r.core.sourcemodel.RModelManagerImpl; |
| import org.eclipse.statet.ltk.buildpath.core.BuildpathElement; |
| import org.eclipse.statet.ltk.buildpath.core.BuildpathUtils; |
| import org.eclipse.statet.ltk.core.Ltk; |
| import org.eclipse.statet.ltk.core.SourceContent; |
| import org.eclipse.statet.ltk.model.core.LtkModels; |
| import org.eclipse.statet.ltk.model.core.SourceUnitManager; |
| import org.eclipse.statet.r.core.RBuildpaths; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.RProject; |
| import org.eclipse.statet.r.core.RProjects; |
| import org.eclipse.statet.r.core.model.RModel; |
| import org.eclipse.statet.r.core.model.RSourceUnit; |
| import org.eclipse.statet.r.core.model.RWorkspaceSourceUnit; |
| |
| |
| public class RBuilder implements IResourceDeltaVisitor, IResourceVisitor { |
| |
| |
| public static void clearMarkers(final IResource resource, final int depth) { |
| try { |
| resource.deleteMarkers(RModel.R_MODEL_PROBLEM_MARKER, false, depth); |
| resource.deleteMarkers("org.eclipse.statet.r.resourceMarkers.Tasks", false, depth); //$NON-NLS-1$ |
| } |
| catch (final CoreException e) { |
| RCorePlugin.logError("R Builder: Failed to remove old markers.", e); |
| } |
| } |
| |
| |
| private static IContainer getContainer(final IProject project, final IPath path) { |
| return (path.isEmpty()) ? project : project.getFolder(path); |
| } |
| |
| |
| private final SourceUnitManager suManager= LtkModels.getSourceUnitManager(); |
| |
| private final List<IFile> toRemoveRSU= new ArrayList<>(); |
| private final ArrayList<RWorkspaceSourceUnit> toUpdateRSU= new ArrayList<>(); |
| |
| private final RModelManagerImpl modelManager; |
| |
| private MultiStatus statusCollector; |
| |
| private IProject project; |
| private IPath pkgRootPath; |
| private ImList<BuildpathElement> sourceContainters; |
| private BuildpathElement currentSourceContainer; |
| |
| |
| public RBuilder() { |
| this.modelManager= RCorePlugin.getInstance().getRModelManager(); |
| } |
| |
| |
| private void initBuildpath(final RProject project) { |
| this.sourceContainters= project.getRawBuildpath(); |
| this.pkgRootPath= project.getPkgRootPath(); |
| this.currentSourceContainer= null; |
| } |
| |
| private boolean isValidSourceFolder(final IResource resource) { |
| if (this.currentSourceContainer != null) { |
| if (!BuildpathUtils.isExcluded(resource, this.currentSourceContainer)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| public IStatus buildIncremental(final RProject rProject, final IResourceDelta delta, final IProgressMonitor monitor) { |
| this.project= rProject.getProject(); |
| this.statusCollector= new MultiStatus(RCore.BUNDLE_ID, 0, "R build status for " + this.project.getName(), null); |
| initBuildpath(rProject); |
| final SubMonitor m= SubMonitor.convert(monitor, 2 + 10 + 1); |
| try { |
| m.subTask(NLS.bind("Collecting resource changes of ''{0}''.", this.project.getName())); |
| IContainer pkgRoot= null; |
| if (this.pkgRootPath != null) { |
| pkgRoot= visitPkgRoot(delta.findMember(this.pkgRootPath.removeFirstSegments(1))); |
| } |
| m.worked(1); |
| |
| for (final BuildpathElement sourceContainer : this.sourceContainters) { |
| final IResourceDelta sourceDelta= delta.findMember( |
| sourceContainer.getPath().removeFirstSegments(1) ); |
| if (sourceDelta != null) { |
| this.currentSourceContainer= sourceContainer; |
| sourceDelta.accept(this); |
| } |
| |
| if (m.isCanceled()) { |
| throw new OperationCanceledException(); |
| } |
| } |
| m.worked(1); |
| |
| this.modelManager.getIndex().update(rProject, pkgRoot, |
| this.toRemoveRSU, this.toUpdateRSU, |
| this.statusCollector, m.newChild(10) ); |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| "An error occurred when indexing the project.", e )); |
| } |
| finally { |
| this.currentSourceContainer= null; |
| |
| for (final RSourceUnit su : this.toUpdateRSU) { |
| if (su != null) { |
| su.disconnect(m); |
| } |
| } |
| this.toRemoveRSU.clear(); |
| this.toUpdateRSU.clear(); |
| this.project= null; |
| } |
| return this.statusCollector; |
| } |
| |
| private IContainer visitPkgRoot(final IResourceDelta delta) throws CoreException { |
| final IResource resource; |
| if (delta != null |
| && (resource= delta.getResource()) instanceof IContainer ) { |
| final IContainer container= (IContainer) resource; |
| |
| this.project.deleteMarkers(RModel.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| |
| switch (delta.getKind()) { |
| |
| case IResourceDelta.ADDED: |
| case IResourceDelta.CHANGED: |
| clearMarkers(resource, IResource.DEPTH_ZERO); |
| break; |
| |
| case IResourceDelta.REMOVED: |
| if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { |
| final IResource movedTo= resource.getWorkspace().getRoot().findMember(delta.getMovedToPath()); |
| if (movedTo != null && !isRPkgRootLocation(movedTo)) { |
| clearMarkers(movedTo, IResource.DEPTH_ZERO); |
| } |
| } |
| break; |
| } |
| |
| visitPkgFile(delta.findMember(RBuildpaths.PKG_DESCRIPTION_FILE_PATH)); |
| |
| return container; |
| } |
| return null; |
| } |
| |
| private boolean visitPkgFile(final IResourceDelta delta) throws CoreException { |
| final IResource resource; |
| if (delta != null |
| && (resource= delta.getResource()) instanceof IFile) { |
| |
| switch (delta.getKind()) { |
| |
| case IResourceDelta.ADDED: |
| case IResourceDelta.CHANGED: |
| clearMarkers(resource, IResource.DEPTH_ZERO); |
| break; |
| |
| case IResourceDelta.REMOVED: |
| if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { |
| final IResource movedTo= resource.getWorkspace().getRoot().findMember(delta.getMovedToPath()); |
| if (movedTo != null && !isRPkgRootLocation(movedTo.getParent())) { |
| clearMarkers(movedTo, IResource.DEPTH_ZERO); |
| } |
| } |
| break; |
| } |
| |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(final IResourceDelta delta) throws CoreException { |
| final IResource resource= delta.getResource(); |
| try { |
| if (!isValidSourceFolder(resource)) { |
| return false; |
| } |
| |
| switch (delta.getKind()) { |
| |
| case IResourceDelta.ADDED: |
| case IResourceDelta.CHANGED: |
| if (resource instanceof IFile) { |
| final IFile file= (IFile) resource; |
| final IContentDescription contentDescription= file.getContentDescription(); |
| if (contentDescription == null) { |
| return true; |
| } |
| final IContentType contentType= contentDescription.getContentType(); |
| if (contentType == null) { |
| return true; |
| } |
| if (RCore.R_CONTENT_ID.equals(contentType.getId())) { |
| clearMarkers(resource, IResource.DEPTH_ZERO); |
| final RWorkspaceSourceUnit su= (RWorkspaceSourceUnit) this.suManager.getSourceUnit( |
| Ltk.PERSISTENCE_CONTEXT, file, contentType, true, null ); |
| if (su != null) { |
| this.toUpdateRSU.add(su); |
| } |
| return true; |
| } |
| if (RCore.RD_CONTENT_ID.equals(contentType.getId())) { |
| clearMarkers(resource, IResource.DEPTH_ZERO); |
| doParseRd(file); |
| return true; |
| } |
| } |
| return true; |
| |
| case IResourceDelta.REMOVED: |
| if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { |
| final IResource movedTo= resource.getWorkspace().getRoot().findMember(delta.getMovedToPath()); |
| if (movedTo != null && !isRSourceLocation(movedTo)) { |
| clearMarkers(movedTo, IResource.DEPTH_INFINITE); |
| } |
| } |
| if (resource instanceof IFile) { |
| this.toRemoveRSU.add((IFile) resource); |
| } |
| return true; |
| } |
| return true; |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| NLS.bind("An error occurred when checking ''{0}''", resource.getFullPath().toString()), e)); |
| return false; |
| } |
| } |
| |
| |
| public IStatus buildFull(final RProject rProject, final IProgressMonitor monitor) { |
| this.project= rProject.getProject(); |
| this.statusCollector= new MultiStatus(RCore.BUNDLE_ID, 0, "R build status for " + this.project.getName(), null); |
| initBuildpath(rProject); |
| final SubMonitor m= SubMonitor.convert(monitor, 2 + 10 + 1); |
| try { |
| m.subTask(NLS.bind("Collecting resource changes of ''{0}''.", this.project.getName())); |
| IContainer pkgRoot= null; |
| if (this.pkgRootPath != null) { |
| pkgRoot= visitPkgRoot( |
| getContainer(this.project, this.pkgRootPath.removeFirstSegments(1)) ); |
| |
| this.project.deleteMarkers(RModel.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| } |
| m.worked(1); |
| |
| for (final BuildpathElement sourceContainer : this.sourceContainters) { |
| final IResource resource= this.project.findMember( |
| sourceContainer.getPath().removeFirstSegments(1) ); |
| if (resource != null) { |
| this.currentSourceContainer= sourceContainer; |
| resource.accept(this); |
| } |
| |
| if (m.isCanceled()) { |
| throw new OperationCanceledException(); |
| } |
| } |
| m.worked(1); |
| |
| this.modelManager.getIndex().update(rProject, pkgRoot, |
| null, this.toUpdateRSU, |
| this.statusCollector, m.newChild(10) ); |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| "An error occurred when indexing the project.", e) ); |
| } |
| finally { |
| this.currentSourceContainer= null; |
| |
| for (final RSourceUnit su : this.toUpdateRSU) { |
| if (su != null) { |
| su.disconnect(m); |
| } |
| } |
| this.toRemoveRSU.clear(); |
| this.toUpdateRSU.clear(); |
| this.project= null; |
| } |
| return this.statusCollector; |
| } |
| |
| private IContainer visitPkgRoot(final IContainer container) throws CoreException { |
| this.project.deleteMarkers(RModel.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| |
| if (container.exists()) { |
| clearMarkers(container, IResource.DEPTH_ZERO); |
| visitPkgFile(container.findMember(RBuildpaths.PKG_DESCRIPTION_FILE_PATH)); |
| } |
| |
| return container; |
| } |
| |
| private void visitPkgFile(final IResource resource) { |
| if (resource instanceof IFile) { |
| clearMarkers(resource, IResource.DEPTH_ZERO); |
| } |
| } |
| |
| @Override |
| public boolean visit(final IResource resource) throws CoreException { |
| try { |
| if (!isValidSourceFolder(resource)) { |
| return false; |
| } |
| |
| if (resource instanceof IFile) { |
| final IFile file= (IFile) resource; |
| final IContentDescription contentDescription= file.getContentDescription(); |
| if (contentDescription == null) { |
| return true; |
| } |
| final IContentType contentType= contentDescription.getContentType(); |
| if (contentType == null) { |
| return true; |
| } |
| if (RCore.R_CONTENT_ID.equals(contentType.getId())) { |
| clearMarkers(resource, IResource.DEPTH_INFINITE); |
| final RWorkspaceSourceUnit su= (RWorkspaceSourceUnit) this.suManager.getSourceUnit( |
| Ltk.PERSISTENCE_CONTEXT, file, contentType, true, null ); |
| if (su != null) { |
| this.toUpdateRSU.add(su); |
| } |
| return true; |
| } |
| if (RCore.RD_CONTENT_ID.equals(contentType.getId())) { |
| clearMarkers(resource, IResource.DEPTH_INFINITE); |
| doParseRd(file); |
| return true; |
| } |
| } |
| return true; |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| NLS.bind("An error occurred when checking ''{0}''", resource.getFullPath().toString()), |
| e )); |
| return false; |
| } |
| } |
| |
| public void clean(final IProject project, final IProgressMonitor monitor) { |
| this.project= project; |
| try { |
| project.deleteMarkers(RModel.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| |
| clearMarkers(project, IResource.DEPTH_INFINITE); |
| |
| this.modelManager.getIndex().clear(project); |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| "An error occurred when indexing the project.", e) ); |
| } |
| finally { |
| this.project= null; |
| } |
| } |
| |
| |
| private boolean isRSourceLocation(final IResource resource) throws CoreException { |
| final IProject project= resource.getProject(); |
| if (project == this.project) { |
| // TODO check buildpath |
| return true; |
| } |
| if (project.hasNature(RProjects.R_NATURE_ID)) { |
| // TODO check buildpath |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isRPkgRootLocation(final IResource resource) throws CoreException { |
| final IProject project= resource.getProject(); |
| if (project == this.project) { |
| return (resource.getFullPath().equals(this.pkgRootPath)); |
| } |
| else if (project.hasNature(RProjects.R_PKG_NATURE_ID)) { |
| final RProjectNature rProject= RProjectNature.getRProject(project); |
| return (rProject != null && resource.getFullPath().equals(rProject.getPkgRootPath())); |
| } |
| return false; |
| } |
| |
| |
| /*-- Rd --*/ |
| |
| private final RTaskMarkerHandler taskMarkerHandler= new RTaskMarkerHandler(); |
| |
| protected void initRd(final RProject project) { |
| this.taskMarkerHandler.init(project); |
| } |
| |
| protected void doParseRd(final IFile file) throws CoreException { |
| try { |
| final SourceContent sourceContent= new SourceContent(0, readFile(file)); |
| this.taskMarkerHandler.setup(sourceContent, file); |
| new RdParser(sourceContent, this.taskMarkerHandler).check(); |
| } |
| catch (final CoreException e) { |
| this.statusCollector.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| NLS.bind("An error occurred when parsing Rd file ''{0}''", file.getFullPath().toString()), |
| e )); |
| } |
| } |
| |
| protected String readFile(final IFile file) throws CoreException { |
| String charset= null; |
| InputStream input= null; |
| try { |
| input= file.getContents(); |
| charset= file.getCharset(); |
| final BufferedReader reader= new BufferedReader(new InputStreamReader(input, charset)); |
| |
| final StringBuilder text= new StringBuilder(1000); |
| final char[] readBuffer= new char[2048]; |
| int n; |
| while ((n= reader.read(readBuffer)) > 0) { |
| text.append(readBuffer, 0, n); |
| } |
| |
| return text.toString(); |
| } |
| catch (final UnsupportedEncodingException e) { |
| throw new CoreException(new Status( |
| IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| NLS.bind(Messages.Builder_error_UnsupportedEncoding_message, new String[] { |
| charset, file.getName() } ), |
| e )); |
| } |
| catch (final IOException e) { |
| throw new CoreException(new Status( |
| IStatus.ERROR, RCore.BUNDLE_ID, 0, |
| NLS.bind(Messages.Builder_error_IOReadingFile_message, file.getName() ), |
| e )); |
| } |
| finally { |
| if (input != null) { |
| try { |
| input.close(); |
| } catch (final IOException ignore) {} |
| } |
| } |
| } |
| |
| } |