src/main/java/org/codinjutsu/tools/mongo/view/editor/MongoObjectFile.java
public class MongoObjectFile extends VirtualFile {

    private final long myModStamp;
    private final ServerConfiguration configuration;
    private final Navigation navigation;
    private final Project project;
    private final String path;

    public MongoObjectFile(Project project, ServerConfiguration configuration, Navigation navigation) {
        this.project = project;
        this.configuration = configuration;
        this.navigation = navigation;
        this.myModStamp = LocalTimeCounter.currentTime();
        this.path = getName();
    }

    @NotNull
    @Override
    public String getName() {
        return String.format("%s/%s", configuration.getLabel(), navigation.getCurrentWayPoint().getLabel());
    }

    @NotNull
    public FileType getFileType() {
        return MongoFakeFileType.INSTANCE;
    }

    @NotNull
    @Override
    public VirtualFileSystem getFileSystem() {
        return MongoFileSystem.getInstance();
    }

    @NotNull
    @Override
    public String getPath() {
        return path;
    }

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

    @Override
    public boolean isWritable() {
        return false;
    }

    @Override
    public boolean isDirectory() {
        return false;
    }

    public ServerConfiguration getConfiguration() {
        return configuration;
    }

    public Navigation getNavigation() {
        return navigation;
    }

    public Project getProject() {
        return project;
    }

    //    Unused methods
    @Override
    public VirtualFile getParent() {
        return null;
    }

    @Override
    public VirtualFile[] getChildren() {
        return new VirtualFile[0];
    }

    @NotNull
    @Override
    public OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) {
        throw new UnsupportedOperationException("MongoResultFile is read-only");
    }

    @Override
    public long getModificationStamp() {
        return myModStamp;
    }

    @NotNull
    @Override
    public byte[] contentsToByteArray() {
        return new byte[0];
    }

    @Override
    public long getTimeStamp() {
        return 0;
    }

    @Override
    public long getLength() {
        return 0;
    }

    @Override
    public void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable) {

    }

    @Override
    public InputStream getInputStream() {
        return null;
    }
}
src/main/java/org/codinjutsu/tools/mongo/utils/GuiUtils.java
public class GuiUtils {

    private static final String ICON_FOLDER = "/icons/";

    public static Icon loadIcon(String iconFilename) {
        return IconLoader.findIcon(ICON_FOLDER + iconFilename);
    }

    public static Icon loadIcon(String iconFilename, String darkIconFilename) {
        String iconPath = ICON_FOLDER;
        if (isUnderDarcula()) {
            iconPath += darkIconFilename;
        } else {
            iconPath += iconFilename;
        }
        return IconLoader.findIcon(iconPath);
    }

    public static void installActionGroupInToolBar(DefaultActionGroup actionGroup, JPanel toolBarPanel, ActionManager actionManager, String toolbarName, boolean horizontal) {
        if (actionManager == null) {
            return;
        }

        JComponent actionToolbar = ActionManager.getInstance().createActionToolbar(toolbarName, actionGroup, horizontal).getComponent();
        toolBarPanel.add(actionToolbar, BorderLayout.CENTER);
    }

    public static Dimension enlargeWidth(Dimension preferredSize, double factor) {
        int enlargedWidth = new Double(preferredSize.width * factor).intValue();
        return new Dimension(enlargedWidth, preferredSize.height);
    }

    public static void showNotification(final JComponent component, final MessageType info, final String message, final Balloon.Position position) {
        UIUtil.invokeLaterIfNeeded(() -> JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(message))
                .setFillColor(info.getPopupBackground())
                .createBalloon()
                .show(new RelativePoint(component, new Point(0, 0)), position));
    }

    private static boolean isUnderDarcula() {
        return UIManager.getLookAndFeel().getName().contains("Darcula");
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/editor/MongoFileSystem.java
public class MongoFileSystem extends VirtualFileSystem implements NonPhysicalFileSystem {

    private static final String PROTOCOL = "mongo";

    public static MongoFileSystem getInstance() {
        return (MongoFileSystem) VirtualFileManager.getInstance().getFileSystem(PROTOCOL);
    }

    @NotNull
    @Override
    public String getProtocol() {
        return PROTOCOL;
    }

    public void openEditor(final MongoObjectFile mongoObjectFile) {
        FileEditorManager fileEditorManager = FileEditorManager.getInstance(mongoObjectFile.getProject());
        fileEditorManager.openFile(mongoObjectFile, true);
        fileEditorManager.setSelectedEditor(mongoObjectFile, "MongoData");
    }

//    Unused methods

    @Nullable
    @Override
    public VirtualFile findFileByPath(@NotNull @NonNls String path) {
        return null;
    }

    @Override
    public void refresh(boolean asynchronous) {

    }

    @Nullable
    @Override
    public VirtualFile refreshAndFindFileByPath(@NotNull String path) {
        return null;
    }

    @Override
    public void addVirtualFileListener(@NotNull VirtualFileListener listener) {

    }

    @Override
    public void removeVirtualFileListener(@NotNull VirtualFileListener listener) {

    }

    @Override
    protected void deleteFile(Object requestor, @NotNull VirtualFile vFile) {

    }

    @Override
    protected void moveFile(Object requestor, @NotNull VirtualFile vFile, @NotNull VirtualFile newParent) {

    }

    @Override
    protected void renameFile(Object requestor, @NotNull VirtualFile vFile, @NotNull String newName) {

    }

    @NotNull
    @Override
    protected VirtualFile createChildFile(Object requestor, @NotNull VirtualFile vDir, @NotNull String fileName) {
        return NullVirtualFile.INSTANCE;
    }

    @NotNull
    @Override
    protected VirtualFile createChildDirectory(Object requestor, @NotNull VirtualFile vDir, @NotNull String dirName) {
        return NullVirtualFile.INSTANCE;
    }

    @NotNull
    @Override
    protected VirtualFile copyFile(Object requestor, @NotNull VirtualFile virtualFile, @NotNull VirtualFile newParent, @NotNull String copyName) {
        return NullVirtualFile.INSTANCE;
    }

    @Override
    public boolean isReadOnly() {
        return true;
    }
}
src/main/java/org/codinjutsu/tools/mongo/utils/GuiUtils.java
public static void showNotification(final JComponent component, final MessageType info, final String message, final Balloon.Position position) {
    UIUtil.invokeLaterIfNeeded(() -> JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(message))
            .setFillColor(info.getPopupBackground())
            .createBalloon()
            .show(new RelativePoint(component, new Point(0, 0)), position));
}
src/main/java/org/codinjutsu/tools/mongo/logic/ConfigurationException.java
public class ConfigurationException extends RuntimeException {
    public ConfigurationException(String message) {
        super(message);
    }

