blob: e2679cf2503b3e05613a6cfb7d453c48c11c25be [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Google, 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Xenos (Google) - Initial implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.nd.indexer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
/**
* Represents a snapshot of all the indexable objects in the workspace at a given moment in time.
*/
public final class WorkspaceSnapshot {
private Map<IPath, List<IJavaElement>> allIndexables;
/**
* Enable this to index the content of output folders, in cases where that content exists and is up-to-date. This is
* much faster than indexing source files directly.
*/
public static boolean EXPERIMENTAL_INDEX_OUTPUT_FOLDERS;
private WorkspaceSnapshot(Map<IPath, List<IJavaElement>> allIndexables) {
this.allIndexables = allIndexables;
}
public Map<IPath, List<IJavaElement>> getAllIndexables() {
return this.allIndexables;
}
public Set<IPath> allLocations() {
return this.allIndexables.keySet();
}
public List<IJavaElement> get(IPath next) {
List<IJavaElement> result = this.allIndexables.get(next);
if (result == null) {
return Collections.emptyList();
}
return result;
}
public static WorkspaceSnapshot create(IWorkspaceRoot root, IProgressMonitor monitor) throws CoreException {
SubMonitor subMonitor = SubMonitor.convert(monitor);
List<IJavaElement> unfilteredIndexables = getAllIndexableObjectsInWorkspace(root, subMonitor.split(3));
// Remove all duplicate indexables (jars which are referenced by more than one project)
Map<IPath, List<IJavaElement>> allIndexables = removeDuplicatePaths(unfilteredIndexables);
return new WorkspaceSnapshot(allIndexables);
}
private static IPath getWorkspacePathForRoot(IJavaElement next) {
IResource resource = next.getResource();
if (resource != null) {
return resource.getFullPath();
}
return Path.EMPTY;
}
private static Map<IPath, List<IJavaElement>> removeDuplicatePaths(List<IJavaElement> allIndexables) {
Map<IPath, List<IJavaElement>> paths = new HashMap<>();
HashSet<IPath> workspacePaths = new HashSet<IPath>();
for (IJavaElement next : allIndexables) {
IPath nextPath = JavaIndex.getLocationForElement(next);
IPath workspacePath = getWorkspacePathForRoot(next);
List<IJavaElement> value = paths.get(nextPath);
if (value == null) {
value = new ArrayList<IJavaElement>();
paths.put(nextPath, value);
} else {
if (workspacePath != null) {
if (workspacePaths.contains(workspacePath)) {
continue;
}
if (!workspacePath.isEmpty()) {
Package.logInfo("Found duplicate workspace path for " + workspacePath.toString()); //$NON-NLS-1$
}
workspacePaths.add(workspacePath);
}
}
value.add(next);
}
return paths;
}
private static List<IJavaElement> getAllIndexableObjectsInWorkspace(IWorkspaceRoot root, IProgressMonitor monitor)
throws CoreException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
List<IJavaElement> allIndexables = new ArrayList<>();
IProject[] projects = root.getProjects();
List<IProject> projectsToScan = new ArrayList<>();
for (IProject next : projects) {
if (next.isOpen()) {
projectsToScan.add(next);
}
}
Set<IPath> scannedPaths = new HashSet<>();
Set<IResource> resourcesToScan = new HashSet<>();
SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size());
for (IProject project : projectsToScan) {
SubMonitor iterationMonitor = projectLoopMonitor.split(1);
try {
if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) {
IJavaProject javaProject = JavaCore.create(project);
IClasspathEntry[] entries = javaProject.getRawClasspath();
if (EXPERIMENTAL_INDEX_OUTPUT_FOLDERS) {
IPath defaultOutputLocation = javaProject.getOutputLocation();
for (IClasspathEntry next : entries) {
IPath nextOutputLocation = next.getOutputLocation();
if (nextOutputLocation == null) {
nextOutputLocation = defaultOutputLocation;
}
IResource resource = root.findMember(nextOutputLocation);
if (resource != null) {
resourcesToScan.add(resource);
}
}
}
IPackageFragmentRoot[] projectRoots = javaProject.getAllPackageFragmentRoots();
SubMonitor rootLoopMonitor = iterationMonitor.setWorkRemaining(projectRoots.length);
for (IPackageFragmentRoot nextRoot : projectRoots) {
rootLoopMonitor.split(1);
if (!nextRoot.exists()) {
continue;
}
IPath filesystemPath = JavaIndex.getLocationForElement(nextRoot);
if (scannedPaths.contains(filesystemPath)) {
continue;
}
scannedPaths.add(filesystemPath);
if (nextRoot.getKind() == IPackageFragmentRoot.K_BINARY) {
if (nextRoot.isArchive()) {
allIndexables.add(nextRoot);
} else {
collectAllClassFiles(root, allIndexables, nextRoot);
}
} else {
collectAllClassFiles(root, allIndexables, nextRoot);
}
}
}
} catch (CoreException e) {
Package.log(e);
}
}
collectAllClassFiles(root, allIndexables, resourcesToScan, subMonitor.split(1));
return allIndexables;
}
private static void collectAllClassFiles(IWorkspaceRoot root, List<? super IClassFile> result,
Collection<? extends IResource> toScan, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor);
ArrayDeque<IResource> resources = new ArrayDeque<>();
resources.addAll(toScan);
while (!resources.isEmpty()) {
subMonitor.setWorkRemaining(Math.max(resources.size(), 3000)).split(1);
IResource next = resources.removeFirst();
if (next instanceof IContainer) {
IContainer container = (IContainer)next;
try {
for (IResource nextChild : container.members()) {
resources.addLast(nextChild);
}
} catch (CoreException e) {
// If an error occurs in one resource, skip it and move on to the next
Package.log(e);
}
} else if (next instanceof IFile) {
IFile file = (IFile) next;
String extension = file.getFileExtension();
if (Objects.equals(extension, "class")) { //$NON-NLS-1$
IJavaElement element = JavaCore.create(file);
if (element instanceof IClassFile) {
result.add((IClassFile)element);
}
}
}
}
}
private static void collectAllClassFiles(IWorkspaceRoot root, List<? super IClassFile> result,
IParent nextRoot) throws CoreException {
for (IJavaElement child : nextRoot.getChildren()) {
try {
int type = child.getElementType();
if (type == IJavaElement.COMPILATION_UNIT) {
continue;
}
if (!child.exists()) {
continue;
}
if (type == IJavaElement.CLASS_FILE) {
result.add((IClassFile)child);
} else if (child instanceof IParent) {
IParent parent = (IParent) child;
collectAllClassFiles(root, result, parent);
}
} catch (CoreException e) {
// Log exceptions, then continue with the next child
Package.log(e);
}
}
}
}