content: package com.bj58.argo.internal;
  
  import com.bj58.argo.Argo;
  import com.bj58.argo.ArgoException;
  import com.bj58.argo.ActionResult;
  import com.bj58.argo.annotations.GET;
  import com.bj58.argo.annotations.POST;
  import com.bj58.argo.annotations.Path;
  import com.bj58.argo.ArgoController;
  import com.bj58.argo.interceptor.PostInterceptor;
  import com.bj58.argo.interceptor.PreInterceptor;
  import com.bj58.argo.interceptor.PostInterceptorAnnotation;
  import com.bj58.argo.interceptor.PreInterceptorAnnotation;
  import com.bj58.argo.route.RouteBag;
  import com.bj58.argo.thirdparty.AnnotationUtils;
  import com.bj58.argo.thirdparty.AntPathMatcher;
  import com.bj58.argo.thirdparty.PathMatcher;
  import com.bj58.argo.utils.ClassUtils;
  import com.bj58.argo.utils.Pair;
  import com.bj58.argo.utils.converter.ConverterFactory;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.Lists;
  
  import java.lang.annotation.Annotation;
  import java.lang.reflect.Method;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  public class ActionInfo {
  
      private final ControllerInfo controllerInfo;
      private final Method method;
      private final Argo argo;
  
      /**
       * path匹配模式，联合了Controller上path，并去除后置"/",
       */
      private final String pathPattern;
  
      /**
       * Http Method GET
       */
      private final boolean isGet;
  
      /**
       * http method POST
       */
      private final boolean isPost;
  
      /**
       * 方法上所有参数名，按顺序排列
       */
      private final List<String> paramNames;
  
      /**
       * 方法上所有参数类型，按顺序排列
       */
      private final List<Class<?>> paramTypes;
  
      /**
       * 所有annotation，包括并覆盖controller上的annotation，
       */
      private final Set<Annotation> annotations;
  
      /**
       * 所有前置拦截器,按拦截器的order升序排列
       */
      private final List<PreInterceptor> preInterceptors;
  
      /**
       * 所有后置拦截器，按拦截器的order降序排列
       */
      private final List<PostInterceptor> postInterceptors;
  
      /**
       * 匹配的优先级
       */
      private final int order;
  
      /**
       * 是否是模版匹配
       */
      private final boolean isPattern;
  
      /**
       * 利用Ant匹配模型处理url
       */
      private final PathMatcher pathMatcher = new AntPathMatcher();
  
      private final ConverterFactory converter = new ConverterFactory();
  
      public ActionInfo(ControllerInfo controllerInfo, Method method, Argo argo) {
          this.controllerInfo = controllerInfo;
          this.method = method;
          this.argo = argo;
  
          Path path = AnnotationUtils.findAnnotation(method, Path.class);
          this.order = path.order();
  
          this.pathPattern = simplyPathPattern(controllerInfo, path);
  
          this.paramTypes = ImmutableList.copyOf(method.getParameterTypes());
          this.paramNames = ImmutableList.copyOf(ClassUtils.getMethodParamNames(controllerInfo.getClazz(), method));
  
          // 计算匹配的优先级,精确匹配还是模版匹配
          isPattern = pathMatcher.isPattern(pathPattern)
                  || paramTypes.size() > 0;
  
          Pair<Boolean, Boolean> httpMethodPair = pickupHttpMethod(controllerInfo, method);
          this.isGet = httpMethodPair.getKey();
          this.isPost = httpMethodPair.getValue();
  
          annotations = collectAnnotations(controllerInfo, method);
  
          // 拦截器
          List<InterceptorInfo> interceptorInfoList = findInterceptors();
          preInterceptors = getPreInterceptorList(interceptorInfoList);
          postInterceptors = getPostInterceptorList(interceptorInfoList);
      }
  
      public String getPathPattern() {
          return pathPattern;
      }
  
  
      ArgoController controller() {
          return controllerInfo.getController();
      }
  
      Method method() {
          return method;
      }
  
      public boolean isGet() {
          return isGet;
      }
  
      public boolean isPost() {
          return isPost;
      }
  
      public List<Class<?>> getParamTypes() {
          return paramTypes;
      }
  
      public List<String> getParamNames() {
          return paramNames;
      }
  
      public Set<Annotation> annotations() {
          return annotations;
      }
  
      public boolean isPattern() {
          return isPattern;
      }
  
      public int getOrder() {
          return order;
      }
  
      public List<PreInterceptor> getPreInterceptors() {
          return preInterceptors;
      }
  
      public List<PostInterceptor> getPostInterceptors() {
          return postInterceptors;
      }
  
      PathMatcher getPathMatcher() {
          return pathMatcher;
      }
  
      Argo getArgo() {
          return argo;
      }
  
      ConverterFactory getConverter() {
          return converter;
      }
  
      boolean match(RouteBag bag, Map<String, String> uriTemplateVariables) {
          return getPathMatcher().doMatch(getPathPattern(), bag.getSimplyPath(), true, uriTemplateVariables);
      }
  
      boolean matchHttpMethod(RouteBag bag) {
  
          return (bag.isGet() && isGet())
                  || (bag.isPost() && isPost());
      }
  
      ActionResult invoke(Map<String, String> urlParams) {
          Object[] param = new Object[getParamTypes().size()];
          for(int index = 0; index < getParamNames().size(); index++){
              String paramName = getParamNames().get(index);
              Class<?> clazz = getParamTypes().get(index);
  
              String v = urlParams.get(paramName);
  
              if (v == null)
                  throw ArgoException.newBuilder("Invoke exception:")
                          .addContextVariable(paramName, "null")
                          .build();
  
              // fixMe: move to init
              if(!getConverter().canConvert(clazz))
                  throw ArgoException.newBuilder("Invoke cannot convert parameter.")
                          .addContextVariable(paramName, "expect " + clazz.getName() + " but value is " + v)
                          .build();
  
              param[index] = getConverter().convert(clazz, v);
          }
  
          try {
              Object result = method().invoke(controller(), param);
              return ActionResult.class.cast(result);
          } catch (Exception e) {
              throw ArgoException.newBuilder("invoke exception.", e)
                      .addContextVariables(urlParams)
                      .build();
          }
      }
  
  
      String simplyPathPattern(ControllerInfo controllerInfo, Path path) {
          String originPathPattern = combinePathPattern(controllerInfo, path);
          return simplyPathPattern(originPathPattern);
      }
  
      /**
       *收集方法上所有Annotation，包括Controller上标志
       * @param controllerInfo controller信息
       * @param method 方法
       * @return 方法上所有Annotation，包括Controller
       */
      ImmutableSet<Annotation> collectAnnotations(ControllerInfo controllerInfo, Method method) {
          return ImmutableSet.<Annotation>builder()
                  .add(method.getAnnotations())
                  .addAll(controllerInfo.getAnnotations())
                  .build();
  
      }
  
      Pair<Boolean, Boolean> pickupHttpMethod(ControllerInfo controllerInfo, Method method) {
          boolean isGet = AnnotationUtils.findAnnotation(method, GET.class) != null;
          boolean isPost = AnnotationUtils.findAnnotation(method, POST.class) != null;
  
          if (!isGet && !isPost) {
              isGet = controllerInfo.isGet();
              isPost = controllerInfo.isPost();
          }
  
          return Pair.build(isGet, isPost);
  
      }
  
      private String simplyPathPattern(String combinedPattern) {
          if (combinedPattern.length() > 1 && combinedPattern.endsWith("/"))
              combinedPattern = combinedPattern.substring(0, combinedPattern.length() - 2);
          return combinedPattern;
      }
  
      private String combinePathPattern(ControllerInfo controllerInfo, Path path) {
          String pathPattern = path.value();
  
          String controllerPattern = controllerInfo.getPathUrl();
          return getPathMatcher().combine(controllerPattern, pathPattern);
      }
  
  
  
      List<PreInterceptor> getPreInterceptorList( List<InterceptorInfo> interceptorInfoList) {
  
          ImmutableList.Builder<PreInterceptor> builder = ImmutableList.builder();
  
          for(InterceptorInfo interceptorInfo : interceptorInfoList) {
              PreInterceptor preInterceptor = interceptorInfo.getPreInterceptor();
              if (preInterceptor != null)
                  builder.add(preInterceptor);
          }
  
          return builder.build();
      }
  
      List<PostInterceptor> getPostInterceptorList( List<InterceptorInfo> interceptorInfoList) {
  
          ImmutableList.Builder<PostInterceptor> builder = ImmutableList.builder();
  
          for(InterceptorInfo interceptorInfo : interceptorInfoList) {
              PostInterceptor postInterceptor = interceptorInfo.getPostInterceptor();
              if (postInterceptor != null)
                  builder.add(postInterceptor);
          }
  
          //反转，对于post先执行排序高的，再执行排序低的
          return builder.build().reverse();
      }
  
      List<InterceptorInfo> findInterceptors() {
  
          List<InterceptorInfo> interceptorInfoList = Lists.newArrayList();
  
          for(Annotation ann : this.annotations()) {
  
              InterceptorInfo interceptorInfo = findInterceptorInfo(ann);
              if (interceptorInfo == null)
                  continue;
  
              interceptorInfoList = merge(interceptorInfoList, interceptorInfo);
          }
  
          return interceptorInfoList;
      }
  
      private InterceptorInfo findInterceptorInfo(Annotation ann) {
          PreInterceptorAnnotation preA = AnnotationUtils.findAnnotation(ann.getClass(), PreInterceptorAnnotation.class);
          PostInterceptorAnnotation postA = AnnotationUtils.findAnnotation(ann.getClass(), PostInterceptorAnnotation.class);
          if (preA == null && postA == null)
              return null;
  
          Object orderObject = AnnotationUtils.getValue(ann, "order");
  
          int order = orderObject == null ? 100
                  : (Integer)orderObject;   // xxx: maybe throw exception.
  
          PreInterceptor preInterceptor = (preA == null ? null : getArgo().getInstance(preA.value()));
          PostInterceptor postInterceptor = (postA == null ? null : getArgo().getInstance(postA.value()));
  
          return new InterceptorInfo(ann, order, preInterceptor, postInterceptor);
      }
  
      private List<InterceptorInfo> merge(List<InterceptorInfo> interceptorInfoList, InterceptorInfo interceptorInfo) {
  
          int position = interceptorInfoList.size();
  
          for (int index = 0; index < interceptorInfoList.size(); index++) {
              InterceptorInfo item = interceptorInfoList.get(index);
              // 如果annotation已存在，则忽略（先处理方法的Annotation，再处理类的Annotation）
              if (item.sample(interceptorInfo))
                  return interceptorInfoList;
  
              if(item.getOrder() > interceptorInfo.getOrder()) {
                  position = index;
              }
          }
  
          interceptorInfoList.add(position, interceptorInfo);
          return interceptorInfoList;
      }
  
      private class InterceptorInfo {
          private final Annotation annotation;
          private final PreInterceptor preInterceptor;
          private final PostInterceptor postInterceptor;
          private final int order;
  
          private InterceptorInfo(Annotation annotation, int order, PreInterceptor preInterceptor, PostInterceptor postInterceptor) {
              this.annotation = annotation;
              this.order = order;
              this.preInterceptor = preInterceptor;
              this.postInterceptor = postInterceptor;
          }
  
          private Annotation getAnnotation() {
              return annotation;
          }
  
          public PreInterceptor getPreInterceptor() {
              return preInterceptor;
          }
  
          public PostInterceptor getPostInterceptor() {
              return postInterceptor;
          }
  
          public int getOrder() {
              return order;
          }
  
          public boolean sample(InterceptorInfo other) {
              return this.getAnnotation().annotationType() == other.getAnnotation().annotationType();
          }
      }
  
  
  }
  