Package org.intellij.plugins.junitgen.action

Source Code of org.intellij.plugins.junitgen.action.JUnitGeneratorActionHandler

package org.intellij.plugins.junitgen.action;

import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.util.StringResourceRepository;
import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
import org.intellij.plugins.junitgen.JUnitGeneratorContext;
import org.intellij.plugins.junitgen.JUnitGeneratorFileCreator;
import org.intellij.plugins.junitgen.bean.MethodComposite;
import org.intellij.plugins.junitgen.bean.TemplateEntry;
import org.intellij.plugins.junitgen.util.DateTool;
import org.intellij.plugins.junitgen.util.JUnitGeneratorUtil;
import org.intellij.plugins.junitgen.util.LogAdapter;

import javax.swing.*;
import java.io.StringWriter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This is where the magic happens.
*
* @author Alex Nazimok (SCI)
* @author Jon Osborn
* @author By: Bryan Gilbert, July 18, 2008
* @since <pre>Sep 3, 2003</pre>
*/
public class JUnitGeneratorActionHandler extends EditorWriteActionHandler {

    private static final Logger logger = JUnitGeneratorUtil.getLogger(JUnitGeneratorActionHandler.class);

    private static final String VIRTUAL_TEMPLATE_NAME = "junitgenerator.vm";

    private final String templateKey;

    private static final Pattern ISGETSET = Pattern.compile("^(is|get|set)(.*)");

    public JUnitGeneratorActionHandler(String name) {
        this.templateKey = name;
    }

    public String getTemplate(Project project) {
        return JUnitGeneratorUtil.getInstance(project).getTemplate(this.templateKey);
    }

