blob: 08cd2e0fe457bd46926b93f33318e3c908e86317 [file] [log] [blame]
/*=============================================================================#
# 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) {}
}
}
}
}