su.opencode.shuffler.ShuffleAction.java Source code

Java tutorial

Introduction

Here is the source code for su.opencode.shuffler.ShuffleAction.java

Source

/*
Shuffler is a plugin for IntelliJ Idea Community Edition,
that performs non-destructive java source code obfuscation.
Copyright (C) 2015 LLC "Open Code" http://www.o-code.ru
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package su.opencode.shuffler;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.module.Module;
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.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.*;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ShuffleAction extends AnAction {

    private static final Logger LOG = Logger.getLogger(ShuffleAction.class.getName());

    private boolean includeLibraries = false;

    private boolean renamePublic = false;
    private boolean renamePackage = true;
    private boolean renameProtected = true;
    private boolean renamePrivate = true;
    private boolean renameDefault = true;

    private static final CacheLoader<PsiMethod, PsiMethod[]> ROOT_METHOD_LOADER = new CacheLoader<PsiMethod, PsiMethod[]>() {
        @Override
        public PsiMethod[] load(PsiMethod psiMethod) throws Exception {
            return psiMethod.findDeepestSuperMethods();
        }
    };

    private static final LoadingCache<PsiMethod, PsiMethod[]> ROOT_METHODS = CacheBuilder.newBuilder()
            .expireAfterAccess(2, TimeUnit.MINUTES).build(ROOT_METHOD_LOADER);

    public static PsiMethod[] findDeepestSuperMethods(final PsiMethod method) {
        try {
            return ROOT_METHODS.get(method);
        } catch (ExecutionException ex) {
            LOG.log(Level.SEVERE, "Exception during root method cache loading", ex);
            return null;
        }
    }

    public static <T extends PsiElement> T findRootPsiByType(PsiElement element, Class<T> parentType) {
        if (element == null)
            return null;
        PsiElement lastFound = null;
        PsiElement current = element.getParent();
        while (current != null) {
            if (parentType.isAssignableFrom(current.getClass())) {
                lastFound = current;
            }
            current = current.getParent();
        }
        return (T) lastFound;
    }

    public static <T extends PsiElement> T findParentPsiByType(PsiElement element, Class<T> parentType) {
        if (element == null)
            return null;
        PsiElement current = element.getParent();
        while (current != null && !parentType.isAssignableFrom(current.getClass())) {
            current = current.getParent();
        }
        return (T) current;
    }

    public static <T extends PsiElement> Set<T> findParentPsisByType(PsiElement element, Class<T> parentType) {
        if (element == null)
            return Collections.<T>emptySet();
        LinkedHashSet<T> result = new LinkedHashSet<T>();
        PsiElement current = element.getParent();
        while (current != null) {
            if (parentType.isAssignableFrom(current.getClass())) {
                result.add((T) current);
            }
            current = current.getParent();
        }
        return result;
    }

    private static boolean isOfType(PsiElement element, Class type) {
        return type == null || element != null && type.isAssignableFrom(element.getClass());
    }

    private static boolean isNamed(PsiElement element, String name) {
        return name == null || element != null && element instanceof PsiNamedElement
                && name.equals(((PsiNamedElement) element).getName());
    }

    public static <T extends PsiElement> Set<T> findChildren(PsiElement element, String name, Class<T> type) {
        LinkedHashSet results = new LinkedHashSet<T>();
        if (element == null)
            return results;

        Queue<PsiElement> elementsToCheck = new LinkedList<PsiElement>();
        elementsToCheck.add(element);

        while (elementsToCheck.size() > 0) {
            PsiElement e = elementsToCheck.poll();
            if (isOfType(e, type) && isNamed(e, name)) {
                results.add((T) e);
            }
            elementsToCheck.addAll(Arrays.asList(e.getChildren()));
        }

        return results;
    }

    public static boolean isCollidingSignature(PsiMethod method1, PsiMethod method2, boolean ignoreName) {
        if (!ignoreName && !method1.getName().equals(method2.getName()))
            return false;
        int paramCount = method1.getParameterList().getParametersCount();
        if (paramCount != method2.getParameterList().getParametersCount())
            return false;

        PsiParameter[] params1 = method1.getParameterList().getParameters();
        PsiParameter[] params2 = method2.getParameterList().getParameters();

        for (int i = 0; i < paramCount; i++) {
            if (params1[i] == null || params2[i] == null)
                return false;
            PsiParameter param1 = params1[i];
            PsiParameter param2 = params2[i];

            if (param1.getType().equals(param2.getType()) && param1.getTypeElement() == null
                    && param2.getTypeElement() == null) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        final Project project = anActionEvent.getProject();

        final GlobalSearchScope scope;
        String scopeName;
        PsiElement psiElement = DataKeys.TARGET_PSI_ELEMENT.getData(anActionEvent.getDataContext());
        VirtualFile directory = DataKeys.VIRTUAL_FILE.getData(anActionEvent.getDataContext());
        Module module = DataKeys.TARGET_MODULE.getData(anActionEvent.getDataContext());
        module = module != null ? module : DataKeys.MODULE.getData(anActionEvent.getDataContext());
        module = module != null ? module : DataKeys.MODULE_CONTEXT.getData(anActionEvent.getDataContext());

        if (psiElement != null && psiElement instanceof PsiDirectory) {
            directory = ((PsiDirectory) psiElement).getVirtualFile();
            scope = GlobalSearchScopes.directoryScope(project, directory, true);
            scopeName = directory.getName();
        } else if (directory != null) {
            scope = GlobalSearchScopes.directoryScope(project, directory, true);
            scopeName = directory.getName();
        } else if (module != null) {
            scope = module.getModuleScope();
            scopeName = module.getName();
        } else {
            scope = GlobalSearchScopes.projectProductionScope(project);
            scopeName = project.getName();
        }

        String warning = String
                .format("Varaiable, class, and method names will be shuffled in %s, comments will be removed. \n"
                        + "It will block Idea and may take awhile. \n" + "Do you want to shuffle?", scopeName);

        int exitCode = Messages.showOkCancelDialog(project, warning, "Shuffle project?", "Shuffle", "Cancel", null);

        if (DialogWrapper.OK_EXIT_CODE != exitCode)
            return;

        Task task = new Task.Modal(project, "Shuffling", false) {

            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                ShuffleRunner runner = new ShuffleRunner(indicator, project, scope);
                runner.run();
            }
        };

        ProgressManager.getInstance().run(task);
    }

    public void processFile(final Project project, final VirtualFile file, final PsiElementVisitor... visitors) {
        runInUI(new Runnable() {

            @Override
            public void run() {
                if (!file.exists())
                    return;
                PsiFile psiFile = PsiManager.getInstance(project).findFile(file);

                if (!psiFile.isWritable() || !psiFile.isPhysical())
                    return;
                if (psiFile != psiFile.getOriginalElement())
                    return;

                for (PsiElementVisitor visitor : visitors) {
                    if (visitor == null)
                        continue;
                    psiFile.accept(visitor);
                }
            }

        });
    }

    private class ShuffleRunner implements Runnable {

        private Project project;
        private ProgressIndicator indicator;
        private GlobalSearchScope shuffleScope;

        private ShuffleRunner(ProgressIndicator indicator, Project project, GlobalSearchScope scope) {
            this.indicator = indicator;
            this.project = project;
            if (scope == null) {
                scope = GlobalSearchScopes.projectProductionScope(project);
            }
            this.shuffleScope = scope;
        }

        @Override
        public void run() {
            ROOT_METHODS.invalidateAll();
            indicator.setFraction(0);

            GlobalSearchScope projectScope = GlobalSearchScopes.projectProductionScope(project);

            Collection<VirtualFile> projectFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, projectScope);
            Collection<VirtualFile> markovChainSourceFiles;

            if (includeLibraries) {
                projectScope = new ProjectAndLibrariesScope(project);
                markovChainSourceFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, projectScope);
            } else {
                markovChainSourceFiles = projectFiles;
            }
            indicator.setFraction(0.05);

            double total = markovChainSourceFiles.size();
            int counter = 0;

            MarkovBuildingVisitor chainBuilder = new MarkovBuildingVisitor();

            indicator.setText("Building Markov chain");
            LOG.info("Building Markov chain in project " + project.getName());
            for (VirtualFile file : markovChainSourceFiles) {
                processFile(project, file, chainBuilder);
                indicator.setText2(file.getCanonicalPath());
                counter++;
                indicator.setFraction(0.05 + 0.1 * counter / total);
            }
            LOG.info("Markov chain building finished, renaming in project " + project.getName());

            //shuffling
            DecommentingVisitor decommenter = new DecommentingVisitor();
            InliningVisitor inliner = null; //new InliningVisitor();

            RenamingVisitor renamer = new RenamingVisitor(chainBuilder);
            renamer.setRenamePrivate(renamePrivate);
            renamer.setRenameProtected(renameProtected);
            renamer.setRenamePublic(renamePublic);
            renamer.setRenamePackage(renamePackage);
            renamer.setRenameDefault(renameDefault);

            counter = 0;

            Collection<VirtualFile> shuffledFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, shuffleScope);
            total = shuffledFiles.size();
            indicator.setText("Shuffling");

            for (VirtualFile file : shuffledFiles) {
                //LOG.info("Shuffling "+file.getName());
                indicator.setText2(file.getCanonicalPath());
                try {
                    processFile(project, file, decommenter, inliner, renamer);
                } catch (Throwable ex) {
                    LOG.log(Level.WARNING, "Failed to shuffle " + file.getName(), ex);
                }
                counter++;
                indicator.setFraction(0.15 + 0.85 * counter / total);
            }

            ROOT_METHODS.invalidateAll();
            LOG.finer("Renaming finished " + project.getName());
        }
    }

    public static void runInUI(Runnable r) {
        new UIRunnable(r).run();
    }
}