com.imaginea.betterdocs.BetterDocsAction.java Source code

Java tutorial

Introduction

Here is the source code for com.imaginea.betterdocs.BetterDocsAction.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.imaginea.betterdocs;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.jetbrains.annotations.NotNull;

public class BetterDocsAction extends AnAction {
    private static final String IMPORT_NAME = "importName";
    private static final String LINE_NUMBERS = "lineNumbers";
    private static final String BETTER_DOCS = "BetterDocs";
    private static final String HITS = "hits";
    private static final String SOURCE = "_source";
    private static final String FILE_CONTENT = "fileContent";
    private static final String IMPORT = "import ";
    private static final String SORT_ORDER = "desc";
    private static final String FILE = "file";
    private static final String TOKENS = "tokens";
    private static final String CUSTOM_TOKENS_IMPORT_NAME = "custom.tokens.importName";
    private static final char DOT = '.';
    private static final String ILLEGAL_FORMAT = "Please Provide valid numbers for distance and size in settings. Using default values for now";
    private static final String INFO = "Info";
    private static final String EMPTY_ES_URL = "Please set/modify proper esURL in idea settings";
    public static final String ES_URL = "esURL";
    public static final String DISTANCE = "distance";
    public static final String SIZE = "size";
    private static final String BETTERDOCS_SEARCH = "/betterdocs/_search?source=";
    private static final String SOURCEFILE_SEARCH = "/sourcefile/_search?source=";
    private static final String FAILED_HTTP_ERROR_CODE = "Failed : HTTP error code : ";
    public static final String ES_URL_DEFAULT = "http://labs.imaginea.com/betterdocs";
    public static final int DISTANCE_DEFAULT_VALUE = 10;
    public static final int SIZE_DEFAULT_VALUE = 30;
    private static final String USER_AGENT = "USER-AGENT";
    private static final String IDEA_PLUGIN = "Idea-Plugin";
    private static final String UTF_8 = "UTF-8";

    private Project project;
    private JTree jTree;
    private Editor windowEditor;

    private int distance;
    private int size;
    private String esURL;

    public void setWindowEditor(Editor windowEditor) {
        this.windowEditor = windowEditor;
    }

    public void setTree(JTree jTree) {
        this.jTree = jTree;
    }

