content: package act.cli.bytecode;
  
  /*-
   * #%L
   * ACT Framework
   * %%
   * Copyright (C) 2014 - 2017 ActFramework
   * %%
   * Licensed 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.
   * #L%
   */
  
  import act.app.App;
  import act.cli.CliContext;
  import act.cli.CommandExecutor;
  import act.cli.ReportProgress;
  import act.cli.meta.CommandMethodMetaInfo;
  import act.cli.meta.CommandParamMetaInfo;
  import act.data.annotation.DateFormatPattern;
  import act.data.annotation.Pattern;
  import act.inject.param.CliContextParamLoader;
  import act.inject.param.ParamValueLoaderManager;
  import act.job.JobManager;
  import act.job.TrackableWorker;
  import act.util.*;
  import com.alibaba.fastjson.PropertyNamingStrategy;
  import com.alibaba.fastjson.serializer.SerializeFilter;
  import com.alibaba.fastjson.serializer.SerializerFeature;
  import com.esotericsoftware.reflectasm.MethodAccess;
  import org.osgl.$;
  import org.osgl.util.E;
  
  import java.lang.annotation.Annotation;
  import java.lang.reflect.Method;
  
  /**
   * Implement {@link act.cli.CommandExecutor} using
   * https://github.com/EsotericSoftware/reflectasm
   */
  public class ReflectedCommandExecutor extends CommandExecutor {
  
      private static final Object[] DUMP_PARAMS = new Object[0];
  
      private CommandMethodMetaInfo methodMetaInfo;
      private App app;
      private CliContextParamLoader paramLoaderService;
      private Class[] paramTypes;
      private Class<?> commanderClass;
      private Method method;
      private MethodAccess methodAccess;
      private int commandIndex;
      private int paramCount;
      private boolean isStatic;
      private CliContext.ParsingContext parsingContext;
      private boolean async;
      private ReportProgress reportProgress;
      private String dateFormatPattern;
      private Class<? extends SerializeFilter> filters[];
      private SerializerFeature features[];
      private PropertyNamingStrategy propertyNamingStrategy;
      private boolean enableCircularReferenceDetect = false;
  
      public ReflectedCommandExecutor(CommandMethodMetaInfo methodMetaInfo, App app) {
          this.methodMetaInfo = $.requireNotNull(methodMetaInfo);
          this.app = $.NPE(app);
          this.paramTypes = paramTypes();
          this.paramCount = methodMetaInfo.paramCount();
          this.isStatic = methodMetaInfo.isStatic();
          this.commanderClass = app.classForName(methodMetaInfo.classInfo().className());
          try {
              this.method = commanderClass.getMethod(methodMetaInfo.methodName(), paramTypes);
              this.async = null != ReflectedInvokerHelper.getAnnotation(Async.class, method);
              this.reportProgress = ReflectedInvokerHelper.getAnnotation(ReportProgress.class, method);
              FastJsonFilter filterAnno = ReflectedInvokerHelper.getAnnotation(FastJsonFilter.class, method);
              if (null != filterAnno) {
                  filters = filterAnno.value();
              }
              FastJsonFeature featureAnno = ReflectedInvokerHelper.getAnnotation(FastJsonFeature.class, method);
              if (null != featureAnno) {
                  features = featureAnno.value();
              }
              FastJsonPropertyNamingStrategy propertyNamingStrategyAnno = ReflectedInvokerHelper.getAnnotation(FastJsonPropertyNamingStrategy.class, method);
              if (null != propertyNamingStrategyAnno) {
                  propertyNamingStrategy = propertyNamingStrategyAnno.value();
              }
          } catch (NoSuchMethodException e) {
              throw E.unexpected(e);
          }
          if (!methodMetaInfo.isStatic()) {
              methodAccess = MethodAccess.get(commanderClass);
              commandIndex = methodAccess.getIndex(methodMetaInfo.methodName(), paramTypes);
          } else {
              method.setAccessible(true);
          }
          DateFormatPattern dfp = ReflectedInvokerHelper.getAnnotation(DateFormatPattern.class, method);
          if (null != dfp) {
              this.dateFormatPattern = dfp.value();
          } else {
              Pattern pattern = ReflectedInvokerHelper.getAnnotation(Pattern.class, method);
              if (null != pattern) {
                  this.dateFormatPattern = pattern.value();
              }
          }
          this.paramLoaderService = app.service(ParamValueLoaderManager.class).get(CliContext.class);
          this.enableCircularReferenceDetect = hasAnnotation(EnableCircularReferenceDetect.class);
          this.buildParsingContext();
      }
  
      @Override
      public Object execute(CliContext context) {
          context.handlerMethod(method);
          if (null != dateFormatPattern) {
              context.dateFormatPattern(dateFormatPattern);
          }
          context.fastjsonFeatures(features);
          context.fastjsonPropertyNamingStrategy(propertyNamingStrategy);
          context.fastjsonFilters(filters);
          context.prepare(parsingContext);
          if (enableCircularReferenceDetect) {
              context.enableCircularReferenceDetect();
          }
          paramLoaderService.preParseOptions(method, methodMetaInfo, context);
          final Object cmd = commanderInstance(context);
          final Object[] params = params(cmd, context);
          if (async) {
              final JobManager jobManager = context.app().jobManager();
              final String jobId = jobManager.randomJobId();
              jobManager.prepare(jobId, new TrackableWorker() {
                  @Override
                  protected void run(ProgressGauge progressGauge) {
                      Object o = invoke(cmd, params);
                      jobManager.cacheResult(jobId, o, methodMetaInfo, null);
                  }
              });
              context.setJobId(jobId);
              jobManager.now(jobId);
              String message = "Async job started: " + jobId;
              if (null != reportProgress) {
                  context.println(message);
                  context.attribute(ReportProgress.CTX_ATTR_KEY, reportProgress);
                  return context.progress();
              } else {
                  return message;
              }
          }
          return invoke(cmd, params);
      }
  
      @Override
      protected void releaseResources() {
          app = null;
          commandIndex = 0;
          commanderClass = null;
          method = null;
          methodAccess = null;
          paramTypes = null;
          super.releaseResources();
      }
  
      private Object commanderInstance(CliContext context) {
          if (isStatic) {
              return null;
          }
          String commander = commanderClass.getName();
          Object inst = context.__commanderInstance(commander);
          if (null == inst) {
              inst = paramLoaderService.loadHostBean(commanderClass, context);
              context.__commanderInstance(commander, inst);
          }
          return inst;
      }
  
      private Class<?>[] paramTypes() {
          int paramCount = methodMetaInfo.paramCount();
          Class<?>[] ca = new Class[paramCount];
          if (0 == paramCount) {
              return ca;
          }
          for (int i = 0; i < paramCount; ++i) {
              CommandParamMetaInfo param = methodMetaInfo.param(i);
              String className = param.type().getClassName();
              ca[i] = app.classForName(className);
          }
          return ca;
      }
  
      private Object[] params(Object cmd, CliContext ctx) {
          if (0 == paramCount) {
              return DUMP_PARAMS;
          }
          return paramLoaderService.loadMethodParams(cmd, method, ctx);
      }
  
      private Object invoke(Object commander, Object[] params) {
          Object result;
          if (null != methodAccess) {
              result = methodAccess.invoke(commander, commandIndex, params);
          } else {
              result = $.invokeStatic(method, params);
          }
          return result;
      }
  
      private boolean hasAnnotation(Class<? extends Annotation> type) {
          return method.isAnnotationPresent(type) || commanderClass.isAnnotationPresent(type);
      }
  
      private void buildParsingContext() {
          CliContextParamLoader loader = app.service(ParamValueLoaderManager.class).get(CliContext.class);
          this.parsingContext = loader.buildParsingContext(commanderClass, method, methodMetaInfo);
      }
  
  }
  