net.harawata.mybatipse.mybatis.JavaMapperUtil.java Source code

Java tutorial

Introduction

Here is the source code for net.harawata.mybatipse.mybatis.JavaMapperUtil.java

Source

/*-******************************************************************************
 * Copyright (c) 2014 Iwao AVE!.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Iwao AVE! - initial API and implementation and/or initial documentation
 *******************************************************************************/

package net.harawata.mybatipse.mybatis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import net.harawata.mybatipse.Activator;
import net.harawata.mybatipse.MybatipseConstants;
import net.harawata.mybatipse.util.NameUtil;

/**
 * @author Iwao AVE!
 */
public class JavaMapperUtil {
    public static IAnnotation getAnnotationAt(IAnnotatable annotatable, int offset) throws JavaModelException {
        IAnnotation[] annotations = annotatable.getAnnotations();
        for (IAnnotation annotation : annotations) {
            ISourceRange sourceRange = annotation.getSourceRange();
            if (isInRange(sourceRange, offset)) {
                return annotation;
            }
        }
        return null;
    }

    private static boolean isInRange(ISourceRange sourceRange, int offset) {
        int start = sourceRange.getOffset();
        int end = start + sourceRange.getLength();
        return start <= offset && offset <= end;
    }

    public static void findMapperMethod(MapperMethodStore store, IJavaProject project, String mapperFqn,
            MethodMatcher annotationFilter) {
        try {
            IType mapperType = project.findType(mapperFqn.replace('$', '.'));
            if (mapperType == null || !mapperType.isInterface())
                return;
            if (mapperType.isBinary()) {
                findMapperMethodBinary(store, project, annotationFilter, mapperType);
            } else {
                findMapperMethodSource(store, project, mapperFqn, annotationFilter, mapperType);
            }
        } catch (JavaModelException e) {
            Activator.log(Status.ERROR, "Failed to find type " + mapperFqn, e);
        }
    }

    private static void findMapperMethodSource(MapperMethodStore methodStore, IJavaProject project,
            String mapperFqn, MethodMatcher annotationFilter, IType mapperType) {
        ICompilationUnit compilationUnit = (ICompilationUnit) mapperType.getAncestor(IJavaElement.COMPILATION_UNIT);
        ASTParser parser = ASTParser.newParser(AST.JLS4);
        parser.setKind(ASTParser.K_COMPILATION_UNIT);
        parser.setSource(compilationUnit);
        parser.setResolveBindings(true);
        // parser.setIgnoreMethodBodies(true);
        CompilationUnit astUnit = (CompilationUnit) parser.createAST(null);
        astUnit.accept(new JavaMapperVisitor(methodStore, project, mapperFqn, annotationFilter));
    }

    private static void findMapperMethodBinary(MapperMethodStore methodStore, IJavaProject project,
            MethodMatcher methodMatcher, IType mapperType) throws JavaModelException {
        for (IMethod method : mapperType.getMethods()) {
            if (methodMatcher.matches(method)) {
                methodStore.add(method);
            }
        }

        String[] superInterfaces = mapperType.getSuperInterfaceNames();
        for (String superInterface : superInterfaces) {
            if (!Object.class.getName().equals(superInterface)) {
                findMapperMethod(methodStore, project, superInterface, methodMatcher);
            }
        }
    }

    public static class JavaMapperVisitor extends ASTVisitor {
        private MapperMethodStore methodStore;

        private IJavaProject project;

        private String mapperFqn;

        private MethodMatcher methodMatcher;

        private int nestLevel;

        @Override
        public boolean visit(TypeDeclaration node) {
            ITypeBinding binding = node.resolveBinding();
            if (binding == null)
                return false;

            if (mapperFqn.equals(binding.getBinaryName()))
                nestLevel = 1;
            else if (nestLevel > 0)
                nestLevel++;

            return true;
        }

        @Override
        public boolean visit(AnonymousClassDeclaration node) {
            return false;
        }

        @Override
        public boolean visit(MethodDeclaration node) {
            if (nestLevel != 1)
                return false;
            // Resolve binding first to support Lombok generated methods.
            // node.getModifiers() returns incorrect access modifiers for them.
            // https://github.com/harawata/stlipse/issues/2
            IMethodBinding method = node.resolveBinding();
            if (method == null)
                return false;

            if (methodMatcher == null)
                return false;

            try {
                if (methodMatcher.matches(method)) {
                    @SuppressWarnings("unchecked")
                    List<SingleVariableDeclaration> params = node.parameters();
                    methodStore.add(method, params);
                }
            } catch (JavaModelException e) {
                Activator.log(Status.ERROR,
                        "Failed to visit method " + node.getName().toString() + " in " + mapperFqn, e);
            }
            return false;
        }

