src/main/java/org/codinjutsu/tools/mongo/view/MongoResultPanel.java
public class MongoResultPanel extends JPanel implements Disposable {

    private final Project project;
    private final MongoPanel.MongoDocumentOperations mongoDocumentOperations;
    private final Notifier notifier;
    private JPanel mainPanel;
    private JPanel containerPanel;
    private final JPanel resultTreePanel;

    JsonTreeTableView resultTreeTableView;

    private ViewMode currentViewMode = ViewMode.TREE;
    private ActionCallback actionCallback;


    public MongoResultPanel(Project project, MongoPanel.MongoDocumentOperations mongoDocumentOperations, Notifier notifier) {
        this.project = project;
        this.mongoDocumentOperations = mongoDocumentOperations;
        this.notifier = notifier;
        setLayout(new BorderLayout());
        add(mainPanel, BorderLayout.CENTER);

        resultTreePanel = new JPanel(new BorderLayout());

        containerPanel.setLayout(new JBCardLayout());
        containerPanel.add(resultTreePanel);

        actionCallback = new ActionCallback() {
            public void onOperationSuccess(String shortMessage, String detailedMessage) {
                notifier.notifyInfo(detailedMessage);
            }

            @Override
            public void onOperationFailure(Exception exception) {
                notifier.notifyError(exception.getMessage());
            }
        };


        Disposer.register(project, this);
    }

    void updateResultView(MongoCollectionResult mongoCollectionResult, Pagination pagination) {
        if (ViewMode.TREE.equals(currentViewMode)) {
            updateResultTreeTable(mongoCollectionResult, pagination);
        } else {
            updateResultTable(mongoCollectionResult);
        }
    }

    private void updateResultTreeTable(MongoCollectionResult mongoCollectionResult, Pagination pagination) {
        resultTreeTableView = new JsonTreeTableView(JsonTreeUtils.buildJsonTree(mongoCollectionResult.getCollectionName(),
                extractDocuments(pagination, mongoCollectionResult.getDocuments()), pagination.getStartIndex()),
                JsonTreeTableView.COLUMNS_FOR_READING);

        resultTreeTableView.setName("resultTreeTable");

        displayResult(resultTreeTableView);

        UIUtil.invokeAndWaitIfNeeded((Runnable)() -> TreeUtil.expand(resultTreeTableView.getTree(), 2));
    }

    private static List<Document> extractDocuments(Pagination pagination, List<Document> documents) {
        if (NbPerPage.ALL.equals(pagination.getNbPerPage())) {
            return documents;
        }
        if (pagination.getNbDocumentsPerPage() >= documents.size()) {
            return documents;
        }

        int startIndex = pagination.getStartIndex();
        int endIndex = startIndex + pagination.getNbDocumentsPerPage();

        return IntStream.range(startIndex, endIndex)
                .mapToObj(documents::get)
                .collect(Collectors.toCollection(LinkedList::new));
    }

    private void updateResultTable(MongoCollectionResult mongoCollectionResult) {
        displayResult(new JsonTableView(JsonTableUtils.buildJsonTable(mongoCollectionResult)));
    }

    private void displayResult(JComponent tableView) {
        resultTreePanel.invalidate();
        resultTreePanel.removeAll();
        resultTreePanel.add(new JBScrollPane(tableView));
        resultTreePanel.validate();
    }


    public void editSelectedMongoDocument() {
        Document mongoDocument = getSelectedMongoDocument();
        if (mongoDocument == null) {
            return;
        }

        MongoEditionDialog
                .create(project, mongoDocumentOperations, actionCallback)
                .initDocument(mongoDocument)
                .show();
    }


    public void addMongoDocument() {
        MongoEditionDialog
                .create(project, mongoDocumentOperations, actionCallback)
                .initDocument(null)
                .show();
    }

    private Document getSelectedMongoDocument() {
        TreeTableTree tree = resultTreeTableView.getTree();
        JsonTreeNode treeNode = (JsonTreeNode) tree.getLastSelectedPathComponent();
        if (treeNode == null) {
            return null;
        }

        MongoNodeDescriptor descriptor = treeNode.getDescriptor();
        if (descriptor instanceof MongoKeyValueDescriptor) {
            MongoKeyValueDescriptor keyValueDescriptor = (MongoKeyValueDescriptor) descriptor;
            if (StringUtils.equals(keyValueDescriptor.getKey(), "_id")) {
                return mongoDocumentOperations.getMongoDocument(keyValueDescriptor.getValue());
            }
        }

        return null;
    }

    private MongoKeyValueDescriptor getObjectIdDescriptorFromSelectedDocument() {
        if (resultTreeTableView == null) {
            return null;
        }
        TreeTableTree tree = resultTreeTableView.getTree();
        JsonTreeNode treeNode = (JsonTreeNode) tree.getLastSelectedPathComponent();
        if (treeNode == null) {
            return null;
        }

        MongoNodeDescriptor descriptor = treeNode.getDescriptor();
        if (!(descriptor instanceof MongoKeyValueDescriptor)) {
            return null;
        }
        MongoKeyValueDescriptor keyValueDescriptor = (MongoKeyValueDescriptor) descriptor;
        if (!"_id".equals(keyValueDescriptor.getKey())
                && !(keyValueDescriptor.getValue() instanceof ObjectId)) {
            return null;
        }

        return keyValueDescriptor;
    }


    public boolean isSelectedNodeId() {
        return getObjectIdDescriptorFromSelectedDocument() != null;
    }


    public boolean isSelectedDBRef() {
        if (resultTreeTableView == null) {
            return false;
        }

        TreeTableTree tree = resultTreeTableView.getTree();
        JsonTreeNode treeNode = (JsonTreeNode) tree.getLastSelectedPathComponent();
        if (treeNode == null) {
            return false;
        }

        MongoNodeDescriptor descriptor = treeNode.getDescriptor();
        if (descriptor instanceof MongoKeyValueDescriptor) {
            if (descriptor.getValue() instanceof DBRef) {
                return true;
            } else {
                JsonTreeNode parentNode = (JsonTreeNode) treeNode.getParent();
                return parentNode.getDescriptor().getValue() instanceof DBRef;
            }
        }

        return false;
    }

    void expandAll() {
        TreeUtil.expandAll(resultTreeTableView.getTree());
    }

    void collapseAll() {
        TreeTableTree tree = resultTreeTableView.getTree();
        TreeUtil.collapseAll(tree, 1);
    }

