content: package com.alibaba.qlexpress4.runtime.operator.base;
  
  import java.util.Collection;
  import java.util.Objects;
  import java.util.Optional;
  
  import com.alibaba.qlexpress4.QLOptions;
  import com.alibaba.qlexpress4.exception.ErrorReporter;
  import com.alibaba.qlexpress4.exception.QLErrorCodes;
  import com.alibaba.qlexpress4.exception.QLRuntimeException;
  import com.alibaba.qlexpress4.runtime.LeftValue;
  import com.alibaba.qlexpress4.runtime.Value;
  import com.alibaba.qlexpress4.runtime.data.DataValue;
  import com.alibaba.qlexpress4.runtime.operator.BinaryOperator;
  import com.alibaba.qlexpress4.runtime.operator.number.NumberMath;
  
  /**
   * @author bingo
   */
  public abstract class BaseBinaryOperator implements BinaryOperator {
      protected boolean isSameType(Value left, Value right) {
          return left.getTypeName() != null && right.getTypeName() != null
              && Objects.equals(left.getTypeName(), right.getTypeName());
      }
      
      protected boolean isInstanceofComparable(Value value) {
          return value.get() instanceof Comparable;
      }
      
      protected boolean isBothBoolean(Value left, Value right) {
          return left.get() instanceof Boolean && right.get() instanceof Boolean;
      }
      
      protected boolean isBooleanAndNull(Value left, Value right) {
          return (left.get() == null && right.get() instanceof Boolean)
              || (left.get() instanceof Boolean && right.get() == null);
      }
      
      protected boolean isBothNumber(Value left, Value right) {
          return left.get() instanceof Number && right.get() instanceof Number;
      }
      
      protected boolean isBothNumberOrChar(Object leftValue, Object rightValue) {
          return (leftValue instanceof Character || leftValue instanceof Number)
              && (rightValue instanceof Character || rightValue instanceof Number);
      }
      
      protected Number char2Number(Object charOrNumber) {
          return charOrNumber instanceof Character ? (int)(Character)charOrNumber : (Number)charOrNumber;
      }
      
      protected boolean isNumberCharacter(Value left, Value right) {
          return (left.get() instanceof Character && right.get() instanceof Number)
              || (left.get() instanceof Number && right.get() instanceof Character);
      }
      
      protected boolean isNumber(Value value) {
          return value.get() instanceof Number;
      }
      
      protected void assertLeftValue(Value left, ErrorReporter errorReporter) {
          if (!(left instanceof LeftValue)) {
              throw errorReporter.reportFormat(QLErrorCodes.INVALID_ASSIGNMENT.name(),
                  QLErrorCodes.INVALID_ASSIGNMENT.getErrorMsg(),
                  "on the left side");
          }
      }
      
      protected Object plus(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          
          if (leftValue instanceof String) {
              return (String)leftValue + rightValue;
          }
          
          if (rightValue instanceof String) {
              return leftValue + (String)rightValue;
          }
          
          if (isBothNumber(left, right)) {
              return add(qlOptions, (Number)leftValue, (Number)rightValue);
          }
          
          if (isBothNumberOrChar(leftValue, rightValue)) {
              return add(qlOptions, char2Number(leftValue), char2Number(rightValue));
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      private Number add(QLOptions qlOptions, Number leftValue, Number rightValue) {
          if (qlOptions.isPrecise()) {
              return NumberMath.add(NumberMath.toBigDecimal(leftValue), NumberMath.toBigDecimal(rightValue));
          }
          else {
              return NumberMath.add(leftValue, rightValue);
          }
      }
      
      protected Object minus(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          
          if (isBothNumber(left, right)) {
              return subtract(qlOptions, (Number)leftValue, (Number)rightValue);
          }
          
          if (isBothNumberOrChar(leftValue, rightValue)) {
              return subtract(qlOptions, char2Number(leftValue), char2Number(rightValue));
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      private Number subtract(QLOptions qlOptions, Number leftValue, Number rightValue) {
          if (qlOptions.isPrecise()) {
              return NumberMath.subtract(NumberMath.toBigDecimal(leftValue), NumberMath.toBigDecimal(rightValue));
          }
          else {
              return NumberMath.subtract(leftValue, rightValue);
          }
      }
      
      protected Object multiply(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          
          if (isBothNumber(left, right)) {
              if (qlOptions.isPrecise()) {
                  return NumberMath.multiply(NumberMath.toBigDecimal((Number)leftValue),
                      NumberMath.toBigDecimal((Number)rightValue));
              }
              else {
                  return NumberMath.multiply((Number)leftValue, (Number)rightValue);
              }
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object divide(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          
          if (isBothNumber(left, right)) {
              try {
                  if (qlOptions.isPrecise()) {
                      return NumberMath.divide(NumberMath.toBigDecimal((Number)leftValue),
                          NumberMath.toBigDecimal((Number)rightValue));
                  }
                  else {
                      return NumberMath.divide((Number)leftValue, (Number)rightValue);
                  }
              }
              catch (ArithmeticException arithmeticException) {
                  throw errorReporter.report(arithmeticException,
                      QLErrorCodes.INVALID_ARITHMETIC.name(),
                      arithmeticException.getMessage());
              }
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object remainder(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          
          if (isBothNumber(left, right)) {
              if (qlOptions.isPrecise()) {
                  return NumberMath.remainder(NumberMath.toBigDecimal((Number)leftValue),
                      NumberMath.toBigDecimal((Number)rightValue));
              }
              else {
                  return NumberMath.remainder((Number)leftValue, (Number)rightValue);
              }
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object bitwiseAnd(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) {
              return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE)
                  & (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE);
          }
          
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.and(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object bitwiseOr(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) {
              return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE)
                  | (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE);
          }
          
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.or(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object bitwiseXor(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) {
              return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE)
                  ^ (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE);
          }
          
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.xor(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object leftShift(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.leftShift(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object rightShift(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.rightShift(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected Object rightShiftUnsigned(Value left, Value right, ErrorReporter errorReporter) {
          if (isBothNumber(left, right)) {
              Number leftValue = (Number)left.get();
              Number rightValue = (Number)right.get();
              return NumberMath.rightShiftUnsigned(leftValue, rightValue);
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      @SuppressWarnings({"rawtypes", "unchecked"})
      protected int compare(Value left, Value right, ErrorReporter errorReporter) {
          if (Objects.equals(left.get(), right.get())) {
              return 0;
          }
          
          if (isBothNumber(left, right)) {
              return NumberMath.compareTo((Number)left.get(), (Number)right.get());
          }
          
          if (isNumberCharacter(left, right)) {
              if (isNumber(left)) {
                  return NumberMath.compareTo((Number)left.get(), (int)(Character)right.get());
              }
              else {
                  return NumberMath.compareTo((int)(Character)left.get(), (Number)right.get());
              }
          }
          
          if (isSameType(left, right) && isInstanceofComparable(left)) {
              return ((Comparable)(left.get())).compareTo(right.get());
          }
          
          throw buildInvalidOperandTypeException(left, right, errorReporter);
      }
      
      protected boolean equals(Value left, Value right, ErrorReporter errorReporter) {
          Object leftValue = left.get();
          Object rightValue = right.get();
          if (isBothNumber(left, right) || isNumberCharacter(left, right)
              || (isSameType(left, right) && isInstanceofComparable(left))) {
              return compare(left, right, errorReporter) == 0;
          }
          else {
              return Objects.equals(leftValue, rightValue);
          }
      }
      
      protected boolean in(Value left, Value right, ErrorReporter errorReporter) {
          Object rightOperand = right.get();
          Object leftOperand = left.get();
          if (leftOperand == null && rightOperand == null) {
              return true;
          }
          else if (leftOperand == null || rightOperand == null) {
              return false;
          }
          
          if (rightOperand instanceof Collection) {
              Collection<?> rightCollection = (Collection<?>)rightOperand;
              for (Object rightElement : rightCollection) {
                  if (equals(left, new DataValue(rightElement), errorReporter)) {
                      return true;
                  }
              }
              return false;
          }
          else if (rightOperand.getClass().isArray()) {
              Object[] rightArray = (Object[])rightOperand;
              for (Object rightElement : rightArray) {
                  if (equals(left, new DataValue(rightElement), errorReporter)) {
                      return true;
                  }
              }
              return false;
          }
          else if (rightOperand instanceof String) {
              return ((String)rightOperand).contains(String.valueOf(leftOperand));
          }
          else {
              throw buildInvalidOperandTypeException(left, right, errorReporter);
          }
      }
      
      protected boolean like(Value left, Value right, ErrorReporter errorReporter) {
          Object target = left.get();
          Object pattern = right.get();
          if (target == null && pattern == null) {
              return true;
          }
          else if (target == null || pattern == null) {
              return false;
          }
          
          if (!(target instanceof String) || !(pattern instanceof String)) {
              throw buildInvalidOperandTypeException(left, right, errorReporter);
          }
          return matchPattern((String)target, (String)pattern);
      }
      
      private static boolean matchPattern(String s, String pattern) {
          int sPointer = 0, pPointer = 0;
          int sLen = s.length(), pLen = pattern.length();
          int sRecall = -1, pRecall = -1;
          while (sPointer < sLen) {
              if (pPointer < pLen && (s.charAt(sPointer) == pattern.charAt(pPointer))) {
                  sPointer++;
                  pPointer++;
              }
              else if (pPointer < pLen && pattern.charAt(pPointer) == '%') {
                  sRecall = sPointer;
                  pRecall = pPointer;
                  pPointer++;
              }
              else if (sRecall >= 0) {
                  sPointer = ++sRecall;
                  pPointer = pRecall + 1;
              }
              else {
                  return false;
              }
          }
          while (pPointer < pLen && pattern.charAt(pPointer) == '%') {
              pPointer++;
          }
          return pPointer == pLen;
      }
      
      protected QLRuntimeException buildInvalidOperandTypeException(Value left, Value right,
          ErrorReporter errorReporter) {
          return errorReporter.reportFormat(QLErrorCodes.INVALID_BINARY_OPERAND.name(),
              QLErrorCodes.INVALID_BINARY_OPERAND.getErrorMsg(),
              getOperator(),
              left.getTypeName(),
              left.get(),
              right.getTypeName(),
              right.get());
      }
  }