        public void endVisit(TypeDeclaration node) {
            if (nestLevel == 1 && (!methodMatcher.needExactMatch() || methodStore.isEmpty())) {
                @SuppressWarnings("unchecked")
                List<Type> superInterfaceTypes = node.superInterfaceTypes();
                if (superInterfaceTypes != null && !superInterfaceTypes.isEmpty()) {
                    for (Type superInterfaceType : superInterfaceTypes) {
                        ITypeBinding binding = superInterfaceType.resolveBinding();
                        if (binding != null) {
                            String superInterfaceFqn = binding.getQualifiedName();
                            if (binding.isParameterizedType()) {
                                // strip parameter part
                                int paramIdx = superInterfaceFqn.indexOf('<');
                                superInterfaceFqn = superInterfaceFqn.substring(0, paramIdx);
                            }
                            findMapperMethod(methodStore, project, superInterfaceFqn, methodMatcher);
                        }
                    }
                }
            }
            nestLevel--;
        }

        private JavaMapperVisitor(MapperMethodStore methodStore, IJavaProject project, String mapperFqn,
                MethodMatcher annotationFilter) {
            this.methodStore = methodStore;
            this.project = project;
            this.mapperFqn = mapperFqn;
            this.methodMatcher = annotationFilter;
        }
    }

    public static interface MapperMethodStore {
        /**
         * Called when adding binary method.
         */
        void add(IMethod method);

        /**
         * Called when adding source method.
         */
        void add(IMethodBinding method, List<SingleVariableDeclaration> params);

        boolean isEmpty();
    }

    public static class MethodNameStore implements MapperMethodStore {
        private List<String> methodNames = new ArrayList<String>();

        public List<String> getMethodNames() {
            return methodNames;
        }

        @Override
        public void add(IMethod method) {
            methodNames.add(method.getElementName());
        }

        @Override
        public void add(IMethodBinding method, List<SingleVariableDeclaration> params) {
            methodNames.add(method.getName());
        }

        @Override
        public boolean isEmpty() {
            return methodNames.isEmpty();
        }
    }

    public static class MethodReturnTypeStore implements MapperMethodStore {
        private String returnType = null;

        public String getReturnType() {
            return returnType;
        }

        @Override
        public void add(IMethod method) {
            try {
                returnType = method.getReturnType();
            } catch (JavaModelException e) {
                Activator.log(Status.ERROR, "Failed to collect return type of method " + method.getElementName()
                        + " in " + method.getDeclaringType().getFullyQualifiedName(), e);
            }
        }

        @Override
        public void add(IMethodBinding method, List<SingleVariableDeclaration> params) {
            ITypeBinding binding = method.getReturnType();
            returnType = binding.getQualifiedName();
        }

        @Override
        public boolean isEmpty() {
            return returnType == null;
        }
    }

    public static class MethodParametersStore implements MapperMethodStore {
        private IJavaProject project;

        private boolean found;

        private Map<String, String> paramMap = new HashMap<String, String>();

        public Map<String, String> getParamMap() {
            return paramMap;
        }

        @Override
        public void add(IMethod method) {
            found = true;
            try {
                ILocalVariable[] parameters = method.getParameters();
                boolean foundParamAnnotation = false;
                for (int i = 0; i < parameters.length; i++) {
                    String paramFqn = Signature.toString(parameters[i].getTypeSignature());
                    foundParamAnnotation = false;
                    for (IAnnotation annotation : parameters[i].getAnnotations()) {
                        if (MybatipseConstants.ANNOTATION_PARAM.equals(annotation.getElementName())) {
                            IMemberValuePair[] valuePairs = annotation.getMemberValuePairs();
                            if (valuePairs.length == 1) {
                                IMemberValuePair valuePair = valuePairs[0];
                                String paramValue = (String) valuePair.getValue();
                                paramMap.put(paramValue, paramFqn);
                                foundParamAnnotation = true;
                            }
                        }
                    }
                    if (!foundParamAnnotation && isParamAttrGenerated()) {
                        paramMap.put(parameters[i].getElementName(), paramFqn);
                    }
                    paramMap.put("param" + (i + 1), paramFqn);
                }
                if (parameters.length == 1 && !foundParamAnnotation) {
                    // Statement has a sole unnamed parameter
                    paramMap.clear();
                    putSoleParam(Signature.toString(parameters[0].getTypeSignature()));
                }
            } catch (JavaModelException e) {
                Activator.log(Status.ERROR, "Failed to collect parameters of method " + method.getElementName()
                        + " in " + method.getDeclaringType().getFullyQualifiedName(), e);
            }
        }