    public String getStringifiedResult() {
        JsonTreeNode rootNode = (JsonTreeNode) resultTreeTableView.getTree().getModel().getRoot();
        return stringifyResult(rootNode);
    }

    public JsonTreeNode getSelectedNode() {
        return (JsonTreeNode) resultTreeTableView.getTree().getLastSelectedPathComponent();
    }

    public String getSelectedNodeStringifiedValue() {
        JsonTreeNode lastSelectedResultNode = getSelectedNode();
        if (lastSelectedResultNode == null) {
            return null;
        }
        MongoNodeDescriptor userObject = lastSelectedResultNode.getDescriptor();
        return userObject.pretty();
    }

    public DBRef getSelectedDBRef() {
        TreeTableTree tree = resultTreeTableView.getTree();
        JsonTreeNode treeNode = (JsonTreeNode) tree.getLastSelectedPathComponent();

        MongoNodeDescriptor descriptor = treeNode.getDescriptor();
        DBRef selectedDBRef = null;
        if (descriptor instanceof MongoKeyValueDescriptor) {
            if (descriptor.getValue() instanceof DBRef) {
                selectedDBRef = (DBRef) descriptor.getValue();
            } else {
                JsonTreeNode parentNode = (JsonTreeNode) treeNode.getParent();
                MongoNodeDescriptor parentDescriptor = parentNode.getDescriptor();
                if (parentDescriptor.getValue() instanceof DBRef) {
                    selectedDBRef = (DBRef) parentDescriptor.getValue();
                }
            }
        }

        return selectedDBRef;
    }


    private String stringifyResult(DefaultMutableTreeNode selectedResultNode) {
        return IntStream.range(0, selectedResultNode.getChildCount())
                .mapToObj(i -> getDescriptor(i, selectedResultNode).pretty())
                .collect(Collectors.joining(",", "[", "]"));
    }

    private static MongoNodeDescriptor getDescriptor(int i, DefaultMutableTreeNode parentNode) {
        DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) parentNode.getChildAt(i);
        return (MongoNodeDescriptor) childNode.getUserObject();
    }

    @Override
    public void dispose() {
        resultTreeTableView = null;
    }

    void setCurrentViewMode(ViewMode viewMode) {
        this.currentViewMode = viewMode;
    }

    ViewMode getCurrentViewMode() {
        return currentViewMode;
    }

    public Document getReferencedDocument(DBRef selectedDBRef) {
        return mongoDocumentOperations.getReferenceDocument(
                selectedDBRef.getCollectionName(), selectedDBRef.getId(), selectedDBRef.getDatabaseName()
        );
    }

    public void deleteSelectedMongoDocument() {
        MongoKeyValueDescriptor descriptor = getObjectIdDescriptorFromSelectedDocument();
        if (descriptor == null) {
            return;
        }

        ObjectId objectId = ((ObjectId) descriptor.getValue());
        mongoDocumentOperations.deleteMongoDocument(objectId);
        notifier.notifyInfo("Document with _id=" + objectId.toString() + " deleted.");
    }

    public interface ActionCallback {

        void onOperationSuccess(String label, String message);

        void onOperationFailure(Exception exception);
    }

    public enum ViewMode {
        TREE, TABLE
    }

}
src/main/java/org/codinjutsu/tools/mongo/view/model/JsonTreeUtils.java
public class JsonTreeUtils {

    public static TreeNode buildJsonTree(String collectionName, List<Document> documents, int startIndex) {
        JsonTreeNode rootNode = new JsonTreeNode(new MongoResultDescriptor(collectionName));

        int i = startIndex;
        for (Document document : documents) {
            JsonTreeNode currentNode = new JsonTreeNode(MongoValueDescriptor.createDescriptor(i++, document));
            processDocument(currentNode, document);
            rootNode.add(currentNode);
        }
        return rootNode;
    }

    public static TreeNode buildJsonTree(Document document) {
        JsonTreeNode rootNode = new JsonTreeNode(new MongoResultDescriptor());//TODO crappy
        if (document != null) {
            processDocument(rootNode, document);
        }
        return rootNode;
    }

    public static void processDocument(JsonTreeNode parentNode, Document document) {
        for (String key : document.keySet()) {
            Object value = document.get(key);
            JsonTreeNode currentNode = new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor(key, value));
            processValue(value, currentNode);
            parentNode.add(currentNode);
        }
    }

    public static void processObjectList(JsonTreeNode parentNode, List objectList) {
        for (int i = 0; i < objectList.size(); i++) {
            Object value = objectList.get(i);
            JsonTreeNode currentNode = new JsonTreeNode(MongoValueDescriptor.createDescriptor(i, value));
            processValue(value, currentNode);
            parentNode.add(currentNode);
        }
    }

    private static void processValue(Object value, JsonTreeNode currentNode) {
        if (value instanceof Document) {
            processDocument(currentNode, (Document) value);
        } else if (value instanceof DBRef) {
            processDBRef(currentNode, (DBRef) value);
        } else if (value instanceof List) {
            processObjectList(currentNode, (List) value);
        }
    }

    private static void processDBRef(JsonTreeNode parentNode, DBRef dbRef) {
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$ref", dbRef.getCollectionName())));
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$id", dbRef.getId())));
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$db", dbRef.getDatabaseName())));
    }

    public static Document buildDocumentObject(JsonTreeNode rootNode) {
        Document document = new Document();
        Enumeration children = rootNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoKeyValueDescriptor descriptor = (MongoKeyValueDescriptor) node.getDescriptor();
            Object value = descriptor.getValue();
            if (value instanceof Document) {
                document.put(descriptor.getKey(), buildDocumentObject(node));
            } else if (value instanceof DBRef) {
                document.put(descriptor.getKey(), buildDBRefObject(node));
            } else if (value instanceof List) {
                document.put(descriptor.getKey(), buildObjectList(node));
            } else {
                document.put(descriptor.getKey(), value);
            }
        }

        return document;
    }

    private static DBRef buildDBRefObject(JsonTreeNode parentNode) {
        Object _id = null;
        String collectionName = null;
        String databaseName = null;
        Enumeration children = parentNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoNodeDescriptor descriptor = node.getDescriptor();
            String formattedKey = descriptor.getKey();
            switch (formattedKey) {
                case "$id":
                    _id = descriptor.getValue();
                    break;
                case "$ref":
                    collectionName = (String) descriptor.getValue();
                    break;
                case "$db":
                    databaseName = (String) descriptor.getValue();
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected key: " + formattedKey);
            }
        }
        if (collectionName == null || _id == null) {
            throw new IllegalArgumentException("When using DBRef, $ref and $id should be set.");
        }
        return new DBRef(databaseName, collectionName, _id);
    }

    private static List buildObjectList(JsonTreeNode parentNode) {
        List<Object> basicDBList = new ArrayList<>();
        Enumeration children = parentNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoValueDescriptor descriptor = (MongoValueDescriptor) node.getDescriptor();
            Object value = descriptor.getValue();
            if (value instanceof Document) {
                basicDBList.add(buildDocumentObject(node));
            } else if (value instanceof List) {
                basicDBList.add(buildObjectList(node));
            } else {
                basicDBList.add(value);
            }
        }
        return basicDBList;
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/action/edition/DeleteKeyAction.java
public class DeleteKeyAction extends AnAction implements DumbAware {