    public ConfigurationException(Exception ex) {
        super(ex);
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/model/MongoTreeBuilder.java
public class MongoTreeBuilder extends AbstractTreeBuilder {


    private static final Icon MONGO_SERVER = GuiUtils.loadIcon("mongo_logo.png");
    private static final Icon MONGO_DATABASE = GuiUtils.loadIcon("database.png");
    private static final Icon MONGO_COLLECTION = GuiUtils.loadIcon("folder.png");
    private static final Icon MONGO_SERVER_ERROR = GuiUtils.loadIcon("mongo_warning.png");

    private final Map<MongoServer, ServerConfiguration> serverConfigurations = new HashMap<>();

    private static final RootDescriptor ROOT_DESCRIPTOR = new RootDescriptor();

    public MongoTreeBuilder(@NotNull Tree tree) {
        init(tree, new DefaultTreeModel(new DefaultMutableTreeNode()), new MyTreeStructure(), (descriptorLeft, descriptorRight) -> {
            if (descriptorLeft instanceof ServerDescriptor && descriptorRight instanceof ServerDescriptor) {
                MongoServer mongoServerLeft = (MongoServer) descriptorLeft.getElement();
                MongoServer mongoServerRight = (MongoServer) descriptorRight.getElement();

                return mongoServerLeft.getLabel().compareTo(mongoServerRight.getLabel());
            } else if (descriptorLeft instanceof DatabaseDescriptor && descriptorRight instanceof DatabaseDescriptor) {
                String databaseDescriptorLeft = ((DatabaseDescriptor) descriptorLeft).getElement().getName();
                String databaseDescriptorRight = ((DatabaseDescriptor) descriptorRight).getElement().getName();

                return databaseDescriptorLeft.compareTo(databaseDescriptorRight);
            } else if (descriptorLeft instanceof CollectionDescriptor && descriptorRight instanceof CollectionDescriptor) {
                String collectionDescriptorLeft = ((CollectionDescriptor) descriptorLeft).getElement().getName();
                String collectionDescriptorRight = ((CollectionDescriptor) descriptorRight).getElement().getName();

                return collectionDescriptorLeft.compareTo(collectionDescriptorRight);
            }
            return 0;
        }, true);
        initRootNode();
    }

    public MongoServer addConfiguration(@NotNull ServerConfiguration serverConfiguration) {
        MongoServer mongoServer = new MongoServer(serverConfiguration);
        serverConfigurations.put(mongoServer, serverConfiguration);
        queueUpdateFrom(RootDescriptor.ROOT, true);
        return mongoServer;
    }

    public void removeConfiguration(MongoServer mongoServer) {
        serverConfigurations.remove(mongoServer);
        queueUpdateFrom(RootDescriptor.ROOT, true);
    }

    public void removeDatabase(MongoDatabase mongoDatabase) {
        MongoServer parentServer = mongoDatabase.getParentServer();
        parentServer.getDatabases().remove(mongoDatabase);
        queueUpdateFrom(parentServer, true);
    }

    public void removeCollection(MongoCollection mongoCollection) {
        MongoDatabase parentDatabase = mongoCollection.getParentDatabase();
        parentDatabase.getCollections().remove(mongoCollection);
        queueUpdateFrom(parentDatabase, true);
    }

    public void expandAll() {
        Arrays.stream(getTreeStructure().getChildElements(RootDescriptor.ROOT))
                .forEach((server) -> this.expand(server, null));
    }

    public void collapseAll() {
        Arrays.stream(getTreeStructure().getChildElements(RootDescriptor.ROOT))
                .forEach((server) -> this.collapseChildren(server, null));
    }

    private class MyTreeStructure extends AbstractTreeStructure {
        @Override
        public Object getRootElement() {
            return RootDescriptor.ROOT;
        }

        @Override
        public Object[] getChildElements(Object element) {
            if (element == RootDescriptor.ROOT) {
                return ArrayUtil.toObjectArray(serverConfigurations.keySet());
            } else if (element instanceof MongoServer) {
                return ArrayUtil.toObjectArray(((MongoServer) element).getDatabases());
            } else if (element instanceof MongoDatabase) {
                return ArrayUtil.toObjectArray(((MongoDatabase) element).getCollections());
            }
            return ArrayUtil.EMPTY_OBJECT_ARRAY;
        }

        @Nullable
        @Override
        public Object getParentElement(Object element) {
//            if (element == RootDescriptor.ROOT) {
//                return null;
//            } else if (element instanceof MongoServer) {
//                return RootDescriptor.ROOT;
//            } else if (element instanceof MongoDatabase) {
//                return ((MongoDatabase) element).getParentServer();
//            } else if (element instanceof MongoCollection) {
//                return ((MongoCollection) element).getParentDatabase();
//            } else {
            return null;
//            }
        }

        @NotNull
        @Override
        public NodeDescriptor createDescriptor(Object element, NodeDescriptor parentDescriptor) {
            if (element == RootDescriptor.ROOT) {
                return ROOT_DESCRIPTOR;
            } else if (element instanceof MongoServer) {
                return new ServerDescriptor(parentDescriptor, (MongoServer) element);
            } else if (element instanceof MongoDatabase) {
                return new DatabaseDescriptor(parentDescriptor, (MongoDatabase) element);
            } else if (element instanceof MongoCollection) {
                return new CollectionDescriptor(parentDescriptor, (MongoCollection) element);
            }

            throw new IllegalStateException("Element not supported : " + element.getClass().getName());
        }


        @Override
        public void commit() {
            // do nothing
        }

        @Override
        public boolean hasSomethingToCommit() {
            return false;
        }
    }

    private static abstract class MyNodeDescriptor<T> extends PresentableNodeDescriptor<T> {
        private final T myObject;

        MyNodeDescriptor(@Nullable NodeDescriptor parentDescriptor, @NotNull T object) {
            super(null, parentDescriptor);
            myObject = object;
        }

        @Override
        public T getElement() {
            return myObject;
        }
    }

    private static class RootDescriptor extends MyNodeDescriptor<Object> {
        static final Object ROOT = new Object();

        private RootDescriptor() {
            super(null, ROOT);
        }

        @Override
        protected void update(PresentationData presentation) {
            presentation.addText("<root>", SimpleTextAttributes.REGULAR_ATTRIBUTES);
        }
    }

    static class ServerDescriptor extends MyNodeDescriptor<MongoServer> {
        ServerDescriptor(NodeDescriptor parentDescriptor, MongoServer server) {
            super(parentDescriptor, server);
        }

        @Override
        protected void update(PresentationData presentation) {
            MongoServer mongoServer = getElement();
            if (MongoServer.Status.ERROR.equals(mongoServer.getStatus())) {
                presentation.setIcon(MONGO_SERVER_ERROR);
            } else {
                presentation.setIcon(MONGO_SERVER);
            }

            String label = mongoServer.getLabel();
            if (MongoServer.Status.LOADING.equals(mongoServer.getStatus())) {
                label += " (loading)";
            }
            presentation.addText(label, SimpleTextAttributes.REGULAR_ATTRIBUTES);
            presentation.setTooltip(mongoServer.getConfiguration().getUrlsInSingleString());
        }
    }

    static class DatabaseDescriptor extends MyNodeDescriptor<MongoDatabase> {
        DatabaseDescriptor(NodeDescriptor parentDescriptor, MongoDatabase database) {
            super(parentDescriptor, database);
        }

        @Override
        protected void update(PresentationData presentation) {
            presentation.setIcon(MONGO_DATABASE);
            presentation.addText(getElement().getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
        }
    }

    static class CollectionDescriptor extends MyNodeDescriptor<MongoCollection> {
        CollectionDescriptor(NodeDescriptor parentDescriptor, MongoCollection collection) {
            super(parentDescriptor, collection);
        }

        @Override
        protected void update(PresentationData presentation) {
            presentation.setIcon(MONGO_COLLECTION);
            presentation.addText(getElement().getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
        }
    }
}
src/main/java/org/codinjutsu/tools/mongo/view/model/navigation/Navigation.java
public class Navigation {

    private final List<WayPoint> wayPoints = new ArrayList<>();

    private WayPoint currentWayPoint = null;

    public void addNewWayPoint(MongoCollection collection, MongoQueryOptions mongoQueryOptions) {
        currentWayPoint = new WayPoint(collection, mongoQueryOptions);
        wayPoints.add(currentWayPoint);
    }

    public WayPoint getCurrentWayPoint() {
        return currentWayPoint;
    }

    public List<WayPoint> getWayPoints() {
        return wayPoints;
    }

    public void moveBackward() {
        if (currentWayPoint != null) {
            int currentWayPointIndex = wayPoints.indexOf(currentWayPoint);
            if (currentWayPointIndex > 0) {
                currentWayPoint = wayPoints.get(currentWayPointIndex - 1);
                wayPoints.remove(currentWayPointIndex);
            }
        }
    }

    public static class WayPoint {
        private final MongoCollection collection;
        private MongoQueryOptions queryOptions;

        WayPoint(MongoCollection collection, MongoQueryOptions queryOptions) {
            this.collection = collection;
            this.queryOptions = queryOptions;
        }

        public String getLabel() {
            return collection.getParentDatabase().getName() + "/" + collection.getName();
        }

        public MongoCollection getCollection() {
            return collection;
        }

        public void setQueryOptions(MongoQueryOptions queryOptions) {
            this.queryOptions = queryOptions;
        }

        public MongoQueryOptions getQueryOptions() {
            return queryOptions;
        }
    }
}
src/main/java/org/codinjutsu/tools/mongo/logic/MongoManager.java
public class MongoManager {

    private static final String DEFAULT_TUNNEL_LOCAL_HOST = "localhost";
    private static final int DEFAULT_TUNNEL_LOCAL_PORT = 9080;

    private static final Document EMPTY_DOC = new Document();

    private final List<MongoServer> mongoServers = new LinkedList<>();

    public static MongoManager getInstance(Project project) {
        return ServiceManager.getService(project, MongoManager.class);
    }

    public void connect(final ServerConfiguration configuration) {
        Task task = mongoClient -> {
            String userDatabase = configuration.getUserDatabase();
            String databaseName = StringUtils.isNotEmpty(userDatabase) ? userDatabase : "test";

            mongoClient.getDatabase(databaseName).listCollectionNames().first();
        };


        executeTask(configuration, task);
    }

    public void cleanUpServers() {
        mongoServers.clear();
    }

    public void registerServer(MongoServer mongoServer) {
        mongoServers.add(mongoServer);
    }

    public List<MongoServer> getServers() {
        return mongoServers;
    }

    public List<MongoDatabase> loadDatabases(MongoServer mongoServer, ServerConfiguration configuration) {
        final List<MongoDatabase> mongoDatabases = new LinkedList<>();
        TaskWithReturnedObject<List<MongoDatabase>> perform = mongoClient -> {
            String userDatabase = configuration.getUserDatabase();

            if (StringUtils.isNotEmpty(userDatabase)) {
                com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(userDatabase);
                MongoDatabase mongoDatabase = new MongoDatabase(database.getName(), mongoServer);
                mongoDatabases.add(createMongoDatabaseAndItsCollections(mongoDatabase, database));
            } else {
                MongoIterable<String> databaseNames = mongoClient.listDatabaseNames();
                for (String databaseName : databaseNames) {
                    com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(databaseName);
                    MongoDatabase mongoDatabase = new MongoDatabase(database.getName(), mongoServer);
                    mongoDatabases.add(createMongoDatabaseAndItsCollections(mongoDatabase, database));
                }
            }
            return mongoDatabases;
        };

        return executeTask(configuration, perform);
    }

    private MongoDatabase createMongoDatabaseAndItsCollections(MongoDatabase mongoDatabase, com.mongodb.client.MongoDatabase database) {
        MongoIterable<String> collectionNames = database.listCollectionNames();
        for (String collectionName : collectionNames) {
            mongoDatabase.addCollection(new MongoCollection(collectionName, mongoDatabase));
        }
        return mongoDatabase;
    }

    public MongoCollectionResult findMongoDocuments(ServerConfiguration configuration, final MongoCollection mongoCollection, final MongoQueryOptions mongoQueryOptions) {
        TaskWithReturnedObject<MongoCollectionResult> task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();

            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());

            MongoCollectionResult mongoCollectionResult = new MongoCollectionResult(mongoCollection.getName());
            if (mongoQueryOptions.isAggregate()) {
                return aggregate(mongoQueryOptions, mongoCollectionResult, collection);
            }

            return find(mongoQueryOptions, mongoCollectionResult, collection);
        };

        return executeTask(configuration, task);
    }

    public Document findMongoDocument(ServerConfiguration configuration, final MongoCollection mongoCollection, final Object _id) {
        TaskWithReturnedObject<Document> task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();
            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());
            FindIterable<Document> foundDocuments = collection.find(new BasicDBObject("_id", _id));

            return foundDocuments.first();
        };

        return executeTask(configuration, task);
    }

