content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.
   */
  package org.apache.weex.utils;
  
  import android.support.annotation.NonNull;
  
  import java.util.LinkedHashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Parser for function like "rotate(30 ) transform(50 , 20)".
   * This class will translate the raw string presentation of a group of function(s) to give type
   * according to the {@link FunctionParser.Mapper}
   */
  public class FunctionParser<K, V> {
  
    public static final char SPACE = ' ';
  
    private Mapper<K, V> mapper;
    private Lexer lexer;
  
    /**
     * Construct a function parser
     * @param source the raw string representation of a group of function(s)
     * @param mapper the mapping rule between string and corresponding type of object.
     */
    public FunctionParser(@NonNull String source, @NonNull Mapper<K, V> mapper) {
      this.lexer = new Lexer(source);
      this.mapper = mapper;
    }
  
    /**
     * Start to parse the raw string. The result will be stored in a sorted map where the order
     * is defined by the function order in the raw string.
     * @return
     */
    public LinkedHashMap<K, V> parse() {
      lexer.moveOn();
      return definition();
    }
  
    private LinkedHashMap<K, V> definition() {
      LinkedHashMap<K, V> result = new LinkedHashMap<>();
      do {
        result.putAll(function());
      } while (lexer.getCurrentToken() == Token.FUNC_NAME);
      return result;
    }
  
    private Map<K, V> function() {
      List<String> list = new LinkedList<>();
      String functionName = match(Token.FUNC_NAME);
      match(Token.LEFT_PARENT);
      list.add(match(Token.PARAM_VALUE));
      while (lexer.getCurrentToken() == Token.COMMA) {
        match(Token.COMMA);
        list.add(match(Token.PARAM_VALUE));
      }
      match(Token.RIGHT_PARENT);
      return mapper.map(functionName, list);
    }
  
    private String match(Token token) {
      try {
        if (token == lexer.getCurrentToken()) {
          String value = lexer.getCurrentTokenValue();
          lexer.moveOn();
          return value;
        }
      } catch (Exception e) {
        WXLogUtils.e(token + "Token doesn't match" + lexer.source);
      }
      return "";
    }
  
    private enum Token {
      FUNC_NAME, PARAM_VALUE, LEFT_PARENT, RIGHT_PARENT, COMMA
    }
  
    public interface Mapper<K, V> {
  
      /**
       * Map one function to a specified type of object
       * @param functionName the name of the raw function.
       * @param raw the list of parameter of the raw function
       * @return the expected mapping relationship, where the key in the map is the same as the
       * key in the return value of {{@link #parse()}},
       * and the value in the map is the type of object that expected by user.
       */
      Map<K, V> map(String functionName, List<String> raw);
    }
  
    private static class WXInterpretationException extends RuntimeException {
  
      private WXInterpretationException(String msg) {
        super(msg);
      }
    }
  
    /**
     * Lexer for the parser. For now,  digit, alphabet, '(', ')', '.', ',', '+', '-' and '%' is
     * valid character, and '(', ')', ',', parameter value and function name is valid token.
     * Parameter value is defined as "(?i)[\+-]?[0-9]+(\.[0-9]+)?(%||deg||px)?" while function
     * name is defined as "[a-zA-Z]+".
     *
     * The Lexer can also be expressed using the following EBNF format.
     * <ul>
     *   <li>definition = {function};</li>
     *   <li>function = name, "(", value, { ",", value } , ")";</li>
     *   <li>name = character, {character};</li>
     *   <li>value = identifier, {identifier};</li>
     *   <li>identifier = character | "." | "%" | "+" | "-";</li>
     *   <li>character = digit | letter;</li>
     *   <li>digit =  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;</li>
     *   <li>letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" |
     *   "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" |
     *   "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" |
     *   "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ;</li>
     * </ul>
     */
    private static class Lexer {
  
      private static final String LEFT_PARENT = "(";
      private static final String RIGHT_PARENT = ")";
      private static final String COMMA = ",";
      private static final char A_LOWER = 'a';
      private static final char Z_LOWER = 'z';
      private static final char A_UPPER = 'A';
      private static final char Z_UPPER = 'Z';
      private static final char ZERO = '0';
      private static final char NINE = '9';
      private static final char DOT = '.';
      private static final char MINUS = '-';
      private static final char PLUS = '+';
      private String source;
      private Token current;
      private String value;
      private int pointer = 0;
  
      private Lexer(String source) {
        this.source = source;
      }
  
      private Token getCurrentToken() {
        return current;
      }
  
      private String getCurrentTokenValue() {
        return value;
      }
  
      private boolean moveOn() {
        int start = pointer;
        char curChar;
        while (pointer < source.length()) {
          curChar = source.charAt(pointer);
          if (curChar == SPACE) {
            if (start == pointer++) {
              start++;
            } else {
              break;
            }
          } else if (isCharacterOrDigit(curChar) || curChar == DOT
                  || curChar == WXUtils.PERCENT || curChar == MINUS || curChar == PLUS) {
            pointer++;
          } else {
            if (start == pointer) {
              pointer++;
            }
            break;
          }
        }
        if (start != pointer) {
          String symbol = source.substring(start, pointer);
          moveOn(symbol);
          return true;
        } else {
          current = null;
          value = null;
          return false;
        }
      }
  
      private void moveOn(String token) {
        if (LEFT_PARENT.equals(token)) {
          current = Token.LEFT_PARENT;
          value = LEFT_PARENT;
        } else if (RIGHT_PARENT.equals(token)) {
          current = Token.RIGHT_PARENT;
          value = RIGHT_PARENT;
        } else if (COMMA.equals(token)) {
          current = Token.COMMA;
          value = COMMA;
        } else if (isFuncName(token)) {
          current = Token.FUNC_NAME;
          value = token;
        } else {
          current = Token.PARAM_VALUE;
          value = token;
        }
      }
  
      private boolean isFuncName(CharSequence funcName) {
        char letter;
        for (int i = 0; i < funcName.length(); i++) {
          letter = funcName.charAt(i);
          if (!((A_LOWER <= letter && letter <= Z_LOWER) ||
                  (A_UPPER <= letter && letter <= Z_UPPER) ||
                  letter == MINUS)) {
            return false;
          }
        }
        return true;
      }
  
      private boolean isCharacterOrDigit(char letter) {
        return (ZERO <= letter && letter <= NINE) || (A_LOWER <= letter && letter <= Z_LOWER) ||
                (A_UPPER <= letter && letter <= Z_UPPER);
      }
    }
  
  }
  