    private final MongoEditionPanel mongoEditionPanel;

    public DeleteKeyAction(MongoEditionPanel mongoEditionPanel) {
        super("Delete this", "Delete the selected node", AllIcons.Actions.Delete);
        registerCustomShortcutSet(KeyEvent.VK_DELETE, KeyEvent.ALT_MASK, mongoEditionPanel);
        this.mongoEditionPanel = mongoEditionPanel;
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        mongoEditionPanel.removeSelectedKey();
    }

    @Override
    public void update(AnActionEvent event) {
        event.getPresentation().setVisible(mongoEditionPanel.getSelectedNode() != null);
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/nodedescriptor/MongoNodeDescriptor.java
public interface MongoNodeDescriptor {

    int MAX_LENGTH = 150;

    void renderValue(ColoredTableCellRenderer cellRenderer, boolean isNodeExpanded);

    void renderNode(ColoredTreeCellRenderer cellRenderer);

    String getKey();

    String getFormattedValue();

    Object getValue();

    void setValue(Object value);

    String pretty();
}
src/main/java/org/codinjutsu/tools/mongo/view/MongoPanel.java
public class MongoPanel extends JPanel implements Disposable {

    private final Project project;
    private final LoadingDecorator loadingDecorator;
    private JPanel rootPanel;
    private Splitter splitter;
    private JPanel toolBar;
    private JPanel errorPanel;
    private JPanel paginationPanel;

    private final JTextField rowLimitField = new JTextField();
    private final JBLabel rowCountLabel = new JBLabel();
    private final JBLabel pageNumberLabel = new JBLabel();

    private final MongoResultPanel resultPanel;
    private final QueryPanel queryPanel;

    private final MongoManager mongoManager;
    private final ServerConfiguration configuration;
    private final Navigation navigation;
    private MongoCollectionResult currentResults;

    private final Pagination pagination;

    public MongoPanel(Project project, final MongoManager mongoManager, final ServerConfiguration configuration, final Navigation navigation) {
        this.project = project;
        this.mongoManager = mongoManager;
        this.navigation = navigation;
        this.configuration = configuration;
        this.pagination = new Pagination();

        this.currentResults = new MongoCollectionResult(navigation.getCurrentWayPoint().getLabel());

        errorPanel.setLayout(new BorderLayout());

        queryPanel = new QueryPanel(project);
        queryPanel.setVisible(false);

        resultPanel = createResultPanel(project, Notifier.getInstance(project));

        loadingDecorator = new LoadingDecorator(resultPanel, this, 0);
        splitter.setOrientation(true);
        splitter.setProportion(0.2f);
        splitter.setSecondComponent(loadingDecorator.getComponent());

        setLayout(new BorderLayout());
        add(rootPanel);

        initToolBar();
        initPaginationPanel();

        pagination.addSetPageListener(() -> showResults(true));
        pagination.addSetPageListener(() -> {
                    pagination.setTotalDocuments(currentResults.getTotalDocumentNumber());
                    if (NbPerPage.ALL.equals(pagination.getNbPerPage())) {
                        pageNumberLabel.setVisible(false);
                    } else {
                        pageNumberLabel.setText(
                                String.format("Page %d/%d",
                                        pagination.getPageNumber(),
                                        pagination.getTotalPageNumber())
                        );
                        pageNumberLabel.setVisible(true);
                    }
                }
        );
    }

    private MongoResultPanel createResultPanel(Project project, Notifier notifier) {
        return new MongoResultPanel(project, new MongoDocumentOperations() {

            public Document getMongoDocument(Object _id) {
                return mongoManager.findMongoDocument(configuration, navigation.getCurrentWayPoint().getCollection(), _id);
            }

            public void updateMongoDocument(Document mongoDocument) {
                mongoManager.update(configuration, navigation.getCurrentWayPoint().getCollection(), mongoDocument);
                executeQuery();
            }

            @Override
            public Document getReferenceDocument(String collection, Object _id, String database) {
                return mongoManager.findMongoDocument(
                        configuration,
                        new MongoCollection(collection, database != null ? new MongoDatabase(database, new MongoServer(configuration)) : navigation.getCurrentWayPoint().getCollection().getParentDatabase()),
                        _id);
            }

            public void deleteMongoDocument(Object objectId) {
                mongoManager.delete(configuration, navigation.getCurrentWayPoint().getCollection(), objectId);
                executeQuery();
            }
        }, notifier);
    }

    private void initToolBar() {
        toolBar.setLayout(new BorderLayout());

        JPanel rowLimitPanel = createRowLimitPanel();
        toolBar.add(rowLimitPanel, BorderLayout.WEST);

        JComponent actionToolBarComponent = createResultActionsComponent();
        toolBar.add(actionToolBarComponent, BorderLayout.CENTER);

        JComponent viewToolbarComponent = createSelectViewActionsComponent();
        toolBar.add(viewToolbarComponent, BorderLayout.EAST);
    }

    private void initPaginationPanel() {
        paginationPanel.setLayout(new BorderLayout());

        JComponent actionToolbarComponent = createPaginationActionsComponent();
        paginationPanel.add(actionToolbarComponent, BorderLayout.CENTER);

        JPanel panel = new JPanel();
        panel.add(pageNumberLabel);
        panel.add(com.intellij.ui.GuiUtils.createVerticalStrut());
        panel.add(rowCountLabel);

        paginationPanel.add(panel, BorderLayout.EAST);
    }

    @NotNull
    private JPanel createRowLimitPanel() {
        rowLimitField.setText(Integer.toString(configuration.getDefaultRowLimit()));
        rowLimitField.setColumns(5);
        rowLimitField.setDocument(new NumberDocument());
        rowLimitField.setText(Integer.toString(configuration.getDefaultRowLimit()));

        JPanel rowLimitPanel = new NonOpaquePanel();
        rowLimitPanel.add(new JLabel("Row limit:"), BorderLayout.WEST);
        rowLimitPanel.add(rowLimitField, BorderLayout.CENTER);
        rowLimitPanel.add(Box.createHorizontalStrut(5), BorderLayout.EAST);
        return rowLimitPanel;
    }

    @NotNull
    private JComponent createResultActionsComponent() {
        DefaultActionGroup actionResultGroup = new DefaultActionGroup("MongoResultGroup", true);
        actionResultGroup.add(new ExecuteQuery(this));
        actionResultGroup.add(new OpenFindAction(this));
        actionResultGroup.add(new EnableAggregateAction(queryPanel));
        actionResultGroup.addSeparator();
        actionResultGroup.add(new AddMongoDocumentAction(resultPanel));
        actionResultGroup.add(new EditMongoDocumentAction(resultPanel));
        actionResultGroup.add(new DeleteMongoDocumentAction(resultPanel));
        actionResultGroup.add(new CopyAllAction(resultPanel));
        actionResultGroup.addSeparator();
        actionResultGroup.add(new NavigateBackwardAction(this));

        addBasicTreeActions(actionResultGroup);
        actionResultGroup.add(new CloseFindEditorAction(this));
//TODO Duplicate
        ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar("MongoResultGroupActions", actionResultGroup, true);
        actionToolBar.setLayoutPolicy(ActionToolbar.AUTO_LAYOUT_POLICY);
        JComponent actionToolBarComponent = actionToolBar.getComponent();
        actionToolBarComponent.setBorder(null);
        actionToolBarComponent.setOpaque(false);
        return actionToolBarComponent;
    }

    @NotNull
    private JComponent createPaginationActionsComponent() {
        DefaultActionGroup actionResultGroup = new DefaultActionGroup("MongoPaginationGroup", false);
        actionResultGroup.add(new ChangeNbPerPageActionComponent(() -> new PaginationPopupComponent(pagination).initUi()));
        actionResultGroup.add(new PaginationAction.Previous(pagination));
        actionResultGroup.add(new PaginationAction.Next(pagination));

//TODO Duplicate
        ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar("MongoPaginationGroupActions", actionResultGroup, true);
        actionToolBar.setLayoutPolicy(ActionToolbar.AUTO_LAYOUT_POLICY);
        JComponent actionToolBarComponent = actionToolBar.getComponent();
        actionToolBarComponent.setBorder(null);
        actionToolBarComponent.setOpaque(false);
        return actionToolBarComponent;
    }

    @NotNull
    private JComponent createSelectViewActionsComponent() {
        DefaultActionGroup viewSelectGroup = new DefaultActionGroup("MongoViewSelectGroup", false);
        viewSelectGroup.add(new ViewAsTreeAction(this));
        viewSelectGroup.add(new ViewAsTableAction(this));

//TODO Duplicate
        ActionToolbar viewToolbar = ActionManager.getInstance().createActionToolbar("MongoViewSelectedActions", viewSelectGroup, true);

        viewToolbar.setLayoutPolicy(ActionToolbar.AUTO_LAYOUT_POLICY);
        JComponent viewToolbarComponent = viewToolbar.getComponent();
        viewToolbarComponent.setBorder(null);
        viewToolbarComponent.setOpaque(false);
        return viewToolbarComponent;
    }

    private void addBasicTreeActions(DefaultActionGroup actionResultGroup) {
        final TreeExpander treeExpander = new TreeExpander() {
            @Override
            public void expandAll() {
                resultPanel.expandAll();
            }

            @Override
            public boolean canExpand() {
                return true;
            }

            @Override
            public void collapseAll() {
                resultPanel.collapseAll();
            }

            @Override
            public boolean canCollapse() {
                return true;
            }
        };

        CommonActionsManager actionsManager = CommonActionsManager.getInstance();
        final AnAction expandAllAction = actionsManager.createExpandAllAction(treeExpander, resultPanel);
        final AnAction collapseAllAction = actionsManager.createCollapseAllAction(treeExpander, resultPanel);

        Disposer.register(this, () -> {
            collapseAllAction.unregisterCustomShortcutSet(resultPanel);
            expandAllAction.unregisterCustomShortcutSet(resultPanel);
        });


        actionResultGroup.addSeparator();
        actionResultGroup.add(expandAllAction);
        actionResultGroup.add(collapseAllAction);
    }

    public Navigation.WayPoint getCurrentWayPoint() {
        return navigation.getCurrentWayPoint();
    }

    public void showResults() {
        showResults(false);
    }

    private void showResults(boolean cached) {
        executeQuery(cached, navigation.getCurrentWayPoint());
    }

    //TODO refactor
    public void executeQuery() {
        Navigation.WayPoint currentWayPoint = navigation.getCurrentWayPoint();
        currentWayPoint.setQueryOptions(queryPanel.getQueryOptions(rowLimitField.getText()));
        MongoQueryOptions queryOptions = queryPanel.getQueryOptions(rowLimitField.getText());
        currentWayPoint.setQueryOptions(queryOptions);
        executeQuery(false, currentWayPoint);
    }

    //TODO refactor
    private void executeQuery(final boolean useCachedResults, final Navigation.WayPoint wayPoint) {
        errorPanel.setVisible(false);
        validateQuery();
        ProgressManager.getInstance().run(new Task.Backgroundable(project, "Get documents from " + wayPoint.getLabel()) {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                try {
                    UIUtil.invokeLaterIfNeeded(() -> loadingDecorator.startLoading(false));

                    final MongoQueryOptions queryOptions = wayPoint.getQueryOptions();
                    if (!useCachedResults) {
                        currentResults = mongoManager.findMongoDocuments(
                                configuration,
                                wayPoint.getCollection(),
                                queryOptions);
                    }
                    UIUtil.invokeLaterIfNeeded(() -> {
                        resultPanel.updateResultView(currentResults, pagination);
                        rowCountLabel.setText(String.format("%s documents", currentResults.getDocuments().size()));
                        initActions(resultPanel.resultTreeTableView);

                    });
                } catch (final Exception ex) {
                    UIUtil.invokeLaterIfNeeded(() -> {
                        errorPanel.invalidate();
                        errorPanel.removeAll();
                        errorPanel.add(new ErrorPanel(ex), BorderLayout.CENTER);
                        errorPanel.validate();
                        errorPanel.setVisible(true);
                    });
                } finally {
                    UIUtil.invokeLaterIfNeeded(loadingDecorator::stopLoading);
                }

            }
        });
    }

    private void initActions(JsonTreeTableView resultTreeTableView) {
        resultTreeTableView.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent mouseEvent) {
                if (mouseEvent.getClickCount() == 2 && resultPanel.isSelectedNodeId()) {
                    resultPanel.editSelectedMongoDocument();
                }
            }
        });

        DefaultActionGroup actionPopupGroup = new DefaultActionGroup("MongoResultPopupGroup", true);
        if (ApplicationManager.getApplication() != null) {
            actionPopupGroup.add(new EditMongoDocumentAction(resultPanel));
            actionPopupGroup.add(new DeleteMongoDocumentAction(resultPanel));
            actionPopupGroup.add(new CopyNodeAction(resultPanel));
            actionPopupGroup.add(new GoToMongoDocumentAction(this));
        }

        PopupHandler.installPopupHandler(resultTreeTableView, actionPopupGroup, "POPUP", ActionManager.getInstance());
    }

    private void validateQuery() {
        queryPanel.validateQuery();
    }

    @Override
    public void dispose() {
        resultPanel.dispose();
    }

    public MongoResultPanel getResultPanel() {
        return resultPanel;
    }

    public void openFindEditor() {
        queryPanel.setVisible(true);
        splitter.setFirstComponent(queryPanel);
        UIUtil.invokeLaterIfNeeded(this::focusOnEditor);
    }

    public void closeFindEditor() {
        splitter.setFirstComponent(null);
        queryPanel.setVisible(false);
    }

    public void focusOnEditor() {
        queryPanel.requestFocusOnEditor();
    }

    public boolean isFindEditorOpened() {
        return splitter.getFirstComponent() == queryPanel;
    }

    public void setViewMode(MongoResultPanel.ViewMode viewMode) {
        if (resultPanel.getCurrentViewMode().equals(viewMode)) {
            return;
        }
        this.resultPanel.setCurrentViewMode(viewMode);
        executeQuery(true, navigation.getCurrentWayPoint());
    }

    public ServerConfiguration getConfiguration() {
        return configuration;
    }

    public void navigateBackward() {
        navigation.moveBackward();
        executeQuery(false, navigation.getCurrentWayPoint());
    }

    public boolean hasNavigationHistory() {
        return navigation.getWayPoints().size() > 1;
    }

    public void goToReferencedDocument() {
        DBRef selectedDBRef = resultPanel.getSelectedDBRef();

        Document referencedDocument = resultPanel.getReferencedDocument(selectedDBRef);
        if (referencedDocument == null) {
            Messages.showErrorDialog(this, "Referenced document was not found");
            return;
        }

        navigation.addNewWayPoint(
                new MongoCollection(
                        selectedDBRef.getCollectionName(),
                        selectedDBRef.getDatabaseName() != null
                                ? new MongoDatabase(selectedDBRef.getDatabaseName(), new MongoServer(configuration))
                                : navigation.getCurrentWayPoint().getCollection().getParentDatabase()
                ),
                new MongoQueryOptions().setFilter(
                        new Document("_id", selectedDBRef.getId())
                ));
        executeQuery(false, navigation.getCurrentWayPoint());
    }

    private static class ChangeNbPerPageActionComponent extends DumbAwareAction implements CustomComponentAction {

        @NotNull
        private final Computable<JComponent> myComponentCreator;

        ChangeNbPerPageActionComponent(@NotNull Computable<JComponent> componentCreator) {
            myComponentCreator = componentCreator;
        }

        @Override
        public JComponent createCustomComponent(Presentation presentation, @NotNull String place) {
            return myComponentCreator.compute();
        }

        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
        }
    }


    public interface MongoDocumentOperations {
        Document getMongoDocument(Object _id);

        void deleteMongoDocument(Object mongoDocument);

        void updateMongoDocument(Document mongoDocument);

        Document getReferenceDocument(String collection, Object _id, String database);
    }
}
src/main/java/org/codinjutsu/tools/mongo/utils/MongoUtils.java
public static final DocumentCodec DOCUMENT_CODEC = new DocumentCodec(
        CodecRegistries.fromRegistries(MongoClient.getDefaultCodecRegistry()),
        new BsonTypeClassMap()
);
src/main/java/org/codinjutsu/tools/mongo/view/model/JsonTreeNode.java
public class JsonTreeNode extends DefaultMutableTreeNode {
    private final MongoNodeDescriptor nodeDescriptor;

