diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java index 26500ca2fcb..8807493fd23 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java @@ -16,11 +16,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.core.resources.IFile; @@ -58,86 +59,130 @@ public class ClasspathComputer { private static final int SEVERITY_WARNING = 2; private static final int SEVERITY_IGNORE = 1; + private final IProject project; + private final IPluginModelBase model; + private final Map sourceLibraryMap; + private final boolean overrideCompliance; + + private final IJavaProject javaProject; + private final IBuild build; + private final boolean isTestPlugin; + private final IClasspathAttribute[] defaultAttrs; + private final IClasspathEntry[] original; + private final Map originalByPath; + private final List reloaded = new ArrayList<>(); + + private ClasspathComputer(IProject project, IPluginModelBase model, Map sourceLibraryMap, + boolean clear, boolean overrideCompliance) throws CoreException { + this.project = project; + this.model = model; + this.sourceLibraryMap = sourceLibraryMap != null ? sourceLibraryMap : new HashMap<>(); + this.overrideCompliance = overrideCompliance; + + this.javaProject = JavaCore.create(project); + this.build = getBuild(project); + this.isTestPlugin = hasTestPluginName(project); + this.defaultAttrs = getClasspathAttributes(project, model); + this.original = clear ? new IClasspathEntry[0] : javaProject.getRawClasspath(); + // ignore duplicates eventually seen in the original classpath + this.originalByPath = mapFirstSeenByPath(Arrays.stream(this.original)); + } + public static void setClasspath(IProject project, IPluginModelBase model) throws CoreException { IClasspathEntry[] entries = getClasspath(project, model, null, false, true); JavaCore.create(project).setRawClasspath(entries, null); } - public static IClasspathEntry[] getClasspath(IProject project, IPluginModelBase model, Map sourceLibraryMap, boolean clear, boolean overrideCompliance) throws CoreException { - IJavaProject javaProject = JavaCore.create(project); - ArrayList result = new ArrayList<>(); - IBuild build = getBuild(project); + public static IClasspathEntry[] getClasspath(IProject project, IPluginModelBase model, + Map sourceLibraryMap, boolean clear, boolean overrideCompliance) throws CoreException { + ClasspathComputer cpc = new ClasspathComputer(project, model, sourceLibraryMap, clear, overrideCompliance); + return cpc.compute(); + } + + private IClasspathEntry[] compute() throws CoreException { // add JRE and set compliance options String ee = getExecutionEnvironment(model.getBundleDescription()); - result.add(createEntryUsingPreviousEntry(javaProject, ee, PDECore.JRE_CONTAINER_PATH)); - setComplianceOptions(JavaCore.create(project), ee, overrideCompliance); + addContainerEntry(getEEPath(ee)); + setComplianceOptions(javaProject, ee, overrideCompliance); // add pde container - result.add(createEntryUsingPreviousEntry(javaProject, ee, PDECore.REQUIRED_PLUGINS_CONTAINER_PATH)); + addContainerEntry(PDECore.REQUIRED_PLUGINS_CONTAINER_PATH); // add own libraries/source - addSourceAndLibraries(project, model, build, clear, sourceLibraryMap, result); + addSourceAndLibraries(); + + IClasspathEntry[] entries = collectInOriginalOrder(); - IClasspathEntry[] entries = result.toArray(new IClasspathEntry[result.size()]); - IJavaModelStatus validation = JavaConventions.validateClasspath(javaProject, entries, javaProject.getOutputLocation()); + IJavaModelStatus validation = JavaConventions.validateClasspath(javaProject, entries, + javaProject.getOutputLocation()); if (!validation.isOK()) { PDECore.logErrorMessage(validation.getMessage()); throw new CoreException(validation); } + return entries; + } + + private IClasspathEntry[] collectInOriginalOrder() { + // preserve original entries which eventually weren't reloaded + Stream reloadedPlusOriginal = Stream.concat(reloaded.stream(), Stream.of(original)); + Map resultByPath = mapFirstSeenByPath(reloadedPlusOriginal); + List result = new ArrayList<>(resultByPath.size()); + // using the original order, collect the resulting entries, and remove + // from the map to prevent from any duplicates (even original ones) + Arrays.stream(original).map(e -> resultByPath.remove(pathWithoutEE(e.getPath()))).filter(e -> e != null) + .forEachOrdered(result::add); + // using the order of reloading, append new entries (in the map still) + reloaded.stream().filter(e -> resultByPath.remove(pathWithoutEE(e.getPath())) != null) + .forEachOrdered(result::add); return result.toArray(new IClasspathEntry[result.size()]); } - private static void addSourceAndLibraries(IProject project, IPluginModelBase model, IBuild build, boolean clear, - Map sourceLibraryMap, ArrayList result) throws CoreException { - boolean isTestPlugin = hasTestPluginName(project); - HashSet paths = new HashSet<>(); - - // keep existing source folders - if (!clear) { - IClasspathEntry[] entries = JavaCore.create(project).getRawClasspath(); - for (IClasspathEntry entry : entries) { - if (entry.getPath() != null ) { - if (PDECore.JRE_CONTAINER_PATH.isPrefixOf(entry.getPath()) - || PDECore.REQUIRED_PLUGINS_CONTAINER_PATH.equals(entry.getPath())) { - continue; - } - } - if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE - || entry.getEntryKind() == IClasspathEntry.CPE_PROJECT - || entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY - || entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { - if (paths.add(entry.getPath())) { - result.add(updateTestAttribute(isTestPlugin, entry)); - } - } - } + private static Map mapFirstSeenByPath(Stream entryStream) { + return entryStream.collect(Collectors.toMap(e -> pathWithoutEE(e.getPath()), e -> e, (first, dupe) -> first)); + } + + private static IPath pathWithoutEE(IPath path) { + if (PDECore.JRE_CONTAINER_PATH.isPrefixOf(path)) { + // The path member of IClasspathEntry for JRE_CONTAINER_PATH may + // also declare an Execution Environment, which is an attribute. + return PDECore.JRE_CONTAINER_PATH; } + return path; + } + + private void addContainerEntry(IPath path) { + IClasspathEntry orig = originalByPath.get(pathWithoutEE(path)); + if (orig != null) { + reloaded.add(JavaCore.newContainerEntry(path, orig.getAccessRules(), orig.getExtraAttributes(), + orig.isExported())); + } else { + reloaded.add(JavaCore.newContainerEntry(path, null, defaultAttrs, false)); + } + } - IClasspathAttribute[] attrs = getClasspathAttributes(project, model); + private void addSourceAndLibraries() throws CoreException { IPluginLibrary[] libraries = model.getPluginBase().getLibraries(); for (IPluginLibrary library : libraries) { IBuildEntry buildEntry = build == null ? null : build.getEntry("source." + library.getName()); //$NON-NLS-1$ if (buildEntry != null) { - addSourceFolder(buildEntry, project, paths, result, isTestPlugin); + addSourceFolders(buildEntry); + continue; + } + if (library.getName().equals(".")) { //$NON-NLS-1$ + addJARdPlugin("."); //$NON-NLS-1$ } else { - IPath sourceAttachment = sourceLibraryMap != null ? sourceLibraryMap.get(library.getName()) : null; - if (library.getName().equals(".")) { //$NON-NLS-1$ - addJARdPlugin(project, ClasspathUtilCore.getFilename(model), sourceAttachment, attrs, result); - } else { - addLibraryEntry(project, library, sourceAttachment, attrs, result); - } + addLibraryEntry(library); } } if (libraries.length == 0) { if (build != null) { IBuildEntry buildEntry = build.getEntry("source.."); //$NON-NLS-1$ if (buildEntry != null) { - addSourceFolder(buildEntry, project, paths, result, isTestPlugin); + addSourceFolders(buildEntry); } } else if (ClasspathUtilCore.hasBundleStructure(model)) { - IPath sourceAttachment = sourceLibraryMap != null ? (IPath) sourceLibraryMap.get(".") : null; //$NON-NLS-1$ - addJARdPlugin(project, ClasspathUtilCore.getFilename(model), sourceAttachment, attrs, result); + addJARdPlugin("."); //$NON-NLS-1$ } } } @@ -202,28 +247,30 @@ private static IClasspathAttribute[] getClasspathAttributes(IProject project, IP return attributes; } - private static void addSourceFolder(IBuildEntry buildEntry, IProject project, HashSet paths, - ArrayList result, boolean isTestPlugin) throws CoreException { + private void addSourceFolders(IBuildEntry buildEntry) throws CoreException { String[] folders = buildEntry.getTokens(); for (String folder : folders) { IPath path = project.getFullPath().append(folder); - if (paths.add(path)) { - if (project.findMember(folder) == null) { - CoreUtility.createFolder(project.getFolder(folder)); - } else { - IPackageFragmentRoot root = JavaCore.create(project).getPackageFragmentRoot(path.toString()); - if (root.exists() && root.getKind() == IPackageFragmentRoot.K_BINARY) { - result.add(root.getRawClasspathEntry()); - continue; - } - } - if (isTestPlugin) { - result.add(JavaCore.newSourceEntry(path, null, null, null, new IClasspathAttribute[] { - JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true") })); //$NON-NLS-1$ - } else { - result.add(JavaCore.newSourceEntry(path)); + IClasspathEntry orig = originalByPath.get(pathWithoutEE(path)); + if (orig != null) { + reloaded.add(orig); + continue; + } + if (project.findMember(folder) == null) { + CoreUtility.createFolder(project.getFolder(folder)); + } else { + IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(path.toString()); + if (root.exists() && root.getKind() == IPackageFragmentRoot.K_BINARY) { + reloaded.add(root.getRawClasspathEntry()); + continue; } } + if (isTestPlugin) { + reloaded.add(JavaCore.newSourceEntry(path, null, null, null, new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true") })); //$NON-NLS-1$ + } else { + reloaded.add(JavaCore.newSourceEntry(path)); + } } } @@ -237,45 +284,63 @@ protected static IBuild getBuild(IProject project) throws CoreException { return (buildModel != null) ? buildModel.getBuild() : null; } - private static void addLibraryEntry(IProject project, IPluginLibrary library, IPath sourceAttachment, IClasspathAttribute[] attrs, ArrayList result) throws JavaModelException { + private void addLibraryEntry(IPluginLibrary library) throws JavaModelException { String name = ClasspathUtilCore.expandLibraryName(library.getName()); IResource jarFile = project.findMember(name); if (jarFile == null) { return; } + IPath sourceAttachment = sourceLibraryMap.get(library.getName()); + boolean isExported = library.isExported(); + IPackageFragmentRoot root = JavaCore.create(project).getPackageFragmentRoot(jarFile); if (root.exists() && root.getKind() == IPackageFragmentRoot.K_BINARY) { IClasspathEntry oldEntry = root.getRawClasspathEntry(); - // If we have the same binary root but new or different source, we should recreate the entry - if ((sourceAttachment == null && oldEntry.getSourceAttachmentPath() != null) || (sourceAttachment != null && sourceAttachment.equals(oldEntry.getSourceAttachmentPath()))) { - if (!result.contains(oldEntry)) { - result.add(oldEntry); - return; - } + // If we have the same binary root but new or different source, + // we should recreate the entry, preserving unrelated attributes. + if (sourceAttachment == null) { + // no override: stick to existing one, if any + sourceAttachment = oldEntry.getSourceAttachmentPath(); } + if (sourceAttachment != null && sourceAttachment.equals(oldEntry.getSourceAttachmentPath())) { + reloaded.add(oldEntry); + return; + } + // Force recreation when the source attachment: + // - is not defined: the default could be available now, or + // - is overridden with a different value. + isExported = oldEntry.isExported(); } - IClasspathEntry entry = createClasspathEntry(project, jarFile, name, sourceAttachment, attrs, library.isExported()); - if (!result.contains(entry)) { - result.add(entry); - } + reloadClasspathEntry(jarFile, name, sourceAttachment, isExported); } - private static void addJARdPlugin(IProject project, String filename, IPath sourceAttachment, IClasspathAttribute[] attrs, ArrayList result) { + private void addJARdPlugin(String libraryName) { + String filename = ClasspathUtilCore.getFilename(model); String name = ClasspathUtilCore.expandLibraryName(filename); IResource jarFile = project.findMember(name); if (jarFile != null) { - IClasspathEntry entry = createClasspathEntry(project, jarFile, filename, sourceAttachment, attrs, true); - if (!result.contains(entry)) { - result.add(entry); - } + IPath sourceAttachment = sourceLibraryMap.get(libraryName); + reloadClasspathEntry(jarFile, filename, sourceAttachment, true); } } - private static IClasspathEntry createClasspathEntry(IProject project, IResource library, String fileName, IPath sourceAttachment, IClasspathAttribute[] attrs, boolean isExported) { - IResource resource = sourceAttachment != null ? project.findMember(sourceAttachment) : project.findMember(ClasspathUtilCore.getSourceZipName(fileName)); - return JavaCore.newLibraryEntry(library.getFullPath(), resource == null ? null : resource.getFullPath(), null, new IAccessRule[0], attrs, isExported); + private void reloadClasspathEntry(IResource library, String fileName, IPath sourceAttachment, boolean isExported) { + IClasspathEntry orig = originalByPath.get(pathWithoutEE(library.getFullPath())); + if (orig != null && sourceAttachment == null) { + sourceAttachment = orig.getSourceAttachmentPath(); + } + IResource source = sourceAttachment != null ? project.findMember(sourceAttachment) + : project.findMember(ClasspathUtilCore.getSourceZipName(fileName)); + sourceAttachment = source == null ? null : source.getFullPath(); + if (orig != null) { + reloaded.add(JavaCore.newLibraryEntry(library.getFullPath(), sourceAttachment, null, orig.getAccessRules(), + orig.getExtraAttributes(), orig.isExported())); + } else { + reloaded.add(JavaCore.newLibraryEntry(library.getFullPath(), sourceAttachment, null, new IAccessRule[0], + defaultAttrs, isExported)); + } } private static String getExecutionEnvironment(BundleDescription bundleDescription) { @@ -398,36 +463,6 @@ private static void setMinimumCompliance(Map map, String key, St } } - /** - * Returns a new classpath container entry for the given execution environment. If the given java project - * has an existing JRE/EE classpath entry, the access rules, extra attributes and isExported settings of - * the existing entry will be added to the new execution entry. - * - * @param javaProject project to check for existing classpath entries - * @param ee id of the execution environment to create an entry for - * @param path id of the container to create an entry for - * - * @return new classpath container entry - * @throws CoreException if there is a problem accessing the classpath entries of the project - */ - public static IClasspathEntry createEntryUsingPreviousEntry(IJavaProject javaProject, String ee, IPath path) throws CoreException { - IClasspathEntry[] entries = javaProject.getRawClasspath(); - for (IClasspathEntry entry : entries) { - if (path.isPrefixOf(entry.getPath()) && path.equals(PDECore.JRE_CONTAINER_PATH)) { - return JavaCore.newContainerEntry(getEEPath(ee), entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported()); - } - if (entry.getPath().equals(path)) { - return JavaCore.newContainerEntry(path, entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported()); - } - } - - if (path.equals(PDECore.JRE_CONTAINER_PATH)) { - return createJREEntry(ee); - } - - return JavaCore.newContainerEntry(path); - } - /** * Returns a classpath container entry for the given execution environment. * @param ee id of the execution environment or null diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDEMinimalTests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDEMinimalTests.java index d7f7ecb091d..aaca045f94a 100644 --- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDEMinimalTests.java +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDEMinimalTests.java @@ -19,6 +19,7 @@ import org.eclipse.pde.core.tests.internal.util.PDESchemaHelperTest; import org.eclipse.pde.ui.tests.build.properties.AllValidatorTests; import org.eclipse.pde.ui.tests.classpathresolver.ClasspathResolverTest; +import org.eclipse.pde.ui.tests.classpathupdater.ClasspathUpdaterTest; import org.eclipse.pde.ui.tests.launcher.AllLauncherTests; import org.eclipse.pde.ui.tests.model.bundle.AllBundleModelTests; import org.eclipse.pde.ui.tests.model.xml.AllXMLModelTests; @@ -49,7 +50,8 @@ AllPDERuntimeTests.class, // ExportBundleTests.class, AllLauncherTests.class, AllLogViewTests.class, ProjectCreationTests.class, BundleRootTests.class, - PluginRegistryTestsMinimal.class, ClasspathResolverTest.class, PDESchemaHelperTest.class, + PluginRegistryTestsMinimal.class, ClasspathResolverTest.class, ClasspathUpdaterTest.class, + PDESchemaHelperTest.class, DynamicPluginProjectReferencesTest.class, // ClasspathContributorTest.class ClasspathResolutionTest.class, diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java index 8e167933fbe..ba76d638676 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java @@ -75,7 +75,7 @@ class UpdateClasspathWorkspaceRunnable implements IWorkspaceRunnable { @Override public void run(IProgressMonitor monitor) throws CoreException { - fCanceled = doUpdateClasspath(monitor, fModels); + fCanceled = !doUpdateClasspath(monitor, fModels); } public boolean isCanceled() {