blob: ac47f5ad8370d475a61cfd744446b1ddd622bf88 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.dltk.internal.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IPath;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.search.indexing.IndexManager;
import org.eclipse.dltk.internal.core.ModelManager.PerProjectInfo;
import org.eclipse.dltk.internal.core.search.ProjectIndexerManager;
public class BuildpathChange {
public static int NO_DELTA = 0x00;
public static int HAS_DELTA = 0x01;
public static int HAS_PROJECT_CHANGE = 0x02;
public static int HAS_LIBRARY_CHANGE = 0x04;
ScriptProject project;
IBuildpathEntry[] oldRawBuildpath;
IBuildpathEntry[] oldResolvedBuildpath;
public BuildpathChange(ScriptProject project,
IBuildpathEntry[] oldRawBuildpath,
IBuildpathEntry[] oldResolvedBuildpath) {
this.project = project;
this.oldRawBuildpath = oldRawBuildpath;
this.oldResolvedBuildpath = oldResolvedBuildpath;
}
private void addBuildpathDeltas(ModelElementDelta delta,
IProjectFragment[] roots, int flag) {
for (int i = 0; i < roots.length; i++) {
IProjectFragment root = roots[i];
delta.changed(root, flag);
if ((flag & IModelElementDelta.F_REMOVED_FROM_BUILDPATH) != 0
/*
* || (flag & IModelElementDelta.F_SOURCEATTACHED) != 0 || (flag &
* IModelElementDelta.F_SOURCEDETACHED) != 0
*/) {
try {
root.close();
} catch (ModelException e) {
// ignore
}
}
}
}
/*
* Returns the index of the item in the list if the given list contains the
* specified entry. If the list does not contain the entry, -1 is returned.
*/
private int buildpathContains(IBuildpathEntry[] list,
IBuildpathEntry entry) {
IPath[] exclusionPatterns = entry.getExclusionPatterns();
IPath[] inclusionPatterns = entry.getInclusionPatterns();
int listLen = list == null ? 0 : list.length;
nextEntry: for (int i = 0; i < listLen; i++) {
IBuildpathEntry other = list[i];
if (other.getContentKind() == entry.getContentKind()
&& other.getEntryKind() == entry.getEntryKind()
&& other.isExported() == entry.isExported()
&& other.getPath().equals(entry.getPath())) {
// check inclusion patterns
IPath[] otherIncludes = other.getInclusionPatterns();
if (inclusionPatterns != otherIncludes) {
if (inclusionPatterns == null)
continue;
int includeLength = inclusionPatterns.length;
if (otherIncludes == null
|| otherIncludes.length != includeLength)
continue;
for (int j = 0; j < includeLength; j++) {
// compare toStrings instead of IPaths
// since IPath.equals is specified to ignore trailing
// separators
if (!inclusionPatterns[j].toString()
.equals(otherIncludes[j].toString()))
continue nextEntry;
}
}
// check exclusion patterns
IPath[] otherExcludes = other.getExclusionPatterns();
if (exclusionPatterns != otherExcludes) {
if (exclusionPatterns == null)
continue;
int excludeLength = exclusionPatterns.length;
if (otherExcludes == null
|| otherExcludes.length != excludeLength)
continue;
for (int j = 0; j < excludeLength; j++) {
// compare toStrings instead of IPaths
// since IPath.equals is specified to ignore trailing
// separators
if (!exclusionPatterns[j].toString()
.equals(otherExcludes[j].toString()))
continue nextEntry;
}
}
return i;
}
}
return -1;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BuildpathChange))
return false;
return this.project.equals(((BuildpathChange) obj).project);
}
/*
* Generates a buildpath change delta for this buildpath change. Returns
* whether a delta was generated, and whether project reference have
* changed.
*/
public int generateDelta(ModelElementDelta delta,
boolean addBuildpathChange) {
ModelManager manager = ModelManager.getModelManager();
DeltaProcessingState state = manager.deltaState;
if (state.findProject(this.project.getElementName()) == null)
// project doesn't exist yet (we're in an IWorkspaceRunnable)
// no need to create a delta here and no need to index (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=133334)
// the delta processor will create an ADDED project delta, and index
// the project
return NO_DELTA;
DeltaProcessor deltaProcessor = state.getDeltaProcessor();
IBuildpathEntry[] newResolvedBuildpath = null;
int result = NO_DELTA;
try {
PerProjectInfo perProjectInfo = this.project.getPerProjectInfo();
// get new info
this.project.resolveBuildpath(perProjectInfo,
false/*
* don't use previous session values
*/, addBuildpathChange);
IBuildpathEntry[] newRawBuildpath;
// use synchronized block to ensure consistency
synchronized (perProjectInfo) {
newRawBuildpath = perProjectInfo.rawBuildpath;
newResolvedBuildpath = perProjectInfo.getResolvedBuildpath();
}
if (newResolvedBuildpath == null) {
// another thread reset the resolved buildpath, use a temporary
// PerProjectInfo
PerProjectInfo temporaryInfo = this.project.newTemporaryInfo();
this.project.resolveBuildpath(temporaryInfo,
false/*
* don't use previous session values
*/, addBuildpathChange);
newRawBuildpath = temporaryInfo.rawBuildpath;
newResolvedBuildpath = temporaryInfo.getResolvedBuildpath();
}
// check if raw buildpath has changed
if (this.oldRawBuildpath != null
&& !ScriptProject.areBuildpathsEqual(this.oldRawBuildpath,
newRawBuildpath)) {
delta.changed(this.project,
IModelElementDelta.F_BUILDPATH_CHANGED);
result |= HAS_DELTA;
// reset containers that are no longer on the buildpath
// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=139446)
for (int i = 0, length = this.oldRawBuildpath.length; i < length; i++) {
IBuildpathEntry entry = this.oldRawBuildpath[i];
if (entry.getEntryKind() == IBuildpathEntry.BPE_CONTAINER) {
if (buildpathContains(newRawBuildpath, entry) == -1)
manager.containerPut(this.project, entry.getPath(),
null);
}
}
}
// if no changes to resolved buildpath, nothing more to do
if (this.oldResolvedBuildpath != null
&& ScriptProject.areBuildpathsEqual(
this.oldResolvedBuildpath, newResolvedBuildpath))
return result;
// close cached info
this.project.close();
// TODO: check is this is relevant here
// ensure caches of dependent projects are reset as well (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=207890)
// deltaProcessor.projectCachesToReset.add(this.project);
} catch (ModelException e) {
if (DeltaProcessor.VERBOSE) {
e.printStackTrace();
}
// project no longer exist
return result;
}
if (this.oldResolvedBuildpath == null)
return result;
delta.changed(this.project,
IModelElementDelta.F_RESOLVED_BUILDPATH_CHANGED);
result |= HAS_DELTA;
state.addForRefresh(this.project);
// ensure external jars are refreshed for this project (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=212769 )
Map<IPath, IProjectFragment> removedRoots = null;
IProjectFragment[] roots = null;
Map<IScriptProject, IProjectFragment[]> allOldRoots;
if ((allOldRoots = deltaProcessor.oldRoots) != null) {
roots = allOldRoots.get(this.project);
}
if (roots != null) {
removedRoots = new HashMap<>();
for (int i = 0; i < roots.length; i++) {
IProjectFragment root = roots[i];
removedRoots.put(root.getPath(), root);
}
}
int newLength = newResolvedBuildpath.length;
int oldLength = this.oldResolvedBuildpath.length;
for (int i = 0; i < oldLength; i++) {
int index = buildpathContains(newResolvedBuildpath,
this.oldResolvedBuildpath[i]);
if (index == -1) {
// remote project changes
int entryKind = this.oldResolvedBuildpath[i].getEntryKind();
if (entryKind == IBuildpathEntry.BPE_PROJECT) {
result |= HAS_PROJECT_CHANGE;
continue;
}
if (entryKind == IBuildpathEntry.BPE_LIBRARY) {
result |= HAS_LIBRARY_CHANGE;
}
IProjectFragment[] pkgFragmentRoots = null;
if (removedRoots != null) {
IProjectFragment oldRoot = removedRoots
.get(this.oldResolvedBuildpath[i].getPath());
if (oldRoot != null) { // use old root if any (could be
// none
// if entry wasn't bound)
pkgFragmentRoots = new IProjectFragment[] { oldRoot };
}
}
if (pkgFragmentRoots == null) {
try {
List<IProjectFragment> accumulatedRoots = new ArrayList<>();
HashSet<String> rootIDs = new HashSet<>(5);
rootIDs.add(this.project.rootID());
this.project.computeProjectFragments(
this.oldResolvedBuildpath[i], accumulatedRoots,
rootIDs, null, // inside original project
false, // don't check existence
false, // don't retrieve exported roots
null); /* no reverse map */
pkgFragmentRoots = accumulatedRoots.toArray(
new IProjectFragment[accumulatedRoots.size()]);
} catch (ModelException e) {
pkgFragmentRoots = new IProjectFragment[] {};
}
}
addBuildpathDeltas(delta, pkgFragmentRoots,
IModelElementDelta.F_REMOVED_FROM_BUILDPATH);
} else {
// remote project changes
if (this.oldResolvedBuildpath[i]
.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
result |= HAS_PROJECT_CHANGE;
continue;
}
if (index != i) { // reordering of the buildpath
addBuildpathDeltas(delta,
this.project.computeProjectFragments(
this.oldResolvedBuildpath[i]),
IModelElementDelta.F_REORDER);
}
// TODO (alex) check source attachment
}
}
for (int i = 0; i < newLength; i++) {
int index = buildpathContains(this.oldResolvedBuildpath,
newResolvedBuildpath[i]);
if (index == -1) {
// remote project changes
int entryKind = newResolvedBuildpath[i].getEntryKind();
if (entryKind == IBuildpathEntry.BPE_PROJECT) {
result |= HAS_PROJECT_CHANGE;
continue;
}
if (entryKind == IBuildpathEntry.BPE_LIBRARY) {
result |= HAS_LIBRARY_CHANGE;
}
addBuildpathDeltas(delta,
this.project.computeProjectFragments(
newResolvedBuildpath[i]),
IModelElementDelta.F_ADDED_TO_BUILDPATH);
} // buildpath reordering has already been generated in previous
// loop
}
return result;
}
@Override
public int hashCode() {
return this.project.hashCode();
}
/*
* Request the indexing of entries that have been added, and remove the
* index for removed entries.
*/
public void requestIndexing() {
IBuildpathEntry[] newResolvedBuildpath = null;
try {
newResolvedBuildpath = this.project.getResolvedBuildpath();
} catch (ModelException e) {
// project doesn't exist
return;
}
ModelManager manager = ModelManager.getModelManager();
IndexManager indexManager = manager.indexManager;
if (indexManager == null)
return;
DeltaProcessingState state = manager.deltaState;
int newLength = newResolvedBuildpath.length;
int oldLength = this.oldResolvedBuildpath == null ? 0
: this.oldResolvedBuildpath.length;
for (int i = 0; i < oldLength; i++) {
int index = buildpathContains(newResolvedBuildpath,
this.oldResolvedBuildpath[i]);
if (index == -1) {
// remote projects are not indexed in this project
if (this.oldResolvedBuildpath[i]
.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
continue;
}
// Remove the source files from the index for a source folder
// For a lib folder or a .zip file, remove the corresponding
// index if not shared.
IBuildpathEntry oldEntry = this.oldResolvedBuildpath[i];
final IPath path = oldEntry.getPath();
int changeKind = this.oldResolvedBuildpath[i].getEntryKind();
switch (changeKind) {
case IBuildpathEntry.BPE_SOURCE:
char[][] inclusionPatterns = ((BuildpathEntry) oldEntry)
.fullInclusionPatternChars();
char[][] exclusionPatterns = ((BuildpathEntry) oldEntry)
.fullExclusionPatternChars();
indexManager.removeSourceFolderFromIndex(this.project, path,
inclusionPatterns, exclusionPatterns);
ProjectIndexerManager.removeProjectFragment(project, path);
break;
case IBuildpathEntry.BPE_LIBRARY:
if (state.otherRoots.get(path) == null) { // if root was
// not
// shared
indexManager.discardJobs(path.toString());
indexManager.removeIndex(path);
ProjectIndexerManager.removeLibrary(project, path);
}
break;
}
}
}
for (int i = 0; i < newLength; i++) {
int index = buildpathContains(this.oldResolvedBuildpath,
newResolvedBuildpath[i]);
if (index == -1 || newResolvedBuildpath[i]
.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) {
// remote projects are not indexed in this project
if (newResolvedBuildpath[i]
.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
continue;
}
// Request indexing
int entryKind = newResolvedBuildpath[i].getEntryKind();
switch (entryKind) {
case IBuildpathEntry.BPE_LIBRARY:
boolean pathHasChanged = true;
IPath newPath = newResolvedBuildpath[i].getPath();
for (int j = 0; j < oldLength; j++) {
IBuildpathEntry oldEntry = this.oldResolvedBuildpath[j];
if (oldEntry.getPath().equals(newPath)) {
pathHasChanged = false;
break;
}
}
if (pathHasChanged) {
// IBuildpathEntry entry = newResolvedBuildpath[i];
// char[][] inclusionPatterns = ((BuildpathEntry) entry)
// .fullInclusionPatternChars();
// char[][] exclusionPatterns = ((BuildpathEntry) entry)
// .fullExclusionPatternChars();
ProjectIndexerManager.indexLibrary(project, newPath);
}
break;
case IBuildpathEntry.BPE_SOURCE:
IBuildpathEntry entry = newResolvedBuildpath[i];
IPath path = entry.getPath();
// char[][] inclusionPatterns = ((BuildpathEntry) entry)
// .fullInclusionPatternChars();
// char[][] exclusionPatterns = ((BuildpathEntry) entry)
// .fullExclusionPatternChars();
ProjectIndexerManager.indexProjectFragment(project, path);
break;
}
}
}
}
@Override
public String toString() {
return "BuildpathChange: " + this.project.getElementName(); //$NON-NLS-1$
}
}