    public JsonTreeNode(MongoNodeDescriptor nodeDescriptor) {
        this.nodeDescriptor = nodeDescriptor;
        setUserObject(nodeDescriptor);
    }

    public MongoNodeDescriptor getDescriptor() {
        return nodeDescriptor;
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/action/edition/AddKeyAction.java
public class AddKeyAction extends AnAction implements DumbAware {

    private final MongoEditionPanel mongoEditionPanel;

    public AddKeyAction(MongoEditionPanel mongoEditionPanel) {
        super("Add a key", "Add a key", AllIcons.General.Add);
        registerCustomShortcutSet(KeyEvent.VK_INSERT, KeyEvent.ALT_MASK, mongoEditionPanel);
        this.mongoEditionPanel = mongoEditionPanel;
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        AddKeyDialog dialog = AddKeyDialog.createDialog(mongoEditionPanel);
        dialog.show();

        if (!dialog.isOK()) {
            return;
        }

        mongoEditionPanel.addKey(dialog.getKey(), dialog.getValue());
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/JsonTreeTableView.java
public class JsonTreeTableView extends TreeTable {

    private static final ColumnInfo KEY = new ColumnInfo("Key") {

        public Object valueOf(Object obj) {
            JsonTreeNode node = (JsonTreeNode) obj;
            return node.getDescriptor();
        }

        @Override
        public Class getColumnClass() {
            return TreeTableModel.class;
        }

        @Override
        public boolean isCellEditable(Object o) {
            return false;
        }
    };

    private static final ColumnInfo READONLY_VALUE = new ReadOnlyValueColumnInfo();

    private static final ColumnInfo WRITABLE_VALUE = new WritableColumnInfo();

    public static final ColumnInfo[] COLUMNS_FOR_READING = new ColumnInfo[]{KEY, READONLY_VALUE};
    public static final ColumnInfo[] COLUMNS_FOR_WRITING = new ColumnInfo[]{KEY, WRITABLE_VALUE};

    private final ColumnInfo[] columns;

    public JsonTreeTableView(TreeNode rootNode, ColumnInfo[] columnInfos) {
        super(new ListTreeTableModelOnColumns(rootNode, columnInfos));
        this.columns = columnInfos;

        final TreeTableTree tree = getTree();

        tree.setShowsRootHandles(true);
        tree.setRootVisible(false);
        UIUtil.setLineStyleAngled(tree);
        setTreeCellRenderer(new MongoKeyCellRenderer());

        new TreeTableSpeedSearch(this, path -> {
            final JsonTreeNode node = (JsonTreeNode) path.getLastPathComponent();
            MongoNodeDescriptor descriptor = node.getDescriptor();
            return descriptor.getKey();
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public TableCellRenderer getCellRenderer(int row, int column) {
        TreePath treePath = getTree().getPathForRow(row);
        if (treePath == null) return super.getCellRenderer(row, column);

        JsonTreeNode node = (JsonTreeNode) treePath.getLastPathComponent();

        TableCellRenderer renderer = this.columns[column].getRenderer(node);
        return renderer == null ? super.getCellRenderer(row, column) : renderer;
    }

    @SuppressWarnings("unchecked")
    @Override
    public TableCellEditor getCellEditor(int row, int column) {
        TreePath treePath = getTree().getPathForRow(row);
        if (treePath == null) return super.getCellEditor(row, column);

        JsonTreeNode node = (JsonTreeNode) treePath.getLastPathComponent();
        TableCellEditor editor = columns[column].getEditor(node);
        return editor == null ? super.getCellEditor(row, column) : editor;
    }


    private static class ReadOnlyValueColumnInfo extends ColumnInfo<JsonTreeNode, MongoNodeDescriptor> {
        private final TableCellRenderer myRenderer = new MongoValueCellRenderer();

        ReadOnlyValueColumnInfo() {
            super("Value");
        }

        public MongoNodeDescriptor valueOf(JsonTreeNode treeNode) {
            return treeNode.getDescriptor();
        }

        @Override
        public TableCellRenderer getRenderer(JsonTreeNode o) {
            return myRenderer;
        }

        @Override
        public boolean isCellEditable(JsonTreeNode o) {
            return false;
        }
    }

    private static class WritableColumnInfo extends ColumnInfo<JsonTreeNode, Object> {

        private final TableCellRenderer myRenderer = new MongoValueCellRenderer();
        private final TableCellEditor defaultEditor = new MongoValueCellEditor();


        WritableColumnInfo() {
            super("Value");
        }

        @Override
        public TableCellRenderer getRenderer(JsonTreeNode o) {
            return myRenderer;
        }


        @Override
        public boolean isCellEditable(JsonTreeNode treeNode) {
            Object value = treeNode.getDescriptor().getValue();
            if (value instanceof Document || value instanceof List) {
                return false;
            }

            return !(value instanceof ObjectId);
        }

        @Nullable
        @Override
        public TableCellEditor getEditor(final JsonTreeNode treeNode) {
            Object value = treeNode.getDescriptor().getValue();
            if (value instanceof Date) {
                return buildDateCellEditor(treeNode);
            }
            return defaultEditor;
        }

        private static MongoDatePickerCellEditor buildDateCellEditor(final JsonTreeNode treeNode) {
            final MongoDatePickerCellEditor dateEditor = new MongoDatePickerCellEditor();

//  Note from dev: Quite ugly because when clicking on the button to open popup calendar, stopCellEdition is invoked.
//                 From that point, impossible to set the selected data in the node description
            dateEditor.addActionListener(actionEvent -> treeNode.getDescriptor().setValue(dateEditor.getCellEditorValue()));
            return dateEditor;
        }

        public Object valueOf(JsonTreeNode treeNode) {
            return treeNode.getDescriptor();

        }

        @Override
        public void setValue(JsonTreeNode treeNode, Object value) {
            treeNode.getDescriptor().setValue(value);
        }
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/action/edition/AddValueAction.java
public class AddValueAction extends AnAction implements DumbAware {

    private final MongoEditionPanel mongoEditionPanel;

    public AddValueAction(MongoEditionPanel mongoEditionPanel) {
        super("Add a value", "Add a value", AllIcons.General.Add);
        this.mongoEditionPanel = mongoEditionPanel;
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        AddValueDialog dialog = AddValueDialog.createDialog(mongoEditionPanel);
        dialog.show();

        if (!dialog.isOK()) {
            return;
        }

        mongoEditionPanel.addValue(dialog.getValue());
    }

    @Override
    public void update(AnActionEvent event) {
        event.getPresentation().setVisible(mongoEditionPanel.canAddValue());
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/nodedescriptor/MongoKeyValueDescriptor.java
public class MongoKeyValueDescriptor implements MongoNodeDescriptor {

    final String key;
    Object value;

    private final SimpleTextAttributes valueTextAttributes;

    public static MongoKeyValueDescriptor createDescriptor(String key, Object value) { //TODO refactor this
        if (value == null) {
            return new MongoKeyNullValueDescriptor(key);
        }

        if (value instanceof Boolean) {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getBooleanAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Boolean.valueOf((String) value);
                }
            };
        } else if (value instanceof Integer) {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getNumberAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Integer.valueOf((String) value);
                }
            };
        } else if (value instanceof Double) {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getNumberAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Double.valueOf((String) value);
                }
            };
        } else if (value instanceof Long) {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getNumberAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Long.valueOf((String) value);
                }
            };
        } else if (value instanceof String) {
            return new MongoKeyStringValueDescriptor(key, (String) value);
        } else if (value instanceof Date) {
            return new MongoKeyDateValueDescriptor(key, (Date) value);
        } else if (value instanceof ObjectId) {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getObjectIdAttribute());
        } else if (value instanceof Document) {
            return new MongoKeyDocumentValueDescriptor(key, (Document) value);
        } else if (value instanceof Binary) {
            return new MongoKeyBinaryValueDescriptor(key, (Binary) value);
        } else if (value instanceof DBRef) {
            return new MongoKeyRefValueDescriptor(key, value);
        } else if (value instanceof List) {
            return new MongoKeyListValueDescriptor(key, value);
        } else {
            return new MongoKeyValueDescriptor(key, value, StyleAttributesProvider.getStringAttribute());
        }
    }

    private MongoKeyValueDescriptor(String key, Object value, SimpleTextAttributes valueTextAttributes) {
        this.key = key;
        this.value = value;
        this.valueTextAttributes = valueTextAttributes;
    }

    public void renderValue(ColoredTableCellRenderer cellRenderer, boolean isNodeExpanded) {
        if (!isNodeExpanded) {
            cellRenderer.append(getFormattedValue(), valueTextAttributes);
        }
    }

    public void renderNode(ColoredTreeCellRenderer cellRenderer) {
        cellRenderer.append(getFormattedKey(), StyleAttributesProvider.getKeyValueAttribute());
    }

    public String getFormattedKey() {
        return key;
    }

    public String getFormattedValue() {
        return StringUtils.abbreviateInCenter(value.toString(), MAX_LENGTH);
    }

    public String getKey() {
        return key;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    @Override
    public String pretty() {
        return getFormattedValue();
    }

    @Override
    public String toString() {
        return value.toString();
    }

    private static class MongoKeyNullValueDescriptor extends MongoKeyValueDescriptor {

        private MongoKeyNullValueDescriptor(String key) {
            super(key, null, StyleAttributesProvider.getNullAttribute());
        }

        @Override
        public String getFormattedValue() {
            return "null";
        }

        @Override
        public String toString() {
            return getFormattedValue();
        }
    }

    private static class MongoKeyStringValueDescriptor extends MongoKeyValueDescriptor {

        private MongoKeyStringValueDescriptor(String key, String value) {
            super(key, value, StyleAttributesProvider.getStringAttribute());
        }

        @Override
        public String getFormattedValue() {
            return value.toString();
        }

        @Override
        public String toString() {
            return getFormattedValue();
        }
    }

    private static class MongoKeyDateValueDescriptor extends MongoKeyValueDescriptor {

        private static final DateFormat DATE_FORMAT = DateUtils.utcDateTime(Locale.getDefault());

        private MongoKeyDateValueDescriptor(String key, Date value) {
            super(key, value, StyleAttributesProvider.getStringAttribute());
        }

        @Override
        public String getFormattedValue() {
            return getFormattedDate();
        }

        @Override
        public String toString() {
            return getFormattedDate();
        }

        private String getFormattedDate() {
            return DATE_FORMAT.format(value);
        }
    }

    private static class MongoKeyDocumentValueDescriptor extends MongoKeyValueDescriptor {

        MongoKeyDocumentValueDescriptor(String key, Document value) {
            super(key, value, StyleAttributesProvider.getDocumentAttribute());
        }

        @Override
        public String getFormattedValue() {
            Document document = (Document) this.value;
            return StringUtils.abbreviateInCenter(document.toJson(DOCUMENT_CODEC), MAX_LENGTH);
        }

        @Override
        public String toString() {
            return getFormattedDocument();
        }

        private String getFormattedDocument() {
            return ((Document) value).toJson(DOCUMENT_CODEC);
        }

        @Override
        public String pretty() {
            return ((Document) value).toJson(WRITER_SETTINGS);
        }
    }

    private static class MongoKeyBinaryValueDescriptor extends MongoKeyValueDescriptor {

        private MongoKeyBinaryValueDescriptor(String key, Binary value) {
            super(key, value, StyleAttributesProvider.getNullAttribute());
        }

        @Override
        public String getFormattedValue() {
            return "Cannot display value";
        }
    }

    private static class MongoKeyRefValueDescriptor extends MongoKeyValueDescriptor {
        MongoKeyRefValueDescriptor(String key, Object value) {
            super(key, value, StyleAttributesProvider.getDocumentAttribute());
        }

        @Override
        public String getFormattedValue() {
            DBRef dbRef = (DBRef) this.value;
            return StringUtils.abbreviateInCenter(dbRef.toString(), MAX_LENGTH);
        }

        @Override
        public String toString() {
            return value.toString();
        }
    }

    private static class MongoKeyListValueDescriptor extends MongoKeyValueDescriptor {

        private static final String TO_STRING_TEMPLATE = "\"%s\" : %s";

        MongoKeyListValueDescriptor(String key, Object value) {
            super(key, value, StyleAttributesProvider.getDocumentAttribute());
        }

        @Override
        public String getFormattedValue() {
            return StringUtils.abbreviateInCenter(getFormattedList(), MAX_LENGTH);
        }

        @Override
        public String toString() {
            return String.format(TO_STRING_TEMPLATE, key, getFormattedList());
        }

        private String getFormattedList() {
            return MongoUtils.stringifyList((List) value);
        }
    }

}
src/main/java/org/codinjutsu/tools/mongo/view/nodedescriptor/MongoValueDescriptor.java
public class MongoValueDescriptor implements MongoNodeDescriptor {

    private final int index;
    Object value;
    private final SimpleTextAttributes valueTextAttributes;

    public static MongoValueDescriptor createDescriptor(int index, Object value) { //TODO refactor this
        if (value == null) {
            return new MongoNullValueDescriptor(index);
        }

        if (value instanceof String) {
            return new MongoStringValueDescriptor(index, (String) value);
        } else if (value instanceof Boolean) {
            return new MongoValueDescriptor(index, value, StyleAttributesProvider.getBooleanAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Boolean.parseBoolean((String) value);
                }
            };
        } else if (value instanceof Number) {
            return new MongoValueDescriptor(index, value, StyleAttributesProvider.getNumberAttribute()) {
                @Override
                public void setValue(Object value) {
                    this.value = Integer.parseInt((String) value);
                }
            };
        } else if (value instanceof Date) {
            return new MongoDateValueDescriptor(index, (Date) value);
        } else if (value instanceof Document) {
            return new MongoDocumentValueDescriptor(index, value);
        } else if (value instanceof List) {
            return new MongoListValueDescriptor(index, value);
        } else if (value instanceof Binary) {
            return new MongoBinaryDescriptor(index, (Binary) value);
        } else {
            return new MongoValueDescriptor(index, value, StyleAttributesProvider.getStringAttribute());
        }
    }

    private MongoValueDescriptor(int index, Object value, SimpleTextAttributes valueTextAttributes) {
        this.index = index;
        this.value = value;
        this.valueTextAttributes = valueTextAttributes;
    }

    public void renderValue(ColoredTableCellRenderer cellRenderer, boolean isNodeExpanded) {
        if (!isNodeExpanded) {
            cellRenderer.append(getFormattedValue(), valueTextAttributes);
        }
    }

    public void renderNode(ColoredTreeCellRenderer cellRenderer) {
        cellRenderer.append(getKey(), StyleAttributesProvider.getIndexAttribute());
    }

    public String getKey() {
        return String.format("[%s]", index);
    }

    public String getFormattedValue() {
        return String.format("%s", StringUtils.abbreviateInCenter(value.toString(), MAX_LENGTH));
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value.toString();
    }

    @Override
    public String pretty() {
        return getFormattedValue();
    }

    private static class MongoStringValueDescriptor extends MongoValueDescriptor {

        private MongoStringValueDescriptor(int index, String value) {
            super(index, value, StyleAttributesProvider.getStringAttribute());
        }

        @Override
        public String getFormattedValue() {
            return StringUtils.abbreviateInCenter(value.toString(), MAX_LENGTH);
        }
    }

    private static class MongoNullValueDescriptor extends MongoValueDescriptor {

        private MongoNullValueDescriptor(int index) {
            super(index, null, StyleAttributesProvider.getNullAttribute());
        }

        @Override
        public String getFormattedValue() {
            return "null";
        }

        @Override
        public String toString() {
            return "null";
        }
    }

    private static class MongoDateValueDescriptor extends MongoValueDescriptor {

        private static final DateFormat DATE_FORMAT = DateUtils.utcDateTime(Locale.getDefault());

        private MongoDateValueDescriptor(int index, Date value) {
            super(index, value, StyleAttributesProvider.getStringAttribute());
        }

        @Override
        public String getFormattedValue() {
            return getFormattedDate();
        }

        @Override
        public String toString() {
            return String.format("\"%s\"", getFormattedDate());
        }

        private String getFormattedDate() {
            return DATE_FORMAT.format(value);
        }
    }

    private static class MongoDocumentValueDescriptor extends MongoValueDescriptor {


        MongoDocumentValueDescriptor(int index, Object value) {
            super(index, value, StyleAttributesProvider.getDocumentAttribute());

        }

        @Override
        public String getFormattedValue() {
            Document document = (Document) value;
            return String.format("%s", StringUtils.abbreviateInCenter(document.toJson(DOCUMENT_CODEC), MAX_LENGTH));
        }

        @Override
        public String toString() {
            return ((Document) value).toJson(DOCUMENT_CODEC);
        }

        @Override
        public String pretty() {
            return ((Document) value).toJson(WRITER_SETTINGS);
        }
    }

    private static class MongoListValueDescriptor extends MongoValueDescriptor {
        MongoListValueDescriptor(int index, Object value) {
            super(index, value, StyleAttributesProvider.getDocumentAttribute());
        }

        @Override
        public String getFormattedValue() {
            return getFormattedList();
        }

        @Override
        public String toString() {
            return getFormattedList();
        }

        private String getFormattedList() {
            return MongoUtils.stringifyList((List) value);
        }
    }

    private static class MongoBinaryDescriptor extends MongoValueDescriptor {

        private MongoBinaryDescriptor(int index, Binary value) {
            super(index, value, StyleAttributesProvider.getNullAttribute());
        }

        @Override
        public String getFormattedValue() {
            return "Cannot display value";
        }
    }
}


Question: import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.tree.TreeUtil;
import org.apache.commons.lang.StringUtils;
import org.bson.Document;
import org.codinjutsu.tools.mongo.view.JsonTreeTableView;
import org.codinjutsu.tools.mongo.view.MongoPanel;
import org.codinjutsu.tools.mongo.view.MongoResultPanel;
import org.codinjutsu.tools.mongo.view.action.edition.AddKeyAction;
import org.codinjutsu.tools.mongo.view.action.edition.AddValueAction;
import org.codinjutsu.tools.mongo.view.action.edition.DeleteKeyAction;
import org.codinjutsu.tools.mongo.view.model.JsonTreeNode;
import org.codinjutsu.tools.mongo.view.model.JsonTreeUtils;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoKeyValueDescriptor;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoNodeDescriptor;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoValueDescriptor;
import javax.swing.*;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import java.awt.*;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import static org.codinjutsu.tools.mongo.utils.MongoUtils.DOCUMENT_CODEC;
/*
 * Copyright (c) 2018 David Boissier.
 *
 * 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 org.codinjutsu.tools.mongo.view.edition;




public class MongoEditionPanel extends JPanel {
    private final MongoPanel.MongoDocumentOperations mongoDocumentOperations;
    private final MongoResultPanel.ActionCallback actionCallback;

    private JsonTreeTableView editTableView;


    MongoEditionPanel(final MongoPanel.MongoDocumentOperations mongoDocumentOperations, final MongoResultPanel.ActionCallback actionCallback) {
        super(new BorderLayout());
        this.mongoDocumentOperations = mongoDocumentOperations;
        this.actionCallback = actionCallback;
    }

    public boolean save() {
        try {
            Document mongoDocument = buildMongoDocument();
            mongoDocumentOperations.updateMongoDocument(mongoDocument);
            actionCallback.onOperationSuccess("Document saved", "Document " +
                    mongoDocument.toJson(DOCUMENT_CODEC) + " saved.");
            return true;

        } catch (Exception exception) {
            actionCallback.onOperationFailure(exception);
            return false;
        }
    }

    public void updateEditionTree(Document mongoDocument) {
        editTableView = new JsonTreeTableView(JsonTreeUtils.buildJsonTree(mongoDocument), JsonTreeTableView.COLUMNS_FOR_WRITING);
        editTableView.setName("editionTreeTable");

        TreeUtil.expand(editTableView.getTree(), 2);

        add(new JBScrollPane(editTableView), BorderLayout.CENTER);

        buildPopupMenu();
    }

    void buildPopupMenu() {
        DefaultActionGroup actionPopupGroup = new DefaultActionGroup("MongoEditorPopupGroup", true);
        if (ApplicationManager.getApplication() != null) {
Answer:             actionPopupGroup.add(new AddKeyAction(this));