    public void update(ServerConfiguration configuration, final MongoCollection mongoCollection, final Document mongoDocument) {
        Task task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();
            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());


            if (!mongoDocument.containsKey("_id")) {
                collection.insertOne(mongoDocument);
            } else {
                collection.findOneAndReplace(
                        new Document("_id", mongoDocument.get("_id")),
                        mongoDocument,
                        new FindOneAndReplaceOptions().upsert(true));
            }
        };

        executeTask(configuration, task);
    }

    public void delete(ServerConfiguration configuration, final MongoCollection mongoCollection, final Object _id) {
        Task task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();

            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());

            collection.deleteOne(new Document("_id", _id));
        };

        executeTask(configuration, task);
    }

    public void removeCollection(ServerConfiguration configuration, final MongoCollection mongoCollection) {
        Task task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();

            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());

            collection.drop();
        };
        executeTask(configuration, task);
    }

    public void removeDatabase(ServerConfiguration configuration, final MongoDatabase selectedDatabase) {
        Task task = mongoClient -> mongoClient.dropDatabase(selectedDatabase.getName());

        executeTask(configuration, task);
    }

    public List<StatInfoEntry> getCollStats(ServerConfiguration configuration, final MongoCollection mongoCollection) {
        TaskWithReturnedObject<List<StatInfoEntry>> task = mongoClient -> {
            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoCollection.getParentDatabase().getName());

            return adaptToCollectionInfos(database.runCommand(new Document("collStats", mongoCollection.getName())));
        };

        return executeTask(configuration, task);
    }

    public List<StatInfoEntry> getDbStats(ServerConfiguration configuration, final MongoDatabase mongoDatabase) {
        TaskWithReturnedObject<List<StatInfoEntry>> task = mongoClient -> {
            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());

            return adaptToDatabaseInfos(database.runCommand(new Document("dbStats", 1)));
        };

        return executeTask(configuration, task);
    }

    private List<StatInfoEntry> adaptToCollectionInfos(Document collectionStatsDocument) {
        List<StatInfoEntry> collectionInfoEntries = new ArrayList<>();

        for (CollectionStatInfoEnum statInfo : CollectionStatInfoEnum.values()) {
            collectionInfoEntries.add(statInfo.getDataBuilder().build(
                    statInfo.name(),
                    statInfo.getDataExtractor().extract(statInfo.name(), collectionStatsDocument)));

            if (CollectionStatInfoEnum.indexSizes.equals(statInfo)) {
                collectionInfoEntries.addAll(createIndexSizeEntryList(
                        (Document) statInfo.getDataExtractor().extract(statInfo.name(), collectionStatsDocument)));
            }
        }

        return collectionInfoEntries;
    }

    private List<StatInfoEntry> createIndexSizeEntryList(Document indexSizesDocument) {
        LinkedList<StatInfoEntry> list = new LinkedList<>();
        for (Map.Entry<String, Object> keyValueEntry : indexSizesDocument.entrySet()) {
            list.add(new StatInfoEntry.ByteSizeStatInfoEntry(keyValueEntry.getKey(), new Long(keyValueEntry.getValue().toString()))); //TODO need refactor
        }
        return list;
    }

    private List<StatInfoEntry> adaptToDatabaseInfos(Document databaseStatsDocument) {
        List<StatInfoEntry> collectionInfoEntries = new ArrayList<>();

        for (DatabaseStatInfoEnum statInfo : DatabaseStatInfoEnum.values()) {
            collectionInfoEntries.add(statInfo.getDataBuilder().build(
                    statInfo.name(),
                    statInfo.getDataExtractor().extract(statInfo.name(), databaseStatsDocument)));
        }

        return collectionInfoEntries;
    }

    private <T> T executeTask(ServerConfiguration configuration, TaskWithReturnedObject<T> perform) {
        if (SshTunnelingConfiguration.isEmpty(configuration.getSshTunnelingConfiguration())) {
            return execute(configuration, perform);
        } else {
            try (SshConnection ignored = SshConnection.create(configuration)) {
                return execute(configuration, perform);
            }
        }
    }

    private <T> T execute(ServerConfiguration configuration, TaskWithReturnedObject<T> perform) {
        try (MongoClient mongo = createMongoClient(configuration)) {
            return perform.run(mongo);
        } catch (MongoException mongoEx) {
            throw new ConfigurationException(mongoEx);
        }
    }

    private void executeTask(ServerConfiguration configuration, Task perform) {
        if (SshTunnelingConfiguration.isEmpty(configuration.getSshTunnelingConfiguration())) {
            execute(configuration, perform);
        } else {
            try (SshConnection ignored = SshConnection.create(configuration)) {
                execute(configuration, perform);
            }
        }
    }

    private void execute(ServerConfiguration configuration, Task task) {
        try (MongoClient mongoClient = createMongoClient(configuration)) {
            task.run(mongoClient);
        } catch (MongoException ex) {
            throw new ConfigurationException(ex);
        }
    }

    private MongoCollectionResult aggregate(MongoQueryOptions mongoQueryOptions, MongoCollectionResult mongoCollectionResult, com.mongodb.client.MongoCollection<Document> collection) {
        AggregateIterable aggregate = collection.aggregate(mongoQueryOptions.getOperations());
        int index = 0;
        Iterator iterator = aggregate.iterator();
        while (iterator.hasNext() && index < mongoQueryOptions.getResultLimit()) {
            mongoCollectionResult.add((Document) iterator.next());
        }
        return mongoCollectionResult;
    }

    private MongoCollectionResult find(MongoQueryOptions mongoQueryOptions, final MongoCollectionResult mongoCollectionResult, com.mongodb.client.MongoCollection<Document> collection) {
        Document filter = mongoQueryOptions.getFilter();
        Document projection = mongoQueryOptions.getProjection();
        Document sort = mongoQueryOptions.getSort();

        FindIterable<Document> cursor = collection.find(filter);
        if (!MongoQueryOptions.EMPTY_DOCUMENT.equals(projection)) {
            cursor.projection(projection);
        }

        if (!MongoQueryOptions.EMPTY_DOCUMENT.equals(sort)) {
            cursor.sort(sort);
        }

        int resultLimit = mongoQueryOptions.getResultLimit();
        if (resultLimit > 0) {
            cursor.limit(resultLimit);
        }

        cursor.forEach((Block<Document>) mongoCollectionResult::add);

        return mongoCollectionResult;
    }

    private MongoClient createMongoClient(ServerConfiguration configuration) {
        List<String> serverUrls = configuration.getServerUrls();
        if (serverUrls.isEmpty()) {
            throw new ConfigurationException("server host is not set");
        }

        List<ServerAddress> serverAddresses = new LinkedList<>();
        if (SshTunnelingConfiguration.isEmpty(configuration.getSshTunnelingConfiguration())) {
            for (String serverUrl : serverUrls) {
                ServerConfiguration.HostAndPort hostAndPort = ServerConfiguration.extractHostAndPort(serverUrl);
                serverAddresses.add(new ServerAddress(hostAndPort.host, hostAndPort.port));
            }
        } else {
            serverAddresses.add(new ServerAddress(DEFAULT_TUNNEL_LOCAL_HOST, DEFAULT_TUNNEL_LOCAL_PORT));
        }

        MongoClientOptions options = MongoClientOptions.builder()
                .sslEnabled(configuration.isSslConnection())
                .readPreference(configuration.getReadPreference())
                .codecRegistry(MongoClient.getDefaultCodecRegistry())
                .build();

        if (StringUtils.isEmpty(configuration.getUsername())) {
            return new MongoClient(serverAddresses, options);
        } else {
            MongoCredential credential = getMongoCredential(configuration);
            return new MongoClient(serverAddresses, credential, options);
        }
    }


    private MongoCredential getMongoCredential(ServerConfiguration configuration) {
        AuthenticationMechanism authenticationMechanism = configuration.getAuthenticationMechanism();
        if (authenticationMechanism == null) {
            return MongoCredential.createPlainCredential(configuration.getUsername(),
                    getAuthenticationDatabase(configuration),
                    configuration.getPassword().toCharArray());
        } else {
            if (AuthenticationMechanism.MONGODB_CR.equals(authenticationMechanism)) {
                return MongoCredential.createMongoCRCredential(configuration.getUsername(),
                        getAuthenticationDatabase(configuration),
                        configuration.getPassword().toCharArray());
            } else if (AuthenticationMechanism.SCRAM_SHA_1.equals(authenticationMechanism)) {
                return MongoCredential.createScramSha1Credential(configuration.getUsername(),
                        getAuthenticationDatabase(configuration),
                        configuration.getPassword().toCharArray());
            }
        }

        throw new IllegalArgumentException("Unsupported authentication macanism: " + authenticationMechanism);
    }

    private static String getAuthenticationDatabase(ServerConfiguration configuration) {
        String authenticationDatabase = configuration.getAuthenticationDatabase();
        return StringUtils.isEmpty(authenticationDatabase) ? "admin" : authenticationDatabase;
    }

    public void importData(ServerConfiguration configuration, MongoCollection mongoCollection, List<Document> mongoDocuments, boolean replaceAllDocuments) {
        Task task = mongoClient -> {
            MongoDatabase mongoDatabase = mongoCollection.getParentDatabase();
            com.mongodb.client.MongoDatabase database = mongoClient.getDatabase(mongoDatabase.getName());
            com.mongodb.client.MongoCollection<Document> collection = database.getCollection(mongoCollection.getName());

            if (replaceAllDocuments) {
                collection.deleteMany(EMPTY_DOC);
            }

            collection.insertMany(mongoDocuments);
        };

        executeTask(configuration, task);
    }

    private interface Task {

        void run(MongoClient mongoClient);
    }


    private interface TaskWithReturnedObject<T> {

        T run(MongoClient mongoClient);
    }
}
src/main/java/org/codinjutsu/tools/mongo/logic/Notifier.java
public class Notifier {

