diff --git a/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactDependencyNode.java b/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactDependencyNode.java new file mode 100644 index 000000000000..671f68bf9176 --- /dev/null +++ b/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactDependencyNode.java @@ -0,0 +1,27 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.idea.maven.aether; + +import org.eclipse.aether.artifact.Artifact; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class ArtifactDependencyNode { + private final Artifact myArtifact; + private final List myDependencies; + + public ArtifactDependencyNode(@NotNull Artifact artifact, @NotNull List dependencies) { + myArtifact = artifact; + myDependencies = dependencies; + } + + @NotNull + public Artifact getArtifact() { + return myArtifact; + } + + @NotNull + public List getDependencies() { + return myDependencies; + } +} diff --git a/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactRepositoryManager.java b/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactRepositoryManager.java index ae7765980c69..ddd5d6608f93 100644 --- a/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactRepositoryManager.java +++ b/aether-dependency-resolver/src/org/jetbrains/idea/maven/aether/ArtifactRepositoryManager.java @@ -10,6 +10,7 @@ import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.DependencyVisitor; import org.eclipse.aether.impl.DefaultServiceLocator; @@ -151,16 +152,34 @@ public class ArtifactRepositoryManager { ); } - public Collection resolveDependency(String groupId, String artifactId, String version, boolean includeTransitiveDependencies) throws Exception { + public Collection resolveDependency(String groupId, String artifactId, String version, boolean includeTransitiveDependencies, + List excludedDependencies) throws Exception { final List files = new ArrayList<>(); - for (Artifact artifact : resolveDependencyAsArtifact(groupId, artifactId, version, EnumSet.of(ArtifactKind.ARTIFACT), includeTransitiveDependencies)) { + for (Artifact artifact : resolveDependencyAsArtifact(groupId, artifactId, version, EnumSet.of(ArtifactKind.ARTIFACT), includeTransitiveDependencies, + excludedDependencies)) { files.add(artifact.getFile()); } return files; } + @Nullable + public ArtifactDependencyNode collectDependencies(String groupId, String artifactId, String versionConstraint) throws Exception { + Set constraints = Collections.singleton(asVersionConstraint(versionConstraint)); + CollectRequest collectRequest = createCollectRequest(groupId, artifactId, constraints, EnumSet.of(ArtifactKind.ARTIFACT)); + ArtifactDependencyTreeBuilder builder = new ArtifactDependencyTreeBuilder(); + DependencyNode root = ourSystem.collectDependencies(mySession, collectRequest).getRoot(); + if (root.getArtifact() == null && root.getChildren().size() == 1) { + root = root.getChildren().get(0); + } + root.accept(new TreeDependencyVisitor(new FilteringDependencyVisitor(builder, createScopeFilter()))); + return builder.getRoot(); + } + @NotNull - public Collection resolveDependencyAsArtifact(String groupId, String artifactId, String versionConstraint, Set artifactKinds, boolean includeTransitiveDependencies) throws Exception {final List artifacts = new ArrayList<>(); + public Collection resolveDependencyAsArtifact(String groupId, String artifactId, String versionConstraint, + Set artifactKinds, boolean includeTransitiveDependencies, + List excludedDependencies) throws Exception { + final List artifacts = new ArrayList<>(); final Set constraints = Collections.singleton(asVersionConstraint(versionConstraint)); for (ArtifactKind kind : artifactKinds) { // RepositorySystem.resolveDependencies() ignores classifiers, so we need to set classifiers explicitly for discovered dependencies. @@ -172,9 +191,11 @@ public class ArtifactRepositoryManager { mySession, createCollectRequest(groupId, artifactId, constraints, EnumSet.of(kind)) ); final ArtifactRequestBuilder builder = new ArtifactRequestBuilder(kind); - collectResult.getRoot().accept(new TreeDependencyVisitor( - new FilteringDependencyVisitor(builder, DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE, JavaScopes.RUNTIME)) - )); + DependencyFilter filter = createScopeFilter(); + if (!excludedDependencies.isEmpty()) { + filter = DependencyFilterUtils.andFilter(filter, new ExcludeDependenciesFilter(excludedDependencies)); + } + collectResult.getRoot().accept(new TreeDependencyVisitor(new FilteringDependencyVisitor(builder, filter))); requests = builder.getRequests(); } else { @@ -220,6 +241,11 @@ public class ArtifactRepositoryManager { return artifacts; } + @NotNull + private static DependencyFilter createScopeFilter() { + return DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE, JavaScopes.RUNTIME); + } + public List getAvailableVersions(String groupId, String artifactId, String versionConstraint, final ArtifactKind artifactKind) throws Exception { final VersionRangeResult result = ourSystem.resolveVersionRange( mySession, createVersionRangeRequest(groupId, artifactId, asVersionConstraint(versionConstraint), artifactKind) @@ -325,4 +351,60 @@ public class ArtifactRepositoryManager { return myRequests; } } + + private static class ExcludeDependenciesFilter implements DependencyFilter { + private final HashSet myExcludedDependencies; + + public ExcludeDependenciesFilter(List excludedDependencies) { + myExcludedDependencies = new HashSet<>(excludedDependencies); + } + + @Override + public boolean accept(DependencyNode node, List parents) { + Artifact artifact = node.getArtifact(); + if (artifact != null && myExcludedDependencies.contains(artifact.getGroupId() + ":" + artifact.getArtifactId())) { + return false; + } + for (DependencyNode parent : parents) { + Artifact parentArtifact = parent.getArtifact(); + if (parentArtifact != null && myExcludedDependencies.contains(parentArtifact.getGroupId() + ":" + parentArtifact.getArtifactId())) { + return false; + } + } + return true; + } + } + + private static class ArtifactDependencyTreeBuilder implements DependencyVisitor { + private final List> myCurrentChildren = new ArrayList<>(); + + public ArtifactDependencyTreeBuilder() { + myCurrentChildren.add(new ArrayList<>()); + } + + @Override + public boolean visitEnter(DependencyNode node) { + Artifact artifact = node.getArtifact(); + if (artifact == null) return false; + + myCurrentChildren.add(new ArrayList<>()); + return true; + } + + @Override + public boolean visitLeave(DependencyNode node) { + Artifact artifact = node.getArtifact(); + if (artifact != null) { + List last = myCurrentChildren.get(myCurrentChildren.size() - 1); + myCurrentChildren.remove(myCurrentChildren.size() - 1); + myCurrentChildren.get(myCurrentChildren.size() - 1).add(new ArtifactDependencyNode(artifact, last)); + } + return true; + } + + public ArtifactDependencyNode getRoot() { + List rootNodes = myCurrentChildren.get(0); + return rootNodes.isEmpty() ? null : rootNodes.get(0); + } + } } diff --git a/aether-dependency-resolver/testSrc/org/jetbrains/idea/maven/aether/ArtifactRepositoryManagerTest.java b/aether-dependency-resolver/testSrc/org/jetbrains/idea/maven/aether/ArtifactRepositoryManagerTest.java index dc504b06f3c2..44fd577020d6 100644 --- a/aether-dependency-resolver/testSrc/org/jetbrains/idea/maven/aether/ArtifactRepositoryManagerTest.java +++ b/aether-dependency-resolver/testSrc/org/jetbrains/idea/maven/aether/ArtifactRepositoryManagerTest.java @@ -18,9 +18,11 @@ package org.jetbrains.idea.maven.aether; import com.intellij.testFramework.UsefulTestCase; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; +import org.eclipse.aether.artifact.Artifact; import java.io.File; import java.util.Collection; +import java.util.Collections; /** * @author nik @@ -36,17 +38,56 @@ public class ArtifactRepositoryManagerTest extends UsefulTestCase { } public void testResolveTransitively() throws Exception { - Collection files = myRepositoryManager.resolveDependency("junit", "junit", "4.12", true); - assertSameElements(ContainerUtil.map(files, File::getName), "junit-4.12.jar", "hamcrest-core-1.3.jar"); + Collection files = myRepositoryManager.resolveDependency("junit", "junit", "4.12", true, Collections.emptyList()); + assertFileNames(files, "junit-4.12.jar", "hamcrest-core-1.3.jar"); } public void testResolveNonTransitively() throws Exception { - Collection files = myRepositoryManager.resolveDependency("junit", "junit", "4.12", false); - assertSameElements(ContainerUtil.map(files, File::getName), "junit-4.12.jar"); + Collection files = myRepositoryManager.resolveDependency("junit", "junit", "4.12", false, Collections.emptyList()); + assertFileNames(files, "junit-4.12.jar"); + } + + public void testExcludeDirectDependency() throws Exception { + Collection all = myRepositoryManager.resolveDependency("junit", "junit", "4.12", true, Collections.singletonList("org.hamcrest:wrong")); + assertFileNames(all, "junit-4.12.jar", "hamcrest-core-1.3.jar"); + Collection excluded = myRepositoryManager.resolveDependency("junit", "junit", "4.12", true, Collections.singletonList("org.hamcrest:hamcrest-core")); + assertFileNames(excluded, "junit-4.12.jar"); + } + + public void testExcludeDependenciesTransitively() throws Exception { + Collection all = myRepositoryManager.resolveDependency("org.apache.httpcomponents", "fluent-hc", "4.5.5", true, Collections.emptyList()); + assertFileNames(all, "fluent-hc-4.5.5.jar", "httpclient-4.5.5.jar", "httpcore-4.4.9.jar", "commons-logging-1.2.jar", "commons-codec-1.10.jar"); + Collection excluded = myRepositoryManager.resolveDependency("org.apache.httpcomponents", "fluent-hc", "4.5.5", true, Collections.singletonList("org.apache.httpcomponents:httpclient")); + assertFileNames(excluded, "fluent-hc-4.5.5.jar", "commons-logging-1.2.jar"); } public void testResolveRuntimeDependencies() throws Exception { - Collection files = myRepositoryManager.resolveDependency("com.netflix.feign", "feign-jackson", "8.18.0", true); + Collection files = myRepositoryManager.resolveDependency("com.netflix.feign", "feign-jackson", "8.18.0", true, + Collections.emptyList()); assertContainsElements(ContainerUtil.map(files, File::getName), "feign-core-8.18.0.jar"); } + + public void testCollectDependencies() throws Exception { + ArtifactDependencyNode result = myRepositoryManager.collectDependencies("org.apache.httpcomponents", "fluent-hc", "4.5.5"); + assertNotNull(result); + assertCoordinates(result.getArtifact(), "org.apache.httpcomponents", "fluent-hc", "4.5.5"); + assertEquals(2, result.getDependencies().size()); + ArtifactDependencyNode first = result.getDependencies().get(0); + ArtifactDependencyNode second = result.getDependencies().get(1); + assertCoordinates(first.getArtifact(), "org.apache.httpcomponents", "httpclient", "4.5.5"); + assertCoordinates(second.getArtifact(), "commons-logging", "commons-logging", "1.2"); + assertEquals(2, first.getDependencies().size()); + assertCoordinates(first.getDependencies().get(0).getArtifact(), "org.apache.httpcomponents", "httpcore", "4.4.9"); + assertCoordinates(first.getDependencies().get(1).getArtifact(), "commons-codec", "commons-codec", "1.10"); + } + + private static void assertCoordinates(Artifact artifact, String groupId, String artifactId, String version) { + assertEquals(groupId, artifact.getGroupId()); + assertEquals(artifactId, artifact.getArtifactId()); + assertEquals(version, artifact.getVersion()); + } + + private static void assertFileNames(Collection files, String... expectedNames) { + assertSameElements(ContainerUtil.map(files, File::getName), expectedNames); + } } diff --git a/java/idea-ui/src/com/intellij/jarRepository/JarRepositoryManager.java b/java/idea-ui/src/com/intellij/jarRepository/JarRepositoryManager.java index cefe6c80e7e4..27cad10ca65f 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/JarRepositoryManager.java +++ b/java/idea-ui/src/com/intellij/jarRepository/JarRepositoryManager.java @@ -53,6 +53,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.concurrency.AsyncPromise; import org.jetbrains.concurrency.Promise; +import org.jetbrains.idea.maven.aether.ArtifactDependencyNode; import org.jetbrains.idea.maven.aether.ArtifactKind; import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager; import org.jetbrains.idea.maven.aether.ProgressConsumer; @@ -265,6 +266,27 @@ public class JarRepositoryManager { return submitBackgroundJob(project, "Looking up available versions for " + libraryDescription.getDisplayName(), new VersionResolveJob(libraryDescription, repos)); } + @Nullable + public static ArtifactDependencyNode loadDependenciesTree(@NotNull RepositoryLibraryDescription description, @NotNull String version, Project project) { + List repositories = RemoteRepositoriesConfiguration.getInstance(project).getRepositories(); + return submitModalJob(project, "Resolving Maven Dependencies", new AetherJob(repositories) { + @Override + protected String getProgressText() { + return "Loading dependencies of " + description.getMavenCoordinates(version); + } + + @Override + protected ArtifactDependencyNode perform(ProgressIndicator progress, ArtifactRepositoryManager manager) throws Exception { + return manager.collectDependencies(description.getGroupId(), description.getArtifactId(), version); + } + + @Override + protected ArtifactDependencyNode getDefaultResult() { + return null; + } + }); + } + private static void notifyArtifactsDownloaded(Project project, Collection roots) { final StringBuilder sb = new StringBuilder(); final String title = "The following files were downloaded:"; @@ -533,7 +555,7 @@ public class JarRepositoryManager { final String version = myDesc.getVersion(); try { return manager.resolveDependencyAsArtifact(myDesc.getGroupId(), myDesc.getArtifactId(), version, myKinds, - myDesc.isIncludeTransitiveDependencies()); + myDesc.isIncludeTransitiveDependencies(), myDesc.getExcludedDependencies()); } catch (TransferCancelledException e) { throw new ProcessCanceledException(e); @@ -548,7 +570,7 @@ public class JarRepositoryManager { } try { return manager.resolveDependencyAsArtifact(myDesc.getGroupId(), myDesc.getArtifactId(), resolvedVersion, myKinds, - myDesc.isIncludeTransitiveDependencies()); + myDesc.isIncludeTransitiveDependencies(), myDesc.getExcludedDependencies()); } catch (TransferCancelledException e1) { throw new ProcessCanceledException(e1); diff --git a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySupportInModuleConfigurable.java b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySupportInModuleConfigurable.java index a7abba887704..c5d879366b61 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySupportInModuleConfigurable.java +++ b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySupportInModuleConfigurable.java @@ -38,7 +38,8 @@ public class RepositoryLibrarySupportInModuleConfigurable extends FrameworkSuppo public RepositoryLibrarySupportInModuleConfigurable(@Nullable Project project, @NotNull RepositoryLibraryDescription libraryDescription) { this.libraryDescription = libraryDescription; RepositoryLibraryProperties defaultProperties = libraryDescription.createDefaultProperties(); - this.model = new RepositoryLibraryPropertiesModel(defaultProperties.getVersion(), false, false, defaultProperties.isIncludeTransitiveDependencies()); + this.model = new RepositoryLibraryPropertiesModel(defaultProperties.getVersion(), false, false, defaultProperties.isIncludeTransitiveDependencies(), + defaultProperties.getExcludedDependencies()); editor = new RepositoryLibraryPropertiesEditor(project, model, libraryDescription); } diff --git a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibraryWithDescriptionEditor.java b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibraryWithDescriptionEditor.java index 02bcd029d7a0..575c4b780f63 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibraryWithDescriptionEditor.java +++ b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibraryWithDescriptionEditor.java @@ -50,7 +50,8 @@ public class RepositoryLibraryWithDescriptionEditor RepositoryLibraryPropertiesModel model = new RepositoryLibraryPropertiesModel( properties.getVersion(), RepositoryUtils.libraryHasSources(myEditorComponent.getLibraryEditor()), - RepositoryUtils.libraryHasJavaDocs(myEditorComponent.getLibraryEditor()), properties.isIncludeTransitiveDependencies()); + RepositoryUtils.libraryHasJavaDocs(myEditorComponent.getLibraryEditor()), properties.isIncludeTransitiveDependencies(), + properties.getExcludedDependencies()); final Project project = myEditorComponent.getProject(); assert project != null : "EditorComponent's project must not be null in order to be used with RepositoryLibraryWithDescriptionEditor"; @@ -65,6 +66,7 @@ public class RepositoryLibraryWithDescriptionEditor } myEditorComponent.getProperties().changeVersion(model.getVersion()); myEditorComponent.getProperties().setIncludeTransitiveDependencies(model.isIncludeTransitiveDependencies()); + myEditorComponent.getProperties().setExcludedDependencies(model.getExcludedDependencies()); if (wasGeneratedName) { myEditorComponent.renameLibrary(RepositoryLibraryType.getInstance().getDescription(properties)); } diff --git a/java/idea-ui/src/com/intellij/jarRepository/settings/DependencyExclusionEditor.java b/java/idea-ui/src/com/intellij/jarRepository/settings/DependencyExclusionEditor.java new file mode 100644 index 000000000000..8c802d6051ed --- /dev/null +++ b/java/idea-ui/src/com/intellij/jarRepository/settings/DependencyExclusionEditor.java @@ -0,0 +1,114 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.jarRepository.settings; + +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.openapi.ui.panel.ComponentPanelBuilder; +import com.intellij.ui.CheckboxTree; +import com.intellij.ui.CheckboxTreeBase; +import com.intellij.ui.CheckedTreeNode; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.ui.UI; +import com.intellij.util.ui.tree.TreeUtil; +import org.eclipse.aether.artifact.Artifact; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.maven.aether.ArtifactDependencyNode; + +import javax.swing.*; +import java.util.*; + +class DependencyExclusionEditor { + private final CheckboxTree myDependenciesTree; + private final CheckedTreeNode myRootNode; + private final JPanel myMainPanel; + + public DependencyExclusionEditor(ArtifactDependencyNode root, JPanel parentComponent) { + myMainPanel = parentComponent; + myRootNode = createDependencyTreeNode(root); + CheckboxTreeBase.CheckPolicy policy = new CheckboxTreeBase.CheckPolicy(false, true, true, false); + myDependenciesTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer() { + { + myIgnoreInheritance = true; + } + + @Override + public void customizeRenderer(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (!(value instanceof CheckedTreeNode)) return; + + Object userObject = ((CheckedTreeNode)value).getUserObject(); + if (!(userObject instanceof Artifact)) return; + + Artifact artifact = (Artifact)userObject; + getTextRenderer().append(artifact.getGroupId() + ":" + artifact.getArtifactId(), SimpleTextAttributes.REGULAR_ATTRIBUTES, true); + getTextRenderer().append(":" + artifact.getVersion(), SimpleTextAttributes.GRAYED_ATTRIBUTES, true); + } + }, myRootNode, policy); + myDependenciesTree.setRootVisible(false); + } + + @Nullable + public Set selectExcludedDependencies(List excludedDependencies) { + uncheckExcludedNodes(myRootNode, new HashSet<>(excludedDependencies), false); + TreeUtil.expandAll(myDependenciesTree); + ComponentPanelBuilder panelBuilder = + UI.PanelFactory.panel(new JBScrollPane(myDependenciesTree)) + .withLabel("Specify which transitive dependencies should be included into the library.") + .moveLabelOnTop(); + DialogBuilder dialogBuilder = + new DialogBuilder(myMainPanel) + .title("Configure Transitive Dependencies") + .centerPanel(panelBuilder.createPanel()); + dialogBuilder.setPreferredFocusComponent(myDependenciesTree); + + if (dialogBuilder.showAndGet()) { + return collectUncheckedNodes(myRootNode, new LinkedHashSet<>()); + } + return null; + } + + private static void uncheckExcludedNodes(CheckedTreeNode node, Set excluded, boolean parentIsExcluded) { + boolean isExcluded = parentIsExcluded || excluded.contains(getGroupAndArtifactId(node)); + node.setChecked(!isExcluded); + Enumeration children = node.children(); + while (children.hasMoreElements()) { + Object child = children.nextElement(); + uncheckExcludedNodes((CheckedTreeNode)child, excluded, isExcluded); + } + } + + private static Set collectUncheckedNodes(CheckedTreeNode node, Set result) { + if (node.isChecked()) { + Enumeration children = node.children(); + while (children.hasMoreElements()) { + Object child = children.nextElement(); + collectUncheckedNodes((CheckedTreeNode)child, result); + } + } + else { + result.add(getGroupAndArtifactId(node)); + } + return result; + } + + @NotNull + private static String getGroupAndArtifactId(CheckedTreeNode node) { + Artifact artifact = (Artifact)node.getUserObject(); + return artifact.getGroupId() + ":" + artifact.getArtifactId(); + } + + @NotNull + private static CheckedTreeNode createDependencyTreeNode(ArtifactDependencyNode node) { + CheckedTreeNode treeNode = new CheckedTreeNode(node.getArtifact()); + for (ArtifactDependencyNode dependency : node.getDependencies()) { + treeNode.add(createDependencyTreeNode(dependency)); + } + return treeNode; + } +} diff --git a/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.form b/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.form index 21f20030595c..dfb9f17863f8 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.form +++ b/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.form @@ -135,14 +135,14 @@ - + - + - - - - + + + + diff --git a/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.java b/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.java index 226243706e5d..017d8bed6665 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.java +++ b/java/idea-ui/src/com/intellij/jarRepository/settings/RepositoryLibraryPropertiesEditor.java @@ -19,15 +19,21 @@ import com.google.common.base.Strings; import com.intellij.jarRepository.JarRepositoryManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.CollectionComboBoxModel; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.labels.SwingActionLink; import com.intellij.util.containers.JBIterable; +import com.intellij.util.ui.ThreeStateCheckBox; +import com.intellij.util.ui.UI; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.maven.aether.ArtifactDependencyNode; import org.jetbrains.idea.maven.utils.library.RepositoryLibraryDescription; import org.jetbrains.idea.maven.utils.library.propertiesEditor.RepositoryLibraryPropertiesModel; @@ -39,11 +45,11 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.*; import java.util.List; public class RepositoryLibraryPropertiesEditor { + private static final Logger LOG = Logger.getInstance(RepositoryLibraryPropertiesEditor.class); @NotNull private final Project project; State currentState; List versions; @@ -59,10 +65,12 @@ public class RepositoryLibraryPropertiesEditor { private JBCheckBox downloadSourcesCheckBox; private JBCheckBox downloadJavaDocsCheckBox; private JBLabel mavenCoordinates; - private JBCheckBox myIncludeTransitiveDepsCheckBox; + private final ThreeStateCheckBox myIncludeTransitiveDepsCheckBox; private JPanel myPropertiesPanel; + private JPanel myTransitiveDependenciesPanel; @NotNull private final ModelChangeListener onChangeListener; + private final SwingActionLink myManageDependenciesLink; public interface ModelChangeListener { void onChange(RepositoryLibraryPropertiesEditor editor); @@ -90,7 +98,18 @@ public class RepositoryLibraryPropertiesEditor { this.project = project == null ? ProjectManager.getInstance().getDefaultProject() : project; repositoryLibraryDescription = description; mavenCoordinates.setCopyable(true); - myIncludeTransitiveDepsCheckBox.setVisible(allowExcludingTransitiveDependencies); + myIncludeTransitiveDepsCheckBox = new ThreeStateCheckBox(UIUtil.replaceMnemonicAmpersand("Include &transitive dependencies")); + myIncludeTransitiveDepsCheckBox.setThirdStateEnabled(false); + myTransitiveDependenciesPanel.add(myIncludeTransitiveDepsCheckBox); + myManageDependenciesLink = new SwingActionLink(new AbstractAction("Configure") { + @Override + public void actionPerformed(ActionEvent e) { + configureTransitiveDependencies(); + } + }); + myManageDependenciesLink.setBorder(UI.Borders.emptyLeft(10)); + myTransitiveDependenciesPanel.add(myManageDependenciesLink); + myTransitiveDependenciesPanel.setVisible(allowExcludingTransitiveDependencies); myReloadButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -104,9 +123,25 @@ public class RepositoryLibraryPropertiesEditor { mavenCoordinates.setText(repositoryLibraryDescription.getMavenCoordinates(model.getVersion())); } }; + updateManageDependenciesLink(); reloadVersionsAsync(); } + private void configureTransitiveDependencies() { + String selectedVersion = getSelectedVersion(); + LOG.assertTrue(selectedVersion != null); + + ArtifactDependencyNode root = JarRepositoryManager.loadDependenciesTree(repositoryLibraryDescription, selectedVersion, project); + if (root == null) return; + + Set dependencies = new DependencyExclusionEditor(root, mainPanel).selectExcludedDependencies(model.getExcludedDependencies()); + if (dependencies != null) { + model.setExcludedDependencies(dependencies); + updateIncludeTransitiveDepsCheckBoxState(); + onChangeListener.onChange(this); + } + } + private static VersionKind getVersionKind(String version) { if (Strings.isNullOrEmpty(version)) { return VersionKind.Unselected; @@ -168,6 +203,7 @@ public class RepositoryLibraryPropertiesEditor { int selection = getSelection(model.getVersion(), versions); versionSelector.setSelectedIndex(selection); onChangeListener.onChange(this); + updateManageDependenciesLink(); } private VersionKind getSelectedVersionKind() { @@ -225,6 +261,7 @@ public class RepositoryLibraryPropertiesEditor { public void itemStateChanged(ItemEvent e) { model.setVersion(getSelectedVersion()); onChangeListener.onChange(RepositoryLibraryPropertiesEditor.this); + updateManageDependenciesLink(); } }); downloadSourcesCheckBox.setSelected(model.isDownloadSources()); @@ -243,16 +280,31 @@ public class RepositoryLibraryPropertiesEditor { onChangeListener.onChange(RepositoryLibraryPropertiesEditor.this); } }); - myIncludeTransitiveDepsCheckBox.setSelected(model.isIncludeTransitiveDependencies()); + updateIncludeTransitiveDepsCheckBoxState(); myIncludeTransitiveDepsCheckBox.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - model.setIncludeTransitiveDependencies(myIncludeTransitiveDepsCheckBox.isSelected()); + updateManageDependenciesLink(); + ThreeStateCheckBox.State state = myIncludeTransitiveDepsCheckBox.getState(); + if (state != ThreeStateCheckBox.State.DONT_CARE) { + model.setExcludedDependencies(Collections.emptyList()); + } + model.setIncludeTransitiveDependencies(state != ThreeStateCheckBox.State.NOT_SELECTED); onChangeListener.onChange(RepositoryLibraryPropertiesEditor.this); } }); + updateManageDependenciesLink(); } + private void updateIncludeTransitiveDepsCheckBoxState() { + myIncludeTransitiveDepsCheckBox.setState(!model.isIncludeTransitiveDependencies() ? ThreeStateCheckBox.State.NOT_SELECTED : + model.getExcludedDependencies().isEmpty() ? ThreeStateCheckBox.State.SELECTED : ThreeStateCheckBox.State.DONT_CARE); + } + + private void updateManageDependenciesLink() { + boolean enable = myIncludeTransitiveDepsCheckBox.getState() != ThreeStateCheckBox.State.NOT_SELECTED && getSelectedVersion() != null; + myManageDependenciesLink.setEnabled(enable); + } private void versionsLoaded(final @Nullable List versions) { this.versions = versions; diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt index e5f16252a56e..35ad1b3ef876 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt @@ -80,7 +80,8 @@ abstract class ConvertToRepositoryLibraryActionBase(protected val context: Struc private fun downloadLibraryAndReplace(library: LibraryEx, mavenCoordinates: JpsMavenRepositoryLibraryDescriptor) { - val libraryProperties = RepositoryLibraryProperties(mavenCoordinates.groupId, mavenCoordinates.artifactId, mavenCoordinates.version, mavenCoordinates.isIncludeTransitiveDependencies) + val libraryProperties = RepositoryLibraryProperties(mavenCoordinates.groupId, mavenCoordinates.artifactId, mavenCoordinates.version, + mavenCoordinates.isIncludeTransitiveDependencies, mavenCoordinates.excludedDependencies) val hasSources = RepositoryUtils.libraryHasSources(library) val hasJavadoc = RepositoryUtils.libraryHasJavaDocs(library) LOG.debug("Resolving $mavenCoordinates") @@ -151,7 +152,8 @@ abstract class ConvertToRepositoryLibraryActionBase(protected val context: Struc return null } - return JpsMavenRepositoryLibraryDescriptor(dialog.coordinateText, dialog.includeTransitiveDependencies) + return JpsMavenRepositoryLibraryDescriptor(dialog.coordinateText, dialog.includeTransitiveDependencies, + emptyList()) } private fun replaceByLibrary(library: Library, configuration: NewLibraryConfiguration) { diff --git a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryLibrarySupport.java b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryLibrarySupport.java index 25afc5b0461a..c58088f08398 100644 --- a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryLibrarySupport.java +++ b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryLibrarySupport.java @@ -74,7 +74,8 @@ public class RepositoryLibrarySupport { libraryDescription.getGroupId(), libraryDescription.getArtifactId(), model.getVersion(), - model.isIncludeTransitiveDependencies()); + model.isIncludeTransitiveDependencies(), + model.getExcludedDependencies()); final LibraryEx library = (LibraryEx)modifiableModel.createLibrary( LibraryEditingUtil.suggestNewLibraryName(modifiableModel, RepositoryLibraryType.getInstance().getDescription(libraryProperties)), RepositoryLibraryType.REPOSITORY_LIBRARY_KIND); diff --git a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/propertiesEditor/RepositoryLibraryPropertiesModel.java b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/propertiesEditor/RepositoryLibraryPropertiesModel.java index 3f71806702a5..c817d118033f 100644 --- a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/propertiesEditor/RepositoryLibraryPropertiesModel.java +++ b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/propertiesEditor/RepositoryLibraryPropertiesModel.java @@ -16,27 +16,35 @@ package org.jetbrains.idea.maven.utils.library.propertiesEditor; import com.google.common.base.Strings; +import com.intellij.util.containers.ContainerUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class RepositoryLibraryPropertiesModel { private String version; private boolean downloadSources; private boolean downloadJavaDocs; private boolean includeTransitiveDependencies; + private List myExcludedDependencies; public RepositoryLibraryPropertiesModel(String version, boolean downloadSources, boolean downloadJavaDocs) { - this(version, downloadSources, downloadJavaDocs, true); + this(version, downloadSources, downloadJavaDocs, true, ContainerUtil.emptyList()); } public RepositoryLibraryPropertiesModel(String version, boolean downloadSources, boolean downloadJavaDocs, - boolean includeTransitiveDependencies) { + boolean includeTransitiveDependencies, List excludedDependencies) { this.version = version; this.downloadSources = downloadSources; this.downloadJavaDocs = downloadJavaDocs; this.includeTransitiveDependencies = includeTransitiveDependencies; + myExcludedDependencies = new ArrayList<>(excludedDependencies); } public RepositoryLibraryPropertiesModel clone() { - return new RepositoryLibraryPropertiesModel(version, downloadSources, downloadJavaDocs, includeTransitiveDependencies); + return new RepositoryLibraryPropertiesModel(version, downloadSources, downloadJavaDocs, includeTransitiveDependencies, + new ArrayList<>(myExcludedDependencies)); } public boolean isValid() { @@ -51,6 +59,14 @@ public class RepositoryLibraryPropertiesModel { this.includeTransitiveDependencies = includeTransitiveDependencies; } + public List getExcludedDependencies() { + return myExcludedDependencies; + } + + public void setExcludedDependencies(Collection excludedDependencies) { + myExcludedDependencies = new ArrayList<>(excludedDependencies); + } + public boolean isDownloadSources() { return downloadSources; } @@ -86,7 +102,7 @@ public class RepositoryLibraryPropertiesModel { if (downloadJavaDocs != model.downloadJavaDocs) return false; if (includeTransitiveDependencies != model.includeTransitiveDependencies) return false; if (version != null ? !version.equals(model.version) : model.version != null) return false; - + if (!myExcludedDependencies.equals(model.myExcludedDependencies)) return false; return true; } @@ -96,6 +112,7 @@ public class RepositoryLibraryPropertiesModel { result = 31 * result + (downloadJavaDocs ? 1 : 0); result = 31 * result + (includeTransitiveDependencies ? 1 : 0); result = 31 * result + (version != null ? version.hashCode() : 0); + result = 31 * result + myExcludedDependencies.hashCode(); return result; } } diff --git a/java/java-tests/testSrc/com/intellij/java/execution/AbstractTestFrameworkIntegrationTest.java b/java/java-tests/testSrc/com/intellij/java/execution/AbstractTestFrameworkIntegrationTest.java index dba0d5694174..affc2acfe2e9 100644 --- a/java/java-tests/testSrc/com/intellij/java/execution/AbstractTestFrameworkIntegrationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/execution/AbstractTestFrameworkIntegrationTest.java @@ -112,7 +112,7 @@ public abstract class AbstractTestFrameworkIntegrationTest extends BaseConfigura ArtifactRepositoryManager repoManager) throws Exception { Collection files = repoManager.resolveDependency(descriptor.getGroupId(), descriptor.getArtifactId(), descriptor.getVersion(), - descriptor.isIncludeTransitiveDependencies()); + descriptor.isIncludeTransitiveDependencies(), descriptor.getExcludedDependencies()); assertFalse("No files retrieved for: " + descriptor.getGroupId(), files.isEmpty()); for (File artifact : files) { VirtualFile libJarLocal = LocalFileSystem.getInstance().findFileByIoFile(artifact); diff --git a/java/java-tests/testSrc/com/intellij/roots/libraries/RepositoryLibrarySerializationTest.java b/java/java-tests/testSrc/com/intellij/roots/libraries/RepositoryLibrarySerializationTest.java new file mode 100644 index 000000000000..30d604d52c9b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/roots/libraries/RepositoryLibrarySerializationTest.java @@ -0,0 +1,53 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.roots.libraries; + +import com.intellij.jarRepository.RepositoryLibraryType; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.impl.libraries.LibraryTableBase; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.roots.ModuleRootManagerTestCase; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties; + +import java.io.IOException; + +public class RepositoryLibrarySerializationTest extends ModuleRootManagerTestCase { + public void testPlain() throws JDOMException, IOException { + RepositoryLibraryProperties properties = loadLibrary("plain"); + assertEquals("junit", properties.getGroupId()); + assertEquals("junit", properties.getArtifactId()); + assertEquals("3.8.1", properties.getVersion()); + assertTrue(properties.isIncludeTransitiveDependencies()); + assertEmpty(properties.getExcludedDependencies()); + } + + public void testWithoutTransitiveDependencies() throws JDOMException, IOException { + RepositoryLibraryProperties properties = loadLibrary("without-transitive-dependencies"); + assertFalse(properties.isIncludeTransitiveDependencies()); + assertEmpty(properties.getExcludedDependencies()); + } + + public void testWithExcludedDependencies() throws JDOMException, IOException { + RepositoryLibraryProperties properties = loadLibrary("with-excluded-dependencies"); + assertTrue(properties.isIncludeTransitiveDependencies()); + assertSameElements(properties.getExcludedDependencies(), "org.apache.httpcomponents:httpclient"); + } + + @NotNull + private RepositoryLibraryProperties loadLibrary(String name) throws JDOMException, IOException { + String libraryPath = "jps/model-serialization/testData/repositoryLibraries/.idea/libraries/" + name + ".xml"; + Element element = JDOMUtil.load(PathManagerEx.findFileUnderCommunityHome(libraryPath)); + LibraryTableBase libraryTable = (LibraryTableBase)LibraryTablesRegistrar.getInstance().getLibraryTable(myProject); + libraryTable.loadState(element); + LibraryEx library = (LibraryEx)libraryTable.getLibraryByName(name); + assertNotNull(library); + assertSame(RepositoryLibraryType.REPOSITORY_LIBRARY_KIND, library.getKind()); + RepositoryLibraryProperties properties = (RepositoryLibraryProperties)library.getProperties(); + assertNotNull(properties); + return properties; + } +} diff --git a/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryDescription.java b/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryDescription.java index 5cf4f8d31c96..fb5d60710d1e 100644 --- a/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryDescription.java +++ b/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryDescription.java @@ -17,7 +17,7 @@ package org.jetbrains.idea.maven.utils.library; import com.intellij.jarRepository.RepositoryLibraryDefinition; import com.intellij.openapi.roots.DependencyScope; -import java.util.HashMap; +import com.intellij.util.containers.ContainerUtil; import icons.OpenapiIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -25,6 +25,7 @@ import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor; import javax.swing.*; import java.util.Collections; +import java.util.HashMap; import java.util.Map; public class RepositoryLibraryDescription { @@ -112,7 +113,7 @@ public class RepositoryLibraryDescription { } public RepositoryLibraryProperties createDefaultProperties() { - return new RepositoryLibraryProperties(getGroupId(), getArtifactId(), ReleaseVersionId, true); + return new RepositoryLibraryProperties(getGroupId(), getArtifactId(), ReleaseVersionId, true, ContainerUtil.emptyList()); } public String getDisplayName(String version) { diff --git a/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryProperties.java b/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryProperties.java index 3628c98d2a87..cfc933415ff3 100644 --- a/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryProperties.java +++ b/java/openapi/src/org/jetbrains/idea/maven/utils/library/RepositoryLibraryProperties.java @@ -3,10 +3,16 @@ package org.jetbrains.idea.maven.utils.library; import com.intellij.openapi.roots.libraries.LibraryProperties; import com.intellij.openapi.util.Comparing; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.util.xmlb.annotations.Transient; +import com.intellij.util.xmlb.annotations.XCollection; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Function; /** @@ -19,15 +25,19 @@ public class RepositoryLibraryProperties extends LibraryProperties excludedDependencies) { + myDescriptor = new JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version, includeTransitiveDependencies, + excludedDependencies); } @Override @@ -56,7 +66,7 @@ public class RepositoryLibraryProperties extends LibraryProperties method) { @@ -89,8 +99,31 @@ public class RepositoryLibraryProperties extends LibraryProperties:"} format. + */ + @Transient + public List getExcludedDependencies() { + return myDescriptor != null ? myDescriptor.getExcludedDependencies() : Collections.emptyList(); + } + + public void setExcludedDependencies(List dependencyMavenIds) { + myDescriptor = new JpsMavenRepositoryLibraryDescriptor(getMavenId(), isIncludeTransitiveDependencies(), dependencyMavenIds); + } + + @SuppressWarnings("unused") //we need to have a separate method here because XmlSerializer fails if the returned list is unmodifiable + @XCollection(propertyElementName = "exclude", elementName = "dependency", valueAttributeName = "maven-id") + public List getExcludedDependenciesBean() { + return myDescriptor != null ? new ArrayList<>(myDescriptor.getExcludedDependencies()) : new ArrayList<>(); + } + + @SuppressWarnings("unused") //used by XmlSerializer + public void setExcludedDependenciesBean(List dependencyMavenIds) { + setExcludedDependencies(dependencyMavenIds); + } + @NotNull public JpsMavenRepositoryLibraryDescriptor getRepositoryLibraryDescriptor() { - return myDescriptor != null ? myDescriptor : new JpsMavenRepositoryLibraryDescriptor(null, true); + return myDescriptor != null ? myDescriptor : new JpsMavenRepositoryLibraryDescriptor(null, true, Collections.emptyList()); } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java index 7aa0899acc9a..0a875a47fb9b 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java @@ -147,7 +147,8 @@ public class DependencyResolvingBuilder extends ModuleLevelBuilder{ context.processMessage(new ProgressMessage("Resolving '" + lib.getName() + "' library...")); LOG.debug("Downloading missing files for " + lib.getName() + " library: " + required); final Collection resolved = repoManager.resolveDependency(descriptor.getGroupId(), descriptor.getArtifactId(), - descriptor.getVersion(), descriptor.isIncludeTransitiveDependencies()); + descriptor.getVersion(), descriptor.isIncludeTransitiveDependencies(), + descriptor.getExcludedDependencies()); if (!resolved.isEmpty()) { syncPaths(required, resolved); } diff --git a/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java b/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java index 564820cdf4e3..8c4ee2437195 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java +++ b/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java @@ -18,6 +18,8 @@ package org.jetbrains.jps.model.library; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -29,25 +31,28 @@ public class JpsMavenRepositoryLibraryDescriptor { private final String myArtifactId; private final String myVersion; private final boolean myIncludeTransitiveDependencies; + private final List myExcludedDependencies; public JpsMavenRepositoryLibraryDescriptor(@NotNull String groupId, @NotNull String artifactId, @NotNull String version) { - this(groupId, artifactId, version, true); + this(groupId, artifactId, version, true, Collections.emptyList()); } public JpsMavenRepositoryLibraryDescriptor(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, - boolean includeTransitiveDependencies) { + boolean includeTransitiveDependencies, @NotNull List excludedDependencies) { myGroupId = groupId; myArtifactId = artifactId; myVersion = version; myIncludeTransitiveDependencies = includeTransitiveDependencies; + myExcludedDependencies = excludedDependencies; myMavenId = groupId + ":" + artifactId + ":" + version; } public JpsMavenRepositoryLibraryDescriptor(@Nullable String mavenId) { - this(mavenId, true); + this(mavenId, true, Collections.emptyList()); } - public JpsMavenRepositoryLibraryDescriptor(@Nullable String mavenId, boolean includeTransitiveDependencies) { + public JpsMavenRepositoryLibraryDescriptor(@Nullable String mavenId, + boolean includeTransitiveDependencies, List excludedDependencies) { myMavenId = mavenId; myIncludeTransitiveDependencies = includeTransitiveDependencies; if (mavenId == null) { @@ -59,6 +64,7 @@ public class JpsMavenRepositoryLibraryDescriptor { myArtifactId = parts.length > 1 ? parts[1] : null; myVersion = parts.length > 2 ? parts[2] : null; } + myExcludedDependencies = excludedDependencies; } @@ -78,6 +84,13 @@ public class JpsMavenRepositoryLibraryDescriptor { return myIncludeTransitiveDependencies; } + /** + * Returns list of excluded transitive dependencies in {@code ":"} format. + */ + public List getExcludedDependencies() { + return myExcludedDependencies; + } + public String getVersion() { return myVersion; } @@ -88,12 +101,13 @@ public class JpsMavenRepositoryLibraryDescriptor { if (o == null || getClass() != o.getClass()) return false; JpsMavenRepositoryLibraryDescriptor that = (JpsMavenRepositoryLibraryDescriptor)o; - return Objects.equals(myMavenId, that.myMavenId) && myIncludeTransitiveDependencies == that.myIncludeTransitiveDependencies; + return Objects.equals(myMavenId, that.myMavenId) && myIncludeTransitiveDependencies == that.myIncludeTransitiveDependencies + && myExcludedDependencies.equals(that.myExcludedDependencies); } @Override public int hashCode() { - return Objects.hashCode(myMavenId) * 31 + (myIncludeTransitiveDependencies ? 1 : 0); + return myExcludedDependencies.hashCode() * 31 * 31 + Objects.hashCode(myMavenId) * 31 + (myIncludeTransitiveDependencies ? 1 : 0); } @Override diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java index 21d394635a0d..56d9ce0ae4af 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java @@ -17,6 +17,7 @@ package org.jetbrains.jps.model.serialization.java; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -387,6 +388,8 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension private static class JpsRepositoryLibraryPropertiesSerializer extends JpsLibraryPropertiesSerializer> { private static final String MAVEN_ID_ATTRIBUTE = "maven-id"; private static final String INCLUDE_TRANSITIVE_DEPS_ATTRIBUTE = "include-transitive-deps"; + private static final String EXCLUDE_TAG = "exclude"; + private static final String DEPENDENCY_TAG = "dependency"; public JpsRepositoryLibraryPropertiesSerializer() { super(JpsRepositoryLibraryType.INSTANCE, JpsRepositoryLibraryType.INSTANCE.getTypeId()); @@ -394,10 +397,19 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension @Override public JpsSimpleElement loadProperties(@Nullable Element elem) { - return JpsElementFactory.getInstance().createSimpleElement(new JpsMavenRepositoryLibraryDescriptor( - elem != null ? elem.getAttributeValue(MAVEN_ID_ATTRIBUTE, (String)null) : null, - elem == null || Boolean.parseBoolean(elem.getAttributeValue(INCLUDE_TRANSITIVE_DEPS_ATTRIBUTE, "true")) - )); + return JpsElementFactory.getInstance().createSimpleElement(loadDescriptor(elem)); + } + + @NotNull + private static JpsMavenRepositoryLibraryDescriptor loadDescriptor(@Nullable Element elem) { + if (elem == null) return new JpsMavenRepositoryLibraryDescriptor(null); + + boolean includeTransitiveDependencies = Boolean.parseBoolean(elem.getAttributeValue(INCLUDE_TRANSITIVE_DEPS_ATTRIBUTE, "true")); + Element excludeTag = elem.getChild(EXCLUDE_TAG); + List dependencyTags = excludeTag != null ? excludeTag.getChildren(DEPENDENCY_TAG) : Collections.emptyList(); + List excludedDependencies = ContainerUtil.map(dependencyTags, it -> it.getAttributeValue(MAVEN_ID_ATTRIBUTE)); + return new JpsMavenRepositoryLibraryDescriptor(elem.getAttributeValue(MAVEN_ID_ATTRIBUTE, (String)null), + includeTransitiveDependencies, excludedDependencies); } @Override @@ -406,6 +418,14 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension if (mavenId != null) { element.setAttribute(MAVEN_ID_ATTRIBUTE, mavenId); } + List excludedDependencies = properties.getData().getExcludedDependencies(); + if (!excludedDependencies.isEmpty()) { + Element excludeTag = new Element(EXCLUDE_TAG); + element.addContent(excludeTag); + for (String dependency : excludedDependencies) { + excludeTag.addContent(new Element(DEPENDENCY_TAG).setAttribute(MAVEN_ID_ATTRIBUTE, dependency)); + } + } } } } diff --git a/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/plain.xml b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/plain.xml new file mode 100644 index 000000000000..25da8abec95a --- /dev/null +++ b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/plain.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/with-excluded-dependencies.xml b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/with-excluded-dependencies.xml new file mode 100644 index 000000000000..107ca2b30465 --- /dev/null +++ b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/with-excluded-dependencies.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/without-transitive-dependencies.xml b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/without-transitive-dependencies.xml new file mode 100644 index 000000000000..8bbd5a827a11 --- /dev/null +++ b/jps/model-serialization/testData/repositoryLibraries/.idea/libraries/without-transitive-dependencies.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/jps/model-serialization/testData/repositoryLibraries/.idea/misc.xml b/jps/model-serialization/testData/repositoryLibraries/.idea/misc.xml new file mode 100644 index 000000000000..78f096fb90ba --- /dev/null +++ b/jps/model-serialization/testData/repositoryLibraries/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsRepositoryLibrarySerializationTest.java b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsRepositoryLibrarySerializationTest.java new file mode 100644 index 000000000000..146fec0873e7 --- /dev/null +++ b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsRepositoryLibrarySerializationTest.java @@ -0,0 +1,43 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.jps.model.serialization; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.JpsSimpleElement; +import org.jetbrains.jps.model.library.JpsLibrary; +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor; +import org.jetbrains.jps.model.library.JpsRepositoryLibraryType; +import org.jetbrains.jps.model.library.JpsTypedLibrary; + +public class JpsRepositoryLibrarySerializationTest extends JpsSerializationTestCase { + public void testPlain() { + JpsMavenRepositoryLibraryDescriptor properties = loadLibrary("plain"); + assertEquals("junit", properties.getGroupId()); + assertEquals("junit", properties.getArtifactId()); + assertEquals("3.8.1", properties.getVersion()); + assertTrue(properties.isIncludeTransitiveDependencies()); + assertEmpty(properties.getExcludedDependencies()); + } + + public void testWithoutTransitiveDependencies() { + JpsMavenRepositoryLibraryDescriptor properties = loadLibrary("without-transitive-dependencies"); + assertFalse(properties.isIncludeTransitiveDependencies()); + assertEmpty(properties.getExcludedDependencies()); + } + + public void testWithExcludedDependencies() { + JpsMavenRepositoryLibraryDescriptor properties = loadLibrary("with-excluded-dependencies"); + assertTrue(properties.isIncludeTransitiveDependencies()); + assertSameElements(properties.getExcludedDependencies(), "org.apache.httpcomponents:httpclient"); + } + + @NotNull + private JpsMavenRepositoryLibraryDescriptor loadLibrary(String name) { + loadProject("/jps/model-serialization/testData/repositoryLibraries"); + JpsLibrary library = myProject.getLibraryCollection().findLibrary(name); + assertNotNull(library); + assertSame(JpsRepositoryLibraryType.INSTANCE, library.getType()); + JpsTypedLibrary> typed = library.asTyped(JpsRepositoryLibraryType.INSTANCE); + assertNotNull(typed); + return typed.getProperties().getData(); + } +}