        @Override
        public void add(IMethodBinding method, List<SingleVariableDeclaration> params) {
            found = true;
            ITypeBinding[] paramTypes = method.getParameterTypes();
            boolean foundParamAnnotation = false;
            for (int i = 0; i < paramTypes.length; i++) {
                String paramFqn = paramTypes[i].getQualifiedName();
                foundParamAnnotation = false;
                if (MybatipseConstants.TYPE_ROW_BOUNDS.equals(paramFqn))
                    continue;

                IAnnotationBinding[] paramAnnotations = method.getParameterAnnotations(i);
                for (IAnnotationBinding annotation : paramAnnotations) {
                    if (MybatipseConstants.ANNOTATION_PARAM
                            .equals(annotation.getAnnotationType().getQualifiedName())) {
                        IMemberValuePairBinding[] valuePairs = annotation.getAllMemberValuePairs();
                        if (valuePairs.length == 1) {
                            IMemberValuePairBinding valuePairBinding = valuePairs[0];
                            String paramValue = (String) valuePairBinding.getValue();
                            paramMap.put(paramValue, paramFqn);
                            foundParamAnnotation = true;
                        }
                    }
                }
                if (!foundParamAnnotation && isParamAttrGenerated()) {
                    SingleVariableDeclaration param = params.get(i);
                    paramMap.put(param.getName().toString(), paramFqn);
                }
                paramMap.put("param" + (i + 1), paramFqn);
            }
            if (paramTypes.length == 1 && !foundParamAnnotation) {
                // Statement has a sole unnamed parameter
                paramMap.clear();
                putSoleParam(paramTypes[0].getQualifiedName());
            }
        }

        private boolean isParamAttrGenerated() {
            IScopeContext[] contexts;
            if (project != null) {
                contexts = new IScopeContext[] { new ProjectScope(project.getProject()), InstanceScope.INSTANCE,
                        ConfigurationScope.INSTANCE, DefaultScope.INSTANCE };
            } else {
                contexts = new IScopeContext[] { InstanceScope.INSTANCE, ConfigurationScope.INSTANCE,
                        DefaultScope.INSTANCE };
            }
            for (int j = 0; j < contexts.length; ++j) {
                String value = contexts[j].getNode(JavaCore.PLUGIN_ID)
                        .get(JavaCore.COMPILER_CODEGEN_METHOD_PARAMETERS_ATTR, null);
                if (value != null) {
                    value = value.trim();
                    if (!value.isEmpty())
                        return "generate".equals(value.trim());
                }
            }
            return false;
        }

        private void putSoleParam(String paramFqn) {
            try {
                if (NameUtil.isArray(paramFqn)) {
                    paramMap.put("array", paramFqn);
                    return;
                } else {
                    String rawTypeFqn = NameUtil.stripTypeArguments(paramFqn);
                    if (!paramFqn.equals(rawTypeFqn)) {
                        // Parameterized type.
                        final IType rawType = project.findType(rawTypeFqn);
                        final ITypeHierarchy supertypes = rawType.newSupertypeHierarchy(new NullProgressMonitor());
                        final IType mapType = project.findType("java.util.Map");
                        if (supertypes.contains(mapType)) {
                            return;
                        }
                        final IType listType = project.findType("java.util.List");
                        final IType collectionType = project.findType("java.util.Collection");
                        if (supertypes.contains(listType)) {
                            paramMap.put("list", paramFqn);
                        }
                        if (supertypes.contains(collectionType)) {
                            paramMap.put("collection", paramFqn);
                            return;
                        }
                    }
                }
                paramMap.put("_parameter", paramFqn);
            } catch (JavaModelException e) {
                Activator.log(Status.ERROR, "Error occurred while putting sole param.", e);
            }
        }

        @Override
        public boolean isEmpty() {
            return !found;
        }

        public MethodParametersStore(IJavaProject project) {
            super();
            this.project = project;
        }
    }

    public static abstract class MethodMatcher {
        abstract boolean matches(IMethod method) throws JavaModelException;

        abstract boolean matches(IMethodBinding method) throws JavaModelException;

        abstract boolean needExactMatch();

        protected boolean nameMatches(String elementId, String matchString, boolean exactMatch) {
            if (exactMatch) {
                return elementId.equals(matchString);
            } else {
                return matchString.length() == 0
                        || CharOperation.camelCaseMatch(matchString.toCharArray(), elementId.toCharArray());
            }
        }
    }

    public static class MethodNameMatcher extends MethodMatcher {
        private String matchString;

        private boolean exactMatch;