    private final NotificationGroup MONGO_GROUP = NotificationGroup.logOnlyGroup("Mongo");

    private final Project project;

    public static Notifier getInstance(Project project) {
        return ServiceManager.getService(project, Notifier.class);
    }

    private Notifier(Project project) {
        this.project = project;
    }

    public void notifyInfo(String message) {
        notify(message, NotificationType.INFORMATION);
    }

    public void notifyError(String message) {
        notify(message, NotificationType.ERROR);
    }

    private void notify(String message, NotificationType notificationType) {
        MONGO_GROUP.createNotification("[MongoPlugin] " + message, notificationType)
                .notify(project);
    }
}
src/main/java/org/codinjutsu/tools/mongo/ServerConfiguration.java
public class ServerConfiguration implements Cloneable {

    public static final int DEFAULT_ROW_LIMIT = 300;

    private static final String DEFAULT_URL = "localhost";
    private static final int DEFAULT_PORT = 27017;


    private String label;

    private List<String> serverUrls = new LinkedList<>();

    private boolean sslConnection;

    private ReadPreference readPreference = ReadPreference.primary();
    private String username;
    private String password;
    private String authenticationDatabase;

    private AuthenticationMechanism authenticationMechanism = null;
    private String userDatabase;

    private List<String> collectionsToIgnore = new LinkedList<>();
    private String shellArgumentsLine;
    private String shellWorkingDir;
    private Integer defaultRowLimit = DEFAULT_ROW_LIMIT;

