content: /*
   * Copyright (C) 2016-2023 ActionTech.
   * based on code by MyCATCopyrightHolder Copyright (c) 2013, OpenCloudDB/MyCAT.
   * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
   */
  package com.actiontech.dble.route.parser.util;
  
  import com.actiontech.dble.config.Versions;
  
  /**
   * @author mycat
   */
  public final class ParseUtil {
      private ParseUtil() {
      }
  
      private static boolean isEOF(char c) {
          return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ';');
      }
  
      public static boolean isEOF(String stmt, int offset) {
          boolean isInhint = false;
          for (; offset < stmt.length(); offset++) {
              if (!isInhint) {
                  if (!ParseUtil.isEOF(stmt.charAt(offset))) {
                      if (stmt.charAt(offset) == '/' &&
                              ((offset + 1) < stmt.length() && stmt.charAt(++offset) == '*')) {
                          isInhint = true;
                      } else {
                          return false;
                      }
                  }
              } else {
                  if (stmt.charAt(offset) == '*' &&
                          ((offset + 1) < stmt.length() && stmt.charAt(++offset) == '/')) {
                      isInhint = false;
                  }
              }
          }
  
          return !isInhint;
      }
  
      public static boolean isMultiEof(String stmt, int offset) {
          for (; offset < stmt.length(); offset++) {
              char c = stmt.charAt(offset);
              if (c == ';') {
                  return true;
              } else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
                  continue;
              } else {
                  return false;
              }
          }
          return true;
      }
  
      public static boolean isSpace(char space) {
          return space == ' ' || space == '\r' || space == '\n' || space == '\t';
      }
  
  
      public static boolean isSpaceOrLeft(char space) {
          return space == ' ' || space == '\r' || space == '\n' || space == '\t' || space == '(';
      }
  
      /**
       * check the tail is unexpected char
       *
       * @param offset
       * @param stmt
       * @return
       */
      public static boolean isErrorTail(int offset, String stmt) {
          for (; offset < stmt.length(); offset++) {
              if (!ParseUtil.isSpace(stmt.charAt(offset))) {
                  return true;
              }
          }
          return false;
      }
  
      public static String parseString(String stmt) {
          int offset = stmt.indexOf('=');
          if (offset != -1) {
              int length = stmt.length();
              while (length > ++offset && stmt.charAt(offset) == ' ') {
                  // do nothing
              }
              return stmt.substring(offset).trim();
          }
          return null;
      }
  
      /**
       * <code>'abc'</code>
       *
       * @param offset stmt.charAt(offset) == first <code>'</code>
       */
      private static String parseString(String stmt, int offset) {
          StringBuilder sb = new StringBuilder();
          loop:
          for (++offset; offset < stmt.length(); ++offset) {
              char c = stmt.charAt(offset);
              if (c == '\\') {
                  switch (c = stmt.charAt(++offset)) {
                      case '0':
                          sb.append('\0');
                          break;
                      case 'b':
                          sb.append('\b');
                          break;
                      case 'n':
                          sb.append('\n');
                          break;
                      case 'r':
                          sb.append('\r');
                          break;
                      case 't':
                          sb.append('\t');
                          break;
                      case 'Z':
                          sb.append((char) 26);
                          break;
                      default:
                          sb.append(c);
                  }
              } else if (c == '\'') {
                  if (offset + 1 < stmt.length() && stmt.charAt(offset + 1) == '\'') {
                      ++offset;
                      sb.append('\'');
                  } else {
                      break loop;
                  }
              } else {
                  sb.append(c);
              }
          }
          return sb.toString();
      }
  
      public static int getSQLId(String stmt, int start) {
          int offset = start;
          while (stmt.length() > offset) {
              if (!isSpace(stmt.charAt(offset))) {
                  break;
              }
              offset++;
          }
          if (stmt.charAt(offset) == '=' && stmt.length() > ++offset) {
              String id = stmt.substring(offset).trim();
              try {
                  return Integer.parseInt(id);
              } catch (NumberFormatException e) {
                  //ignore error
              }
          }
          return -1;
      }
  
      /**
       * <code>"abc"</code>
       *
       * @param offset stmt.charAt(offset) == first <code>"</code>
       */
      private static String parseString2(String stmt, int offset) {
          StringBuilder sb = new StringBuilder();
          loop:
          for (++offset; offset < stmt.length(); ++offset) {
              char c = stmt.charAt(offset);
              if (c == '\\') {
                  switch (c = stmt.charAt(++offset)) {
                      case '0':
                          sb.append('\0');
                          break;
                      case 'b':
                          sb.append('\b');
                          break;
                      case 'n':
                          sb.append('\n');
                          break;
                      case 'r':
                          sb.append('\r');
                          break;
                      case 't':
                          sb.append('\t');
                          break;
                      case 'Z':
                          sb.append((char) 26);
                          break;
                      default:
                          sb.append(c);
                  }
              } else if (c == '"') {
                  if (offset + 1 < stmt.length() && stmt.charAt(offset + 1) == '"') {
                      ++offset;
                      sb.append('"');
                  } else {
                      break loop;
                  }
              } else {
                  sb.append(c);
              }
          }
          return sb.toString();
      }
  
      /**
       * <code>AS `abc`</code>
       *
       * @param offset stmt.charAt(offset) == first <code>`</code>
       */
      private static String parseIdentifierEscape(String stmt, int offset) {
          StringBuilder sb = new StringBuilder();
          loop:
          for (++offset; offset < stmt.length(); ++offset) {
              char c = stmt.charAt(offset);
              if (c == '`') {
                  if (offset + 1 < stmt.length() && stmt.charAt(offset + 1) == '`') {
                      ++offset;
                      sb.append('`');
                  } else {
                      break loop;
                  }
              } else {
                  sb.append(c);
              }
          }
          return sb.toString();
      }
  
      /**
       * @param aliasIndex for <code>AS id</code>, index of 'i'
       */
      public static String parseAlias(String stmt, final int aliasIndex) {
          if (aliasIndex < 0 || aliasIndex >= stmt.length()) {
              return null;
          }
          switch (stmt.charAt(aliasIndex)) {
              case '\'':
                  return parseString(stmt, aliasIndex);
              case '"':
                  return parseString2(stmt, aliasIndex);
              case '`':
                  return parseIdentifierEscape(stmt, aliasIndex);
              default:
                  int offset = aliasIndex;
                  for (; offset < stmt.length() && CharTypes.isIdentifierChar(stmt.charAt(offset)); ++offset) {
                      //do nothing
                  }
                  return stmt.substring(aliasIndex, offset);
          }
      }
  
      /**
       * return the index of the end of hint in stmt
       *
       * @param stmt
       * @param offset
       * @return
       */
      public static int comment(String stmt, int offset) {
          int len = stmt.length();
          int n = offset;
          switch (stmt.charAt(n)) {
              case '/':
                  if (len > ++n && stmt.charAt(n++) == '*' && len > n + 1) {
                      for (int i = n; i < len; ++i) {
                          if (stmt.charAt(i) == '*') {
                              int m = i + 1;
                              if (len > m && stmt.charAt(m) == '/') {
                                  return m;
                              }
                          }
                      }
                  }
                  break;
              case '#':
                  for (int i = n + 1; i < len; ++i) {
                      if (stmt.charAt(i) == '\n') {
                          return i;
                      }
                  }
                  break;
              default:
                  break;
          }
          return offset;
      }
  
  
      /**
       * return the index of the end of hint in stmt
       * return -1 if the comment is a dble hint
       *
       * @param stmt
       * @param offset
       * @return
       */
      public static int commentHint(String stmt, int offset) {
          int len = stmt.length();
          int n = offset;
          char[] annotation = Versions.ANNOTATION_NAME.toCharArray();
          switch (stmt.charAt(n)) {
              case '/':
                  if (len > ++n && stmt.charAt(n++) == '*' && len > n + 1) {
                      if (stmt.length() > n + 6) {
                          int x = n;
                          char ch1 = stmt.charAt(x);
                          if ((ch1 == '!' || ch1 == '#')) {
                              boolean flag = true;
                              for (char y : annotation) {
                                  if (stmt.charAt(++x) != y) {
                                      flag = false;
                                      break;
                                  }
                              }
                              if (flag) {
                                  return -1;
                              }
                          }
                      }
                      for (int i = n; i < len; ++i) {
                          if (stmt.charAt(i) == '*') {
                              int m = i + 1;
                              if (len > m && stmt.charAt(m) == '/') {
                                  return m;
                              }
                          }
                      }
                  }
                  break;
              default:
                  break;
          }
          return offset;
      }
  
      /**
       * return whether beginning with double dash(--) in stmt
       *
       * @param stmt
       * @param offset
       * @return
       */
      public static int commentDoubleDash(String stmt, int offset) {
          int len = stmt.length();
          boolean inComment = false;
          while (len > offset) {
              char c = stmt.charAt(offset);
              if (!inComment && c == '-') {
                  if (len > ++offset && stmt.charAt(offset) == '-') {
                      if (len == offset + 1 || (stmt.charAt(++offset) != ' ' &&
                              stmt.charAt(offset) != '\t')) {
                          break;
                      }
                      inComment = true;
                  } else {
                      return len;
                  }
              } else if (c == '\n' || len == offset + 1) {
                  break;
              } else {
                  ++offset;
              }
          }
          return offset;
      }
  
      public static int move(String stmt, int offset, int length) {
          int i = offset;
          for (; i < stmt.length(); ++i) {
              switch (stmt.charAt(i)) {
                  case ' ':
                  case '\t':
                  case '\r':
                  case '\n':
                      continue;
                  case '/':
                  case '#':
                      i = comment(stmt, i);
                      continue;
                  default:
                      return i + length;
              }
          }
          return i;
      }
  
      public static boolean compare(String s, int offset, char[] keyword) {
          if (s.length() >= offset + keyword.length) {
              for (int i = 0; i < keyword.length; ++i, ++offset) {
                  if (Character.toUpperCase(s.charAt(offset)) != keyword[i]) {
                      return false;
                  }
              }
              return true;
          }
          return false;
      }
  
  
      /**
       * compare the prefix use keyword string.
       * <p>
       * Distinct from others,this implementation doesn't need {@code String.toCharArray()} to pass  {@code keyword} argument.It prevent memory copy.
       *
       * @param origin
       * @param offsetInS
       * @param keyword   must upper case
       * @return
       */
      public static boolean compare(String origin, int offsetInS, String keyword) {
          assert keyword.toUpperCase().equals(keyword);
          if (origin.length() >= offsetInS + keyword.length()) {
              for (int i = 0; i < keyword.length(); ++i, offsetInS++) {
                  if (Character.toUpperCase(origin.charAt(offsetInS)) != keyword.charAt(i)) {
                      return false;
                  }
              }
              return true;
          }
          return false;
      }
  
  
      public static int findNextBreak(String sql) {
          return findNextBreak(sql, false);
      }
  
      /**
       * @param sql
       * @param inProcedure
       * @return
       */
      private static int findNextBreak(String sql, boolean inProcedure) {
          //is the char in apostrophe
          boolean inApostrophe = false;
          //is the char after procedure begin
          boolean procedureBegin = false;
          String bodyLeft = null;
          for (int i = 0; i < sql.length(); i++) {
              char c = sql.charAt(i);
              switch (c) {
                  case '\\':
                      i++;
                      break;
                  case '\'':
                  case '\"':
                      if (!inApostrophe) {
                          inApostrophe = true;
                          bodyLeft = String.valueOf(c);
                      } else {
                          String rightTest = String.valueOf(c);
                          if (rightTest.equals(bodyLeft)) {
                              inApostrophe = false;
                          }
                      }
                      break;
                  case ';':
                      if (!inApostrophe && !procedureBegin) {
                          return i;
                      }
                      break;
                  case 'p':
                  case 'P':
                      if (!inApostrophe) {
                          i = getProcedureEndPos(sql, i);
                      }
                      break;
                  case 'F':
                  case 'f':
                      if (!inApostrophe) {
                          i = getFinctionEndPos(sql, i);
                      }
                      break;
                  case 'B':
                      if (inProcedure && !inApostrophe && isBegin(sql, i)) {
                          procedureBegin = true;
                          i = i + 4;
                      }
                      break;
                  case 'E':
                      if (!inApostrophe && procedureBegin && isEnd(sql, i)) {
                          procedureBegin = false;
                          i = i + 2;
                      }
                      break;
                  default:
              }
          }
          return sql.length() - 1;
      }
  
      private static boolean isBegin(String stmt, int offset) {
          if (offset <= 0 || !ParseUtil.isSpace(stmt.charAt(offset - 1)) || stmt.length() <= offset + "EGIN ".length()) {
              return false;
          }
          char c1 = stmt.charAt(++offset);
          char c2 = stmt.charAt(++offset);
          char c3 = stmt.charAt(++offset);
          char c4 = stmt.charAt(++offset);
          char c5 = stmt.charAt(++offset);
          return c1 == 'E' &&
                  c2 == 'G' &&
                  c3 == 'I' &&
                  c4 == 'N' &&
                  ParseUtil.isSpace(c5);
      }
  
      private static boolean isEnd(String stmt, int offset) {
          if (offset <= 0 || !ParseUtil.isSpace(stmt.charAt(offset - 1)) || stmt.length() <= offset + "ND".length()) {
              return false;
          }
          char c1 = stmt.charAt(++offset);
          char c2 = stmt.charAt(++offset);
          if (c1 == 'N' && c2 == 'D') {
              offset++;
              for (; offset < stmt.length(); offset++) {
                  char tmp = stmt.charAt(offset);
                  if (ParseUtil.isSpace(tmp)) {
                      continue;
                  }
                  return tmp == ';';
              }
              return true;
          }
          return false;
      }
  
      private static int getFinctionEndPos(String stmt, int offset) {
          int toffset = offset;
          if (stmt.length() > offset + "UNCTION ".length()) {
              char c1 = stmt.charAt(++toffset);
              char c2 = stmt.charAt(++toffset);
              char c3 = stmt.charAt(++toffset);
              char c4 = stmt.charAt(++toffset);
              char c5 = stmt.charAt(++toffset);
              char c6 = stmt.charAt(++toffset);
              char c7 = stmt.charAt(++toffset);
              if ((c1 == 'u' || c1 == 'U') &&
                      (c2 == 'n' || c2 == 'N') &&
                      (c3 == 'c' || c3 == 'C') &&
                      (c4 == 't' || c4 == 'T') &&
                      (c5 == 'i' || c5 == 'I') &&
                      (c6 == 'o' || c6 == 'O') &&
                      (c7 == 'n' || c7 == 'N')) {
                  String testSql = stmt.substring(++toffset).toUpperCase();
                  //offset + length  -1 for checking ';' outside
                  return toffset - 1 + findNextBreak(testSql, true);
              }
          }
          return offset;
      }
  
      private static int getProcedureEndPos(String stmt, int offset) {
          int toffset = offset;
          if (stmt.length() > offset + "ROCEDURE ".length()) {
              char c1 = stmt.charAt(++toffset);
              char c2 = stmt.charAt(++toffset);
              char c3 = stmt.charAt(++toffset);
              char c4 = stmt.charAt(++toffset);
              char c5 = stmt.charAt(++toffset);
              char c6 = stmt.charAt(++toffset);
              char c7 = stmt.charAt(++toffset);
              char c8 = stmt.charAt(++toffset);
              if ((c1 == 'r' || c1 == 'R') &&
                      (c2 == 'o' || c2 == 'O') &&
                      (c3 == 'c' || c3 == 'C') &&
                      (c4 == 'e' || c4 == 'E') &&
                      (c5 == 'd' || c5 == 'D') &&
                      (c6 == 'u' || c6 == 'U') &&
                      (c7 == 'r' || c7 == 'R') &&
                      (c8 == 'e' || c8 == 'E')) {
                  String testSql = stmt.substring(++toffset).toUpperCase();
                  //offset + length  -1 for checking ';' outside
                  return toffset - 1 + findNextBreak(testSql, true);
              }
          }
          return offset;
      }
  
      public static int skipSpace(String stmt, int offset) {
          do {
              if (isSpace(stmt.charAt(offset)))
                  continue;
              break;
          } while (stmt.length() > ++offset);
          return offset;
      }
  
      public static int skipSpaceUtil(String stmt, int offset, char util) {
          int len = stmt.length();
          char tmp;
          do {
              tmp = stmt.charAt(offset);
              if (!isSpace(tmp)) {
                  if (tmp == util) {
                      return offset;
                  } else {
                      return -1;
                  }
              }
          } while (len > ++offset);
          return -1;
      }
  
  }
  