    /**
     * Executed upon action in the Editor
     *
     * @param editor      IDEA Editor
     * @param dataContext DataCOntext
     */
    public void executeWriteAction(Editor editor, DataContext dataContext) {
        PsiJavaFile file = JUnitGeneratorUtil.getSelectedJavaFile(dataContext);

        if (file == null) {
            return;
        }
        if (this.templateKey == null || this.templateKey.trim().length() == 0 ||
                getTemplate(DataKeys.PROJECT.getData(dataContext)) == null) {
            JOptionPane.showMessageDialog(null,
                    JUnitGeneratorUtil.getProperty("junit.generator.error.noselectedtemplate"),
                    JUnitGeneratorUtil.getProperty("junit.generator.error.title"),
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        PsiClass[] psiClasses = file.getClasses();

        if (psiClasses == null) {
            return;
        }

        for (PsiClass psiClass : psiClasses) {
            if ((psiClass != null) && (psiClass.getQualifiedName() != null)) {
                final JUnitGeneratorContext genCtx = new JUnitGeneratorContext(dataContext, file, psiClass);
                final List<TemplateEntry> entryList = new ArrayList<TemplateEntry>();

                try {

                    if (!psiClass.isInterface()) {
                        boolean getPrivate = true;

                        List<PsiMethod> methodList = new ArrayList<PsiMethod>();
                        List<PsiMethod> pMethodList = new ArrayList<PsiMethod>();
                        List<String> fieldList = new ArrayList<String>();

                        List<MethodComposite> methodCompositeList = new ArrayList<MethodComposite>();
                        List<MethodComposite> privateMethodCompositeList = new ArrayList<MethodComposite>();

                        buildMethodList(psiClass.getMethods(), methodList, !getPrivate);
                        buildMethodList(psiClass.getMethods(), pMethodList, getPrivate);
                        buildFieldList(psiClass.getFields(), fieldList);
                        PsiClass[] innerClass = psiClass.getAllInnerClasses();

                        for (PsiClass innerClas : innerClass) {
                            buildMethodList(innerClas.getMethods(), methodList, !getPrivate);
                            buildMethodList(innerClas.getMethods(), pMethodList, getPrivate);
                            buildFieldList(psiClass.getFields(), fieldList);
                        }

                        processMethods(genCtx, methodList, methodCompositeList);
                        processMethods(genCtx, pMethodList, privateMethodCompositeList);

                        entryList.add(new TemplateEntry(genCtx.getClassName(false),
                                genCtx.getPackageName(),
                                methodCompositeList,
                                privateMethodCompositeList,
                                fieldList));
                        process(genCtx, entryList);
                    }
                } catch (Exception e) {
                    logger.error(e);
                }
            }
        }
    }


    /**
     * Creates a list of methods with set and get methods combined together.
     *
     * @param genCtx              the generator context
     * @param methodList          list of methods to process
     * @param methodCompositeList the composite list
     */
    private void processMethods(JUnitGeneratorContext genCtx, List<PsiMethod> methodList, List<MethodComposite> methodCompositeList) {
        List<String> methodNames = new ArrayList<String>();
        List<MethodComposite> methodComposites;

        methodComposites = toComposites(genCtx, methodList);

        if (JUnitGeneratorUtil.getInstance(genCtx.getProject()).isGenerateForOverloadedMethods()) {
            methodComposites = updateOverloadedMethods(genCtx, methodComposites);
        }

        for (MethodComposite method : methodComposites) {
            String methodName = method.getName();

            if (JUnitGeneratorUtil.getInstance(genCtx.getProject()).isCombineGetterAndSetter() &&
                    ISGETSET.matcher(methodName).find()) {
                methodName = parseAccessorMutator(methodName, methodList);
            }

            if (!methodNames.contains(methodName)) {
                methodNames.add(methodName);
                method.setName(methodName);
                methodCompositeList.add(method);
            }
        }
    }

    /**
     * Create a MethodComposite object for each of the methods passed in
     *
     * @param genCtx     the context
     * @param methodList the method list
     * @return the list of methods
     */
    private List<MethodComposite> toComposites(JUnitGeneratorContext genCtx, List<PsiMethod> methodList) {

        final List<MethodComposite> compositeList = new ArrayList<MethodComposite>();

        for (PsiMethod method : methodList) {
            compositeList.add(toComposite(genCtx, method));
        }

        //now that we have the complete list, we want to see if any of the methods are overloaded with each other
        //this will find methods with the same 'name'
        for (MethodComposite composite : compositeList) {
            composite.setOverloadedMethods(findOverloadedMethods(composite, compositeList));
        }
        return compositeList;
    }

    protected List<MethodComposite> findOverloadedMethods(MethodComposite source, List<MethodComposite> list) {
        final List<MethodComposite> overloadedMethods = new ArrayList<MethodComposite>();
        for (MethodComposite method : list) {
            if (!source.equals(method) && source.getName().equals(method.getName())) {
                overloadedMethods.add(method);
            }
        }
        return overloadedMethods;
    }

    /**
     * Generate the method composite class. This method will recurse until we get to the top of the chain
     *
     * @param genCtx the generator context
     *               * @param method the method in question
     * @return the method composite object
     */
    private MethodComposite toComposite(JUnitGeneratorContext genCtx, PsiMethod method) {
        List<String> paramClassList = new ArrayList<String>();
        for (PsiParameter param : method.getParameterList().getParameters()) {
            paramClassList.add(param.getType().getCanonicalText());
        }

        List<String> paramNameList = new ArrayList<String>();
        for (PsiParameter param : method.getParameterList().getParameters()) {
            paramNameList.add(param.getName());
        }

        String signature = createSignature(method);

        List<String> reflectionCode = createReflectionCode(genCtx, method);

        //create the composite object to send to the template
        final MethodComposite composite = new MethodComposite();
        composite.setMethod(method);
        composite.setName(method.getName());
        composite.setParamClasses(paramClassList);
        composite.setParamNames(paramNameList);
        composite.setReflectionCode(reflectionCode);
        composite.setSignature(signature);

        //if the super method is not the same as us, grab the data from that also
        final PsiMethod[] superMethods = method.findSuperMethods();
        if (superMethods.length > 0) {
            composite.setBase(toComposite(genCtx, superMethods[0]));
        }
        return composite;
    }

    private String createSignature(PsiMethod method) {

        String signature;
        String params = "";

        for (PsiParameter param : method.getParameterList().getParameters()) {
            params += param.getText() + ", ";
        }

        if (params.endsWith(", ")) {
            params = params.substring(0, params.length() - 2);
        }

        signature = method.getName() + "(" + params + ")";

        return signature;

    }

    private List<String> createReflectionCode(JUnitGeneratorContext genCtx, PsiMethod method) {

        String getMethodText = "\"" + method.getName() + "\"";

        for (PsiParameter param : method.getParameterList().getParameters()) {
            String className = (new StringTokenizer(param.getText(), " ")).nextToken();
            getMethodText = getMethodText + ", " + className + ".class";
        }

        List<String> reflectionCode = new ArrayList<String>();
        reflectionCode.add("/*");
        reflectionCode.add("try {");
        reflectionCode.add("   Method method = " + genCtx.getClassName(false) + ".getClass().getMethod(" + getMethodText + ");");
        reflectionCode.add("   method.setAccessible(true);");
        reflectionCode.add("   method.invoke(<Object>, <Parameters>);");
        reflectionCode.add("} catch(NoSuchMethodException e) {");
        reflectionCode.add("} catch(IllegalAccessException e) {");
        reflectionCode.add("} catch(InvocationTargetException e) {");
        reflectionCode.add("}");
        reflectionCode.add("*/");

        return reflectionCode;
    }

    private List<MethodComposite> updateOverloadedMethods(JUnitGeneratorContext context, List<MethodComposite> methodList) {

        HashMap<String, Integer> methodNameMap = new HashMap<String, Integer>();
        HashMap<String, Integer> overloadMethodNameMap = new HashMap<String, Integer>();

        for (MethodComposite method : methodList) {
            String methodName = method.getName();
            if (!methodNameMap.containsKey(methodName)) {
                methodNameMap.put(methodName, 1);
            } else {
                Integer count = methodNameMap.get(methodName);
                methodNameMap.remove(methodName);
                methodNameMap.put(methodName, count + 1);
            }
        }

        for (String key : methodNameMap.keySet()) {
            if (methodNameMap.get(key) > 1) {
                overloadMethodNameMap.put(key, methodNameMap.get(key));
            }
        }

        for (int i = 0; i < methodList.size(); i++) {

            MethodComposite method = methodList.get(i);
            String methodName = method.getName();
            if (overloadMethodNameMap.containsKey(methodName)) {
                int count = overloadMethodNameMap.get(methodName);
                overloadMethodNameMap.remove(methodName);
                overloadMethodNameMap.put(methodName, count - 1);
                methodList.set(i, mutateOverloadedMethodName(context, method, count));
            }
        }

        return methodList;
    }

    private MethodComposite mutateOverloadedMethodName(JUnitGeneratorContext context, MethodComposite method, int count) {

        String stringToAppend = "";
        final String overloadType = JUnitGeneratorUtil.getInstance(context.getProject()).getListOverloadedMethodsBy();

        if (JUnitGeneratorUtil.NUMBER.equalsIgnoreCase(overloadType)) {
            stringToAppend += count;
        } else if (JUnitGeneratorUtil.PARAM_CLASS.equalsIgnoreCase(overloadType)) {

            if (method.getParamClasses().size() > 1) {
                stringToAppend += "For";
            }

            for (String paramClass : method.getParamClasses()) {
                paramClass = paramClass.substring(0, 1).toUpperCase() + paramClass.substring(1, paramClass.length());
                stringToAppend += paramClass;
            }
        } else if (JUnitGeneratorUtil.PARAM_NAME.equalsIgnoreCase(overloadType)) {

            if (method.getParamNames().size() > 1) {
                stringToAppend += "For";
            }

            for (String paramName : method.getParamNames()) {
                paramName = paramName.substring(0, 1).toUpperCase() + paramName.substring(1, paramName.length());
                stringToAppend += paramName;
            }
        }

        method.setName(method.getName() + stringToAppend);

        return method;
    }

    /**
     * This method takes in an accessor or mutator method that is named using get*, set*, or is* and combines
     * the method name to provide one method name: "GetSet<BaseName>"
     *
     * @param methodName - Name of accessor or mutator method
     * @param methodList - Entire list of method using to create test
     * @return String updated method name if list contains both accessor and modifier for base name
     */
    @SuppressWarnings("unchecked")
    private String parseAccessorMutator(String methodName, List methodList) {

        String baseName;

        Matcher matcher = ISGETSET.matcher(methodName);
        if (matcher.find()) {
            baseName = matcher.group(2);
        } else {
            baseName = methodName;
        }
        //enumerate the method list to see if we have methods with set and is or get in them
        boolean setter = false;
        boolean getter = false;
        for (PsiMethod method : (List<PsiMethod>) methodList) {
            matcher = ISGETSET.matcher(method.getName());
            if (matcher.find() && baseName.equals(matcher.group(2))) {
                if ("set".equals(matcher.group(1))) {
                    setter = true;
                } else if ("is".equals(matcher.group(1)) || "get".equals(matcher.group(1))) {
                    getter = true;
                }
            }
        }
        //if we have a getter and setter, then fix the method to the same name
        if (getter && setter) {
            return "GetSet" + baseName;
        }

        return methodName;
    }

    /**
     * Builds a list of class scope fields from an array of PsiFields
     *
     * @param fields    an array of fields
     * @param fieldList list to be populated
     */
    private void buildFieldList(PsiField[] fields, List<String> fieldList) {
        for (PsiField field : fields) {
            fieldList.add(field.getName());
        }
    }

    /**
     * Builds method List from an array of PsiMethods
     *
     * @param methods    array of methods
     * @param methodList list to be populated
     * @param getPrivate boolean value, if true returns only private methods, if false only returns none private methods
     */
    private void buildMethodList(PsiMethod[] methods, List<PsiMethod> methodList, boolean getPrivate) {

        for (PsiMethod method : methods) {
            if (!method.isConstructor()) {
                PsiModifierList modifiers = method.getModifierList();

                if ((!modifiers.hasModifierProperty("private") && !getPrivate) || (modifiers.hasModifierProperty("private") && getPrivate)) {
                    methodList.add(method);
                }
            }
        }
    }

    /**
     * Sets all the needed vars in VelocityContext and
     * merges the template
     *
     * @param genCtx    the context
     * @param entryList the list of entries to go into velocity scope
     */
    protected void process(JUnitGeneratorContext genCtx, List<TemplateEntry> entryList) {
        try {
            final Properties velocityProperties = new Properties();
            //use the 'string' resource loader because the template comes from a 'string'
            velocityProperties.setProperty(VelocityEngine.RESOURCE_LOADER, "string");
            velocityProperties.setProperty("string.resource.loader.class", "org.apache.velocity.runtime.resource.loader.StringResourceLoader");
            velocityProperties.setProperty("string.resource.loader.repository.class", "org.apache.velocity.runtime.resource.loader.StringResourceRepositoryImpl");
            velocityProperties.setProperty("string.resource.loader.repository.static", "false");
            velocityProperties.setProperty("string.resource.loader.repository.name", "JUnitGenerator");

            //create the velocity engine with an externalized resource template
            final VelocityEngine ve = new VelocityEngine(velocityProperties);
            //set our custom log adapter
            ve.setProperty("runtime.log.logsystem", new LogAdapter());
            //manage the repository and put our template in with a name
            StringResourceRepository repository = new StringResourceRepositoryImpl();
            repository.putStringResource(VIRTUAL_TEMPLATE_NAME, getTemplate(genCtx.getProject()));
            ve.setApplicationAttribute("JUnitGenerator", repository);

            //init the engine
            ve.init();

            final VelocityContext context = new VelocityContext();
            context.put("entryList", entryList);
            context.put("today", JUnitGeneratorUtil.formatDate("MM/dd/yyyy"));
            context.put("date", new DateTool());

            final Template template = ve.getTemplate(VIRTUAL_TEMPLATE_NAME);
            final StringWriter writer = new StringWriter();

            template.merge(context, writer);
            String outputFileName = (String) context.get("testClass");
            if (outputFileName == null || outputFileName.trim().length() == 0) {
                if (entryList != null && entryList.size() > 0) {
                    outputFileName = entryList.get(0).getClassName() + "Test";
                } else {
                    outputFileName = "UnknownTestCaseNameTest";
                }
            }
            ApplicationManager.getApplication()
                    .runWriteAction(
                            new JUnitGeneratorFileCreator(
                                    JUnitGeneratorUtil.resolveOutputFileName(genCtx, outputFileName),
                                    writer, genCtx));
        } catch (Exception e) {
            logger.error(e);
        }
    }

}
TOP

Related Classes of org.intellij.plugins.junitgen.action.JUnitGeneratorActionHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.