blob: 59b6d242d988c016c4aa9d827e1b6455f288d49c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017,2019 Willink Transformations 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
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation based on org.eclipse.xtext.builder.nature.XtextNature
*******************************************************************************/
package org.eclipse.ocl.xtext.base.ui.builder;
import java.util.Map;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.ocl.pivot.utilities.StringUtil;
import org.eclipse.ocl.pivot.utilities.TracingOption;
import org.eclipse.ocl.xtext.base.ui.BaseUIActivator;
import org.eclipse.ocl.xtext.base.ui.BaseUiModule;
import org.eclipse.ocl.xtext.base.ui.BaseUiPluginHelper;
import org.eclipse.ocl.xtext.base.ui.messages.BaseUIMessages;
/**
* Abstract Builder for OCL or QVTd contributions. Currently this involves identifying relevant files subject to
* extension filtering defined by the excludeExtension/includeExtensions comma-separated file extensionlist
* and path filtering defined by excludePaths/includepaths comma-separated classpath-style regexes.
* Default values are supplied as part of the .project buildCommand when the OCL nature is added.
*
* The identified files are passed to a separate MultiValidationJob for concurrent non-blocking validation.
*/
public abstract class AbstractValidatingBuilder extends IncrementalProjectBuilder
{
public static final @NonNull TracingOption BUILDER = new TracingOption(BaseUiPluginHelper.PLUGIN_ID, "builder");
/**
* This is a copy of org.eclipse.jdt.internal.compiler.util.Util.isExcluded
*
* FIXME BUG 529789 requests its availability in CharOperation.
*/
final static boolean isExcluded(char[] path, char[][] inclusionPatterns, char[][] exclusionPatterns, boolean isFolderPath) {
if (inclusionPatterns == null && exclusionPatterns == null) return false;
inclusionCheck: if (inclusionPatterns != null) {
for (int i = 0, length = inclusionPatterns.length; i < length; i++) {
char[] pattern = inclusionPatterns[i];
char[] folderPattern = pattern;
if (isFolderPath) {
int lastSlash = CharOperation.lastIndexOf('/', pattern);
if (lastSlash != -1 && lastSlash != pattern.length-1){ // trailing slash -> adds '**' for free (see http://ant.apache.org/manual/dirtasks.html)
int star = CharOperation.indexOf('*', pattern, lastSlash);
if ((star == -1
|| star >= pattern.length-1
|| pattern[star+1] != '*')) {
folderPattern = CharOperation.subarray(pattern, 0, lastSlash);
}
}
}
if (CharOperation.pathMatch(folderPattern, path, true, '/')) {
break inclusionCheck;
}
}
return true; // never included
}
if (isFolderPath) {
path = CharOperation.concat(path, new char[] {'*'}, '/');
}
if (exclusionPatterns != null) {
for (int i = 0, length = exclusionPatterns.length; i < length; i++) {
if (CharOperation.pathMatch(exclusionPatterns[i], path, true, '/')) {
return true;
}
}
}
return false;
}
protected static enum BuildType
{
CLEAN, FULL, INCREMENTAL, RECOVERY
}
@Override
protected IProject[] build(final int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
if (BUILDER.isActive()) {
BUILDER.println(getDebugName() + " build " + getKindAsString(kind));
}
long startTime = System.currentTimeMillis();
int selectionSize = -1;
int unselectionSize = -1;
IProject project = getProject();
assert project != null;
String builderName = getBuilderName();
String projectName = project.getName();
try {
String initializingMessage = StringUtil.bind(BaseUIMessages.MultiValidationJob_Initializing, builderName, projectName);
SubMonitor subMonitor = SubMonitor.convert(monitor, initializingMessage, 3);
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " converted from: " + NameUtil.debugSimpleName(monitor));
// }
// Work item 1: MultiValidationJob_Initializing
//
AbstractBuildSelector buildSelector;
IResourceDelta delta;
if (kind == FULL_BUILD) {
getProject().deleteMarkers(getMarkerId(), true, IResource.DEPTH_INFINITE);
buildSelector = createBuildSelector(project, BuildType.FULL, args, subMonitor);
delta = null;
} else if (kind == AUTO_BUILD){
delta = getDelta(getProject());
buildSelector = createBuildSelector(project, BuildType.INCREMENTAL, args, subMonitor);
} else if (kind == INCREMENTAL_BUILD){
// delta = getDelta(getProject());
// buildSelector = createBuildSelector(project, BuildType.INCREMENTAL, args, subMonitor);
return null; // FIXME BUG 544189 there is no incremental support.
} else { // CLEAN_BUILD never happens
// delta = getDelta(getProject());
// buildSelector = createBuildSelector(project, BuildType.INCREMENTAL, args, subMonitor);
return null;
}
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " worked 1");
// }
subMonitor.worked(1);
//
// Work item 2: MultiValidationJob_Selecting
//
String selectingMessage = StringUtil.bind(BaseUIMessages.MultiValidationJob_Selecting, builderName, projectName);
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " subTask: " + selectingMessage);
// }
subMonitor.subTask(selectingMessage);
selectionSize = buildSelector.selectResources(delta);
unselectionSize = buildSelector.deleteRemovedResourceMarkers();
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " worked: 1");
// }
subMonitor.worked(1);
//
// Work item 3: MultiValidationJob_Queuing
//
if (selectionSize > 0) {
String queueingMessage = StringUtil.bind(BaseUIMessages.MultiValidationJob_Queuing, builderName, selectionSize, projectName);
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " subTask: " + queueingMessage);
// }
subMonitor.subTask(queueingMessage);
buildSelector.buildResources();
// if (BUILDER.isActive()) {
// long endTime = System.currentTimeMillis();
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " worked: 1");
// BUILDER.println("Selected " + selectionSize + " elements in " + (endTime-startTime) + " ms for " + projectName + " on " + Thread.currentThread().getName());
// }
subMonitor.worked(1);
}
//
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(subMonitor) + " done");
// }
subMonitor.done();
} catch (CoreException e) {
getLog().error(e.getMessage(), e);
throw e;
} catch (OperationCanceledException e) {
handleCanceled(e);
} catch (Exception e) {
getLog().error(e.getMessage(), e);
forgetLastBuiltState();
} finally {
if (BUILDER.isActive()) {
long endTime = System.currentTimeMillis();
BUILDER.println((endTime-startTime) + " ms to select/unselect " + selectionSize + "/" + unselectionSize + " elements on \"" + Thread.currentThread().getName() + "\"");
}
if (monitor != null) {
// if (BUILDER.isActive()) {
// BUILDER.println(Thread.currentThread().getName() + " " + NameUtil.debugSimpleName(monitor) + " done2");
// }
monitor.done();
}
// if (BUILDER.isActive()) {
// String message = "Pre-build " + getProject().getName() + " in " + (System.currentTimeMillis() - startTime) + " ms";
// getLog().info(message);
// BUILDER.println(Thread.currentThread().getName() + " log " + message);
// }
}
return null;
}
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
if (BUILDER.isActive()) {
BUILDER.println(getDebugName() + " clean");
}
BaseUIActivator.cancelMultiValidationJob();
getProject().deleteMarkers(EValidator.MARKER, true, IResource.DEPTH_INFINITE); // Temporary zap, see Bug 544737
}
protected abstract @NonNull AbstractBuildSelector createBuildSelector(@NonNull IProject project, @NonNull BuildType buildType,
@Nullable Map<String, String> args, @NonNull IProgressMonitor monitor);
protected abstract @NonNull String getBuilderName();
protected String getDebugName() {
String name = getClass().getName();
int lastIndex = name.lastIndexOf(".");
return (lastIndex >= 0 ? name.substring(lastIndex+1) : name) + "-" + getBuildConfig(); // + "@" + Integer.toHexString(System.identityHashCode(this));
}
private String getKindAsString(int kind) {
switch (kind) {
case IncrementalProjectBuilder.AUTO_BUILD: return "AUTO_BUILD";
case IncrementalProjectBuilder.CLEAN_BUILD: return "CLEAN_BUILD";
case IncrementalProjectBuilder.FULL_BUILD: return "FULL_BUILD";
case IncrementalProjectBuilder.INCREMENTAL_BUILD: return "INCREMENTAL_BUILD";
}
return "OTHER_BUILD";
}
protected abstract Logger getLog();
protected /*abstract*/ @NonNull String getMarkerId() { // FIXME Chn age to abxstract after 2019-03M3 once QVTd catches up
return BaseUiModule.MARKER_ID;
}
private void handleCanceled(Throwable t) {
BaseUIActivator.cancelMultiValidationJob();
}
}