blob: 876a3ba7722c95b498e2ee058db6c5319124205d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.core.hierarchy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.compiler.util.HashtableOfObject;
import org.eclipse.dltk.compiler.util.HashtableOfObjectToInt;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISearchableEnvironment;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.search.DLTKSearchParticipant;
import org.eclipse.dltk.core.search.IDLTKSearchConstants;
import org.eclipse.dltk.core.search.IDLTKSearchScope;
import org.eclipse.dltk.core.search.SearchParticipant;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.indexing.IIndexConstants;
import org.eclipse.dltk.core.search.indexing.IndexManager;
import org.eclipse.dltk.core.search.matching.MatchLocator;
import org.eclipse.dltk.internal.compiler.env.AccessRuleSet;
import org.eclipse.dltk.internal.core.IPathRequestor;
import org.eclipse.dltk.internal.core.Member;
import org.eclipse.dltk.internal.core.ModelManager;
import org.eclipse.dltk.internal.core.Openable;
import org.eclipse.dltk.internal.core.ScriptProject;
import org.eclipse.dltk.internal.core.search.IndexQueryRequestor;
import org.eclipse.dltk.internal.core.search.SubTypeSearchJob;
import org.eclipse.dltk.internal.core.search.matching.SuperTypeReferencePattern;
import org.eclipse.dltk.internal.core.util.HandleFactory;
public class IndexBasedHierarchyBuilder extends HierarchyBuilder {
public static final int MAXTICKS = 800; // heuristic so that there still
// progress for deep hierachies
/**
* A temporary cache of compilation units to handles to speed info to handle
* translation - it only contains the entries for the types in the region
* (in other words, it contains no supertypes outside the region).
*/
protected Map cuToHandle;
/**
* The scope this hierarchy builder should restrain results to.
*/
protected IDLTKSearchScope scope;
/**
* Cache used to record binaries recreated from index matches
*/
protected Map binariesFromIndexMatches;
/**
* Collection used to queue subtype index queries
*/
static class Queue {
public char[][] names = new char[10][];
public int start = 0;
public int end = -1;
public void add(char[] name) {
if (++this.end == this.names.length) {
this.end -= this.start;
System.arraycopy(this.names, this.start,
this.names = new char[this.end * 2][], 0, this.end);
this.start = 0;
}
this.names[this.end] = name;
}
public char[] retrieve() {
if (this.start > this.end) {
return null; // none
}
char[] name = this.names[this.start++];
if (this.start > this.end) {
this.start = 0;
this.end = -1;
}
return name;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer("Queue:\n"); //$NON-NLS-1$
for (int i = this.start; i <= this.end; i++) {
buffer.append(this.names[i]).append('\n');
}
return buffer.toString();
}
}
public IndexBasedHierarchyBuilder(TypeHierarchy hierarchy,
IDLTKSearchScope scope) throws ModelException {
super();
setRequestor(hierarchy);
this.cuToHandle = new HashMap(5);
this.binariesFromIndexMatches = new HashMap(10);
this.scope = scope;
}
@Override
public void build(boolean computeSubtypes) {
ModelManager manager = ModelManager.getModelManager();
try {
// optimize access to zip files while building hierarchy
manager.cacheZipFiles();
if (computeSubtypes) {
// Note by construction there always is a focus type here
IType focusType = getType();
boolean focusIsObject = focusType.getElementName().equals(
new String(IIndexConstants.OBJECT));
int amountOfWorkForSubtypes = focusIsObject ? 5 : 80; // percentage
// of
// work
// needed
// to
// get
// possible
// subtypes
IProgressMonitor possibleSubtypesMonitor = this.hierarchy.progressMonitor == null ? null
: new SubProgressMonitor(
this.hierarchy.progressMonitor,
amountOfWorkForSubtypes);
HashSet localTypes = new HashSet(10); // contains the paths
// that have potential
// subtypes that are
// local/anonymous types
String[] allPossibleSubtypes;
if (((Member) focusType).getOuterMostLocalContext() == null) {
// top level or member type
allPossibleSubtypes = this.determinePossibleSubTypes(
localTypes, possibleSubtypesMonitor);
} else {
// local or anonymous type
allPossibleSubtypes = CharOperation.NO_STRINGS;
}
if (allPossibleSubtypes != null) {
IProgressMonitor buildMonitor = this.hierarchy.progressMonitor == null ? null
: new SubProgressMonitor(
this.hierarchy.progressMonitor,
100 - amountOfWorkForSubtypes);
this.hierarchy.initialize(allPossibleSubtypes.length);
buildFromPotentialSubtypes(allPossibleSubtypes, localTypes,
buildMonitor);
}
} else {
this.hierarchy.initialize(1);
this.buildSupertypes();
}
} finally {
manager.flushZipFiles();
}
}
private void buildForProject(ScriptProject project,
ArrayList potentialSubtypes,
org.eclipse.dltk.core.ISourceModule[] workingCopies,
HashSet localTypes, IProgressMonitor monitor) throws ModelException {
// resolve
int openablesLength = potentialSubtypes.size();
if (openablesLength > 0) {
// copy vectors into arrays
Openable[] openables = new Openable[openablesLength];
potentialSubtypes.toArray(openables);
// sort in the order of roots and in reverse alphabetical order for
// .class file
// since requesting top level types in the process of caching an
// enclosing type is
// not supported by the lookup environment
IProjectFragment[] roots = project.getProjectFragments();
int rootsLength = roots.length;
final HashtableOfObjectToInt indexes = new HashtableOfObjectToInt(
openablesLength);
for (int i = 0; i < openablesLength; i++) {
IModelElement root = openables[i]
.getAncestor(IModelElement.PROJECT_FRAGMENT);
int index;
for (index = 0; index < rootsLength; index++) {
if (roots[index].equals(root)) {
break;
}
}
indexes.put(openables[i], index);
}
Arrays.sort(openables, (a, b) -> {
int aIndex = indexes.get(a);
int bIndex = indexes.get(b);
if (aIndex != bIndex) {
return aIndex - bIndex;
}
return b.getElementName()
.compareTo(a.getElementName());
});
IType focusType = this.getType();
boolean inProjectOfFocusType = focusType != null
&& focusType.getScriptProject().equals(project);
org.eclipse.dltk.core.ISourceModule[] unitsToLookInside = null;
if (inProjectOfFocusType) {
org.eclipse.dltk.core.ISourceModule unitToLookInside = focusType
.getSourceModule();
if (unitToLookInside != null) {
int wcLength = workingCopies == null ? 0
: workingCopies.length;
if (wcLength == 0) {
unitsToLookInside = new org.eclipse.dltk.core.ISourceModule[] { unitToLookInside };
} else {
unitsToLookInside = new org.eclipse.dltk.core.ISourceModule[wcLength + 1];
unitsToLookInside[0] = unitToLookInside;
System.arraycopy(workingCopies, 0, unitsToLookInside,
1, wcLength);
}
} else {
unitsToLookInside = workingCopies;
}
}
ISearchableEnvironment searchableEnvironment = project
.newSearchableNameEnvironment(unitsToLookInside);
this.nameLookup = searchableEnvironment.getNameLookup();
// Map options = project.getOptions(true);
// disable task tags to speed up parsing
// options.put(DLTKCore.COMPILER_TASK_TAGS, ""); //$NON-NLS-1$
this.hierarchyResolver = new HierarchyResolver(this);
// searchableEnvironment, options, this,
//new DefaultProblemFactory());
if (focusType != null) {
Member declaringMember = ((Member) focusType)
.getOuterMostLocalContext();
if (declaringMember == null) {
// top level or member type
// if (!inProjectOfFocusType) {
// char[] typeQualifiedName = focusType
// .getTypeQualifiedName('.').toCharArray();
// String[] packageName = ((PackageFragment) focusType
// .getPackageFragment()).names;
// if (searchableEnvironment.findType(typeQualifiedName,
// Util.toCharArrays(packageName)) == null) {
// // focus type is not visible in this project: no
// // need to go further
// return;
// }
// }
} else {
// local or anonymous type
Openable openable;
openable = (Openable) declaringMember.getSourceModule();
localTypes = new HashSet();
localTypes.add(openable.getPath().toString());
this.hierarchyResolver.resolve(new Openable[] { openable }, localTypes);
return;
}
}
this.hierarchyResolver.resolve(openables, localTypes);
}
}
/**
* Configure this type hierarchy based on the given potential subtypes.
*/
private void buildFromPotentialSubtypes(String[] allPotentialSubTypes,
HashSet localTypes, IProgressMonitor monitor) {
IType focusType = this.getType();
// substitute compilation units with working copies
HashMap wcPaths = new HashMap(); // a map from path to working copies
int wcLength;
org.eclipse.dltk.core.ISourceModule[] workingCopies = this.hierarchy.workingCopies;
if (workingCopies != null && (wcLength = workingCopies.length) > 0) {
String[] newPaths = new String[wcLength];
for (int i = 0; i < wcLength; i++) {
org.eclipse.dltk.core.ISourceModule workingCopy = workingCopies[i];
String path = workingCopy.getPath().toString();
wcPaths.put(path, workingCopy);
newPaths[i] = path;
}
int potentialSubtypesLength = allPotentialSubTypes.length;
System.arraycopy(allPotentialSubTypes, 0,
allPotentialSubTypes = new String[potentialSubtypesLength
+ wcLength], 0, potentialSubtypesLength);
System.arraycopy(newPaths, 0, allPotentialSubTypes,
potentialSubtypesLength, wcLength);
}
int length = allPotentialSubTypes.length;
// inject the compilation unit of the focus type (so that types in
// this cu have special visibility permission (this is also usefull
// when the cu is a working copy)
Openable focusCU = (Openable) focusType.getSourceModule();
String focusPath = null;
if (focusCU != null) {
focusPath = focusCU.getPath().toString();
if (length > 0) {
System.arraycopy(allPotentialSubTypes, 0,
allPotentialSubTypes = new String[length + 1], 0,
length);
allPotentialSubTypes[length] = focusPath;
} else {
allPotentialSubTypes = new String[] { focusPath };
}
length++;
}
/*
* Sort in alphabetical order so that potential subtypes are grouped per
* project
*/
Arrays.sort(allPotentialSubTypes);
ArrayList potentialSubtypes = new ArrayList();
try {
// create element infos for subtypes
HandleFactory factory = new HandleFactory();
IScriptProject currentProject = null;
if (monitor != null) {
monitor.beginTask("", length * 2); //$NON-NLS-1$ // 1 for build binding, 1 for connect hierarchy
}
for (int i = 0; i < length; i++) {
try {
String resourcePath = allPotentialSubTypes[i];
// skip duplicate paths (e.g. if focus path was injected
// when it was already a potential subtype)
if (i > 0
&& resourcePath.equals(allPotentialSubTypes[i - 1])) {
continue;
}
Openable handle;
org.eclipse.dltk.core.ISourceModule workingCopy = (org.eclipse.dltk.core.ISourceModule) wcPaths
.get(resourcePath);
if (workingCopy != null) {
handle = (Openable) workingCopy;
} else {
handle = resourcePath.equals(focusPath) ? focusCU
: factory.createOpenable(resourcePath,
this.scope);
if (handle == null) {
continue; // match is outside classpath
}
}
IScriptProject project = handle.getScriptProject();
if (currentProject == null) {
currentProject = project;
potentialSubtypes = new ArrayList(5);
} else if (!currentProject.equals(project)) {
// build current project
this.buildForProject((ScriptProject) currentProject,
potentialSubtypes, workingCopies, localTypes,
monitor);
currentProject = project;
potentialSubtypes = new ArrayList(5);
}
potentialSubtypes.add(handle);
} catch (ModelException e) {
continue;
}
}
// build last project
try {
if (currentProject == null) {
// case of no potential subtypes
currentProject = focusType.getScriptProject();
potentialSubtypes.add(focusType.getSourceModule());
}
this.buildForProject((ScriptProject) currentProject,
potentialSubtypes, workingCopies, localTypes, monitor);
} catch (ModelException e) {
// ignore
}
// Compute hierarchy of focus type if not already done (case of a
// type with potential subtypes that are not real subtypes)
if (!this.hierarchy.contains(focusType)) {
try {
currentProject = focusType.getScriptProject();
potentialSubtypes = new ArrayList();
potentialSubtypes.add(focusType.getSourceModule());
this.buildForProject((ScriptProject) currentProject,
potentialSubtypes, workingCopies, localTypes,
monitor);
} catch (ModelException e) {
// ignore
}
}
// Add focus if not already in (case of a type with no explicit
// super type)
// if (!this.hierarchy.contains(focusType)) {
// this.hierarchy.addRootClass(focusType);
// }
} finally {
if (monitor != null) {
monitor.done();
}
}
}
@Override
protected ISourceModule createCompilationUnitFromPath(Openable handle,
IFile file) {
ISourceModule unit = super.createCompilationUnitFromPath(handle,
file);
this.cuToHandle.put(unit, handle);
return unit;
}
/**
* Returns all of the possible subtypes of this type hierarchy. Returns null
* if they could not be determine.
*/
protected String[] determinePossibleSubTypes(final HashSet localTypes,
IProgressMonitor monitor) {
class PathCollector implements IPathRequestor {
HashSet paths = new HashSet(10);
@Override
public void acceptPath(String path, boolean containsLocalTypes) {
this.paths.add(path);
if (containsLocalTypes) {
localTypes.add(path);
}
}
}
PathCollector collector = new PathCollector();
try {
if (monitor != null) {
monitor.beginTask("", MAXTICKS); //$NON-NLS-1$
}
searchAllPossibleSubTypes(this.getType(), this.scope,
this.binariesFromIndexMatches, collector,
IDLTKSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor);
} finally {
if (monitor != null) {
monitor.done();
}
}
HashSet paths = collector.paths;
int length = paths.size();
String[] result = new String[length];
int count = 0;
for (Iterator iter = paths.iterator(); iter.hasNext();) {
result[count++] = (String) iter.next();
}
return result;
}
/**
* Find the set of candidate subtypes of a given type.
*
* The requestor is notified of super type references (with actual path of
* its occurrence) for all types which are potentially involved inside a
* particular hierarchy. The match locator is not used here to narrow down
* the results, the type hierarchy resolver is rather used to compute the
* whole hierarchy at once.
*
* @param type
* @param scope
* @param binariesFromIndexMatches
* @param pathRequestor
* @param waitingPolicy
* @param progressMonitor
*/
public static void searchAllPossibleSubTypes(IType type,
IDLTKSearchScope scope, final Map binariesFromIndexMatches,
final IPathRequestor pathRequestor, int waitingPolicy, // WaitUntilReadyToSearch
// |
// ForceImmediateSearch
// |
// CancelIfNotReadyToSearch
IProgressMonitor progressMonitor) {
/*
* embed constructs inside arrays so as to pass them to (inner)
* collector
*/
final Queue queue = new Queue();
final HashtableOfObject foundSuperNames = new HashtableOfObject(5);
IndexManager indexManager = ModelManager.getModelManager()
.getIndexManager();
/* use a special collector to collect paths and queue new subtype names */
IndexQueryRequestor searchRequestor = new IndexQueryRequestor() {
@Override
public boolean acceptIndexMatch(String documentPath,
SearchPattern indexRecord, SearchParticipant participant,
AccessRuleSet access) {
SuperTypeReferencePattern record = (SuperTypeReferencePattern) indexRecord;
boolean isLocalOrAnonymous = record.enclosingTypeName == IIndexConstants.ONE_ZERO;
pathRequestor.acceptPath(documentPath, isLocalOrAnonymous);
char[] typeName = record.simpleName;
if (!isLocalOrAnonymous // local or anonymous types cannot have
// subtypes outside the cu that define
// them
&& !foundSuperNames.containsKey(typeName)) {
foundSuperNames.put(typeName, typeName);
queue.add(typeName);
}
return true;
}
};
int superRefKind;
superRefKind = SuperTypeReferencePattern.ALL_SUPER_TYPES;
SuperTypeReferencePattern pattern = new SuperTypeReferencePattern(null,
null, superRefKind, SearchPattern.R_EXACT_MATCH
| SearchPattern.R_CASE_SENSITIVE, scope.getLanguageToolkit());
MatchLocator.setFocus(pattern, type);
SubTypeSearchJob job = new SubTypeSearchJob(pattern,
new DLTKSearchParticipant(), // java search only
scope, searchRequestor);
int ticks = 0;
queue.add(type.getElementName().toCharArray());
try {
while (queue.start <= queue.end) {
if (progressMonitor != null && progressMonitor.isCanceled()) {
return;
}
// all subclasses of OBJECT are actually all types
char[] currentTypeName = queue.retrieve();
if (CharOperation.equals(currentTypeName,
IIndexConstants.OBJECT)) {
currentTypeName = null;
}
// search all index references to a given supertype
pattern.superSimpleName = currentTypeName;
indexManager.performConcurrentJob(job, waitingPolicy, null); // no
// sub
// progress
// monitor
// since
// its
// too
// costly
// for
// deep
// hierarchies
if (progressMonitor != null && ++ticks <= MAXTICKS) {
progressMonitor.worked(1);
}
// in case, we search all subtypes, no need to search further
if (currentTypeName == null) {
break;
}
}
} finally {
job.finished();
}
}
}