1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Stephan Herrmann <stephan@cs.tu-berlin.de> - inconsistent initialization of classpath container backed by external class folder, see https://bugs.eclipse.org/320618 14 * Thirumala Reddy Mutchukota <thirumala@google.com> - Contribution to bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=411423 15 * Terry Parker <tparker@google.com> - [performance] Low hit rates in JavaModel caches - https://bugs.eclipse.org/421165 16 * Andrey Loskutov <loskutov@gmx.de> - ExternalFoldersManager.RefreshJob interrupts auto build job - https://bugs.eclipse.org/476059 17 *******************************************************************************/ 18 package org.eclipse.jdt.internal.core; 19 20 import java.io.File; 21 import java.io.FileOutputStream; 22 import java.io.IOException; 23 import java.nio.file.Files; 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.Iterator; 28 import java.util.LinkedHashMap; 29 import java.util.LinkedHashSet; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Map.Entry; 33 import java.util.Set; 34 import java.util.concurrent.atomic.AtomicInteger; 35 36 import org.eclipse.core.resources.IFolder; 37 import org.eclipse.core.resources.IProject; 38 import org.eclipse.core.resources.IProjectDescription; 39 import org.eclipse.core.resources.IResource; 40 import org.eclipse.core.resources.IResourceStatus; 41 import org.eclipse.core.resources.IWorkspace; 42 import org.eclipse.core.resources.IWorkspaceRoot; 43 import org.eclipse.core.resources.ResourcesPlugin; 44 import org.eclipse.core.resources.WorkspaceJob; 45 import org.eclipse.core.runtime.CoreException; 46 import org.eclipse.core.runtime.IPath; 47 import org.eclipse.core.runtime.IProgressMonitor; 48 import org.eclipse.core.runtime.IStatus; 49 import org.eclipse.core.runtime.MultiStatus; 50 import org.eclipse.core.runtime.Platform; 51 import org.eclipse.core.runtime.Status; 52 import org.eclipse.core.runtime.jobs.Job; 53 import org.eclipse.jdt.core.IClasspathEntry; 54 import org.eclipse.jdt.core.JavaCore; 55 import org.eclipse.jdt.core.JavaModelException; 56 import org.eclipse.jdt.internal.core.DeltaProcessor.RootInfo; 57 import org.eclipse.jdt.internal.core.util.Messages; 58 import org.eclipse.jdt.internal.core.util.Util; 59 60 public class ExternalFoldersManager { 61 private static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows"); //$NON-NLS-1$//$NON-NLS-2$ 62 private static final String EXTERNAL_PROJECT_NAME = ".org.eclipse.jdt.core.external.folders"; //$NON-NLS-1$ 63 private static final String LINKED_FOLDER_NAME = ".link"; //$NON-NLS-1$ 64 private Map<IPath, IFolder> folders; 65 private Set<IPath> pendingFolders; // subset of keys of 'folders', for which linked folders haven't been created yet. 66 private final AtomicInteger counter = new AtomicInteger(0); 67 /* Singleton instance */ 68 private static ExternalFoldersManager MANAGER; 69 private RefreshJob refreshJob; 70 ExternalFoldersManager()71 private ExternalFoldersManager() { 72 // Prevent instantiation 73 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=377806 74 if (Platform.isRunning()) { 75 /* 76 * The code here runs during JavaCore start-up. 77 * So if we need to open the external folders project, we do this from a job. 78 * Otherwise workspace jobs that attempt to access JDT core functionality can cause a deadlock. 79 * 80 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=542860. 81 */ 82 class InitializeFolders extends WorkspaceJob { 83 public InitializeFolders() { 84 super("Initialize external folders"); //$NON-NLS-1$ 85 } 86 87 @Override 88 public IStatus runInWorkspace(IProgressMonitor monitor) { 89 getFolders(); 90 return Status.OK_STATUS; 91 } 92 93 @Override 94 public boolean belongsTo(Object family) { 95 return family == InitializeFolders.class; 96 } 97 } 98 InitializeFolders initializeFolders = new InitializeFolders(); 99 IProject project = getExternalFoldersProject(); 100 initializeFolders.setRule(project); 101 initializeFolders.schedule(); 102 } 103 } 104 getExternalFoldersManager()105 public static synchronized ExternalFoldersManager getExternalFoldersManager() { 106 if (MANAGER == null) { 107 MANAGER = new ExternalFoldersManager(); 108 } 109 return MANAGER; 110 } 111 112 /** 113 * Returns a set of external paths to external folders referred to on the given classpath. 114 * Returns <code>null</code> if there are none. 115 */ getExternalFolders(IClasspathEntry[] classpath)116 public static Set<IPath> getExternalFolders(IClasspathEntry[] classpath) { 117 if (classpath == null) 118 return null; 119 Set<IPath> folders = null; 120 for (int i = 0; i < classpath.length; i++) { 121 IClasspathEntry entry = classpath[i]; 122 if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { 123 IPath entryPath = entry.getPath(); 124 if (isExternalFolderPath(entryPath)) { 125 if (folders == null) 126 folders = new LinkedHashSet<>(); 127 folders.add(entryPath); 128 } 129 IPath attachmentPath = entry.getSourceAttachmentPath(); 130 if (isExternalFolderPath(attachmentPath)) { 131 if (folders == null) 132 folders = new LinkedHashSet<>(); 133 folders.add(attachmentPath); 134 } 135 } 136 } 137 return folders; 138 } 139 140 /** 141 * Returns <code>true</code> if the provided path is a folder external to the project. 142 * The path is expected to be one matching the {@link IClasspathEntry#CPE_LIBRARY} case in 143 * {@link IClasspathEntry#getPath()} definition. 144 */ isExternalFolderPath(IPath externalPath)145 public static boolean isExternalFolderPath(IPath externalPath) { 146 if (externalPath == null || externalPath.isEmpty()) { 147 return false; 148 } 149 150 JavaModelManager manager = JavaModelManager.getJavaModelManager(); 151 if (manager.isExternalFile(externalPath) || manager.isAssumedExternalFile(externalPath)) { 152 return false; 153 } 154 if (!externalPath.isAbsolute() 155 || (WINDOWS && (externalPath.getDevice() == null && !externalPath.isUNC()))) { 156 // can be only project relative path 157 return false; 158 } 159 // Test if this an absolute path in local file system (not the workspace path) 160 File externalFolder = externalPath.toFile(); 161 if (Files.isRegularFile(externalFolder.toPath())) { 162 manager.addExternalFile(externalPath); 163 return false; 164 } 165 if (Files.isDirectory(externalFolder.toPath())) { 166 return true; 167 } 168 // this can be now only full workspace path or an external path to a not existing file or folder 169 if (isInternalFilePath(externalPath)) { 170 return false; 171 } 172 if (isInternalContainerPath(externalPath)) { 173 return false; 174 } 175 // From here on the legacy code assumes that not existing resource must be external. 176 // We just follow the old assumption. 177 if (externalPath.getFileExtension() != null/*likely a .jar, .zip, .rar or other file*/) { 178 manager.addAssumedExternalFile(externalPath); 179 // assume not existing external (?) file (?) (can also be a folder with dotted name!) 180 return false; 181 } 182 // assume not existing external (?) folder (?) 183 return true; 184 } 185 186 /** 187 * @param path full absolute workspace path 188 */ isInternalFilePath(IPath path)189 private static boolean isInternalFilePath(IPath path) { 190 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 191 // in case this is full workspace path it should start with project segment 192 if(path.segmentCount() > 1 && wsRoot.getFile(path).exists()) { 193 return true; 194 } 195 return false; 196 } 197 198 /** 199 * @param path full absolute workspace path 200 */ isInternalContainerPath(IPath path)201 private static boolean isInternalContainerPath(IPath path) { 202 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 203 // in case this is full workspace path it should start with project segment 204 int segmentCount = path.segmentCount(); 205 if(segmentCount == 1 && wsRoot.getProject(path.segment(0)).exists()) { 206 return true; 207 } 208 if(segmentCount > 1 && wsRoot.getFolder(path).exists()) { 209 return true; 210 } 211 return false; 212 } 213 isInternalPathForExternalFolder(IPath resourcePath)214 public static boolean isInternalPathForExternalFolder(IPath resourcePath) { 215 return EXTERNAL_PROJECT_NAME.equals(resourcePath.segment(0)); 216 } 217 addFolder(IPath externalFolderPath, boolean scheduleForCreation)218 public IFolder addFolder(IPath externalFolderPath, boolean scheduleForCreation) { 219 return addFolder(externalFolderPath, getExternalFoldersProject(), scheduleForCreation); 220 } 221 addFolder(IPath externalFolderPath, IProject externalFoldersProject, boolean scheduleForCreation)222 private IFolder addFolder(IPath externalFolderPath, IProject externalFoldersProject, boolean scheduleForCreation) { 223 Map<IPath, IFolder> knownFolders = getFolders(); 224 225 IFolder existing; 226 synchronized (this) { 227 existing = knownFolders.get(externalFolderPath); 228 if (existing != null) { 229 return existing; 230 } 231 } 232 233 IFolder result; 234 do { 235 result = externalFoldersProject.getFolder(LINKED_FOLDER_NAME + this.counter.incrementAndGet()); 236 } while (result.exists()); 237 238 synchronized (this) { 239 if (scheduleForCreation) { 240 if (this.pendingFolders == null) 241 this.pendingFolders = new LinkedHashSet<>(); 242 this.pendingFolders.add(externalFolderPath); 243 } 244 existing = knownFolders.get(externalFolderPath); 245 if (existing != null) { 246 return existing; 247 } 248 knownFolders.put(externalFolderPath, result); 249 } 250 return result; 251 } 252 253 /** 254 * Try to remove the argument from the list of folders pending for creation. 255 * @param externalPath to link to 256 * @return true if the argument was found in the list of pending folders and could be removed from it. 257 */ removePendingFolder(Object externalPath)258 public synchronized boolean removePendingFolder(Object externalPath) { 259 if (this.pendingFolders == null) 260 return false; 261 return this.pendingFolders.remove(externalPath); 262 } 263 createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready, IProgressMonitor monitor)264 public IFolder createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready, IProgressMonitor monitor) throws CoreException { 265 IProject externalFoldersProject = createExternalFoldersProject(monitor); // run outside synchronized as this can create a resource 266 return createLinkFolder(externalFolderPath, refreshIfExistAlready, externalFoldersProject, monitor); 267 } 268 createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready, IProject externalFoldersProject, IProgressMonitor monitor)269 private IFolder createLinkFolder(IPath externalFolderPath, boolean refreshIfExistAlready, 270 IProject externalFoldersProject, IProgressMonitor monitor) throws CoreException { 271 272 IFolder result = addFolder(externalFolderPath, externalFoldersProject, false); 273 if (!result.exists()) { 274 try { 275 result.createLink(externalFolderPath, IResource.ALLOW_MISSING_LOCAL, monitor); 276 } catch (CoreException e) { 277 // If we managed to create the folder in the meantime, don't complain 278 if (!result.exists()) { 279 throw e; 280 } 281 } 282 } else if (refreshIfExistAlready) { 283 result.refreshLocal(IResource.DEPTH_INFINITE, monitor); 284 } 285 return result; 286 } 287 createPendingFolders(IProgressMonitor monitor)288 public void createPendingFolders(IProgressMonitor monitor) throws JavaModelException{ 289 synchronized (this) { 290 if (this.pendingFolders == null || this.pendingFolders.isEmpty()) return; 291 } 292 293 IProject externalFoldersProject = null; 294 try { 295 externalFoldersProject = createExternalFoldersProject(monitor); 296 } 297 catch(CoreException e) { 298 throw new JavaModelException(e); 299 } 300 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=368152 301 // To avoid race condition (from addFolder and removeFolder, load the map elements into an array and clear the map immediately. 302 // The createLinkFolder being in the synchronized block can cause a deadlock and hence keep it out of the synchronized block. 303 Object[] arrayOfFolders = null; 304 synchronized (this) { 305 arrayOfFolders = this.pendingFolders.toArray(); 306 this.pendingFolders.clear(); 307 } 308 309 for (int i=0; i < arrayOfFolders.length; i++) { 310 try { 311 createLinkFolder((IPath) arrayOfFolders[i], false, externalFoldersProject, monitor); 312 } catch (CoreException e) { 313 Util.log(e, "Error while creating a link for external folder :" + arrayOfFolders[i]); //$NON-NLS-1$ 314 } 315 } 316 } 317 cleanUp(IProgressMonitor monitor)318 public void cleanUp(IProgressMonitor monitor) throws CoreException { 319 List<Entry<IPath, IFolder>> toDelete = getFoldersToCleanUp(monitor); 320 if (toDelete == null) 321 return; 322 for (Entry<IPath, IFolder> entry : toDelete) { 323 IFolder folder = entry.getValue(); 324 folder.delete(true, monitor); 325 IPath key = entry.getKey(); 326 this.folders.remove(key); 327 } 328 IProject project = getExternalFoldersProject(); 329 if (project.isAccessible() && project.members().length == 1/*remaining member is .project*/) 330 project.delete(true, monitor); 331 } 332 getFoldersToCleanUp(IProgressMonitor monitor)333 private List<Entry<IPath, IFolder>> getFoldersToCleanUp(IProgressMonitor monitor) throws CoreException { 334 DeltaProcessingState state = JavaModelManager.getDeltaState(); 335 Map<IPath, RootInfo> roots = state.roots; 336 Map<IPath, IPath> sourceAttachments = state.sourceAttachments; 337 if (roots == null && sourceAttachments == null) 338 return null; 339 Map<IPath, IFolder> knownFolders = getFolders(); 340 List<Entry<IPath, IFolder>> result = null; 341 synchronized (knownFolders) { 342 Iterator<Entry<IPath, IFolder>> iterator = knownFolders.entrySet().iterator(); 343 while (iterator.hasNext()) { 344 Entry<IPath, IFolder> entry = iterator.next(); 345 IPath path = entry.getKey(); 346 if ((roots != null && !roots.containsKey(path)) 347 && (sourceAttachments != null && !sourceAttachments.containsKey(path))) { 348 if (entry.getValue() != null) { 349 if (result == null) 350 result = new ArrayList<>(); 351 result.add(entry); 352 } 353 } 354 } 355 } 356 return result; 357 } 358 getExternalFoldersProject()359 public IProject getExternalFoldersProject() { 360 return ResourcesPlugin.getWorkspace().getRoot().getProject(EXTERNAL_PROJECT_NAME); 361 } 362 createExternalFoldersProject(IProgressMonitor monitor)363 public IProject createExternalFoldersProject(IProgressMonitor monitor) throws CoreException { 364 IProject project = getExternalFoldersProject(); 365 if (!project.isAccessible()) { 366 if (!project.exists()) { 367 createExternalFoldersProject(project, monitor); 368 } 369 openExternalFoldersProject(project, monitor); 370 } 371 return project; 372 } 373 374 /* 375 * Attempt to open the given project (assuming it exists). 376 * If failing to open, make all attempts to recreate the missing pieces. 377 */ openExternalFoldersProject(IProject project, IProgressMonitor monitor)378 private void openExternalFoldersProject(IProject project, IProgressMonitor monitor) throws CoreException { 379 try { 380 project.open(monitor); 381 } catch (CoreException e1) { 382 if (e1.getStatus().getCode() == IResourceStatus.FAILED_READ_METADATA) { 383 // workspace was moved 384 // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=241400 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=252571 ) 385 project.delete(false/*don't delete content*/, true/*force*/, monitor); 386 createExternalFoldersProject(project, monitor); 387 } else { 388 // .project or folder on disk have been deleted, recreate them 389 IPath stateLocation = JavaCore.getPlugin().getStateLocation(); 390 IPath projectPath = stateLocation.append(EXTERNAL_PROJECT_NAME); 391 try { 392 Files.createDirectories(projectPath.toFile().toPath()); 393 try (FileOutputStream output = new FileOutputStream(projectPath.append(".project").toOSString())){ //$NON-NLS-1$ 394 output.write(( 395 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //$NON-NLS-1$ 396 "<projectDescription>\n" + //$NON-NLS-1$ 397 " <name>" + EXTERNAL_PROJECT_NAME + "</name>\n" + //$NON-NLS-1$ //$NON-NLS-2$ 398 " <comment></comment>\n" + //$NON-NLS-1$ 399 " <projects>\n" + //$NON-NLS-1$ 400 " </projects>\n" + //$NON-NLS-1$ 401 " <buildSpec>\n" + //$NON-NLS-1$ 402 " </buildSpec>\n" + //$NON-NLS-1$ 403 " <natures>\n" + //$NON-NLS-1$ 404 " </natures>\n" + //$NON-NLS-1$ 405 "</projectDescription>").getBytes()); //$NON-NLS-1$ 406 } 407 } catch (IOException e) { 408 // fallback to re-creating the project 409 project.delete(false/*don't delete content*/, true/*force*/, monitor); 410 createExternalFoldersProject(project, monitor); 411 } 412 } 413 project.open(monitor); 414 } 415 } 416 417 createExternalFoldersProject(IProject project, IProgressMonitor monitor)418 private void createExternalFoldersProject(IProject project, IProgressMonitor monitor) throws CoreException { 419 IProjectDescription desc = project.getWorkspace().newProjectDescription(project.getName()); 420 IPath stateLocation = JavaCore.getPlugin().getStateLocation(); 421 desc.setLocation(stateLocation.append(EXTERNAL_PROJECT_NAME)); 422 try { 423 project.create(desc, IResource.HIDDEN, monitor); 424 } catch (CoreException e) { 425 // If we managed to create the project in the meantime, don't complain 426 if (!project.exists()) { 427 throw e; 428 } 429 } 430 } 431 getFolder(IPath externalFolderPath)432 public IFolder getFolder(IPath externalFolderPath) { 433 return getFolders().get(externalFolderPath); 434 } 435 getFolders()436 Map<IPath, IFolder> getFolders() { 437 if (this.folders == null) { 438 Map<IPath, IFolder> tempFolders = new LinkedHashMap<>(); 439 IProject project = getExternalFoldersProject(); 440 try { 441 if (!project.isAccessible()) { 442 if (project.exists()) { 443 // workspace was moved (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=252571 ) 444 openExternalFoldersProject(project, null/*no progress*/); 445 } else { 446 // if project doesn't exist, do not open and recreate it as it means that there are no external folders 447 return this.folders = Collections.synchronizedMap(tempFolders); 448 } 449 } 450 IResource[] members = project.members(); 451 for (IResource member : members) { 452 if (member.getType() == IResource.FOLDER && member.isLinked() && member.getName().startsWith(LINKED_FOLDER_NAME)) { 453 IPath externalFolderPath = member.getLocation(); 454 tempFolders.put(externalFolderPath, (IFolder) member); 455 } 456 } 457 } catch (CoreException e) { 458 Util.log(e, "Exception while initializing external folders"); //$NON-NLS-1$ 459 } 460 synchronized (this) { 461 if (this.folders == null) { 462 this.folders = Collections.synchronizedMap(tempFolders); 463 } 464 } 465 } 466 return this.folders; 467 } 468 469 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=313153 470 // Use the same RefreshJob if the job is still available runRefreshJob(Collection<IPath> paths)471 private synchronized void runRefreshJob(Collection<IPath> paths) { 472 if (paths == null || paths.isEmpty()) { 473 return; 474 } 475 if (this.refreshJob == null) { 476 this.refreshJob = new RefreshJob(); 477 } 478 this.refreshJob.addFoldersToRefresh(paths); 479 } 480 481 /* 482 * Refreshes the external folders referenced on the classpath of the given source project 483 */ refreshReferences(final IProject[] sourceProjects, IProgressMonitor monitor)484 public void refreshReferences(final IProject[] sourceProjects, IProgressMonitor monitor) { 485 IProject externalProject = getExternalFoldersProject(); 486 try { 487 Set<IPath> externalFolders = null; 488 for (int index = 0; index < sourceProjects.length; index++) { 489 if (sourceProjects[index].equals(externalProject)) 490 continue; 491 if (!JavaProject.hasJavaNature(sourceProjects[index])) 492 continue; 493 494 Set<IPath> foldersInProject = getExternalFolders(((JavaProject) JavaCore.create(sourceProjects[index])).getResolvedClasspath()); 495 496 if (foldersInProject == null || foldersInProject.size() == 0) 497 continue; 498 if (externalFolders == null) 499 externalFolders = new LinkedHashSet<>(); 500 501 externalFolders.addAll(foldersInProject); 502 } 503 runRefreshJob(externalFolders); 504 505 } catch (CoreException e) { 506 Util.log(e, "Exception while refreshing external project"); //$NON-NLS-1$ 507 } 508 } 509 refreshReferences(IProject source, IProgressMonitor monitor)510 public void refreshReferences(IProject source, IProgressMonitor monitor) { 511 IProject externalProject = getExternalFoldersProject(); 512 if (source.equals(externalProject)) 513 return; 514 if (!JavaProject.hasJavaNature(source)) 515 return; 516 try { 517 Set<IPath> externalFolders = getExternalFolders(((JavaProject) JavaCore.create(source)).getResolvedClasspath()); 518 runRefreshJob(externalFolders); 519 } catch (CoreException e) { 520 Util.log(e, "Exception while refreshing external project"); //$NON-NLS-1$ 521 } 522 } 523 removeFolder(IPath externalFolderPath)524 public IFolder removeFolder(IPath externalFolderPath) { 525 return getFolders().remove(externalFolderPath); 526 } 527 528 static class RefreshJob extends Job { 529 530 final LinkedHashSet<IPath> externalFolders; 531 RefreshJob()532 RefreshJob(){ 533 super(Messages.refreshing_external_folders); 534 // bug 476059: don't interrupt autobuild by using rule and system flag. 535 setSystem(true); 536 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 537 setRule(workspace.getRuleFactory().refreshRule(workspace.getRoot())); 538 this.externalFolders = new LinkedHashSet<>(); 539 } 540 541 @Override belongsTo(Object family)542 public boolean belongsTo(Object family) { 543 return family == ResourcesPlugin.FAMILY_MANUAL_REFRESH; 544 } 545 546 /* 547 * Add the collection of paths to be refreshed to the already 548 * existing set of paths and schedules the job 549 */ addFoldersToRefresh(Collection<IPath> paths)550 public void addFoldersToRefresh(Collection<IPath> paths) { 551 boolean shouldSchedule; 552 synchronized (this.externalFolders) { 553 this.externalFolders.addAll(paths); 554 shouldSchedule = !this.externalFolders.isEmpty(); 555 } 556 if (shouldSchedule) { 557 schedule(); 558 } 559 } 560 561 @Override run(IProgressMonitor pm)562 protected IStatus run(IProgressMonitor pm) { 563 MultiStatus errors = new MultiStatus(JavaCore.PLUGIN_ID, IStatus.OK, 564 "Exception while refreshing external folders", null); //$NON-NLS-1$ 565 while (true) { 566 IPath externalPath; 567 synchronized (this.externalFolders) { 568 if (this.externalFolders.isEmpty()) { 569 return errors.isOK()? Status.OK_STATUS : errors; 570 } 571 // keep the path in the list to avoid re-adding it while we are working 572 externalPath = this.externalFolders.iterator().next(); 573 } 574 575 try { 576 IFolder folder = getExternalFoldersManager().getFolder(externalPath); 577 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=321358 578 if (folder != null) { 579 folder.refreshLocal(IResource.DEPTH_INFINITE, pm); 580 } 581 } catch (CoreException e) { 582 errors.merge(e.getStatus()); 583 } finally { 584 // we should always remove the path to avoid endless loop trying to refresh it 585 synchronized (this.externalFolders) { 586 this.externalFolders.remove(externalPath); 587 } 588 } 589 } 590 } 591 } 592 593 } 594