content: /*
   * Copyright 1999-2012 Alibaba Group.
   *  
   * 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.
   */
  /**
   * (created at 2011-8-3)
   */
  package com.alibaba.cobar.route.hint;
  
  import java.sql.SQLSyntaxErrorException;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  
  import com.alibaba.cobar.parser.util.Pair;
  import com.alibaba.cobar.route.RouteResultsetNode;
  
  /**
   * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a>
   */
  public final class CobarHint {
  
      // index start from 1
      // /*!cobar: $dataNodeId=0.0, $table='offer'*/
      // /*!cobar: $dataNodeId=[0,1,5.2], $table='offer'*/
      // /*!cobar: $partitionOperand=('member_id'='m1'), $table='offer'*/
      // /*!cobar: $partitionOperand=('member_id'=['m1','m2']), $table='offer',
      // $replica=2*/
      // /*!cobar: $partitionOperand=(['offer_id','group_id']=[123,'3c']),
      // $table='offer'*/
      // /*!cobar:
      // $partitionOperand=(['offer_id','group_id']=[[123,'3c'],[234,'food']]),
      // $table='offer'*/
  
      public static final String COBAR_HINT_PREFIX = "/*!cobar:";
      private static final Map<String, HintParser> HINT_PARSERS = new HashMap<String, HintParser>();
      {
          HINT_PARSERS.put("table", new SimpleHintParser());
          HINT_PARSERS.put("replica", new SimpleHintParser());
          HINT_PARSERS.put("dataNodeId", new DataNodeHintParser());
          HINT_PARSERS.put("partitionOperand", new PartitionOperandHintParser());
      }
  
      private int replica = RouteResultsetNode.DEFAULT_REPLICA_INDEX;
      private String table;
      private List<Pair<Integer, Integer>> dataNodes;
      private Pair<String[], Object[][]> partitionOperand;
  
      /**
       * @param offset index of first char of {@link #COBAR_HINT_PREFIX}
       */
      public static CobarHint parserCobarHint(String sql, int offset) throws SQLSyntaxErrorException {
          CobarHint hint = new CobarHint();
          hint.currentIndex = offset + COBAR_HINT_PREFIX.length();
          hint.parse(sql);
          return hint;
      }
  
      /**
       * @return String[] in upper-case
       */
      public Pair<String[], Object[][]> getPartitionOperand() {
          return partitionOperand;
      }
  
      public void setPartitionOperand(Pair<String[], Object[][]> partitionOperand) {
          String[] columns = partitionOperand.getKey();
          if (columns == null) {
              this.partitionOperand = partitionOperand;
          } else {
              String[] colUp = new String[columns.length];
              for (int i = 0; i < columns.length; ++i) {
                  colUp[i] = columns[i].toUpperCase();
              }
              this.partitionOperand = new Pair<String[], Object[][]>(colUp, partitionOperand.getValue());
          }
      }
  
      public List<Pair<Integer, Integer>> getDataNodes() {
          return dataNodes;
      }
  
      public void addDataNode(Integer dataNodeIndex, Integer replica) {
          if (dataNodeIndex == null) {
              throw new IllegalArgumentException("data node index is null");
          }
          if (replica == RouteResultsetNode.DEFAULT_REPLICA_INDEX || replica < 0) {
              replica = null;
          }
          if (dataNodes == null) {
              dataNodes = new LinkedList<Pair<Integer, Integer>>();
          }
          dataNodes.add(new Pair<Integer, Integer>(dataNodeIndex, replica));
      }
  
      /**
       * @return upper case
       */
      public String getTable() {
          return table;
      }
  
      public void setTable(String table) {
          this.table = table.toUpperCase();
      }
  
      public int getReplica() {
          return replica;
      }
  
      public void setReplica(int replica) {
          this.replica = replica;
      }
  
      private void parse(String sql) throws SQLSyntaxErrorException {
          cobarHint: for (;;) {
              skip: for (;;) {
                  switch (sql.charAt(currentIndex)) {
                  case '$':
                      break skip;
                  case '*':
                      currentIndex += 2;
                      break cobarHint;
                  default:
                      ++currentIndex;
                  }
              }
              int hintNameEnd = sql.indexOf('=', currentIndex);
              String hintName = sql.substring(currentIndex + 1, hintNameEnd).trim();
              HintParser hintParser = HINT_PARSERS.get(hintName);
              if (hintParser != null) {
                  currentIndex = 1 + sql.indexOf('=', hintNameEnd);
                  hintParser.process(this, hintName, sql);
              } else {
                  throw new SQLSyntaxErrorException("unrecognized hint name: ${" + hintName + "}");
              }
          }
          outputSql = sql.substring(currentIndex);
      }
  
      private String outputSql;
      private int currentIndex;
  
      public int getCurrentIndex() {
          return currentIndex;
      }
  
      public CobarHint increaseCurrentIndex() {
          ++currentIndex;
          return this;
      }
  
      public void setCurrentIndex(int ci) {
          currentIndex = ci;
      }
  
      public String getOutputSql() {
          return outputSql;
      }
  }
  