    public BetterDocsAction() {
        super(BETTER_DOCS, BETTER_DOCS, AllIcons.Actions.Refresh);
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
        setProject(anActionEvent.getProject());
        PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
        try {
            this.distance = propertiesComponent.getOrInitInt(DISTANCE, DISTANCE_DEFAULT_VALUE);
            this.size = propertiesComponent.getOrInitInt(SIZE, SIZE_DEFAULT_VALUE);
            this.esURL = propertiesComponent.getValue(ES_URL, ES_URL_DEFAULT);
        } catch (NumberFormatException ne) {
            this.distance = DISTANCE_DEFAULT_VALUE;
            this.size = SIZE_DEFAULT_VALUE;
            Messages.showInfoMessage(String.format(ILLEGAL_FORMAT), INFO);
        }

        try {
            runAction(anActionEvent);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    private static MutableTreeNode getNodes(String projectName, Iterable<CodeInfo> codeInfoCollection) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(projectName);
        Collection<String> fileNameSet = new HashSet<String>();
        for (CodeInfo codeInfo : codeInfoCollection) {
            if (!fileNameSet.contains(codeInfo.getFileName())) {
                node.add(new DefaultMutableTreeNode(codeInfo));
                fileNameSet.add(codeInfo.getFileName());
            }
        }
        return node;
    }

    public void runAction(final AnActionEvent e) throws IOException {
        final Editor projectEditor = DataKeys.EDITOR.getData(e.getDataContext());

        if (projectEditor != null) {

            Set<String> imports = getImports(projectEditor.getDocument());
            Set<String> lines = getLines(projectEditor, projectEditor.getDocument());
            Set<String> importsInLines = importsInLines(lines, imports);

            DefaultTreeModel model = (DefaultTreeModel) jTree.getModel();
            DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
            root.removeAllChildren();

            if (!importsInLines.isEmpty()) {
                jTree.setVisible(true);
                String esQueryJson = getESQueryJson(importsInLines);
                String esResultJson = getESResultJson(esQueryJson, esURL + BETTERDOCS_SEARCH);

                if (!esResultJson.equals(EMPTY_ES_URL)) {
                    Map<String, String> fileTokensMap = getFileTokens(esResultJson);
                    Map<String, ArrayList<CodeInfo>> projectNodes = new HashMap<String, ArrayList<CodeInfo>>();

                    updateProjectNodes(imports, fileTokensMap, projectNodes);
                    updateRoot(root, projectNodes);

                    model.reload(root);
                    jTree.addTreeSelectionListener(getTreeSelectionListener(root));
                } else {
                    Messages.showInfoMessage(EMPTY_ES_URL, INFO);
                }
            } else {
                jTree.updateUI();
            }
        }
    }

    private void updateProjectNodes(Collection<String> imports, Map<String, String> fileTokensMap,
            Map<String, ArrayList<CodeInfo>> projectNodes) {
        for (Map.Entry<String, String> entry : fileTokensMap.entrySet()) {
            String fileName = entry.getKey();
            String tokens = entry.getValue();

            ArrayList<Integer> lineNumbers = getLineNumbers(imports, tokens);
            String contents = getContentsForFile(fileName);
            CodeInfo codeInfo = new CodeInfo(fileName, lineNumbers, contents);

            //Taking projectName as name till 2nd '/'
            int startIndex = fileName.indexOf('/');
            int endIndex = fileName.indexOf('/', startIndex + 1);

            String projectName = fileName.substring(0, endIndex);

            if (projectNodes.containsKey(projectName)) {
                projectNodes.get(projectName).add(codeInfo);
            } else {
                projectNodes.put(projectName, new ArrayList<CodeInfo>(Collections.singletonList(codeInfo)));
            }
        }
    }

    private static DefaultMutableTreeNode updateRoot(DefaultMutableTreeNode root,
            Map<String, ArrayList<CodeInfo>> projectNodes) {
        for (Map.Entry<String, ArrayList<CodeInfo>> entry : projectNodes.entrySet()) {
            root.add(getNodes(entry.getKey(), entry.getValue()));
        }
        return root;
    }

    private TreeSelectionListener getTreeSelectionListener(final TreeNode root) {
        return new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
                DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();

                if (selectedNode == null) {

                } else if (selectedNode.isLeaf() && root.getChildCount() > 0) {
                    final CodeInfo codeInfo = (CodeInfo) selectedNode.getUserObject();
                    final Document windowEditorDocument = windowEditor.getDocument();

                    writeToDocument(codeInfo, windowEditorDocument);

                    final List<Integer> linesForFolding = codeInfo.getLineNumbers();
                    linesForFolding.add(windowEditorDocument.getLineCount() + 1);
                    java.util.Collections.sort(linesForFolding);
                    addFoldings(windowEditorDocument, linesForFolding);
                }
            }
        };
    }

    private void writeToDocument(final CodeInfo codeInfo, final Document windowEditorDocument) {
        new WriteCommandAction(project) {
            @Override
            protected void run(@NotNull Result result) throws Throwable {
                windowEditorDocument.setReadOnly(false);
                windowEditorDocument.setText(codeInfo.getContents());
                windowEditorDocument.setReadOnly(true);
            }
        }.execute();

    }

    private void addFoldings(final Document windowEditorDocument, final Iterable<Integer> linesForFolding) {
        windowEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
            @Override
            public void run() {
                int prevLine = 0;
                cleanFoldingRegions(windowEditor);

                for (int line : linesForFolding) {
                    int currentLine = line - 1;
                    if (prevLine < windowEditorDocument.getLineCount()) {

                        int startOffset = windowEditorDocument.getLineStartOffset(prevLine);
                        int endOffset = windowEditorDocument.getLineEndOffset(currentLine - 1);

                        if (startOffset < endOffset && windowEditor.getFoldingModel() != null) {
                            windowEditor.getFoldingModel().addFoldRegion(startOffset, endOffset, "...")
                                    .setExpanded(false);
                        }
                        prevLine = currentLine + 1;
                    }
                }
            }
        });
    }

    private static void cleanFoldingRegions(Editor windowEditor) {
        FoldRegion[] foldRegions = windowEditor.getFoldingModel().getAllFoldRegions();
        for (FoldRegion currentRegion : foldRegions) {
            windowEditor.getFoldingModel().removeFoldRegion(currentRegion);
        }
    }

    private ArrayList<Integer> getLineNumbers(Collection<String> imports, String tokens) {
        ArrayList<Integer> lineNumbers = new ArrayList<Integer>();
        JsonReader reader = new JsonReader(new StringReader(tokens));
        reader.setLenient(true);
        JsonArray tokensArray = new JsonParser().parse(reader).getAsJsonArray();

        for (JsonElement token : tokensArray) {
            JsonObject jObject = token.getAsJsonObject();
            String importName = jObject.getAsJsonPrimitive(IMPORT_NAME).getAsString();
            if (imports.contains(importName)) {
                JsonArray lineNumbersArray = jObject.getAsJsonArray(LINE_NUMBERS);
                for (JsonElement lineNumber : lineNumbersArray) {
                    lineNumbers.add(lineNumber.getAsInt());
                }
            }
        }
        return lineNumbers;
    }

    private String getContentsForFile(String file) {
        String esFileQueryJson = getJsonForFileContent(file);
        String esFileResultJson = getESResultJson(esFileQueryJson, esURL + SOURCEFILE_SEARCH);

        JsonReader reader = new JsonReader(new StringReader(esFileResultJson));
        reader.setLenient(true);
        JsonElement jsonElement = new JsonParser().parse(reader);
        JsonObject jsonObject = jsonElement.getAsJsonObject();
        JsonObject hitsObject = jsonObject.getAsJsonObject(HITS);
        JsonArray hitsArray = hitsObject.getAsJsonArray(HITS);
        JsonObject hitObject = hitsArray.get(0).getAsJsonObject();
        JsonObject sourceObject = hitObject.getAsJsonObject(SOURCE);
        //Replacing \r as it's treated as bad end of line character
        String fileContent = sourceObject.getAsJsonPrimitive(FILE_CONTENT).getAsString().replaceAll("\r", "");
        return fileContent;
    }

    public void setProject(Project project) {
        this.project = project;
    }

    private static Set<String> importsInLines(Iterable<String> lines, Iterable<String> imports) {
        Set<String> importsInLines = new HashSet<String>();

        for (String line : lines) {
            for (String nextImport : imports) {
                if (line.contains(nextImport.substring(nextImport.lastIndexOf(DOT) + 1))) {
                    importsInLines.add(nextImport);
                }
            }
        }
        return importsInLines;
    }

    private Set<String> getLines(Editor projectEditor, Document document) {
        Set<String> lines = new HashSet<String>();
        int startLine;
        int endLine;

        if (projectEditor.getSelectionModel().hasSelection()) {
            startLine = document.getLineNumber(projectEditor.getSelectionModel().getSelectionStart());
            endLine = document.getLineNumber(projectEditor.getSelectionModel().getSelectionEnd());
        } else {
            int currentLine = document.getLineNumber(projectEditor.getCaretModel().getOffset());
            startLine = currentLine - distance >= 0 ? currentLine - distance : 0;
            endLine = currentLine + distance <= document.getLineCount() - 1 ? currentLine + distance
                    : document.getLineCount() - 1;
        }

        for (int i = startLine; i <= endLine; i++) {
            String line = document.getCharsSequence()
                    .subSequence(document.getLineStartOffset(i), document.getLineEndOffset(i)).toString();
            if (!line.contains(IMPORT)) {
                lines.add(line);
            }
        }
        return lines;
    }

    private static Set<String> getImports(Document document) {
        int startLine = 0;
        int endLine = document.getLineCount() - 1;
        Set<String> imports = new HashSet<String>();
        for (int i = startLine; i <= endLine; i++) {
            String line = document.getCharsSequence().subSequence(document.getLineStartOffset(i),
                    document.getLineEndOffset(i) + document.getLineSeparatorLength(i)).toString();
            if (line.contains(IMPORT) && !line.contains("*")) {
                imports.add(line.replace(IMPORT, "").replace(";", "").trim());
            }
        }
        return imports;
    }

    private String getESQueryJson(Set<String> importsInLines) {
        ESQuery esQuery = new ESQuery();
        ESQuery.Query query = new ESQuery.Query();
        esQuery.setQuery(query);
        esQuery.setFrom(0);
        esQuery.setSize(size);

        List<ESQuery.Sort> sortList = new ArrayList<ESQuery.Sort>();

        ESQuery.Sort sort = new ESQuery.Sort();

        ESQuery.Score score = new ESQuery.Score();
        score.setOrder(SORT_ORDER);

        sort.setScore(score);

        sortList.add(sort);
        esQuery.setSort(sortList);

        ESQuery.Bool bool = new ESQuery.Bool();
        query.setBool(bool);

        List<ESQuery.Must> mustList = new ArrayList<ESQuery.Must>();

        ESQuery.Must must;
        ESQuery.Term term;

        for (String nextImport : importsInLines) {
            must = new ESQuery.Must();
            bool.setMust(mustList);
            bool.setMustNot(new ArrayList<ESQuery.Must>());
            bool.setShould(new ArrayList<ESQuery.Must>());
            term = new ESQuery.Term();
            must.setTerm(term);
            term.setImportName(nextImport);
            mustList.add(must);
        }

        Gson gson = new Gson();
        return gson.toJson(esQuery).replaceAll(IMPORT_NAME, CUSTOM_TOKENS_IMPORT_NAME);
    }

    private static String getESResultJson(String esQueryJson, String url) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            HttpClient httpClient = new DefaultHttpClient();
            String encodedJson = URLEncoder.encode(esQueryJson, UTF_8);
            String esGetURL = url + encodedJson;

            HttpGet getRequest = new HttpGet(esGetURL);
            getRequest.setHeader(USER_AGENT, IDEA_PLUGIN);

            HttpResponse response = httpClient.execute(getRequest);
            if (response.getStatusLine().getStatusCode() != 200) {
                throw new RuntimeException(FAILED_HTTP_ERROR_CODE + url + response.getStatusLine().getStatusCode());
            }

            BufferedReader br = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
            String output;
            while ((output = br.readLine()) != null) {
                stringBuilder.append(output);
            }
            httpClient.getConnectionManager().shutdown();
        } catch (IllegalStateException e) {
            e.printStackTrace();
            return EMPTY_ES_URL;
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return EMPTY_ES_URL;
        } catch (IOException e) {
            e.printStackTrace();
            return EMPTY_ES_URL;
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return EMPTY_ES_URL;
        }
        return stringBuilder.toString();
    }

    private static Map<String, String> getFileTokens(String esResultJson) {
        Map<String, String> fileTokenMap = new HashMap<String, String>();
        JsonReader reader = new JsonReader(new StringReader(esResultJson));
        reader.setLenient(true);
        JsonElement jsonElement = new JsonParser().parse(reader);
        JsonObject jsonObject = jsonElement.getAsJsonObject();
        JsonObject hitsObject = jsonObject.getAsJsonObject(HITS);
        JsonArray hitsArray = hitsObject.getAsJsonArray(HITS);

        for (JsonElement hits : hitsArray) {
            JsonObject hitObject = hits.getAsJsonObject();
            JsonObject sourceObject = hitObject.getAsJsonObject(SOURCE);
            String fileName = sourceObject.getAsJsonPrimitive(FILE).getAsString();
            String tokens = sourceObject.get(TOKENS).toString();
            fileTokenMap.put(fileName, tokens);
        }
        return fileTokenMap;
    }

    private static String getJsonForFileContent(String fileName) {
        ESFileContent esFileContent = new ESFileContent();
        ESFileContent.Query query = new ESFileContent.Query();
        esFileContent.setQuery(query);
        ESFileContent.Term term = new ESFileContent.Term();
        query.setTerm(term);
        term.setFileName(fileName);
        Gson gson = new Gson();
        return gson.toJson(esFileContent);
    }
}