blob: 4783ec217345d43206edfc2523f64cf4e4c396a7 [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.internal.core.model.deltas;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.rcptt.core.model.IElementChangedListener;
import org.eclipse.rcptt.core.model.IQ7Element;
import org.eclipse.rcptt.core.model.IQ7Element.HandleType;
import org.eclipse.rcptt.core.model.IQ7ElementDelta;
import org.eclipse.rcptt.core.model.IQ7Folder;
import org.eclipse.rcptt.core.model.IQ7Model;
import org.eclipse.rcptt.core.model.IQ7NamedElement;
import org.eclipse.rcptt.core.model.IQ7Project;
import org.eclipse.rcptt.core.model.ModelException;
import org.eclipse.rcptt.core.model.Q7ElementChangedEvent;
import org.eclipse.rcptt.core.workspace.RcpttCore;
import org.eclipse.rcptt.internal.core.model.ModelInfo;
import org.eclipse.rcptt.internal.core.model.ModelManager;
import org.eclipse.rcptt.internal.core.model.Openable;
import org.eclipse.rcptt.internal.core.model.Q7ElementInfo;
import org.eclipse.rcptt.internal.core.model.Q7FolderInfo;
import org.eclipse.rcptt.internal.core.model.Q7Model;
import org.eclipse.rcptt.internal.core.model.Q7NamedElement;
import org.eclipse.rcptt.internal.core.model.Q7Project;
import org.eclipse.rcptt.internal.core.model.Q7ProjectInfo;
import org.eclipse.rcptt.internal.core.model.index.IndexManager;
import org.eclipse.rcptt.internal.core.model.index.ProjectIndexerManager;
public class DeltaProcessor {
public static boolean DEBUG = false;
public static boolean VERBOSE = false;
public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with
// ElementChangedEvent
// event masks
public static long getTimeStamp(IFile file) {
long lmodif = 0;
lmodif = file.getModificationStamp();
return lmodif;
}
/*
* The global state of delta processing.
*/
private final DeltaProcessingState state;
/*
* The q7 model manager
*/
ModelManager manager;
/*
* The <code>ModelElementDelta</code> corresponding to the
* <code>IResourceDelta</code> being translated.
*/
private Q7ElementDelta currentDelta;
/*
* The model element that was last created (see createElement(IResource)).
* This is used as a stack of model elements (using getParent() to pop it,
* and using the various get(...) to push it.
*/
private Openable currentElement;
/*
* Queue of deltas created explicily by the q7 Model that have yet to be
* fired.
*/
public ArrayList<IQ7ElementDelta> modelDeltas = new ArrayList<IQ7ElementDelta>();
/*
* Queue of reconcile deltas on working copies that have yet to be fired.
* This is a table form IWorkingCopy to IQ7ElementDelta
*/
@SuppressWarnings("rawtypes")
public HashMap reconcileDeltas = new HashMap();
/*
* Turns delta firing on/off. By default it is on.
*/
private boolean isFiring = true;
/*
* Used to update the Model for <code>IQ7ElementDelta</code>s.
*/
private final ModelUpdater modelUpdater = new ModelUpdater();
/* A set of IDLTKProject whose caches need to be reset */
private final HashSet<IQ7Project> projectCachesToReset = new HashSet<IQ7Project>();
/*
* A list of IQ7Element used as a scope for external archives refresh during
* POST_CHANGE. This is null if no refresh is needed.
*/
private HashSet<IQ7Element> refreshedElements;
/* A set of IDylanProject whose package fragment roots need to be refreshed */
private final HashSet<IQ7Project> rootsToRefresh = new HashSet<IQ7Project>();
/** {@link Runnable}s that should be called after model is updated */
private final ArrayList<Runnable> postActions = new ArrayList<Runnable>();
/*
* Type of event that should be processed no matter what the real event type
* is.
*/
public int overridenEventType = -1;
// /*
// * Map from IProject to BuildpathChange
// */
// public HashMap buildpathChanges = new HashMap();
public DeltaProcessor(DeltaProcessingState state, ModelManager manager) {
this.state = state;
this.manager = manager;
}
/*
* Adds the dependents of the given project to the list of the projects to
* update.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void addDependentProjects(IQ7Project project, HashMap projectDependencies,
HashSet result) {
IQ7Project[] dependents = (IQ7Project[]) projectDependencies.get(project);
if (dependents == null) {
return;
}
for (int i = 0, length = dependents.length; i < length; i++) {
IQ7Project dependent = dependents[i];
if (result.contains(dependent)) {
continue; // no need to go further as the project is already
}
// known
result.add(dependent);
this.addDependentProjects(dependent, projectDependencies, result);
}
}
/*
* Adds the given element to the list of elements used as a scope for
* external jars refresh.
*/
public void addForRefresh(IQ7Element element) {
if (this.refreshedElements == null) {
this.refreshedElements = new HashSet<IQ7Element>();
}
this.refreshedElements.add(element);
}
/*
* Adds the given child handle to its parent's cache of children.
*/
private void addToParentInfo(Openable child) {
Openable parent = (Openable) child.getParent();
if (parent != null && parent.isOpen()) {
try {
Q7ElementInfo info = (Q7ElementInfo) parent.getElementInfo();
info.addChild(child);
} catch (ModelException e) {
// do nothing - we already checked if open
}
}
}
/*
* Adds the given project and its dependents to the list of the roots to
* refresh.
*/
private void addToRootsToRefreshWithDependents(IQ7Project q7Project) {
this.rootsToRefresh.add(q7Project);
this.addDependentProjects(q7Project, this.state.projectDependencies,
this.rootsToRefresh);
}
private void checkProjectsBeingAddedOrRemoved(IResourceDelta delta) {
IResource resource = delta.getResource();
IResourceDelta[] children = null;
switch (resource.getType()) {
case IResource.ROOT:
// workaround for bug 15168 circular errors not reported
this.state.getOldProjectNames(); // force list to be computed
children = delta.getAffectedChildren();
break;
case IResource.PROJECT:
// NB: No need to check project's nature as if the project is not a
// q7 project:
// - if the project is added or changed this is a noop for
// projectsBeingDeleted
// - if the project is closed, it has already lost its q7 nature
IProject project = (IProject) resource;
Q7Project q7Project = (Q7Project) RcpttCore.create(project);
switch (delta.getKind()) {
case IResourceDelta.ADDED:
// remember project and its dependents
this.addToRootsToRefreshWithDependents(q7Project);
// workaround for bug 15168 circular errors not reported
if (RcpttCore.hasRcpttNature(project)) {
this.addToParentInfo(q7Project);
}
break;
case IResourceDelta.CHANGED:
if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
// project opened or closed: remember project and its
// dependents
this.addToRootsToRefreshWithDependents(q7Project);
// workaround for bug 15168 circular errors not reported
if (project.isOpen()) {
if (RcpttCore.hasRcpttNature(project)) {
this.addToParentInfo(q7Project);
// readRawBuildpath(q7Project);
// ensure project references are updated
}
} else {
try {
q7Project.close();
} catch (ModelException e) {
// q7 project doesn't exist: ignore
}
this.removeFromParentInfo(q7Project);
}
} else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
boolean isQ7Project = RcpttCore.hasRcpttNature(project);
// q7 nature added or removed: remember project and
// its dependents
this.addToRootsToRefreshWithDependents(q7Project);
// workaround for bug 15168 circular errors not reported
if (isQ7Project) {
this.addToParentInfo(q7Project);
} else {
// close project
try {
q7Project.close();
} catch (ModelException e) {
// q7 project doesn't exist: ignore
}
this.removeFromParentInfo(q7Project);
}
} else {
if (RcpttCore.hasRcpttNature(project)) { // need
this.addToParentInfo(q7Project);
children = delta.getAffectedChildren();
}
}
break;
case IResourceDelta.REMOVED:
break;
}
// in all cases, refresh the external jars for this project
this.addForRefresh(q7Project);
break;
// case IResource.FILE:
// IFile file = (IFile) resource;
// break;
}
if (children != null) {
for (int i = 0; i < children.length; i++) {
this.checkProjectsBeingAddedOrRemoved(children[i]);
}
}
}
private void close(Openable element) {
try {
element.close();
} catch (ModelException e) {
// do nothing
}
}
private void contentChanged(Openable element) {
boolean isPrimaryWorkingCopy = false;
if (element.getElementType().equals(HandleType.Context)
|| element.getElementType().equals(HandleType.Verification)
|| element.getElementType().equals(HandleType.TestCase)
|| element.getElementType().equals(HandleType.TestSuite)) {
Q7NamedElement cu = (Q7NamedElement) element;
isPrimaryWorkingCopy = cu.isWorkingCopy();
}
if (element.getElementType().equals(HandleType.ProjectMetadata)) {
this.currentDelta().changed(element.getQ7Project(),
IQ7ElementDelta.F_DESCRIPTION);
}
if (isPrimaryWorkingCopy) {
this.currentDelta().changed(element, IQ7ElementDelta.F_PRIMARY_RESOURCE);
} else {
this.currentDelta().changed(element, IQ7ElementDelta.F_CONTENT);
}
}
/*
* Creates the openables corresponding to this resource. Returns null if
* none was found.
*/
private Openable createElement(IResource resource, HandleType elementType,
Q7Project rootInfo) {
if (resource == null) {
return null;
}
IPath path = resource.getFullPath();
IQ7Element element = null;
switch (elementType) {
case Project:
// note that non-q7 resources rooted at the project level will
// also enter this code with
// an elementType SCRIPT_PROJECT (see #elementType(...)).
if (resource instanceof IProject) {
this.popUntilPrefixOf(path);
if (this.currentElement != null
&& this.currentElement.getElementType()
.equals(HandleType.Project)
&& ((IQ7Project) this.currentElement).getProject().equals(
resource)) {
return this.currentElement;
}
if (rootInfo != null && rootInfo.getProject().equals(resource)) {
element = rootInfo;
break;
}
IProject proj = (IProject) resource;
if (RcpttCore.hasRcpttNature(proj)) {
element = RcpttCore.create(proj);
} else {
element = this.state.findProject(proj.getName());
}
}
break;
case Folder:
if (rootInfo != null) {
if (rootInfo.contains(resource)) {
// create package handle
IPath pkgPath = path.removeFirstSegments(rootInfo.getPath()
.segmentCount());
element = rootInfo.getFolder(pkgPath);
}
} else {
// find the element that encloses the resource
this.popUntilPrefixOf(path);
if (this.currentElement == null) {
element = RcpttCore.create(resource);
} else {
rootInfo = (Q7Project) currentElement.getQ7Project();
if (((Q7Project) rootInfo.getProject()).contains(resource)) {
// create package handle
IPath pkgPath = path.removeFirstSegments(rootInfo.getPath()
.segmentCount());
element = rootInfo.getFolder(pkgPath);
}
}
}
break;
case TestCase:
case Context:
case TestSuite:
case ProjectMetadata:
case Verification:
// find the element that encloses the resource
this.popUntilPrefixOf(path);
if (this.currentElement == null) {
element = rootInfo == null ? RcpttCore.create(resource) : ModelManager
.create(resource, rootInfo);
} else {
// find the package
IQ7Folder pkgFragment = null;
switch (this.currentElement.getElementType()) {
case Folder:
Openable pkg = this.currentElement;
if (pkg.getPath().equals(path.removeLastSegments(1))) {
pkgFragment = (IQ7Folder) pkg;
} // else case of package x which is a prefix of
// x.y
break;
case TestCase:
case Context:
case TestSuite:
case ProjectMetadata:
case Verification:
pkgFragment = (IQ7Folder) this.currentElement.getParent();
break;
}
if (pkgFragment == null) {
element = rootInfo == null ? RcpttCore.create(resource) : ModelManager
.create(resource, rootInfo);
} else {
if (elementType.equals(HandleType.Context)
|| elementType.equals(HandleType.Verification)
|| elementType.equals(HandleType.TestCase)
|| elementType.equals(HandleType.TestSuite)
|| elementType.equals(HandleType.TestSuite)) {
String fileName = path.lastSegment();
element = pkgFragment.getNamedElement(fileName);
}
}
}
break;
}
if (element == null) {
return null;
}
this.currentElement = (Openable) element;
return this.currentElement;
}
private Q7ElementDelta currentDelta() {
if (this.currentDelta == null) {
this.currentDelta = new Q7ElementDelta(this.manager.getModel());
}
return this.currentDelta;
}
/*
* Note that the project is about to be deleted.
*/
private void deleting(IProject project) {
try {
// discard indexing jobs that belong to this project so that the
// project can be
// deleted without interferences from the index manager
this.manager.getIndexManager().discardJobs(project.getName());
Q7Project q7Project = (Q7Project) RcpttCore.create(project);
q7Project.close();
// workaround for bug 15168 circular errors not reported
this.state.getOldProjectNames(); // foce list to be computed
this.removeFromParentInfo(q7Project);
} catch (ModelException e) {
// q7 project doesn't exist: ignore
}
}
private void elementAdded(Openable element, IResourceDelta delta) {
HandleType elementType = element.getElementType();
if (elementType.equals(HandleType.Project)) {
// project add is handled by DylanProject.configure() because
// when a project is created, it does not yet have a q7 nature
if (delta != null && RcpttCore.hasRcpttNature((IProject) delta.getResource())) {
this.addToParentInfo(element);
if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) {
Openable movedFromElement = (Openable) element.getModel().getProject(
delta.getMovedFromPath().lastSegment());
this.currentDelta().movedTo(element, movedFromElement);
} else {
this.close(element);
this.currentDelta().added(element);
}
final IQ7Project project = (IQ7Project) element;
this.rootsToRefresh.add(project);
this.projectCachesToReset.add(project);
}
} else {
if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_FROM) == 0) {
// regular element addition
if (this.isWorkingCopy(element, elementType)) {
this.currentDelta().changed(element,
IQ7ElementDelta.F_PRIMARY_RESOURCE);
} else {
this.addToParentInfo(element);
this.close(element);
this.currentDelta().added(element);
}
} else {
// element is moved
this.addToParentInfo(element);
this.close(element);
IPath movedFromPath = delta.getMovedFromPath();
IResource res = delta.getResource();
IResource movedFromRes;
if (res instanceof IFile) {
movedFromRes = res.getWorkspace().getRoot().getFile(movedFromPath);
} else {
movedFromRes = res.getWorkspace().getRoot().getFolder(movedFromPath);
}
// find the element type of the moved from element
Q7Project movedFromInfo = (Q7Project) RcpttCore.create(movedFromRes
.getProject());
HandleType movedFromType = element.getParent().getElementType();
// reset current element as it might be inside a nested root
// (popUntilPrefixOf() may use the outer root)
this.currentElement = null;
// create the moved from element
Openable movedFromElement = this.createElement(movedFromRes,
movedFromType, movedFromInfo);
if (movedFromElement == null) {
// moved from outside buildpath
this.currentDelta().added(element);
} else {
this.currentDelta().movedTo(element, movedFromElement);
}
}
switch (elementType) {
case Folder:
// reset project's package fragment cache
Q7Project project = (Q7Project) element.getQ7Project();
this.projectCachesToReset.add(project);
break;
}
}
}
/*
* Generic processing for a removed element:<ul> <li>Close the element,
* removing its structure from the cache <li>Remove the element from its
* parent's cache of children <li>Add a REMOVED entry in the delta </ul>
* Delta argument could be null if processing an external ZIP change
*/
private void elementRemoved(Openable element, IResourceDelta delta, Q7Project rootInfo) {
HandleType elementType = element.getElementType();
if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_TO) == 0) {
// regular element removal
if (this.isWorkingCopy(element, elementType)) {
// filter out changes to primary compilation unit in working
// copy mode
// just report a change to the resource (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
this.currentDelta().changed(element, IQ7ElementDelta.F_PRIMARY_RESOURCE);
} else {
this.close(element);
this.removeFromParentInfo(element);
this.currentDelta().removed(element);
}
} else {
// element is moved
this.close(element);
this.removeFromParentInfo(element);
IPath movedToPath = delta.getMovedToPath();
IResource res = delta.getResource();
IResource movedToRes;
switch (res.getType()) {
case IResource.PROJECT:
movedToRes = res.getWorkspace().getRoot()
.getProject(movedToPath.lastSegment());
break;
case IResource.FOLDER:
movedToRes = res.getWorkspace().getRoot().getFolder(movedToPath);
break;
case IResource.FILE:
movedToRes = res.getWorkspace().getRoot().getFile(movedToPath);
break;
default:
return;
}
// find the element type of the moved from element
Q7Project movedToInfo = (Q7Project) RcpttCore.create(movedToRes.getProject());
HandleType movedToType = element.getElementType();
// reset current element as it might be inside a nested root
// (popUntilPrefixOf() may use the outer root)
this.currentElement = null;
// create the moved To element
Openable movedToElement = this.createElement(movedToRes, movedToType,
movedToInfo);
if (movedToElement == null) {
// moved outside buildpath
this.currentDelta().removed(element);
} else {
this.currentDelta().movedFrom(element, movedToElement);
}
}
switch (elementType) {
case Model:
this.manager.getIndexManager().reset();
break;
case Project: {
final IQ7Project project = (IQ7Project) element;
this.rootsToRefresh.add(project);
this.projectCachesToReset.add(project);
break;
}
case Folder: {
// reset package fragment cache
IQ7Project project = element.getQ7Project();
this.projectCachesToReset.add(project);
break;
}
}
}
/*
* Flushes all deltas without firing them.
*/
public void flush() {
this.modelDeltas = new ArrayList<IQ7ElementDelta>();
}
/*
* Fire q7 Model delta, flushing them after the fact after post_change
* notification. If the firing mode has been turned off, this has no effect.
*/
public void fire(IQ7ElementDelta customDelta, int eventType) {
if (!this.isFiring) {
return;
}
if (DEBUG) {
System.out
.println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
}
IQ7ElementDelta deltaToNotify;
if (customDelta == null) {
deltaToNotify = this.mergeDeltas(this.modelDeltas);
} else {
deltaToNotify = customDelta;
}
// Notification
// Important: if any listener reacts to notification by updating the
// listeners list or mask, these lists will
// be duplicated, so it is necessary to remember original lists in a
// variable (since field values may change under us)
IElementChangedListener[] listeners;
int[] listenerMask;
int listenerCount;
synchronized (this.state) {
listeners = this.state.elementChangedListeners;
listenerMask = this.state.elementChangedListenerMasks;
listenerCount = this.state.elementChangedListenerCount;
}
switch (eventType) {
case DEFAULT_CHANGE_EVENT:
this.firePostChangeDelta(deltaToNotify, listeners, listenerMask,
listenerCount);
this.fireReconcileDelta(listeners, listenerMask, listenerCount);
break;
case Q7ElementChangedEvent.POST_CHANGE:
this.firePostChangeDelta(deltaToNotify, listeners, listenerMask,
listenerCount);
this.fireReconcileDelta(listeners, listenerMask, listenerCount);
break;
}
}
private void firePostChangeDelta(IQ7ElementDelta deltaToNotify,
IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) {
// post change deltas
if (DEBUG) {
System.out
.println("FIRING POST_CHANGE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
System.out
.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
}
if (deltaToNotify != null) {
// flush now so as to keep listener reactions to post their own
// deltas for subsequent iteration
this.flush();
this.notifyListeners(deltaToNotify, Q7ElementChangedEvent.POST_CHANGE,
listeners, listenerMask, listenerCount);
}
}
@SuppressWarnings("rawtypes")
private void fireReconcileDelta(IElementChangedListener[] listeners,
int[] listenerMask, int listenerCount) {
IQ7ElementDelta deltaToNotify = this.mergeDeltas(this.reconcileDeltas.values());
if (DEBUG) {
System.out
.println("FIRING POST_RECONCILE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
System.out
.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
}
if (deltaToNotify != null) {
// flush now so as to keep listener reactions to post their own
// deltas for subsequent iteration
this.reconcileDeltas = new HashMap();
this.notifyListeners(deltaToNotify, Q7ElementChangedEvent.POST_RECONCILE,
listeners, listenerMask, listenerCount);
}
}
/*
* Returns whether a given delta contains some information relevant to the
* Model, in particular it will not consider SYNC or MARKER only deltas.
*/
private boolean isAffectedBy(IResourceDelta rootDelta) {
// if (rootDelta == null) System.out.println("NULL DELTA");
// long start = System.currentTimeMillis();
if (rootDelta != null) {
// use local exception to quickly escape from delta traversal
class FoundRelevantDeltaException extends RuntimeException {
private static final long serialVersionUID = 7137113252936111022L; // backward
// compatible
// only the class name is used (to differenciate from other
// RuntimeExceptions)
}
try {
rootDelta.accept(new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) /*
* throws
* CoreException
*/{
switch (delta.getKind()) {
case IResourceDelta.ADDED:
case IResourceDelta.REMOVED:
throw new FoundRelevantDeltaException();
case IResourceDelta.CHANGED:
// if any flag is set but SYNC or MARKER, this
// delta
// should be considered
if (delta.getAffectedChildren().length == 0 // only
// check
// leaf
// delta
// nodes
&& (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0) {
throw new FoundRelevantDeltaException();
}
}
return true;
}
});
} catch (FoundRelevantDeltaException e) {
// System.out.println("RELEVANT DELTA detected in: "+
// (System.currentTimeMillis() - start));
return true;
} catch (CoreException e) { // ignore delta if not able to traverse
}
}
// System.out.println("IGNORE SYNC DELTA took: "+
// (System.currentTimeMillis() - start));
return false;
}
/*
* Returns whether the given element is a primary compilation unit in
* working copy mode.
*/
private boolean isWorkingCopy(IQ7Element element, HandleType elementType) {
if (elementType.equals(HandleType.Context)
|| elementType.equals(HandleType.Verification)
|| elementType.equals(HandleType.TestCase)
|| elementType.equals(HandleType.TestSuite)) {
IQ7NamedElement cu = (IQ7NamedElement) element;
return cu.isWorkingCopy();
}
return false;
}
/*
* Merges all awaiting deltas.
*/
@SuppressWarnings("rawtypes")
private IQ7ElementDelta mergeDeltas(Collection deltas) {
if (deltas.size() == 0) {
return null;
}
if (deltas.size() == 1) {
return (IQ7ElementDelta) deltas.iterator().next();
}
if (VERBOSE) {
System.out
.println("MERGING " + deltas.size() + " DELTAS [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Iterator iterator = deltas.iterator();
Q7ElementDelta rootDelta = new Q7ElementDelta(this.manager.getModel());
boolean insertedTree = false;
while (iterator.hasNext()) {
Q7ElementDelta delta = (Q7ElementDelta) iterator.next();
if (VERBOSE) {
System.out.println(delta.toString());
}
IQ7Element element = delta.getElement();
if (this.manager.getModel().equals(element)) {
IQ7ElementDelta[] children = delta.getAffectedChildren();
for (int j = 0; j < children.length; j++) {
Q7ElementDelta projectDelta = (Q7ElementDelta) children[j];
rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
insertedTree = true;
}
IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
if (resourceDeltas != null) {
for (int i = 0, length = resourceDeltas.length; i < length; i++) {
rootDelta.addResourceDelta(resourceDeltas[i]);
insertedTree = true;
}
}
} else {
rootDelta.insertDeltaTree(element, delta);
insertedTree = true;
}
}
if (insertedTree) {
return rootDelta;
}
return null;
}
private void notifyListeners(IQ7ElementDelta deltaToNotify, int eventType,
IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) {
final Q7ElementChangedEvent extraEvent = new Q7ElementChangedEvent(deltaToNotify,
eventType);
for (int i = 0; i < listenerCount; i++) {
if ((listenerMask[i] & eventType) != 0) {
final IElementChangedListener listener = listeners[i];
long start = -1;
if (VERBOSE) {
System.out.print("Listener #" + (i + 1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
start = System.currentTimeMillis();
}
// wrap callbacks with Safe runnable for subsequent listeners to
// be called when some are causing grief
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
exception.printStackTrace();
}
public void run() throws Exception {
listener.elementChanged(extraEvent);
}
});
if (VERBOSE) {
System.out
.println(" -> " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
/*
* Generic processing for elements with changed contents:<ul> <li>The
* element is closed such that any subsequent accesses will re-open the
* element reflecting its new structure. <li>An entry is made in the delta
* reporting a content change (K_CHANGE with F_CONTENT flag set). </ul>
*/
private void nonQ7ResourcesChanged(Openable element, IResourceDelta delta)
throws ModelException {
// reset non-q7 resources if element was open
if (element.isOpen()) {
Q7ElementInfo info = (Q7ElementInfo) element.getElementInfo();
switch (element.getElementType()) {
case Model:
((ModelInfo) info).foreignResources = null;
this.currentDelta().addResourceDelta(delta);
return;
case Project:
((Q7ProjectInfo) info).setForeignResources(null);
// if a package fragment root is the project, clear it too
break;
case Folder:
((Q7FolderInfo) info).setForeignResources(null);
break;
}
}
Q7ElementDelta current = this.currentDelta();
Q7ElementDelta elementDelta = current.find(element);
if (elementDelta == null) {
// don't use find after creating the delta as it can be null (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=63434)
elementDelta = current.changed(element, IQ7ElementDelta.F_CONTENT);
}
elementDelta.addResourceDelta(delta);
}
private void popUntilPrefixOf(IPath path) {
while (this.currentElement != null) {
IPath currentElementPath = null;
IResource currentElementResource = this.currentElement.getResource();
if (currentElementResource != null) {
currentElementPath = currentElementResource.getFullPath();
}
if (currentElementPath != null) {
if (this.currentElement instanceof IQ7Folder
&& ((IQ7Folder) this.currentElement).isRootFolder()
&& currentElementPath.segmentCount() != path.segmentCount() - 1) {
// default package and path is not a direct child
this.currentElement = (Openable) this.currentElement.getParent();
}
if (currentElementPath.isPrefixOf(path)) {
return;
}
}
this.currentElement = (Openable) this.currentElement.getParent();
}
}
private IQ7ElementDelta processResourceDelta(IResourceDelta changes) {
try {
IQ7Model model = this.manager.getModel();
if (!model.isOpen()) {
// force opening of q7 model so that model element delta are
// reported
try {
model.open(null);
} catch (ModelException e) {
if (VERBOSE) {
e.printStackTrace();
}
return null;
}
}
this.currentElement = null;
// get the workspace delta, and start processing there.
IResourceDelta[] deltas = changes.getAffectedChildren();
for (int i = 0; i < deltas.length; i++) {
IResourceDelta delta = deltas[i];
IResource res = delta.getResource();
// find out the element type
Q7Project rootInfo = null;
HandleType elementType;
IProject proj = (IProject) res;
boolean wasQ7Project = this.state.findProject(proj.getName()) != null;
boolean isDylanProject = RcpttCore.hasRcpttNature(proj);
if (!wasQ7Project && !isDylanProject) {
elementType = HandleType.NonQ7;
} else {
rootInfo = (Q7Project) RcpttCore.create(res.getProject());
elementType = HandleType.Project;
}
// traverse delta
Set<IQ7NamedElement> toIndex = new HashSet<IQ7NamedElement>();
this.traverseDelta(delta, elementType, rootInfo, toIndex);
if (toIndex.size() > 0) {
ProjectIndexerManager.indexNamedElements(toIndex);
}
if (elementType.equals(HandleType.NonQ7)
|| (wasQ7Project != isDylanProject && (delta.getKind()) == IResourceDelta.CHANGED)) {
/*
* project has changed nature (deq7ion or open/closed)
*/
try {
// add child as non q7 resource
this.nonQ7ResourcesChanged((Q7Model) model, delta);
} catch (ModelException e) {
// q7 model could not be opened
}
}
}
this.resetProjectCaches();
this.executePostActions();
return this.currentDelta;
} finally {
this.currentDelta = null;
this.rootsToRefresh.clear();
this.projectCachesToReset.clear();
this.postActions.clear();
}
}
private void executePostActions() {
if (postActions.size() == 0) {
return;
}
for (Iterator<Runnable> i = postActions.iterator(); i.hasNext();) {
i.next().run();
}
}
/*
* Traverse the set of projects which have changed namespace, and reset
* their caches and their dependents
*/
@SuppressWarnings("rawtypes")
private void resetProjectCaches() {
if (this.projectCachesToReset.size() == 0) {
return;
}
Iterator iterator = this.projectCachesToReset.iterator();
HashMap projectDepencies = this.state.projectDependencies;
HashSet affectedDependents = new HashSet();
while (iterator.hasNext()) {
Q7Project project = (Q7Project) iterator.next();
project.resetCaches();
this.addDependentProjects(project, projectDepencies, affectedDependents);
}
// reset caches of dependent projects
iterator = affectedDependents.iterator();
while (iterator.hasNext()) {
Q7Project project = (Q7Project) iterator.next();
project.resetCaches();
}
}
/*
* Registers the given delta with this delta processor.
*/
public void registerModelDelta(IQ7ElementDelta delta) {
this.modelDeltas.add(delta);
}
/*
* Removes the given element from its parents cache of children. If the
* element does not have a parent, or the parent is not currently open, this
* has no effect.
*/
private void removeFromParentInfo(Openable child) {
Openable parent = (Openable) child.getParent();
if (parent != null && parent.isOpen()) {
try {
Q7ElementInfo info = (Q7ElementInfo) parent.getElementInfo();
info.removeChild(child);
} catch (ModelException e) {
// do nothing - we already checked if open
}
}
}
/*
* Notification that some resource changes have happened on the platform,
* and that the q7 Model should update any required internal structures such
* that its elements remain consistent. Translates
* <code>IResourceDeltas</code> into <code>IQ7ElementDeltas</code>.
*
* @see IResourceDelta
*
* @see IResource
*/
@SuppressWarnings("unused")
public void resourceChanged(IResourceChangeEvent event) {
int eventType = this.overridenEventType == -1 ? event.getType()
: this.overridenEventType;
IResource resource = event.getResource();
IResourceDelta delta = event.getDelta();
switch (eventType) {
case IResourceChangeEvent.PRE_DELETE:
if (resource.getType() == IResource.PROJECT
&& RcpttCore.hasRcpttNature((IProject) resource)) {
this.deleting((IProject) resource);
}
return;
case IResourceChangeEvent.POST_CHANGE:
if (this.isAffectedBy(delta)) { // avoid populating for SYNC or
// MARKER
// deltas
try {
try {
this.stopDeltas();
this.checkProjectsBeingAddedOrRemoved(delta);
// generate external archive change deltas
if (this.refreshedElements != null) {
Set<IQ7Element> refreshedElementsCopy = null;
if (refreshedElements != null) {
refreshedElementsCopy = new HashSet<IQ7Element>();
refreshedElementsCopy.addAll(refreshedElements);
// To avoid concurrent modifications
this.refreshedElements = null;
}
}
IQ7ElementDelta translatedDelta = this
.processResourceDelta(delta);
if (translatedDelta != null) {
this.registerModelDelta(translatedDelta);
}
} finally {
this.startDeltas();
}
// Call update for model
updateModel(null);
IElementChangedListener[] listeners;
// int listenerCount;
synchronized (this.state) {
listeners = this.state.elementChangedListeners;
// listenerCount =
// this.state.elementChangedListenerCount;
}
this.fire(null, Q7ElementChangedEvent.POST_CHANGE);
} finally {
// workaround for bug 15168 circular errors not reported
this.state.resetOldProjectNames();
}
}
return;
case IResourceChangeEvent.PRE_BUILD:
if (!this.isAffectedBy(delta)) {
return; // avoid populating for SYNC or MARKER deltas
}
// does not fire any deltas
return;
case IResourceChangeEvent.POST_BUILD:
return;
}
}
/*
* Turns the firing mode to on. That is, deltas that are/have been
* registered will be fired.
*/
private void startDeltas() {
this.isFiring = true;
}
/*
* Turns the firing mode to off. That is, deltas that are/have been
* registered will not be fired until deltas are started again.
*/
private void stopDeltas() {
this.isFiring = false;
}
private void traverseDelta(IResourceDelta delta, HandleType elementType,
Q7Project rootInfo, Set<IQ7NamedElement> toIndex) {
IResource res = delta.getResource();
// set stack of elements
if (this.currentElement == null && rootInfo != null) {
this.currentElement = rootInfo;
}
// process current delta
boolean processChildren = true;
if (res instanceof IProject) {
processChildren = this.updateCurrentDeltaAndIndex(delta, elementType,
rootInfo, toIndex);
} else if (rootInfo != null) {
processChildren = this.updateCurrentDeltaAndIndex(delta, elementType,
rootInfo, toIndex);
} else {
// not yet inside a package fragment root
processChildren = true;
}
// process children if needed
if (processChildren) {
IResourceDelta[] children = delta.getAffectedChildren();
int length = children.length;
for (int i = 0; i < length; i++) {
IResourceDelta child = children[i];
IResource childRes = child.getResource();
// find out whether the child is a package fragment root of the
// current project
int childKind = childRes.getType();
// compute child type
HandleType childType = elementType(childRes, childKind, rootInfo);
this.traverseDelta(child, childType, rootInfo, toIndex);
}
}
}
private HandleType elementType(IResource childRes, int childKind, Q7Project rootInfo) {
switch (childKind) {
case IResource.FOLDER:
return HandleType.Folder;
case IResource.PROJECT:
return HandleType.Project;
case IResource.ROOT:
return HandleType.Model;
case IResource.FILE:
IPath childPath = childRes.getFullPath();
if (RcpttCore.isQ7File(childPath)) {
if (RcpttCore.isQ7Context(childPath))
return HandleType.Context;
else if (RcpttCore.isQ7Verification(childPath))
return HandleType.Verification;
else if (RcpttCore.isQ7TestSuite(childPath))
return HandleType.TestSuite;
else if (RcpttCore.isQ7ProjectMetadata(childPath))
return HandleType.ProjectMetadata;
else
return HandleType.TestCase;
}
}
return HandleType.NonQ7;
}
public boolean updateCurrentDeltaAndIndex(IResourceDelta delta,
HandleType elementType, Q7Project rootInfo, Set<IQ7NamedElement> toIndex) {
Openable element;
switch (delta.getKind()) {
case IResourceDelta.ADDED:
IResource deltaRes = delta.getResource();
element = this.createElement(deltaRes, elementType, rootInfo);
if (element == null) {
return true;
}
this.updateIndex(element, delta, toIndex);
this.elementAdded(element, delta);
return elementType.equals(HandleType.Folder);
case IResourceDelta.REMOVED:
deltaRes = delta.getResource();
element = this.createElement(deltaRes, elementType, rootInfo);
if (element == null) {
// resource might be containing shared roots (see bug 19058)
return true;
}
this.updateIndex(element, delta, toIndex);
this.elementRemoved(element, delta, rootInfo);
return elementType.equals(HandleType.Folder);
case IResourceDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IResourceDelta.CONTENT) != 0
|| (flags & IResourceDelta.ENCODING) != 0) {
// content or encoding has changed
element = this.createElement(delta.getResource(), elementType, rootInfo);
if (element == null) {
return false;
}
this.updateIndex(element, delta, toIndex);
this.contentChanged(element);
} else if (elementType.equals(HandleType.Project)) {
if ((flags & IResourceDelta.OPEN) != 0) {
// project has been opened or closed
final IProject res = (IProject) delta.getResource();
element = this.createElement(res, elementType, rootInfo);
if (element == null) {
return false;
}
if (res.isOpen()) {
if (RcpttCore.hasRcpttNature(res)) {
this.addToParentInfo(element);
this.currentDelta().opened(element);
// refresh pkg fragment roots and caches of the
// project (and its dependents)
final IQ7Project project = (IQ7Project) element;
this.rootsToRefresh.add(project);
this.projectCachesToReset.add(project);
this.postActions.add(new Runnable() {
public void run() {
ProjectIndexerManager.indexProject(res);
}
});
}
} else {
if (this.state.findProject(res.getName()) != null) {
this.close(element);
this.removeFromParentInfo(element);
this.currentDelta().closed(element);
this.manager.getIndexManager().discardJobs(element.getName());
final IPath projectPath = res.getFullPath();
this.manager.getIndexManager().removeIndex(projectPath);
ProjectIndexerManager.removeProject(projectPath);
}
}
return false; // when a project is open/closed don't
// process children
}
if ((flags & IResourceDelta.DESCRIPTION) != 0) {
IProject res = (IProject) delta.getResource();
boolean isQ7Project = RcpttCore.hasRcpttNature(res);
element = this.createElement(res, elementType, rootInfo);
if (element == null) {
return false; // note its resources are still
}
// project's nature has been added or removed
// visible as roots to other
// projects
if (isQ7Project) {
this.elementAdded(element, delta);
ProjectIndexerManager.indexProject(res);
} else {
this.elementRemoved(element, delta, rootInfo);
this.manager.getIndexManager().discardJobs(element.getName());
final IPath projectPath = res.getFullPath();
this.manager.getIndexManager().removeIndex(projectPath);
ProjectIndexerManager.removeProject(projectPath);
}
return false; // when a project's nature is
// added/removed don't process children
}
}
return true;
}
return true;
}
private void updateIndex(Openable element, IResourceDelta delta,
Set<IQ7NamedElement> toIndex) {
IndexManager indexManager = this.manager.getIndexManager();
if (indexManager == null) {
return;
}
switch (element.getElementType()) {
case Project:
switch (delta.getKind()) {
case IResourceDelta.ADDED:
final IQ7Project q7Project = element.getQ7Project();
this.postActions.add(new Runnable() {
public void run() {
ProjectIndexerManager.indexProject(q7Project);
}
});
break;
case IResourceDelta.REMOVED:
final IPath projectPath = element.getQ7Project().getProject()
.getFullPath();
indexManager.removeIndex(projectPath);
ProjectIndexerManager.removeProject(projectPath);
// NB: Discarding index jobs belonging to this project
// was done
// during PRE_DELETE
break;
// NB: Update of index if project is opened, closed, or its
// q7
// nature is added or removed
// is done in updateCurrentDeltaAndIndex
}
break;
case Folder:
switch (delta.getKind()) {
case IResourceDelta.ADDED:
case IResourceDelta.REMOVED:
IQ7Folder pkg = null;
if (element instanceof IQ7Project) {
IQ7Project root = (IQ7Project) element;
pkg = root.getFolder(Path.EMPTY);
} else {
pkg = (IQ7Folder) element;
}
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++) {
IResourceDelta child = children[i];
IResource resource = child.getResource();
// TODO (philippe) Why do this? Every child is added
// anyway
// as the delta is walked
if (resource instanceof IFile) {
String name = resource.getName();
if (RcpttCore.isQ7File(resource.getFullPath())) {
Openable cu = (Openable) pkg.getNamedElement(name);
this.updateIndex(cu, child, toIndex);
}
}
}
}
break;
case TestCase:
case TestSuite:
case Context:
case Verification:
case ProjectMetadata:
IFile file = (IFile) delta.getResource();
switch (delta.getKind()) {
case IResourceDelta.CHANGED:
// no need to index if the content has not changed
int flags = delta.getFlags();
if ((flags & IResourceDelta.CONTENT) == 0
&& (flags & IResourceDelta.ENCODING) == 0) {
break;
}
case IResourceDelta.ADDED:
if (ProjectIndexerManager.isIndexerEnabled(file.getProject())) {
// ProjectIndexerManager
// .indexNamedElement((IQ7NamedElement) element);
toIndex.add((IQ7NamedElement) element);
}
// Clean file from secondary types cache but do not
// update
// indexing secondary type cache as it will be updated
// through
// indexing itself
// this.manager.secondaryTypesRemoving(file, false);
break;
case IResourceDelta.REMOVED:
final IProject project = file.getProject();
/* remove project segment */
final String path = file.getFullPath().removeFirstSegments(1).toString();
ProjectIndexerManager.removeNamedElement(RcpttCore.create(project), path);
break;
}
}
}
/*
* Update Model given some delta
*/
public void updateModel(IQ7ElementDelta customDelta) {
if (customDelta == null) {
for (int i = 0, length = this.modelDeltas.size(); i < length; i++) {
IQ7ElementDelta delta = this.modelDeltas.get(i);
this.modelUpdater.processDelta(delta);
}
} else {
this.modelUpdater.processDelta(customDelta);
}
}
}