        public MethodNameMatcher(String matchString, boolean exactMatch) {
            super();
            this.matchString = matchString;
            this.exactMatch = exactMatch;
        }

        @Override
        public boolean matches(IMethod method) throws JavaModelException {
            return nameMatches(method.getElementName(), matchString, exactMatch);
        }

        @Override
        public boolean matches(IMethodBinding method) throws JavaModelException {
            return nameMatches(method.getName(), matchString, exactMatch);
        }

        @Override
        boolean needExactMatch() {
            return exactMatch;
        }
    }

    public static class RejectStatementAnnotation extends MethodMatcher {
        private String matchString;

        private boolean exactMatch;

        public RejectStatementAnnotation(String matchString, boolean exactMatch) {
            super();
            this.matchString = matchString;
            this.exactMatch = exactMatch;
        }

        @Override
        public boolean matches(IMethod method) throws JavaModelException {
            for (IAnnotation annotation : method.getAnnotations()) {
                String annotationName = annotation.getElementName();
                if (MybatipseConstants.STATEMENT_ANNOTATIONS.contains(annotationName)) {
                    return false;
                }
            }
            return nameMatches(method.getElementName(), matchString, exactMatch);
        }

        @Override
        public boolean matches(IMethodBinding method) throws JavaModelException {
            for (IAnnotationBinding annotation : method.getAnnotations()) {
                String annotationName = annotation.getAnnotationType().getQualifiedName();
                if (MybatipseConstants.STATEMENT_ANNOTATIONS.contains(annotationName)) {
                    return false;
                }
            }
            return nameMatches(method.getName(), matchString, exactMatch);
        }

        @Override
        boolean needExactMatch() {
            return exactMatch;
        }
    }

    public static class HasSelectAnnotation extends MethodMatcher {
        private String matchString;

        private boolean exactMatch;

        public HasSelectAnnotation(String matchString, boolean exactMatch) {
            super();
            this.matchString = matchString;
            this.exactMatch = exactMatch;
        }

        @Override
        public boolean matches(IMethod method) throws JavaModelException {
            for (IAnnotation annotation : method.getAnnotations()) {
                String annotationName = annotation.getElementName();
                if (MybatipseConstants.ANNOTATION_SELECT.equals(annotationName)
                        || MybatipseConstants.ANNOTATION_SELECT_PROVIDER.equals(annotationName)) {
                    return nameMatches(method.getElementName(), matchString, exactMatch);
                }
            }
            return false;
        }

        @Override
        public boolean matches(IMethodBinding method) throws JavaModelException {
            for (IAnnotationBinding annotation : method.getAnnotations()) {
                String annotationName = annotation.getAnnotationType().getQualifiedName();
                if (MybatipseConstants.ANNOTATION_SELECT.equals(annotationName)
                        || MybatipseConstants.ANNOTATION_SELECT_PROVIDER.equals(annotationName)) {
                    return nameMatches(method.getName(), matchString, exactMatch);
                }
            }
            return false;
        }

        @Override
        boolean needExactMatch() {
            return exactMatch;
        }
    }

    public static class ResultsAnnotationWithId extends MethodMatcher {
        private String matchString;

        private boolean exactMatch;

        public ResultsAnnotationWithId(String matchString, boolean exactMatch) {
            super();
            this.matchString = matchString;
            this.exactMatch = exactMatch;
        }

        @Override
        public boolean matches(IMethod method) throws JavaModelException {
            for (IAnnotation annotation : method.getAnnotations()) {
                String annotationName = annotation.getElementName();
                if (MybatipseConstants.ANNOTATION_RESULTS.equals(annotationName)) {
                    IMemberValuePair[] valuePairs = annotation.getMemberValuePairs();
                    for (IMemberValuePair valuePair : valuePairs) {
                        if ("id".equals(valuePair.getMemberName())) {
                            String resultsId = (String) valuePair.getValue();
                            return nameMatches(resultsId, matchString, exactMatch);
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean matches(IMethodBinding method) throws JavaModelException {
            for (IAnnotationBinding annotation : method.getAnnotations()) {
                String annotationName = annotation.getAnnotationType().getQualifiedName();
                if (MybatipseConstants.ANNOTATION_RESULTS.equals(annotationName)) {
                    IMemberValuePairBinding[] valuePairs = annotation.getAllMemberValuePairs();
                    for (IMemberValuePairBinding valuePair : valuePairs) {
                        if ("id".equals(valuePair.getName())) {
                            String resultsId = (String) valuePair.getValue();
                            return nameMatches(resultsId, matchString, exactMatch);
                        }
                    }
                }
            }
            return false;
        }

        @Override
        boolean needExactMatch() {
            return exactMatch;
        }
    }
}