package org.eclipse.jdt.internal.core;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
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.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.DeltaProcessor.RootInfo;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
public class ExternalFoldersManager {
private static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows");
private static final String EXTERNAL_PROJECT_NAME = ".org.eclipse.jdt.core.external.folders";
private static final String LINKED_FOLDER_NAME = ".link";
private Map<IPath, IFolder> folders;
private Set<IPath> pendingFolders;
private final AtomicInteger counter = new AtomicInteger(0);
private static ExternalFoldersManager MANAGER;
private RefreshJob refreshJob;
private ExternalFoldersManager() {
if (Platform.isRunning()) {
class InitializeFolders extends WorkspaceJob {
public InitializeFolders() {
super("Initialize external folders");
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) {
getFolders();
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family == InitializeFolders.class;
}
}
InitializeFolders initializeFolders = new InitializeFolders();
IProject project = getExternalFoldersProject();
initializeFolders.setRule(project);
initializeFolders.schedule();
}
}
public static synchronized ExternalFoldersManager getExternalFoldersManager() {
if (MANAGER == null) {
MANAGER = new ExternalFoldersManager();
}
return MANAGER;
}
public static Set<IPath> getExternalFolders(IClasspathEntry[] classpath) {
if (classpath == null)
return null;
Set<IPath> folders = null;
for (int i = 0; i < classpath.length; i++) {
IClasspathEntry entry = classpath[i];
if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
IPath entryPath = entry.getPath();
if (isExternalFolderPath(entryPath)) {
if (folders == null)
folders = new LinkedHashSet<>();
folders.add(entryPath);
}
IPath attachmentPath = entry.getSourceAttachmentPath();
if (isExternalFolderPath(attachmentPath)) {
if (folders == null)
folders = new LinkedHashSet<>();
folders.add(attachmentPath);
}
}
}
return folders;
}
public static boolean isExternalFolderPath(IPath externalPath) {
if (externalPath == null || externalPath.isEmpty()) {
return false;
}
JavaModelManager manager = JavaModelManager.getJavaModelManager();
if (manager.isExternalFile(externalPath) || manager.isAssumedExternalFile(externalPath)) {
return false;
}
if (!externalPath.isAbsolute()
|| (WINDOWS && (externalPath.getDevice() == null && !externalPath.isUNC()))) {
return false;
}
File externalFolder = externalPath.toFile();
if (Files.isRegularFile(externalFolder.toPath())) {
manager.addExternalFile(externalPath);
return false;
}
if (Files.isDirectory(externalFolder.toPath())) {
return true;
}
if (isInternalFilePath(externalPath)) {
return false;
}
if (isInternalContainerPath(externalPath)) {
return false;
}
if (externalPath.getFileExtension() != null) {
manager.addAssumedExternalFile(externalPath);
return false;
}
return true;
}
private static boolean isInternalFilePath(IPath path) {
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
if(path.segmentCount() > 1 && wsRoot.getFile(path).exists()) {
return true;
}
return false;
}
private static boolean isInternalContainerPath(IPath path) {
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
int segmentCount = path.segmentCount();
if(segmentCount == 1 && wsRoot.getProject(path.segment(0)).exists()) {
return true;
}
if(segmentCount > 1 && wsRoot.getFolder(path).exists()) {
return true;
}
return false;
}
public static boolean isInternalPathForExternalFolder(IPath resourcePath) {
return EXTERNAL_PROJECT_NAME.equals(resourcePath.segment(0));
}
public IFolder addFolder(IPath externalFolderPath, boolean scheduleForCreation) {
return addFolder(externalFolderPath, getExternalFoldersProject(), scheduleForCreation);
}
private IFolder addFolder(IPath externalFolderPath, IProject externalFoldersProject, boolean scheduleForCreation) {
Map<IPath, IFolder> knownFolders = getFolders();
IFolder existing;
synchronized (this) {
existing = knownFolders.get(externalFolderPath);
if (existing != null) {
return existing;
}
}
IFolder result;
do {
result = externalFoldersProject.getFolder(LINKED_FOLDER_NAME + this.counter.incrementAndGet());
} while (result.exists());
synchronized (this) {
if (scheduleForCreation) {
if (this.pendingFolders == null)
this.pendingFolders = new LinkedHashSet<>();
this.pendingFolders.add(externalFolderPath);
}
existing = knownFolders.get(externalFolderPath);
if (existing != null) {
return existing;
}
knownFolders.put(externalFolderPath, result);
}
return result;
}
public synchronized boolean removePendingFolder(Object externalPath) {
if (this.pendingFolders == null)
return false;
return this.pendingFolders.remove(externalPath);
}
public IFolder createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready, IProgressMonitor monitor) throws CoreException {
IProject externalFoldersProject = createExternalFoldersProject(monitor);
return createLinkFolder(externalFolderPath, refreshIfExistAlready, externalFoldersProject, monitor);
}
private IFolder createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready,
IProject externalFoldersProject, IProgressMonitor monitor) throws CoreException {
IFolder result = addFolder(externalFolderPath, externalFoldersProject, false);
if (!result.exists()) {
try {
result.createLink(externalFolderPath, IResource.ALLOW_MISSING_LOCAL, monitor);
} catch (CoreException e) {
if (!result.exists()) {
throw e;
}
}
} else if (refreshIfExistAlready) {
result.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
return result;
}
public void createPendingFolders(IProgressMonitor monitor) throws JavaModelException{
synchronized (this) {
if (this.pendingFolders == null || this.pendingFolders.isEmpty()) return;
}
IProject externalFoldersProject = null;
try {
externalFoldersProject = createExternalFoldersProject(monitor);
}
catch(CoreException e) {
throw new JavaModelException(e);
}
Object[] arrayOfFolders = null;
synchronized (this) {
arrayOfFolders = this.pendingFolders.toArray();
this.pendingFolders.clear();
}
for (int i=0; i < arrayOfFolders.length; i++) {
try {
createLinkFolder((IPath) arrayOfFolders[i], false, externalFoldersProject, monitor);
} catch (CoreException e) {
Util.log(e, "Error while creating a link for external folder :" + arrayOfFolders[i]);
}
}
}
public void cleanUp(IProgressMonitor monitor) throws CoreException {
List<Entry<IPath, IFolder>> toDelete = getFoldersToCleanUp(monitor);
if (toDelete == null)
return;
for (Entry<IPath, IFolder> entry : toDelete) {
IFolder folder = entry.getValue();
folder.delete(true, monitor);
IPath key = entry.getKey();
this.folders.remove(key);
}
IProject project = getExternalFoldersProject();
if (project.isAccessible() && project.members().length == 1)
project.delete(true, monitor);
}
private List<Entry<IPath, IFolder>> getFoldersToCleanUp(IProgressMonitor monitor) throws CoreException {
DeltaProcessingState state = JavaModelManager.getDeltaState();
Map<IPath, RootInfo> roots = state.roots;
Map<IPath, IPath> sourceAttachments = state.sourceAttachments;
if (roots == null && sourceAttachments == null)
return null;
Map<IPath, IFolder> knownFolders = getFolders();
List<Entry<IPath, IFolder>> result = null;
synchronized (knownFolders) {
Iterator<Entry<IPath, IFolder>> iterator = knownFolders.entrySet().iterator();
while (iterator.hasNext()) {
Entry<IPath, IFolder> entry = iterator.next();
IPath path = entry.getKey();
if ((roots != null && !roots.containsKey(path))
&& (sourceAttachments != null && !sourceAttachments.containsKey(path))) {
if (entry.getValue() != null) {
if (result == null)
result = new ArrayList<>();
result.add(entry);
}
}
}
}
return result;
}
public IProject getExternalFoldersProject() {
return ResourcesPlugin.getWorkspace().getRoot().getProject(EXTERNAL_PROJECT_NAME);
}
public IProject createExternalFoldersProject(IProgressMonitor monitor) throws CoreException {
IProject project = getExternalFoldersProject();
if (!project.isAccessible()) {
if (!project.exists()) {
createExternalFoldersProject(project, monitor);
}
openExternalFoldersProject(project, monitor);
}
return project;
}
private void openExternalFoldersProject(IProject project, IProgressMonitor monitor) throws CoreException {
try {
project.open(monitor);
} catch (CoreException e1) {
if (e1.getStatus().getCode() == IResourceStatus.FAILED_READ_METADATA) {
project.delete(false, true, monitor);
createExternalFoldersProject(project, monitor);
} else {
IPath stateLocation = JavaCore.getPlugin().getStateLocation();
IPath projectPath = stateLocation.append(EXTERNAL_PROJECT_NAME);
try {
Files.createDirectories(projectPath.toFile().toPath());
try (FileOutputStream output = new FileOutputStream(projectPath.append(".project").toOSString())){
output.write((
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<projectDescription>\n" +
" <name>" + EXTERNAL_PROJECT_NAME + "</name>\n" +
" <comment></comment>\n" +
" <projects>\n" +
" </projects>\n" +
" <buildSpec>\n" +
" </buildSpec>\n" +
" <natures>\n" +
" </natures>\n" +
"</projectDescription>").getBytes());
}
} catch (IOException e) {
project.delete(false, true, monitor);
createExternalFoldersProject(project, monitor);
}
}
project.open(monitor);
}
}
private void createExternalFoldersProject(IProject project, IProgressMonitor monitor) throws CoreException {
IProjectDescription desc = project.getWorkspace().newProjectDescription(project.getName());
IPath stateLocation = JavaCore.getPlugin().getStateLocation();
desc.setLocation(stateLocation.append(EXTERNAL_PROJECT_NAME));
try {
project.create(desc, IResource.HIDDEN, monitor);
} catch (CoreException e) {
if (!project.exists()) {
throw e;
}
}
}
public IFolder getFolder(IPath externalFolderPath) {
return getFolders().get(externalFolderPath);
}
Map<IPath, IFolder> getFolders() {
if (this.folders == null) {
Map<IPath, IFolder> tempFolders = new LinkedHashMap<>();
IProject project = getExternalFoldersProject();
try {
if (!project.isAccessible()) {
if (project.exists()) {
openExternalFoldersProject(project, null);
} else {
return this.folders = Collections.synchronizedMap(tempFolders);
}
}
IResource[] members = project.members();
for (IResource member : members) {
if (member.getType() == IResource.FOLDER && member.isLinked() && member.getName().startsWith(LINKED_FOLDER_NAME)) {
IPath externalFolderPath = member.getLocation();
tempFolders.put(externalFolderPath, (IFolder) member);
}
}
} catch (CoreException e) {
Util.log(e, "Exception while initializing external folders");
}
synchronized (this) {
if (this.folders == null) {
this.folders = Collections.synchronizedMap(tempFolders);
}
}
}
return this.folders;
}
private synchronized void runRefreshJob(Collection<IPath> paths) {
if (paths == null || paths.isEmpty()) {
return;
}
if (this.refreshJob == null) {
this.refreshJob = new RefreshJob();
}
this.refreshJob.addFoldersToRefresh(paths);
}
public void refreshReferences(final IProject[] sourceProjects, IProgressMonitor monitor) {
IProject externalProject = getExternalFoldersProject();
try {
Set<IPath> externalFolders = null;
for (int index = 0; index < sourceProjects.length; index++) {
if (sourceProjects[index].equals(externalProject))
continue;
if (!JavaProject.hasJavaNature(sourceProjects[index]))
continue;
Set<IPath> foldersInProject = getExternalFolders(((JavaProject) JavaCore.create(sourceProjects[index])).getResolvedClasspath());
if (foldersInProject == null || foldersInProject.size() == 0)
continue;
if (externalFolders == null)
externalFolders = new LinkedHashSet<>();
externalFolders.addAll(foldersInProject);
}
runRefreshJob(externalFolders);
} catch (CoreException e) {
Util.log(e, "Exception while refreshing external project");
}
}
public void refreshReferences(IProject source, IProgressMonitor monitor) {
IProject externalProject = getExternalFoldersProject();
if (source.equals(externalProject))
return;
if (!JavaProject.hasJavaNature(source))
return;
try {
Set<IPath> externalFolders = getExternalFolders(((JavaProject) JavaCore.create(source)).getResolvedClasspath());
runRefreshJob(externalFolders);
} catch (CoreException e) {
Util.log(e, "Exception while refreshing external project");
}
}
public IFolder removeFolder(IPath externalFolderPath) {
return getFolders().remove(externalFolderPath);
}
static class RefreshJob extends Job {
final LinkedHashSet<IPath> externalFolders;
RefreshJob(){
super(Messages.refreshing_external_folders);
setSystem(true);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
setRule(workspace.getRuleFactory().refreshRule(workspace.getRoot()));
this.externalFolders = new LinkedHashSet<>();
}
@Override
public boolean belongsTo(Object family) {
return family == ResourcesPlugin.FAMILY_MANUAL_REFRESH;
}
public void addFoldersToRefresh(Collection<IPath> paths) {
boolean shouldSchedule;
synchronized (this.externalFolders) {
this.externalFolders.addAll(paths);
shouldSchedule = !this.externalFolders.isEmpty();
}
if (shouldSchedule) {
schedule();
}
}
@Override
protected IStatus run(IProgressMonitor pm) {
MultiStatus errors = new MultiStatus(JavaCore.PLUGIN_ID, IStatus.OK,
"Exception while refreshing external folders", null);
while (true) {
IPath externalPath;
synchronized (this.externalFolders) {
if (this.externalFolders.isEmpty()) {
return errors.isOK()? Status.OK_STATUS : errors;
}
externalPath = this.externalFolders.iterator().next();
}
try {
IFolder folder = getExternalFoldersManager().getFolder(externalPath);
if (folder != null) {
folder.refreshLocal(IResource.DEPTH_INFINITE, pm);
}
} catch (CoreException e) {
errors.merge(e.getStatus());
} finally {
synchronized (this.externalFolders) {
this.externalFolders.remove(externalPath);
}
}
}
}
}
}