diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.form b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.form
index 46fab8e0e386..9d488f181bf2 100644
--- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.form
+++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.form
@@ -72,7 +72,7 @@
-
+
diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.java
index 8ac75b75c656..c18310ddd6fb 100644
--- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.java
+++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateConfigurable.java
@@ -7,10 +7,12 @@ import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.*;
+import com.intellij.ui.AnActionButton;
+import com.intellij.ui.AnActionButtonRunnable;
+import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.components.JBCheckBox;
-import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
+import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Consumer;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
@@ -19,11 +21,11 @@ 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.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -42,9 +44,12 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
private JPanel myCertificatesListPanel;
private JPanel myDetailsPanel;
- private JBList myCertificatesList;
private MutableTrustManager myTrustManager;
+ private Tree myTree = new Tree();
+ private CertificateTreeBuilder myTreeBuilder;
+ private Set myCertificates;
+
public CertificateConfigurable() {
// not fully functional by now
@@ -55,20 +60,12 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
// show newly added certificates
myTrustManager.addListener(this);
- myCertificatesList = new JBList(new CollectionListModel());
- myCertificatesList.getEmptyText().setText("No certificates");
- myCertificatesList.setCellRenderer(new ListCellRendererWrapper() {
- @Override
- public void customize(JList list, X509Certificate certificate, int index, boolean selected, boolean hasFocus) {
- if (!new CertificateWrapper(certificate).isValid()) {
- setForeground(UIUtil.getLabelDisabledForeground());
- }
- setText(getCommonName(certificate));
- }
- });
+ myTree.getEmptyText().setText("No certificates");
+ myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ myTree.setRootVisible(false);
+ //myTree.setShowsRootHandles(false);
- ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myCertificatesList).disableUpDownActions();
- decorator.setVisibleRowCount(5);
+ ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree).disableUpDownActions();
decorator.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
@@ -81,13 +78,14 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
if (certificate == null) {
Messages.showErrorDialog(myRootPanel, "Malformed X509 server certificate", "Not Imported");
}
- else if (getCertificates().contains(certificate)) {
+ else if (myCertificates.contains(certificate)) {
Messages.showWarningDialog(myRootPanel, "Certificate already exists", "Not Imported");
}
else {
- getListModel().add(certificate);
+ myCertificates.add(certificate);
+ myTreeBuilder.addCertificate(certificate);
addCertificatePanel(certificate);
- myCertificatesList.setSelectedValue(certificate, true);
+ myTreeBuilder.selectCertificate(certificate);
}
}
});
@@ -96,28 +94,24 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
@Override
public void run(AnActionButton button) {
// allow to delete several certificates at once
- CollectionListModel model = getListModel();
- // In JDK 1.7 getSelectedValuesList can be used instead
- List selected = new ArrayList();
- for (int i : myCertificatesList.getSelectedIndices()) {
- selected.add((X509Certificate)myCertificatesList.getModel().getElementAt(i));
+ for (X509Certificate certificate : myTreeBuilder.getSelectedCertificates()) {
+ myCertificates.remove(certificate);
+ myTreeBuilder.removeCertificate(certificate);
}
- for (X509Certificate certificate : selected) {
- model.remove(certificate);
- }
- if (getListModel().getSize() > 0) {
- myCertificatesList.setSelectedIndex(0);
- } else {
+ if (myCertificates.isEmpty()) {
myDetailsPanel.removeAll();
myDetailsPanel.repaint();
}
+ else {
+ myTreeBuilder.selectFirstCertificate();
+ }
}
});
- myCertificatesList.addListSelectionListener(new ListSelectionListener() {
+ myTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
- public void valueChanged(ListSelectionEvent e) {
- X509Certificate certificate = getSelectedCertificate();
+ public void valueChanged(TreeSelectionEvent e) {
+ X509Certificate certificate = myTreeBuilder.getFirstSelectedCertificate();
if (certificate != null) {
String uniqueName = getCardName(certificate);
((CardLayout)myDetailsPanel.getLayout()).show(myDetailsPanel, uniqueName);
@@ -140,19 +134,6 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
return certificate.getSubjectX500Principal().getName();
}
- private CollectionListModel getListModel() {
- //noinspection unchecked
- return (CollectionListModel)myCertificatesList.getModel();
- }
-
- private List getCertificates() {
- return getListModel().getItems();
- }
-
- private X509Certificate getSelectedCertificate() {
- return (X509Certificate)myCertificatesList.getSelectedValue();
- }
-
@NotNull
@Override
public String getId() {
@@ -188,18 +169,18 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
CertificatesManager.Config state = CertificatesManager.getInstance().getState();
return myCheckHostname.isSelected() != state.checkHostname ||
myCheckValidityPeriod.isSelected() != state.checkValidity ||
- !getCertificates().equals(myTrustManager.getCertificates());
+ !myCertificates.equals(new HashSet(myTrustManager.getCertificates()));
}
@Override
public void apply() throws ConfigurationException {
List existing = myTrustManager.getCertificates();
- Set added = new HashSet(getCertificates());
+ Set added = new HashSet(myCertificates);
added.removeAll(existing);
Set removed = new HashSet(existing);
- removed.removeAll(getCertificates());
+ removed.removeAll(myCertificates);
for (X509Certificate certificate : added) {
if (!myTrustManager.addCertificate(certificate)) {
@@ -212,22 +193,6 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
throw new ConfigurationException("Cannot remove certificate for " + getCommonName(certificate), "Cannot Remove Certificate");
}
}
-
- //Set old = new HashSet(existing);
- //for (X509Certificate certificate : getListModel().getItems()) {
- // if (old.contains(certificate)) {
- // old.remove(certificate);
- // }
- // else if (!myTrustManager.addCertificate(certificate)) {
- // throw new ConfigurationException("Cannot add certificate", "Cannot Add Certificate");
- // }
- //}
- //// all that remains were removed
- //for (X509Certificate certificate : old) {
- // if (!myTrustManager.removeCertificate(certificate)) {
- // throw new ConfigurationException("Cannot remove certificate", "Cannot Remove Certificate");
- // }
- //}
CertificatesManager.Config state = CertificatesManager.getInstance().getState();
state.checkHostname = myCheckHostname.isSelected();
state.checkValidity = myCheckValidityPeriod.isSelected();
@@ -235,15 +200,14 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
@Override
public void reset() {
- //noinspection unchecked
- myCertificatesList.setModel(new CollectionListModel(myTrustManager.getCertificates()));
+ List original = myTrustManager.getCertificates();
+ myTreeBuilder = new CertificateTreeBuilder(myTree, original);
+ myCertificates = new HashSet(original);
+
// fill lower panel with cards
- for (X509Certificate certificate : getCertificates()) {
+ for (X509Certificate certificate : original) {
addCertificatePanel(certificate);
}
- if (!getCertificates().isEmpty()) {
- myCertificatesList.setSelectedIndex(0);
- }
CertificatesManager.Config state = CertificatesManager.getInstance().getState();
myCheckHostname.setSelected(state.checkHostname);
myCheckValidityPeriod.setSelected(state.checkValidity);
@@ -251,7 +215,7 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
@Override
public void disposeUIResources() {
- // do nothing
+ myTrustManager.removeListener(this);
}
@Override
@@ -259,9 +223,9 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
- CollectionListModel model = getListModel();
- if (model.getElementIndex(certificate) < 0) {
- model.add(certificate);
+ if (!myCertificates.contains(certificate)) {
+ myCertificates.add(certificate);
+ myTreeBuilder.addCertificate(certificate);
}
addCertificatePanel(certificate);
}
@@ -272,9 +236,9 @@ public class CertificateConfigurable implements SearchableConfigurable, Configur
public void certificateRemoved(final X509Certificate certificate) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
- CollectionListModel model = getListModel();
- if (model.getElementIndex(certificate) >= 0) {
- model.remove(certificate);
+ if (myCertificates.contains(certificate)) {
+ myCertificates.remove(certificate);
+ myTreeBuilder.removeCertificate(certificate);
}
}
});
diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateTreeBuilder.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateTreeBuilder.java
new file mode 100644
index 000000000000..bf952d7097e5
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateTreeBuilder.java
@@ -0,0 +1,260 @@
+package com.intellij.util.net.ssl;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.util.treeView.AbstractTreeBuilder;
+import com.intellij.ide.util.treeView.AbstractTreeStructure;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.ide.util.treeView.PresentableNodeDescriptor;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.containers.MultiMap;
+import com.intellij.util.ui.tree.TreeUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import static com.intellij.util.net.ssl.CertificateWrapper.CommonField.COMMON_NAME;
+import static com.intellij.util.net.ssl.CertificateWrapper.CommonField.ORGANIZATION;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class CertificateTreeBuilder extends AbstractTreeBuilder {
+ private static final SimpleTextAttributes STRIKEOUT_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_STRIKEOUT, null);
+ private static final RootDescriptor ROOT_DESCRIPTOR = new RootDescriptor();
+
+ private final MultiMap myCertificates = new MultiMap();
+
+ public CertificateTreeBuilder(@NotNull Tree tree, @NotNull Collection certificates) {
+ for (X509Certificate certificate : certificates) {
+ addCertificate(certificate);
+ }
+ init(tree, new DefaultTreeModel(new DefaultMutableTreeNode()), new MyTreeStructure(), new Comparator() {
+ @Override
+ public int compare(NodeDescriptor o1, NodeDescriptor o2) {
+ if (o1 instanceof OrganizationDescriptor && o2 instanceof OrganizationDescriptor) {
+ return ((String)o1.getElement()).compareTo((String)o2.getElement());
+ }
+ else if (o1 instanceof CertificateDescriptor && o2 instanceof CertificateDescriptor) {
+ String cn1 = ((CertificateDescriptor)o1).getElement().getSubjectField(COMMON_NAME);
+ String cn2 = ((CertificateDescriptor)o2).getElement().getSubjectField(COMMON_NAME);
+ return cn1.compareTo(cn2);
+ }
+ return 0;
+ }
+ }, true);
+ initRootNode();
+ }
+
+ public void addCertificate(@NotNull X509Certificate certificate) {
+ CertificateWrapper wrapper = new CertificateWrapper(certificate);
+ myCertificates.putValue(wrapper.getSubjectField(ORGANIZATION), wrapper);
+ queueUpdateFrom(RootDescriptor.ROOT, true);
+ }
+
+ /**
+ * Remove specified certificate and corresponding organization, if after removal it contains no certificates.
+ */
+ public void removeCertificate(@NotNull X509Certificate certificate) {
+ CertificateWrapper wrapper = new CertificateWrapper(certificate);
+ myCertificates.remove(wrapper.getSubjectField(ORGANIZATION), wrapper);
+ queueUpdateFrom(RootDescriptor.ROOT, true);
+ }
+
+ public List getCertificates() {
+ return ContainerUtil.map(myCertificates.values(), new Function() {
+ @Override
+ public X509Certificate fun(CertificateWrapper wrapper) {
+ return wrapper.getCertificate();
+ }
+ });
+ }
+
+ public boolean isEmpty() {
+ return myCertificates.isEmpty();
+ }
+
+ public void selectCertificate(@NotNull X509Certificate certificate) {
+ select(new CertificateWrapper(certificate));
+ }
+
+ public void selectFirstCertificate() {
+ if (!isEmpty()) {
+ Tree tree = (Tree)getTree();
+ TreePath path = TreeUtil.getFirstLeafNodePath(tree);
+ tree.addSelectionPath(path);
+ }
+ }
+
+ /**
+ * Returns certificates selected in the tree. If organization node is selected, all its certificates
+ * will be returned.
+ *
+ * @return - selected certificates
+ */
+ @NotNull
+ public Set getSelectedCertificates() {
+ Set selected = new HashSet();
+ for (Object o : getSelectedElements()) {
+ if (o instanceof CertificateWrapper) {
+ selected.add(((CertificateWrapper)o).getCertificate());
+ }
+ else if (o instanceof String) {
+ selected.addAll(getCertificatesByOrganization((String)o));
+ }
+ }
+ return selected;
+ }
+
+ @Nullable
+ public X509Certificate getFirstSelectedCertificate() {
+ Set certificates = getSelectedCertificates();
+ return certificates.isEmpty() ? null : certificates.iterator().next();
+ }
+
+ @NotNull
+ public List getCertificatesByOrganization(@NotNull String organizationName) {
+ Collection wrappers = myCertificates.get(organizationName);
+ return extract(wrappers);
+ }
+
+ private static List extract(Collection wrappers) {
+ return ContainerUtil.map(wrappers, new Function() {
+ @Override
+ public X509Certificate fun(CertificateWrapper wrapper) {
+ return wrapper.getCertificate();
+ }
+ });
+ }
+
+ @Override
+ protected Object transformElement(Object object) {
+ if (object instanceof CertificateWrapper) {
+ return ((CertificateWrapper)object).getCertificate();
+ }
+ return object;
+ }
+
+ @Override
+ protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
+ return super.isAutoExpandNode(nodeDescriptor) || nodeDescriptor instanceof OrganizationDescriptor;
+ }
+
+ class MyTreeStructure extends AbstractTreeStructure {
+ @Override
+ public Object getRootElement() {
+ return RootDescriptor.ROOT;
+ }
+
+ @Override
+ public Object[] getChildElements(Object element) {
+ if (element == RootDescriptor.ROOT) {
+ return ArrayUtil.toStringArray(myCertificates.keySet());
+ }
+ else if (element instanceof String) {
+ return ArrayUtil.toObjectArray(myCertificates.get((String)element));
+ }
+ return ArrayUtil.EMPTY_OBJECT_ARRAY;
+ }
+
+ @Nullable
+ @Override
+ public Object getParentElement(Object element) {
+ if (element == RootDescriptor.ROOT) {
+ return null;
+ }
+ else if (element instanceof String) {
+ return RootDescriptor.ROOT;
+ }
+ return ((CertificateWrapper)element).getSubjectField(ORGANIZATION);
+ }
+
+ @NotNull
+ @Override
+ public NodeDescriptor createDescriptor(Object element, NodeDescriptor parentDescriptor) {
+ if (element == RootDescriptor.ROOT) {
+ return ROOT_DESCRIPTOR;
+ }
+ else if (element instanceof String) {
+ return new OrganizationDescriptor(parentDescriptor, (String)element);
+ }
+ return new CertificateDescriptor(parentDescriptor, (CertificateWrapper)element);
+ }
+
+
+ @Override
+ public void commit() {
+ // do nothing
+ }
+
+ @Override
+ public boolean hasSomethingToCommit() {
+ return false;
+ }
+ }
+
+ // Auxiliary node descriptors
+
+ static abstract class MyNodeDescriptor extends PresentableNodeDescriptor {
+ private final T myObject;
+
+ MyNodeDescriptor(@Nullable NodeDescriptor parentDescriptor, @NotNull T object) {
+ super(null, parentDescriptor);
+ myObject = object;
+ }
+
+ @Override
+ public T getElement() {
+ return myObject;
+ }
+ }
+
+ static class RootDescriptor extends MyNodeDescriptor