    private SshTunnelingConfiguration sshTunnelingConfiguration;

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public List<String> getServerUrls() {
        return serverUrls;
    }

    public void setServerUrls(List<String> serverUrls) {
        this.serverUrls = serverUrls;
    }

    public boolean isSslConnection() {
        return sslConnection;
    }

    public void setSslConnection(boolean sslConnection) {
        this.sslConnection = sslConnection;
    }

    public ReadPreference getReadPreference() {
        return readPreference;
    }

    public void setReadPreference(ReadPreference readPreference) {
        this.readPreference = readPreference;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getAuthenticationDatabase() {
        return authenticationDatabase;
    }

    public void setAuthenticationDatabase(String authenticationDatabase) {
        this.authenticationDatabase = authenticationDatabase;
    }

    public void setUserDatabase(String userDatabase) {
        this.userDatabase = userDatabase;
    }

    public String getUserDatabase() {
        return userDatabase;
    }

    public void setCollectionsToIgnore(List<String> collectionsToIgnore) {
        this.collectionsToIgnore = collectionsToIgnore;
    }

    public List<String> getCollectionsToIgnore() {
        return collectionsToIgnore;
    }

    public void setAuthenticationMechanism(AuthenticationMechanism authenticationMechanism) {
        this.authenticationMechanism = authenticationMechanism;
    }

    public AuthenticationMechanism getAuthenticationMechanism() {
        return authenticationMechanism;
    }

    public String getShellArgumentsLine() {
        return shellArgumentsLine;
    }

    public void setShellArgumentsLine(String shellArgumentsLine) {
        this.shellArgumentsLine = shellArgumentsLine;
    }

    public String getShellWorkingDir() {
        return shellWorkingDir;
    }

    public void setShellWorkingDir(String shellWorkingDir) {
        this.shellWorkingDir = shellWorkingDir;
    }

    public Integer getDefaultRowLimit() {
        return defaultRowLimit;
    }

    public void setDefaultRowLimit(Integer defaultRowLimit) {
        this.defaultRowLimit = defaultRowLimit;
    }

    public void setSshTunnelingConfiguration(SshTunnelingConfiguration sshTunnelingConfiguration) {
        this.sshTunnelingConfiguration = sshTunnelingConfiguration;
    }

    public SshTunnelingConfiguration getSshTunnelingConfiguration() {
        return sshTunnelingConfiguration;
    }

    public static ServerConfiguration byDefault() {
        ServerConfiguration serverConfiguration = new ServerConfiguration();
        serverConfiguration.setServerUrls(singletonList(String.format("%s:%s", DEFAULT_URL, DEFAULT_PORT)));
        serverConfiguration.setSshTunnelingConfiguration(SshTunnelingConfiguration.EMPTY);
        return serverConfiguration;
    }

    public String getUrlsInSingleString() {
        return StringUtils.join(serverUrls, ",");
    }

    public boolean isSingleServer() {
        return serverUrls.size() == 1;
    }

    public static HostAndPort extractHostAndPort(@NotNull String serverUrl) {
        String[] hostAndPort = StringUtils.split(serverUrl, ":");
        return new HostAndPort(hostAndPort[0], Integer.parseInt(hostAndPort[1]));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ServerConfiguration)) return false;
        ServerConfiguration that = (ServerConfiguration) o;
        return sslConnection == that.sslConnection &&
                Objects.equals(label, that.label) &&
                Objects.equals(serverUrls, that.serverUrls) &&
                Objects.equals(readPreference, that.readPreference) &&
                Objects.equals(username, that.username) &&
                Objects.equals(password, that.password) &&
                Objects.equals(authenticationDatabase, that.authenticationDatabase) &&
                authenticationMechanism == that.authenticationMechanism &&
                Objects.equals(userDatabase, that.userDatabase) &&
                Objects.equals(collectionsToIgnore, that.collectionsToIgnore) &&
                Objects.equals(shellArgumentsLine, that.shellArgumentsLine) &&
                Objects.equals(shellWorkingDir, that.shellWorkingDir) &&
                Objects.equals(defaultRowLimit, that.defaultRowLimit) &&
                Objects.equals(sshTunnelingConfiguration, that.sshTunnelingConfiguration);
    }

    @Override
    public int hashCode() {

        return Objects.hash(label, serverUrls, sslConnection, readPreference, username, password, authenticationDatabase, authenticationMechanism, userDatabase, collectionsToIgnore, shellArgumentsLine, shellWorkingDir, defaultRowLimit, sshTunnelingConfiguration);
    }

    public ServerConfiguration clone() {
        try {
            return (ServerConfiguration) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    public static class HostAndPort {

        public final String host;
        public final int port;

        public HostAndPort(String host, int port) {
            this.host = host;
            this.port = port;
        }
    }
}
src/main/java/org/codinjutsu/tools/mongo/MongoConfiguration.java
@State(
        name = "MongoConfiguration",
        storages = {
                @Storage(file = "$PROJECT_CONFIG_DIR$/mongoSettings.xml")
        }
)
public class MongoConfiguration implements PersistentStateComponent<MongoConfiguration> {

    private List<ServerConfiguration> serverConfigurations = new LinkedList<>();
    private String shellPath;

    public static MongoConfiguration getInstance(Project project) {
        return ServiceManager.getService(project, MongoConfiguration.class);
    }


    public MongoConfiguration getState() {
        return this;
    }

    public void loadState(MongoConfiguration mongoConfiguration) {
        XmlSerializerUtil.copyBean(mongoConfiguration, this);
    }

    public void setServerConfigurations(List<ServerConfiguration> serverConfigurations) {
        this.serverConfigurations = serverConfigurations;
    }

    public List<ServerConfiguration> getServerConfigurations() {
        return serverConfigurations;
    }

    public String getShellPath() {
        return shellPath;
    }

    public void setShellPath(String shellPath) {
        this.shellPath = shellPath;
    }

    public void addServerConfiguration(ServerConfiguration serverConfiguration) {
        serverConfigurations.add(serverConfiguration);
    }

    public void updateServerConfiguration(ServerConfiguration previousConfiguration, ServerConfiguration updatedConfiguration) {
        if (previousConfiguration.equals(updatedConfiguration)) {
            return;
        }

        int index = getServerConfigurationIndex(previousConfiguration);
        serverConfigurations.set(index, updatedConfiguration);
    }

    private int getServerConfigurationIndex(ServerConfiguration configuration) {
        int index = 0;
        for (ServerConfiguration serverConfiguration : serverConfigurations) {
            if (serverConfiguration.equals(configuration)) {
                return index;
            }
            index++;
        }

        throw new IllegalArgumentException("Unable to find the configuration to updated");
    }

    public void removeServerConfiguration(ServerConfiguration configuration) {
        serverConfigurations.remove(configuration);
    }
}


Question: import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.TreeExpander;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ui.UIUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.bson.Document;
import org.bson.json.JsonParseException;
import org.codinjutsu.tools.mongo.MongoConfiguration;
import org.codinjutsu.tools.mongo.ServerConfiguration;
import org.codinjutsu.tools.mongo.logic.ConfigurationException;
import org.codinjutsu.tools.mongo.logic.MongoManager;
import org.codinjutsu.tools.mongo.logic.Notifier;
import org.codinjutsu.tools.mongo.model.*;
import org.codinjutsu.tools.mongo.utils.GuiUtils;
import org.codinjutsu.tools.mongo.view.action.explorer.*;
import org.codinjutsu.tools.mongo.view.editor.MongoFileSystem;
import org.codinjutsu.tools.mongo.view.editor.MongoObjectFile;
import org.codinjutsu.tools.mongo.view.model.MongoTreeBuilder;
import org.codinjutsu.tools.mongo.view.model.navigation.Navigation;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static org.codinjutsu.tools.mongo.utils.GuiUtils.showNotification;
/*
 * 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;




public class MongoExplorerPanel extends JPanel implements Disposable {

    private static final URL pluginSettingsUrl = GuiUtils.class.getResource("/general/add.png");
    private final MongoInfosTable mongoInfosTable;

    private JPanel rootPanel;

    private JPanel treePanel;
    private final Tree mongoTree;

    private JPanel toolBarPanel;
    private JPanel statsPanel;
    private JPanel containerPanel;

    private final Project project;
    private final MongoManager mongoManager;
    private final Notifier notifier;

    private final MongoTreeBuilder mongoTreeBuilder;

    public MongoExplorerPanel(Project project, MongoManager mongoManager, Notifier notifier) {
        this.project = project;
        this.mongoManager = mongoManager;
        this.notifier = notifier;

        treePanel = new JPanel(new BorderLayout());
        treePanel.setLayout(new BorderLayout());

        mongoTree = createTree();
        mongoTreeBuilder = new MongoTreeBuilder(mongoTree);


        setLayout(new BorderLayout());
        treePanel.add(new JBScrollPane(mongoTree), BorderLayout.CENTER);

        mongoInfosTable = new MongoInfosTable();

        statsPanel = new JPanel(new BorderLayout());
        statsPanel.add(new JBScrollPane(mongoInfosTable));

        Splitter splitter = new Splitter(true, 0.6f);
        splitter.setFirstComponent(treePanel);
        splitter.setSecondComponent(statsPanel);

        containerPanel.add(splitter, BorderLayout.CENTER);

        add(rootPanel, BorderLayout.CENTER);

        toolBarPanel.setLayout(new BorderLayout());

        loadAllServerConfigurations();

        installActions();
    }

    private void loadAllServerConfigurations() {
        this.mongoManager.cleanUpServers();

        List<ServerConfiguration> serverConfigurations = getServerConfigurations();
        for (ServerConfiguration serverConfiguration : serverConfigurations) {
            addConfiguration(serverConfiguration);
        }
    }

    public void addConfiguration(ServerConfiguration serverConfiguration) {
        MongoServer mongoServer = mongoTreeBuilder.addConfiguration(serverConfiguration);
        mongoManager.registerServer(mongoServer);
    }

    public void closeServer(MongoServer mongoServer) {
     ApplicationManager.getApplication().invokeLater(() -> {
         mongoServer.setDatabases(Collections.emptyList());
         mongoTreeBuilder.queueUpdateFrom(mongoServer, true);
     });
    }

    public void openServer(final MongoServer mongoServer) {
        ProgressManager.getInstance().run(new Task.Backgroundable(project, "Connecting to " + mongoServer.getLabel()) {

            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                mongoTree.setPaintBusy(true);
                ApplicationManager.getApplication().invokeLater(() -> {
                    try {
                        List<MongoDatabase> mongoDatabases = mongoManager.loadDatabases(mongoServer, mongoServer.getConfiguration());
                        if (mongoDatabases.isEmpty()) {
                            return;
                        }
                        mongoServer.setDatabases(mongoDatabases);
                        mongoTreeBuilder.queueUpdateFrom(mongoServer, true)
                                .doWhenDone(() -> mongoTreeBuilder.expand(mongoServer, null));

                    } catch (ConfigurationException confEx) {
                        mongoServer.setStatus(MongoServer.Status.ERROR);

                        String errorMessage = String.format("Error when connecting to %s", mongoServer.getLabel());
                        notifier.notifyError(errorMessage + ": " + confEx.getMessage());
                        UIUtil.invokeLaterIfNeeded(() -> showNotification(treePanel, MessageType.ERROR, errorMessage, Balloon.Position.atLeft));
                    } finally {
                        mongoTree.setPaintBusy(false);
                    }
                });
            }
        });
    }

    private List<ServerConfiguration> getServerConfigurations() {
Answer:         return MongoConfiguration.getInstance(project).getServerConfigurations();