blob: a514f65ef600c8217c0537d8baf9cfc4ceb08ee5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2019 Xored Software Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Xored Software Inc - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.rcptt.core.internal.builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
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.IResourceStatus;
import org.eclipse.core.resources.IncrementalProjectBuilder;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.rcptt.core.builder.IQ7ProblemReporter.ProblemType;
import org.eclipse.rcptt.core.builder.IQ7Validator;
import org.eclipse.rcptt.core.model.IContext;
import org.eclipse.rcptt.core.model.IQ7Element;
import org.eclipse.rcptt.core.model.IQ7ElementDelta;
import org.eclipse.rcptt.core.model.IQ7NamedElement;
import org.eclipse.rcptt.core.model.IQ7Project;
import org.eclipse.rcptt.core.model.IVerification;
import org.eclipse.rcptt.core.model.ModelException;
import org.eclipse.rcptt.core.model.Q7ElementChangedEvent;
import org.eclipse.rcptt.core.model.search.Q7SearchCore;
import org.eclipse.rcptt.core.nature.RcpttNature;
import org.eclipse.rcptt.core.utils.ModelCycleDetector;
import org.eclipse.rcptt.core.utils.ModelCycleDetector.CycleGraph;
import org.eclipse.rcptt.core.workspace.RcpttCore;
import org.eclipse.rcptt.internal.core.RcpttPlugin;
import org.eclipse.rcptt.internal.core.WorkspaceMonitor;
import org.eclipse.rcptt.internal.core.model.BackReferencesProjectScope;
import org.eclipse.rcptt.internal.core.model.ModelManager;
import org.eclipse.rcptt.internal.core.model.ReferencedProjectScope;
import org.eclipse.rcptt.internal.core.model.deltas.Q7ElementDelta;
import org.eclipse.rcptt.internal.core.model.index.NamedElementCollector;
public class Q7Builder extends IncrementalProjectBuilder {
private static final String MSG_Q7_Builder = "RCP Testing Tool: Checking model consistency";
private static boolean isEnable = true;
private static List<Runnable> listeners = new ArrayList<Runnable>();
/** Listener is called at the end of each project build */
public static synchronized void addListener(Runnable runnable) {
listeners.add(runnable);
}
public static synchronized void removeListener(Runnable runnable) {
listeners.remove(runnable);
}
class Q7DeltaVisitor implements IResourceDeltaVisitor {
private List<IQ7NamedElement> elements;
private List<IPath> removed = new ArrayList<IPath>();
private Set<IPath> changedResources = new HashSet<IPath>();
public Q7DeltaVisitor(List<IQ7NamedElement> elements) {
this.elements = elements;
}
public List<IPath> getRemoved() {
return removed;
}
/**
* Get changed resource files from provided delta. Required to find contexts linked with resource
*
* @return
*/
public Set<IPath> getChangedResources() {
return changedResources;
}
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
switch (delta.getKind()) {
case IResourceDelta.ADDED:
// handle added resource
addResource(resource, elements);
break;
case IResourceDelta.REMOVED:
// handle removed resource
removed.add(resource.getFullPath());
break;
case IResourceDelta.CHANGED:
// handle changed resource
addResource(resource, elements);
break;
}
// search for direct modifications of resources
IResourceDelta[] affectedChildren = delta.getAffectedChildren();
if (affectedChildren.length == 0) {
changedResources.add(resource.getFullPath());
}
// return true to continue visiting children.
return true;
}
}
public static final String BUILDER_ID = RcpttNature.BUILDER_ID;
public static final String MARKER_TYPE = "org.eclipse.rcptt.core.builder.q7Problem";
@SuppressWarnings("unused")
private static final int NTHREDS = 10;
private Q7ValidatorManager validatorMgr;
public static boolean debug_sleep = false;
public static IMarker addMarker(IFile file, String message, int lineNumber,
int severity, int offset, int length, int sourceId) {
try {
IMarker marker = file.createMarker(MARKER_TYPE);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
if (lineNumber == -1) {
lineNumber = 1;
}
if (sourceId > 0) {
marker.setAttribute(IMarker.SOURCE_ID, sourceId);
}
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
if (offset >= 0 && length > 0) {
marker.setAttribute(IMarker.CHAR_START, offset);
marker.setAttribute(IMarker.CHAR_END, offset + length);
}
return marker;
} catch (CoreException e) {
}
return null;
}
public static void setEnabled(boolean isEnable) {
Q7Builder.isEnable = isEnable;
}
static void log(IStatus status) {
//These redundant checks already were here before my refactoring. Gulevich.
try {
Q7BuilderActivator pl = Q7BuilderActivator.getDefault();
if (pl != null && pl.getLog() != null) {
pl.getLog().log(status);
} else {
if (status.getException() != null)
status.getException().printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
if (status.getException() != null)
status.getException().printStackTrace();
}
}
static void log(Throwable e) {
log (new Status(IStatus.ERROR,
Q7BuilderActivator.PLUGIN_ID,
IResourceStatus.BUILD_FAILED,
"Internal error in RCP Testing Tool Builder", e));
}
@SuppressWarnings("rawtypes")
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
if (!isEnable) {
return null;
}
ModelManager.getModelManager().projectStartBuilding(getProject());
try {
while (debug_sleep) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
ModelManager.getModelManager().getIndexManager()
.waitUntilReady(new NullProgressMonitor());// new
// SubProgressMonitor(monitor,
// -1));
getQ7Project().getMetadata();
if (kind == FULL_BUILD) {
fullBuild(monitor);
} else {
IResourceDelta delta = getDelta(getProject());
if (delta == null) {
fullBuild(monitor);
} else {
try {
incrementalBuild(delta, monitor);
} catch (CoreException e) {
log (e);
fullBuild(monitor);
}
}
}
} catch (RuntimeException e) {
log(e);
} finally {
monitor.done();
ModelManager.getModelManager().projectStopBuilding(getProject());
synchronized(this) {
for (Runnable listener: listeners) {
listener.run();
}
}
}
return null;
}
private static boolean addResource(IResource resource,
List<IQ7NamedElement> elements) {
if (resource instanceof IFile
&& RcpttCore.isQ7File(resource.getFullPath())
&& !WorkspaceMonitor.isIgnored(resource)) {
IFile file = (IFile) resource;
deleteMarkers(file);
elements.add((IQ7NamedElement) RcpttCore.create(file));
}
return true;
}
private static void deleteMarkers(IFile file) {
try {
file.deleteMarkers(MARKER_TYPE, false, IResource.DEPTH_ZERO);
} catch (CoreException ce) {
}
}
protected void fullBuild(final IProgressMonitor monitor)
throws CoreException {
try {
monitor.beginTask(MSG_Q7_Builder, 100);
NamedElementCollector collector = new NamedElementCollector() {
@Override
public boolean visit(IQ7Element element) {
if (monitor.isCanceled()) {
return false;
}
return super.visit(element);
}
};
IQ7Project project = getQ7Project();
project.accept(collector);
monitor.worked(10);
List<IQ7NamedElement> elements = collector.getElements();
// simply rebuild all contexts each time
IContext[] contexts = Q7SearchCore.findAllContexts();
for (IContext c : contexts)
elements.add(c);
// List<IQ7NamedElement> elements = calculateExtraDependencies(
// collector.getElements(), null, new SubProgressMonitor(
// monitor, 10));
buildElements(elements, new SubProgressMonitor(monitor, 90));
monitor.done();
} catch (CoreException e) {
}
}
private IQ7Project getQ7Project() {
return RcpttCore.create(getProject());
}
protected void incrementalBuild(IResourceDelta delta,
IProgressMonitor monitor) throws CoreException {
monitor.beginTask(MSG_Q7_Builder, 40);
// Check for project metadata file change
boolean needFullBuildProjectMetadataChange = false;
IResourceDelta[] affectedChildren = delta.getAffectedChildren();
for (IResourceDelta res : affectedChildren) {
if (RcpttCore.isQ7ProjectMetadata(res.getFullPath())) {
needFullBuildProjectMetadataChange = true;
break;
}
}
if (needFullBuildProjectMetadataChange
|| (delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
if (delta.getResource() instanceof IProject) {
fullBuild(monitor);
return;
}
}
// the visitor does the work.
List<IQ7NamedElement> elements = new ArrayList<IQ7NamedElement>();
Q7DeltaVisitor visitor = new Q7DeltaVisitor(elements);
delta.accept(visitor);
List<IPath> removed = visitor.getRemoved();
Set<IPath> changedResources = visitor.getChangedResources();
@SuppressWarnings("unused")
long start = System.currentTimeMillis();
elements = calculateExtraDependencies(elements, removed, changedResources,
new SubProgressMonitor(monitor, 10));
// System.out.println("Calculate extra deps:"
// + Long.toString(System.currentTimeMillis() - start));
buildElements(elements, new SubProgressMonitor(monitor, 20));
monitor.done();
}
private void buildElements(List<IQ7NamedElement> elements,
final IProgressMonitor monitor) {
try {
final Q7ProblemCollector collector = new Q7ProblemCollector();
final IQ7Validator[] validators = getValidators();
monitor.beginTask("Validate RCPTT model:", elements.size() + 20);
ExecutorService executor = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors() + 1);
for (final IQ7NamedElement element : elements) {
if (monitor.isCanceled()) {
monitor.done();
return;
}
executor.execute(new Runnable() {
public void run() {
IProgressMonitor subMonitor = new SubProgressMonitor(
monitor, 1);
subMonitor.beginTask(
"Validate RCPTT element " + element.getName(), 2 + validators.length);
subMonitor.subTask("Validate RCPTT element "
+ element.getName());
deleteMarkers((IFile) element.getResource());
subMonitor.worked(1);
try {
final IQ7NamedElement indexingWorkingCopy = element.getIndexingWorkingCopy(null);
try {
for (IQ7Validator validator : validators) {
if (monitor.isCanceled()) {
monitor.done();
subMonitor.done();
return;
}
IProgressMonitor mon = new SubProgressMonitor(
subMonitor, 1) {
@Override
public void setTaskName(String name) {
}
};
validator.validate(indexingWorkingCopy, collector, mon);
mon.done();
}
} finally {
indexingWorkingCopy.discardWorkingCopy();
}
} catch (Exception e) {
RcpttPlugin.log(e);
collector.reportProblem((IFile) element.getResource(), ProblemType.Error, "Validation fail", -1, -1, -1, -1);
} finally {
subMonitor.done();
}
}
});
}
executor.shutdown();
while (!executor.isTerminated()) {
try {
Thread.sleep(50);
} catch (Throwable e) {
// ignore
}
}
CycleGraph graph = ModelCycleDetector.buildCycles(
elements.toArray(new IQ7NamedElement[elements.size()]),
null, new SubProgressMonitor(monitor, 10));
if (graph.cycle) {
Set<String> set = graph.graph.keySet();
for (String id : set) {
if (monitor.isCanceled()) {
monitor.done();
return;
}
IQ7NamedElement element = graph.elementsMap.get(id);
if (element != null) {
List<String> toElementID = graph.graph.get(id);
StringBuilder names = new StringBuilder();
for (String eid : toElementID) {
if (monitor.isCanceled()) {
return;
}
IQ7NamedElement toElement = graph.elementsMap
.get(eid);
if (toElement != null) {
if (names.length() > 0) {
names.append(", ");
}
names.append(toElement.getName());
} else {
names.append(eid);
}
}
collector.reportProblem(
(IFile) element.getResource(),
ProblemType.Error,
"Circular reference to context('s) "
+ names.toString() + " is detected",
-1, -1, -1, -1);
}
}
}
collector.flushProblems(new SubProgressMonitor(monitor, 10));
ModelManager mm = ModelManager.getModelManager();
Q7ElementDelta delta = new Q7ElementDelta(mm.getModel());
for (IQ7NamedElement el : elements) {
delta.changed(el, IQ7ElementDelta.F_MARKERS);
}
mm.getDeltaProcessor().fire(delta,
Q7ElementChangedEvent.POST_CHANGE);
} finally {
monitor.done();
}
}
private IQ7Validator[] getValidators() {
if (validatorMgr == null) {
validatorMgr = new Q7ValidatorManager();
}
return validatorMgr.getExtensions();
}
private List<IQ7NamedElement> calculateExtraDependencies(
List<IQ7NamedElement> elements, List<IPath> removed, Set<IPath> changedResources,
IProgressMonitor monitor) throws CoreException {
Set<IQ7NamedElement> result = new HashSet<IQ7NamedElement>();
result.addAll(elements);
while (true) {
if (monitor.isCanceled()) {
break;
}
int cur = result.size();
List<IQ7NamedElement> temp = new ArrayList<IQ7NamedElement>();
temp.addAll(result);
for (IQ7NamedElement iFile : temp) {
if (monitor.isCanceled()) {
break;
}
if (iFile instanceof IContext) {
// Add all dependent elements
try {
String id = Q7SearchCore.findIDByDocument(iFile);
if (id == null) {
id = iFile.getID();
}
IQ7NamedElement[] usage = Q7SearchCore
.findContextUsage(id,
new BackReferencesProjectScope(
getQ7Project()), monitor);
result.addAll(Arrays.asList(usage));
} catch (ModelException e) {
RcpttPlugin.log(e);
}
}
if (iFile instanceof IVerification) {
// Add all dependent elements
try {
String id = Q7SearchCore.findIDByDocument(iFile);
if (id == null) {
id = iFile.getID();
}
IQ7NamedElement[] usage = Q7SearchCore
.findVerificationUsage(id,
new BackReferencesProjectScope(
getQ7Project()), monitor);
result.addAll(Arrays.asList(usage));
} catch (ModelException e) {
RcpttPlugin.log(e);
}
}
// Find element with same ID
try {
IQ7NamedElement[] sameID = Q7SearchCore.findById(
iFile.getID(),
new BackReferencesProjectScope(iFile.getQ7Project()),
monitor);
if (sameID != null) {
result.addAll(Arrays.asList(sameID));
}
} catch (ModelException e) {
RcpttPlugin.log(e);
}
}
if (removed != null && removed.size() > 0) {
result.addAll(Arrays.asList(Q7SearchCore
.findElementsWithUnusedReferences(new ReferencedProjectScope(getQ7Project()),
monitor)));
result.addAll(findMarkedElements());
// Process removal of items referenced by test suits
// TODO: check performance here
result.addAll(Arrays.asList(Q7SearchCore.findAllTestSuites(new ReferencedProjectScope(getQ7Project()))));
}
if (cur == result.size()) {
break;
}
}
// search for contexts linked with changed resources
if (changedResources != null && changedResources.size() > 0) {
IContext[] contexts = Q7SearchCore.findContextsWithLinks(
new BackReferencesProjectScope(getQ7Project()), changedResources, monitor);
for (IContext c : contexts) {
result.add(c);
}
}
// TODO: Add check for contexts with unresolved dependencies.
monitor.done();
return new ArrayList<IQ7NamedElement>(result);
}
/// Tests and contexts with problems (same id, for example)
Collection<IQ7NamedElement> findMarkedElements() throws CoreException {
final List<IQ7NamedElement> rv = new ArrayList<IQ7NamedElement>();
for (IMarker marker: getProject().findMarkers(MARKER_TYPE, true, IResource.DEPTH_INFINITE)) {
IResource resource = marker.getResource();
if (resource instanceof IFile && RcpttCore.isQ7File(resource.getFullPath())) {
rv.add((IQ7NamedElement)RcpttCore.create(resource));
}
}
return rv;
}
}