blob: aeef576c4e47cf61b17c3861cb318b2e4327974e [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.gradle.variant.profiles;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.Variant;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.variant.conflict.Conflict;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.TreeExpander;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator;
import com.intellij.openapi.ui.DetailsComponent;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.*;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.table.JBTable;
import com.intellij.util.Function;
import com.intellij.util.ui.tree.TreeUtil;
import icons.AndroidIcons;
import org.jetbrains.android.util.BooleanCellRenderer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.text.Collator;
import java.util.*;
import java.util.List;
import static com.android.tools.idea.gradle.util.GradleUtil.getDirectLibraryDependencies;
import static com.android.tools.idea.gradle.util.Projects.getAndroidModel;
public class ProjectProfileSelectionDialog extends DialogWrapper {
private static final SimpleTextAttributes UNRESOLVED_ATTRIBUTES =
new SimpleTextAttributes(SimpleTextAttributes.STYLE_STRIKEOUT, SimpleTextAttributes.GRAY_ATTRIBUTES.getFgColor());
@NotNull private final Project myProject;
@NotNull private final List<Conflict> myConflicts;
@NotNull private final JPanel myPanel;
private CheckboxTreeView myProjectStructureTree;
private ConflictsTable myConflictsTable;
private CheckboxTreeView myConflictTree;
private DetailsComponent myConflictDetails;
public ProjectProfileSelectionDialog(@NotNull Project project, @NotNull List<Conflict> conflicts) {
super(project);
myProject = project;
myConflicts = conflicts;
for (Conflict conflict : conflicts) {
conflict.refreshStatus();
}
myPanel = new JPanel(new BorderLayout());
Splitter splitter = new Splitter(false, .35f);
splitter.setHonorComponentsMinimumSize(true);
myPanel.add(splitter, BorderLayout.CENTER);
splitter.setFirstComponent(createProjectStructurePanel());
splitter.setSecondComponent(createConflictsPanel());
init();
myProjectStructureTree.expandAll();
}
@NotNull
private JComponent createProjectStructurePanel() {
createProjectStructureTree();
DetailsComponent details = new DetailsComponent();
details.setText("Project Structure");
details.setContent(createTreePanel(myProjectStructureTree));
removeEmptyBorder(details);
return details.getComponent();
}
private void createProjectStructureTree() {
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
Object data = ((DefaultMutableTreeNode)value).getUserObject();
ColoredTreeCellRenderer textRenderer = getTextRenderer();
if (data instanceof ModuleTreeElement) {
ModuleTreeElement moduleElement = (ModuleTreeElement)data;
textRenderer.append(moduleElement.myModule.getName());
if (!moduleElement.myConflicts.isEmpty()) {
boolean allResolved = true;
for (Conflict conflict : moduleElement.myConflicts) {
if (!conflict.isResolved()) {
allResolved = false;
break;
}
}
SimpleTextAttributes attributes = allResolved ? UNRESOLVED_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES;
textRenderer.append(" ");
textRenderer.append(myConflicts.size() == 1 ? "[Conflict]" : "[Conflicts]", attributes);
}
textRenderer.setIcon(AllIcons.Actions.Module);
}
else if (data instanceof String) {
textRenderer.append((String)data, SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES);
textRenderer.setIcon(AndroidIcons.Variant);
}
else if (data instanceof DependencyTreeElement) {
DependencyTreeElement dependency = (DependencyTreeElement)data;
textRenderer.append(dependency.myModule.getName());
if (!StringUtil.isEmpty(dependency.myVariant)) {
textRenderer.append(" (" + dependency.myVariant + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
}
Icon icon = dependency.myConflict != null ? AllIcons.RunConfigurations.TestFailed : AllIcons.RunConfigurations.TestPassed;
textRenderer.setIcon(icon);
}
}
}
};
CheckedTreeNode rootNode = new FilterAwareCheckedTreeNode(null);
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
Module[] modules = moduleManager.getModules();
Arrays.sort(modules, ModulesAlphaComparator.INSTANCE);
Map<String, Module> modulesByGradlePath = Maps.newHashMap();
for (Module module : modules) {
String gradlePath = GradleUtil.getGradlePath(module);
if (StringUtil.isEmpty(gradlePath)) {
// The top-level module representing the project usually does not have a Gradle path.
// We always want to include it, therefore we don't give users a chance to uncheck it in the "Project Structure" pane.
continue;
}
modulesByGradlePath.put(gradlePath, module);
ModuleTreeElement moduleElement = new ModuleTreeElement(module);
CheckedTreeNode moduleNode = new FilterAwareCheckedTreeNode(moduleElement);
rootNode.add(moduleNode);
IdeaAndroidProject androidModel = getAndroidModel(module);
if (androidModel == null) {
continue;
}
Multimap<String, DependencyTreeElement> dependenciesByVariant = HashMultimap.create();
for (Variant variant : androidModel.getAndroidProject().getVariants()) {
for (AndroidLibrary library : getDirectLibraryDependencies(variant, androidModel)) {
gradlePath = library.getProject();
if (gradlePath == null) {
continue;
}
Module dependency = modulesByGradlePath.get(gradlePath);
if (dependency == null) {
dependency = GradleUtil.findModuleByGradlePath(myProject, gradlePath);
}
if (dependency == null) {
continue;
}
Conflict conflict = getConflict(dependency);
modulesByGradlePath.put(gradlePath, dependency);
DependencyTreeElement dependencyElement =
new DependencyTreeElement(dependency, gradlePath, library.getProjectVariant(), conflict);
dependenciesByVariant.put(variant.getName(), dependencyElement);
}
}
List<String> variantNames = Lists.newArrayList(dependenciesByVariant.keySet());
Collections.sort(variantNames);
List<String> consolidatedVariants = Lists.newArrayList();
List<String> variantsToSkip = Lists.newArrayList();
int variantCount = variantNames.size();
for (int i = 0; i < variantCount; i++) {
String variant1 = variantNames.get(i);
if (variantsToSkip.contains(variant1)) {
continue;
}
Collection<DependencyTreeElement> set1 = dependenciesByVariant.get(variant1);
for (int j = i + 1; j < variantCount; j++) {
String variant2 = variantNames.get(j);
Collection<DependencyTreeElement> set2 = dependenciesByVariant.get(variant2);
if (set1.equals(set2)) {
variantsToSkip.add(variant2);
if (!consolidatedVariants.contains(variant1)) {
consolidatedVariants.add(variant1);
}
consolidatedVariants.add(variant2);
}
}
String variantName = variant1;
if (!consolidatedVariants.isEmpty()) {
variantName = Joiner.on(", ").join(consolidatedVariants);
}
DefaultMutableTreeNode variantNode = new DefaultMutableTreeNode(variantName);
moduleNode.add(variantNode);
List<DependencyTreeElement> dependencyElements = Lists.newArrayList(set1);
Collections.sort(dependencyElements);
for (DependencyTreeElement dependencyElement : dependencyElements) {
if (dependencyElement.myConflict != null) {
moduleElement.addConflict(dependencyElement.myConflict);
}
variantNode.add(new DefaultMutableTreeNode(dependencyElement));
}
consolidatedVariants.clear();
}
}
myProjectStructureTree = new CheckboxTreeView(renderer, rootNode) {
@Override
protected void onNodeStateChanged(@NotNull CheckedTreeNode node) {
Module module = null;
Object data = node.getUserObject();
if (data instanceof ModuleTreeElement) {
module = ((ModuleTreeElement)data).myModule;
}
if (module == null) {
return;
}
boolean updated = false;
Enumeration variantNodes = myConflictTree.myRoot.children();
while (variantNodes.hasMoreElements()) {
Object child = variantNodes.nextElement();
if (!(child instanceof CheckedTreeNode)) {
continue;
}
CheckedTreeNode variantNode = (CheckedTreeNode)child;
Enumeration moduleNodes = variantNode.children();
while (moduleNodes.hasMoreElements()) {
child = moduleNodes.nextElement();
if (!(child instanceof CheckedTreeNode)) {
continue;
}
CheckedTreeNode moduleNode = (CheckedTreeNode)child;
data = moduleNode.getUserObject();
if (!(data instanceof Conflict.AffectedModule)) {
continue;
}
Conflict.AffectedModule affected = (Conflict.AffectedModule)data;
boolean checked = node.isChecked();
if (module.equals(affected.getTarget()) && moduleNode.isChecked() != checked) {
affected.setSelected(checked);
moduleNode.setChecked(checked);
updated = true;
}
}
}
if (updated) {
repaintAll();
}
}
};
myProjectStructureTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myProjectStructureTree.setRootVisible(false);
}
@Nullable
private Conflict getConflict(@NotNull Module source) {
for (Conflict conflict : myConflicts) {
if (source.equals(conflict.getSource())) {
return conflict;
}
}
return null;
}
@NotNull
private JComponent createConflictsPanel() {
createConflictTree();
myConflictDetails = new DetailsComponent();
myConflictDetails.setText("Conflict Detail");
myConflictDetails.setContent(createTreePanel(myConflictTree));
removeEmptyBorder(myConflictDetails);
createConflictsTable();
myConflictsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
showConflictDetail();
}
});
Splitter splitter = new Splitter(true, .25f);
splitter.setHonorComponentsMinimumSize(true);
DetailsComponent details = new DetailsComponent();
details.setText("Variant Selection Conflicts");
details.setContent(ScrollPaneFactory.createScrollPane(myConflictsTable));
removeEmptyBorder(details);
splitter.setFirstComponent(details.getComponent());
splitter.setSecondComponent(myConflictDetails.getComponent());
return splitter;
}
@NotNull static JPanel createTreePanel(@NotNull CheckboxTreeView tree) {
JPanel treePanel = new JPanel(new BorderLayout());
DefaultActionGroup group = new DefaultActionGroup();
CommonActionsManager actions = CommonActionsManager.getInstance();
group.addAll(actions.createExpandAllAction(tree, treePanel), actions.createCollapseAllAction(tree, treePanel));
ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar("", group, true);
JPanel buttonsPanel = new JPanel(new BorderLayout());
buttonsPanel.add(actionToolBar.getComponent(), BorderLayout.CENTER);
buttonsPanel.setBorder(new SideBorder(JBColor.border(), SideBorder.TOP | SideBorder.LEFT | SideBorder.RIGHT, 1));
treePanel.add(buttonsPanel, BorderLayout.NORTH);
treePanel.add(ScrollPaneFactory.createScrollPane(tree), BorderLayout.CENTER);
return treePanel;
}
private static void removeEmptyBorder(@NotNull DetailsComponent details) {
JComponent gutter = details.getContentGutter();
for (Component child : gutter.getComponents()) {
if (child instanceof Wrapper) {
((Wrapper)child).setBorder(null);
}
}
}
private void createConflictsTable() {
ConflictsTableModel tableModel = new ConflictsTableModel(myConflicts, new Function<List<ConflictTableRow>, Void>() {
@Override
public Void fun(List<ConflictTableRow> rows) {
filterProjectStructure(rows);
return null;
}
});
myConflictsTable = new ConflictsTable(tableModel);
if (!tableModel.myRows.isEmpty()) {
showConflictDetail();
}
}
private void showConflictDetail() {
myConflictTree.myRoot.removeAllChildren();
int selectedIndex = myConflictsTable.getSelectedRow();
ConflictsTableModel tableModel = (ConflictsTableModel)myConflictsTable.getModel();
ConflictTableRow row = tableModel.myRows.get(selectedIndex);
Conflict conflict = row.myConflict;
myConflictDetails.setText("Conflict Detail: " + conflict.getSource().getName());
List<String> variants = Lists.newArrayList(conflict.getVariants());
Collections.sort(variants);
for (String variant : variants) {
CheckedTreeNode variantNode = new CheckedTreeNode(variant);
myConflictTree.myRoot.add(variantNode);
for (Conflict.AffectedModule module : conflict.getModulesExpectingVariant(variant)) {
CheckedTreeNode moduleNode = new CheckedTreeNode(module);
variantNode.add(moduleNode);
}
}
myConflictTree.reload();
myConflictTree.expandAll();
}
private void filterProjectStructure(@NotNull List<ConflictTableRow> rows) {
List<Module> selectedConflictSources = Lists.newArrayList();
for (ConflictTableRow row : rows) {
if (row.myFilter) {
selectedConflictSources.add(row.myConflict.getSource());
}
}
Enumeration moduleNodes = myProjectStructureTree.myRoot.children();
while (moduleNodes.hasMoreElements()) {
boolean show = false;
Object child = moduleNodes.nextElement();
if (!(child instanceof FilterAwareCheckedTreeNode)) {
continue;
}
FilterAwareCheckedTreeNode moduleNode = (FilterAwareCheckedTreeNode)child;
Object data = moduleNode.getUserObject();
if (!(data instanceof ModuleTreeElement)) {
continue;
}
ModuleTreeElement moduleElement = (ModuleTreeElement)data;
if (selectedConflictSources.isEmpty()) {
show = true;
}
else {
// We show the modules that depend on any of the selected conflict sources.
for (Conflict conflict : moduleElement.myConflicts) {
if (selectedConflictSources.contains(conflict.getSource())) {
show = true;
break;
}
}
// We show the conflict sources as well.
if (!show && selectedConflictSources.contains(moduleElement.myModule)) {
show = true;
}
}
moduleNode.myVisible = show;
}
myProjectStructureTree.reload();
myProjectStructureTree.expandAll();
}
private void createConflictTree() {
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
Object data = ((DefaultMutableTreeNode)value).getUserObject();
ColoredTreeCellRenderer textRenderer = getTextRenderer();
if (data instanceof Conflict.AffectedModule) {
textRenderer.append(((Conflict.AffectedModule)data).getTarget().getName());
textRenderer.setIcon(AllIcons.Actions.Module);
}
else if (data instanceof String) {
textRenderer.append((String)data, SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES);
textRenderer.setIcon(AndroidIcons.Variant);
}
}
}
};
FilterAwareCheckedTreeNode root = new FilterAwareCheckedTreeNode(null);
myConflictTree = new CheckboxTreeView(renderer, root) {
@Override
protected void onNodeStateChanged(@NotNull CheckedTreeNode node) {
Object data = node.getUserObject();
if (!(data instanceof Conflict.AffectedModule)) {
return;
}
Conflict.AffectedModule affected = (Conflict.AffectedModule)data;
Module module = affected.getTarget();
Enumeration moduleNodes = myProjectStructureTree.myRoot.children();
while (moduleNodes.hasMoreElements()) {
Object child = moduleNodes.nextElement();
if (!(child instanceof CheckedTreeNode)) {
continue;
}
CheckedTreeNode moduleNode = (CheckedTreeNode)child;
data = moduleNode.getUserObject();
if (data instanceof ModuleTreeElement) {
ModuleTreeElement moduleElement = (ModuleTreeElement)data;
boolean checked = node.isChecked();
if (module.equals(moduleElement.myModule) && moduleNode.isChecked() != checked) {
moduleNode.setChecked(checked);
affected.setSelected(checked);
repaintAll();
break;
}
}
}
}
};
}
private void repaintAll() {
myConflictsTable.repaint();
myConflictTree.repaint();
myProjectStructureTree.repaint();
}
@Override
@NotNull
protected JComponent createCenterPanel() {
return myPanel;
}
private static class ConflictsTable extends JBTable {
ConflictsTable(@NotNull ConflictsTableModel model) {
super(model);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
if (!model.myRows.isEmpty()) {
// select first row by default.
getSelectionModel().setSelectionInterval(0, 0);
}
TableColumn filterColumn = getColumnByIndex(ConflictsTableModel.FILTER_COLUMN);
filterColumn.setCellEditor(new BooleanTableCellEditor());
setUpBooleanColumn(filterColumn, true);
TableColumn resolvedColumn = getColumnByIndex(ConflictsTableModel.RESOLVED_COLUMN);
setUpBooleanColumn(resolvedColumn, false);
}
private static void setUpBooleanColumn(@NotNull TableColumn column, boolean editable) {
TableCellRenderer renderer = editable ? new BooleanTableCellRenderer() : new BooleanCellRenderer();
column.setCellRenderer(renderer);
column.setMaxWidth(50);
}
@NotNull
private TableColumn getColumnByIndex(int index) {
return getColumn(getColumnName(index));
}
}
private static class ConflictsTableModel extends DefaultTableModel {
static final Object[] COLUMN_NAMES = { "Filter", "Source", "Resolved" };
static final int FILTER_COLUMN = 0;
static final int SOURCE_COLUMN = 1;
static final int RESOLVED_COLUMN = 2;
final List<ConflictTableRow> myRows = Lists.newArrayList();
final Function<List<ConflictTableRow>, Void> myFilterFunction;
ConflictsTableModel(@NotNull List<Conflict> conflicts, Function<List<ConflictTableRow>, Void> filterFunction) {
super(COLUMN_NAMES, conflicts.size());
myFilterFunction = filterFunction;
for (Conflict conflict : conflicts) {
myRows.add(new ConflictTableRow(conflict));
}
Collections.sort(myRows);
}
@Override
public Object getValueAt(int row, int column) {
ConflictTableRow tableRow = myRows.get(row);
switch (column) {
case FILTER_COLUMN:
return tableRow.myFilter;
case SOURCE_COLUMN:
return tableRow.myConflict.getSource().getName();
case RESOLVED_COLUMN:
return tableRow.myConflict.isResolved();
}
throw new IllegalArgumentException(String.format("Column index '%d' is not valid", column));
}
@Override
public boolean isCellEditable(int row, int column) {
return column == FILTER_COLUMN;
}
@Override
public void setValueAt(Object aValue, int row, int column) {
if (column == FILTER_COLUMN && aValue instanceof Boolean) {
ConflictTableRow conflictTableRow = myRows.get(row);
conflictTableRow.myFilter = (Boolean)aValue;
myFilterFunction.fun(myRows);
}
}
}
private static class ConflictTableRow implements Comparable<ConflictTableRow> {
final Conflict myConflict;
boolean myFilter;
ConflictTableRow(Conflict conflict) {
myConflict = conflict;
}
@Override
public int compareTo(@NotNull ConflictTableRow other) {
return Collator.getInstance().compare(myConflict.getSource().getName(), other.myConflict.getSource().getName());
}
}
private static abstract class CheckboxTreeView extends CheckboxTree implements TreeExpander {
@NotNull final CheckedTreeNode myRoot;
CheckboxTreeView(@NotNull CheckboxTreeCellRenderer cellRenderer, @NotNull CheckedTreeNode root) {
super(cellRenderer, root);
myRoot = root;
}
@Override
public void expandAll() {
TreeUtil.expandAll(this);
}
@Override
public boolean canExpand() {
return canCollapse();
}
@Override
public void collapseAll() {
TreeUtil.collapseAll(this, 1);
}
@Override
public boolean canCollapse() {
return myRoot.getChildCount() > 0;
}
@Override
public DefaultTreeModel getModel() {
return (DefaultTreeModel)super.getModel();
}
void reload() {
((DefaultTreeModel)super.getModel()).reload();
}
}
private static class FilterAwareCheckedTreeNode extends CheckedTreeNode {
boolean myVisible = true;
FilterAwareCheckedTreeNode(@Nullable Object userObject) {
//noinspection ConstantConditions
super(userObject);
}
@Override
public TreeNode getChildAt(int index) {
if (children == null) {
// We use the same error message as super.
throw new ArrayIndexOutOfBoundsException("node has no children");
}
int realIndex = -1;
int visibleIndex = -1;
Enumeration e = children.elements();
while (e.hasMoreElements()) {
Object child = e.nextElement();
if (child instanceof FilterAwareCheckedTreeNode) {
FilterAwareCheckedTreeNode node = (FilterAwareCheckedTreeNode)child;
if (node.myVisible) {
visibleIndex++;
}
}
else {
visibleIndex++;
}
realIndex++;
if (visibleIndex == index) {
return (TreeNode) children.elementAt(realIndex);
}
}
throw new ArrayIndexOutOfBoundsException("index unmatched");
}
@Override
public int getChildCount() {
if (children == null) {
return 0;
}
int count = 0;
Enumeration e = children.elements();
while (e.hasMoreElements()) {
Object child = e.nextElement();
if (child instanceof FilterAwareCheckedTreeNode) {
FilterAwareCheckedTreeNode node = (FilterAwareCheckedTreeNode)child;
if (node.myVisible) {
count++;
}
}
else {
count++;
}
}
return count;
}
}
private static class ModuleTreeElement {
@NotNull final Module myModule;
@NotNull final List<Conflict> myConflicts = Lists.newArrayList();
ModuleTreeElement(@NotNull Module module) {
myModule = module;
}
void addConflict(@NotNull Conflict conflict) {
myConflicts.add(conflict);
}
}
private static class DependencyTreeElement implements Comparable<DependencyTreeElement> {
@NotNull final Module myModule;
@NotNull final String myGradlePath;
@Nullable final String myVariant;
@Nullable final Conflict myConflict;
DependencyTreeElement(@NotNull Module module,
@NotNull String gradlePath,
@Nullable String variant,
@Nullable Conflict conflict) {
myGradlePath = gradlePath;
myVariant = variant;
myModule = module;
myConflict = conflict;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DependencyTreeElement that = (DependencyTreeElement)o;
return Objects.equal(myGradlePath, that.myGradlePath) && Objects.equal(myVariant, that.myVariant);
}
@Override
public int hashCode() {
return Objects.hashCode(myGradlePath, myVariant);
}
@Override
public int compareTo(@NotNull DependencyTreeElement other) {
return Collator.getInstance().compare(myModule.getName(), other.myModule.getName());
}
}
}