content: package com.alibaba.qlexpress4.aparser;
  
  import com.alibaba.qlexpress4.utils.QLStringUtils;
  import org.antlr.v4.runtime.Token;
  import org.antlr.v4.runtime.tree.TerminalNode;
  
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  /**
   * Author: DQinYuan
   */
  public class OutVarNamesVisitor extends ScopeStackVisitor {
      
      private final Set<String> outVars = new HashSet<>();
      
      private final ImportManager importManager;
      
      public OutVarNamesVisitor(ImportManager importManager) {
          super(new ExistVarStack(null));
          this.importManager = importManager;
      }
      
      private static class ExistVarStack implements ExistStack {
          private final ExistVarStack parent;
          
          private final Set<String> existVars = new HashSet<>();
          
          private ExistVarStack(ExistVarStack parent) {
              this.parent = parent;
          }
          
          public void add(String varName) {
              existVars.add(varName);
          }
          
          public boolean exist(String varName) {
              if (existVars.contains(varName)) {
                  return true;
              }
              return parent != null && parent.exist(varName);
          }
          
          public ExistVarStack push() {
              return new ExistVarStack(this);
          }
          
          public ExistVarStack pop() {
              return parent;
          }
      }
      
      // handle import
      
      @Override
      public Void visitImportCls(QLParser.ImportClsContext ctx) {
          String importClsPath = ctx.varId()
              .stream()
              .map(QLParser.VarIdContext::getStart)
              .map(Token::getText)
              .collect(Collectors.joining("."));
          importManager.addImport(ImportManager.importCls(importClsPath));
          return null;
      }
      
      @Override
      public Void visitImportPack(QLParser.ImportPackContext ctx) {
          List<QLParser.VarIdContext> importPackPathTokens = ctx.varId();
          boolean isInnerCls =
              !Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
          String importPath = importPackPathTokens.stream()
              .map(QLParser.VarIdContext::getStart)
              .map(Token::getText)
              .collect(Collectors.joining("."));
          importManager
              .addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
          return null;
      }
      
      // collect exist variable name
      
      /**
       * @param ctx int a = 10;
       * @return a
       */
      @Override
      public Void visitVariableDeclaratorId(QLParser.VariableDeclaratorIdContext ctx) {
          QLParser.VarIdContext varIdContext = ctx.varId();
          getStack().add(varIdContext.getText());
          return null;
      }
      
      @Override
      public Void visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
          List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
          String leftVarName = ctx.varId().getText();
          if (pathPartContexts.isEmpty()) {
              getStack().add(leftVarName);
          }
          else if (!getStack().exist(leftVarName)) {
              outVars.add(leftVarName);
          }
          return null;
      }
      
      // selector
      
      @Override
      public Void visitContextSelectExpr(QLParser.ContextSelectExprContext ctx) {
          String variableName = ctx.SelectorVariable_VANME().getText().trim();
          if (!getStack().exist(variableName)) {
              outVars.add(variableName);
          }
          return null;
      }
      
      // exclude function call
      
      @Override
      public Void visitPrimary(QLParser.PrimaryContext ctx) {
          QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
          if (primaryNoFixPathableContext != null) {
              List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
              if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
                  && pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
                  // function call
                  for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
                      pathPartContext.accept(this);
                  }
                  return null;
              }
              if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext) {
                  int restIndex = parseVarIdInPath(((QLParser.VarIdExprContext)primaryNoFixPathableContext).varId(),
                      pathPartContexts);
                  for (int i = restIndex; i < pathPartContexts.size(); i++) {
                      pathPartContexts.get(i).accept(this);
                  }
                  return null;
              }
          }
          
          return super.visitPrimary(ctx);
      }
      
      private int parseVarIdInPath(QLParser.VarIdContext idContext, List<QLParser.PathPartContext> pathPartContexts) {
          List<String> headPartIds = new ArrayList<>();
          String primaryId = idContext.getText();
          headPartIds.add(primaryId);
          for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
              if (pathPartContext instanceof QLParser.FieldAccessContext) {
                  headPartIds.add(parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId()));
              }
              else {
                  break;
              }
          }
          ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
          if (loadPartQualifiedResult.getCls() != null) {
              return loadPartQualifiedResult.getRestIndex() - 1;
          }
          else {
              if (!getStack().exist(primaryId)) {
                  outVars.add(primaryId);
              }
              return 0;
          }
      }
      
      private String parseFieldId(QLParser.FieldIdContext ctx) {
          TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
          if (quoteStringLiteral != null) {
              return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
          }
          return ctx.getStart().getText();
      }
      
      // collect out variables name
      
      @Override
      public Void visitVarIdExpr(QLParser.VarIdExprContext ctx) {
          String varName = ctx.varId().getText();
          if (!getStack().exist(varName)) {
              outVars.add(varName);
          }
          return null;
      }
      
      public Set<String> getOutVars() {
          return outVars;
      }
  }
  