blob: e414037cd0f090552f2d2f2e2eb4c803e96f3746 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 David Green and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* David Green - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.wikitext.ui.validation;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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.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.SubMonitor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Region;
import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin;
import org.eclipse.mylyn.internal.wikitext.ui.editor.MarkupEditor;
import org.eclipse.mylyn.internal.wikitext.ui.editor.validation.ResourceMarkerMarkupValidator;
import org.eclipse.mylyn.wikitext.ui.WikiText;
import org.eclipse.mylyn.wikitext.validation.ValidationProblem;
import org.eclipse.osgi.util.NLS;
/**
* A project builder that invokes validation on wikitext files
*
* @author David Green
*/
public class ValidationProjectBuilder extends IncrementalProjectBuilder {
public static final String ID = "org.eclipse.mylyn.wikitext.ui.wikiTextValidationBuilder"; //$NON-NLS-1$
private static final IProject[] NO_PROJECTS = new IProject[0];
private static int CHECK_ANCESTORS;
private static Method RESOURCE_DERIVED_WITH_FLAGS_METHOD;
static {
try {
CHECK_ANCESTORS = ((Integer) IResource.class.getDeclaredField("CHECK_ANCESTORS").get(null)).intValue(); //$NON-NLS-1$
} catch (Exception e) {
CHECK_ANCESTORS = 0;
}
try {
RESOURCE_DERIVED_WITH_FLAGS_METHOD = IResource.class.getDeclaredMethod("isDerived", //$NON-NLS-1$
new Class[] { int.class });
} catch (Exception e) {
RESOURCE_DERIVED_WITH_FLAGS_METHOD = null;
}
}
public ValidationProjectBuilder() {
}
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
getProject().deleteMarkers(ValidationProblem.DEFAULT_MARKER_ID, true, IResource.DEPTH_INFINITE);
}
@SuppressWarnings("rawtypes")
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
IResourceDelta resourceDelta = getDelta(project);
// find files that need validating. We do this first so that we can accurately represent progress
List<ValidationInfo> files = null;
if ((kind != IncrementalProjectBuilder.INCREMENTAL_BUILD && kind != IncrementalProjectBuilder.AUTO_BUILD)
|| resourceDelta == null) {
files = collect(project, monitor);
} else {
files = collect(resourceDelta, monitor);
}
// validate if needed
if (files != null && !files.isEmpty() && !isInterrupted() && !monitor.isCanceled()) {
validate(files, monitor);
}
// be sure to cancel properly so that calling mechanisms will know
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
return NO_PROJECTS;
}
private static class ValidationInfo {
private final IFile file;
private final String languageName;
public ValidationInfo(IFile file, String languageName) {
this.file = file;
this.languageName = languageName;
}
}
/**
* collect resources for validation from a resource delta
*/
private List<ValidationInfo> collect(IResourceDelta resourceDelta, final IProgressMonitor monitor)
throws CoreException {
final List<ValidationInfo> files = new ArrayList<>();
resourceDelta.accept(new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) throws CoreException {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
if (isInterrupted()) {
return false;
}
IResource resource = delta.getResource();
if (resource instanceof IFile) {
if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) != 0) {
IFile file = (IFile) resource;
ValidationProjectBuilder.this.visit(files, file);
}
} else if (resource instanceof IContainer) {
if (filtered((IContainer) resource)) {
return false;
}
}
return true;
}
});
return files;
}
private List<ValidationInfo> collect(IProject project, final IProgressMonitor monitor) throws CoreException {
final List<ValidationInfo> files = new ArrayList<>();
project.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
if (isInterrupted()) {
return false;
}
if (resource instanceof IFile) {
IFile file = (IFile) resource;
ValidationProjectBuilder.this.visit(files, file);
} else if (resource instanceof IContainer) {
if (filtered((IContainer) resource)) {
return false;
}
}
return true;
}
});
return files;
}
private boolean filtered(IContainer container) {
return false;
}
private void visit(final List<ValidationInfo> files, IFile file) {
boolean derived = false;
// bug 258154 - avoid 3.4 API compilation dependency
if (RESOURCE_DERIVED_WITH_FLAGS_METHOD != null) {
try {
derived = ((Boolean) RESOURCE_DERIVED_WITH_FLAGS_METHOD.invoke(file, CHECK_ANCESTORS));
} catch (Exception e) {
derived = file.isDerived();
}
} else {
derived = file.isDerived();
}
if (derived) {
return;
}
// only validate files for which there is a known language
String language = getMarkupLanguageForFile(file);
if (language != null) {
files.add(new ValidationInfo(file, language));
}
}
private void validate(List<ValidationInfo> files, IProgressMonitor monitor) throws CoreException {
if (files.isEmpty()) {
return;
}
final int factor = 1000;
monitor.beginTask(Messages.ValidationProjectBuilder_validationTask, files.size() * factor);
for (ValidationInfo file : files) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
if (isInterrupted()) {
break;
}
validate(file, SubMonitor.convert(monitor, factor));
}
monitor.done();
}
/**
* validate a single file
*/
private void validate(ValidationInfo file, IProgressMonitor monitor) throws CoreException {
int totalWork = 1000;
monitor.beginTask(
NLS.bind(Messages.ValidationProjectBuilder_validatingFileTask, new Object[] { file.file.getName() }),
totalWork);
ResourceMarkerMarkupValidator validator = new ResourceMarkerMarkupValidator();
validator.setMarkupLanguage(WikiText.getMarkupLanguage(file.languageName));
validator.setResource(file.file);
if (validator.getMarkupLanguage() != null) {
StringWriter writer = new StringWriter();
String charset = file.file.getCharset();
try {
Reader reader = charset == null
? new InputStreamReader(new BufferedInputStream(file.file.getContents()))
: new InputStreamReader(new BufferedInputStream(file.file.getContents()), charset);
try {
int c;
while ((c = reader.read()) != -1) {
writer.append((char) c);
}
} finally {
reader.close();
}
} catch (IOException ioe) {
throw new CoreException(WikiTextUiPlugin.getDefault().createStatus(IStatus.ERROR, ioe));
}
monitor.worked(totalWork / 2);
IDocument document = new Document(writer.toString());
validator.validate(SubMonitor.convert(monitor, totalWork / 2), document,
new Region(0, document.getLength()));
}
monitor.done();
}
private String getMarkupLanguageForFile(IFile file) {
String language = MarkupEditor.getMarkupLanguagePreference(file);
if (language == null) {
language = WikiText.getMarkupLanguageNameForFilename(file.getName());
}
return language;
}
}