text: // 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
  // 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 com.cloud.utils.db;
  
  import java.lang.reflect.Field;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import com.cloud.utils.Pair;
  import com.cloud.utils.db.SearchBase.Condition;
  import com.cloud.utils.db.SearchBase.Select;
  
  /**
   * big joins or high performance searches, it is much better to
   */
  public class SearchCriteria<K> {
      public enum Op {
          GT(" > ? ", 1), GTEQ(" >= ? ", 1), LT(" < ? ", 1), LTEQ(" <= ? ", 1), EQ(" = ? ", 1), NEQ(" != ? ", 1), BETWEEN(" BETWEEN ? AND ? ", 2), NBETWEEN(
                  " NOT BETWEEN ? AND ? ",
                  2), IN(" IN () ", -1), NOTIN(" NOT IN () ", -1), LIKE(" LIKE ? ", 1), NLIKE(" NOT LIKE ? ", 1), NIN(" NOT IN () ", -1), NULL(" IS NULL ", 0), NNULL(
                  " IS NOT NULL ",
                  0), SC(" () ", 1), TEXT("  () ", 1), RP("", 0), AND(" AND ", 0), OR(" OR ", 0), NOT(" NOT ", 0), FIND_IN_SET(" ) ", 1);
  
          private final String op;
          int params;
  
          Op(String op, int params) {
              this.op = op;
              this.params = params;
          }
  
          @Override
          public String toString() {
              return op;
          }
  
          public int getParams() {
              return params;
          }
      }
  
      public enum Func {
          NATIVE("@", 1), MAX("MAX(@)", 1), MIN("MIN(@)", 1), FIRST("FIRST(@)", 1), LAST("LAST(@)", 1), SUM("SUM(@)", 1), COUNT("COUNT(@)", 1), DISTINCT("DISTINCT(@)", 1);
  
          private String func;
          private int count;
  
          Func(String func, int params) {
              this.func = func;
              this.count = params;
          }
  
          @Override
          public String toString() {
              return func;
          }
  
          public int getCount() {
              return count;
          }
      }
  
      public enum SelectType {
          Fields, Entity, Single, Result
      }
  
      private final Map<String, Attribute> _attrs;
      private final ArrayList<Condition> _conditions;
      private ArrayList<Condition> _additionals = null;
      private HashMap<String, Object[]> _params = new HashMap<String, Object[]>();
      private int _counter;
      private HashMap<String, JoinBuilder<SearchCriteria<?>>> _joins;
      private final ArrayList<Select> _selects;
      private final GroupBy<? extends SearchBase<?, ?, K>, ?, K> _groupBy;
      private final List<Object> _groupByValues;
      private final Class<K> _resultType;
      private final SelectType _selectType;
  
      protected SearchCriteria(SearchBase<?, ?, K> sb) {
          this._attrs = sb._attrs;
          this._conditions = sb._conditions;
          this._additionals = new ArrayList<Condition>();
          this._counter = 0;
          this._joins = null;
          if (sb._joins != null) {
              _joins = new HashMap<String, JoinBuilder<SearchCriteria<?>>>(sb._joins.size());
              for (Map.Entry<String, JoinBuilder<SearchBase<?, ?, ?>>> entry : sb._joins.entrySet()) {
                  JoinBuilder<SearchBase<?, ?, ?>> value = entry.getValue();
                  _joins.put(entry.getKey(),
                      new JoinBuilder<SearchCriteria<?>>(value.getT().create(), value.getFirstAttribute(), value.getSecondAttribute(), value.getType()));
              }
          }
          _selects = sb._selects;
          _groupBy = sb._groupBy;
          if (_groupBy != null) {
              _groupByValues = new ArrayList<Object>();
          } else {
              _groupByValues = null;
          }
          _resultType = sb._resultType;
          _selectType = sb._selectType;
      }
  
      protected void setParameters(HashMap<String, Object[]> parameters) {
          _params = parameters;
      }
  
      public SelectType getSelectType() {
          return _selectType;
      }
  
      public void getSelect(StringBuilder str, int insertAt) {
          if (_selects == null || _selects.size() == 0) {
              return;
          }
  
          for (Select select : _selects) {
              String func = select.func.toString() + ",";
              if (select.attr == null) {
                  func = func.replace("@", "*");
              } else {
                  func = func.replace("@", select.attr.table + "." + select.attr.columnName);
              }
              str.insert(insertAt, func);
              insertAt += func.length();
              if (select.field == null) {
                  break;
              }
          }
  
          str.delete(insertAt - 1, insertAt);
      }
  
      public List<Field> getSelectFields() {
          List<Field> fields = new ArrayList<Field>(_selects.size());
          for (Select select : _selects) {
              fields.add(select.field);
          }
  
          return fields;
      }
  
      public void setParameters(String conditionName, Object... params) {
          assert _conditions.contains(new Condition(conditionName)) || _additionals.contains(new Condition(conditionName)) : "Couldn't find " + conditionName;
          _params.put(conditionName, params);
      }
  
      public boolean isSelectAll() {
          return _selects == null || _selects.size() == 0;
      }
  
      protected JoinBuilder<SearchCriteria<?>> findJoin(Map<String, JoinBuilder<SearchCriteria<?>>> jbmap, String joinName) {
          JoinBuilder<SearchCriteria<?>> jb = jbmap.get(joinName);
          if (jb != null) {
              return jb;
          }
  
          for (JoinBuilder<SearchCriteria<?>> j2 : jbmap.values()) {
              SearchCriteria<?> sc = j2.getT();
              if (sc._joins != null)
                  jb = findJoin(sc._joins, joinName);
              if (jb != null) {
                  return jb;
              }
          }
  
          assert (false) : "Unable to find a join by the name " + joinName;
          return null;
      }
  
      public void setJoinParameters(String joinName, String conditionName, Object... params) {
          JoinBuilder<SearchCriteria<?>> join = findJoin(_joins, joinName);
          assert (join != null) : "Incorrect join name specified: " + joinName;
          join.getT().setParameters(conditionName, params);
  
      }
  
      public SearchCriteria<?> getJoin(String joinName) {
          return _joins.get(joinName).getT();
      }
  
      public Pair<GroupBy<?, ?, ?>, List<Object>> getGroupBy() {
          return _groupBy == null ? null : new Pair<GroupBy<?, ?, ?>, List<Object>>(_groupBy, _groupByValues);
      }
  
      public void setGroupByValues(Object... values) {
          for (Object value : values) {
              _groupByValues.add(value);
          }
      }
  
      public Class<K> getResultType() {
          return _resultType;
      }
  
      @Deprecated
      public void addAnd(String field, Op op, Object... values) {
          String name = Integer.toString(_counter++);
          addCondition(name, " AND ", field, op);
          setParameters(name, values);
      }
  
      @Deprecated
      public void addAnd(Attribute attr, Op op, Object... values) {
          String name = Integer.toString(_counter++);
          addCondition(name, " AND ", attr, op);
          setParameters(name, values);
      }
  
      @Deprecated
      public void addOr(String field, Op op, Object... values) {
          String name = Integer.toString(_counter++);
          addCondition(name, " OR ", field, op);
          setParameters(name, values);
      }
  
      protected void addCondition(String conditionName, String cond, String fieldName, Op op) {
          Attribute attr = _attrs.get(fieldName);
          assert attr != null : "Unable to find field: " + fieldName;
          addCondition(conditionName, cond, attr, op);
      }
  
      protected void addCondition(String conditionName, String cond, Attribute attr, Op op) {
          Condition condition = new Condition(conditionName, /*(_conditions.size() + _additionals.size()) == 0 ? "" : */cond, attr, op);
          _additionals.add(condition);
      }
  
      public String getWhereClause() {
          StringBuilder sql = new StringBuilder();
          int i = 0;
          for (Condition condition : _conditions) {
              if (condition.isPreset()) {
                  _params.put(condition.name, condition.presets);
              }
              Object[] params = _params.get(condition.name);
              if ((condition.op == null || condition.op.params == 0) || (params != null)) {
                  condition.toSql(sql, params, i++);
              }
          }
  
          for (Condition condition : _additionals) {
              if (condition.isPreset()) {
                  _params.put(condition.name, condition.presets);
              }
              Object[] params = _params.get(condition.name);
              if ((condition.op.params == 0) || (params != null)) {
                  condition.toSql(sql, params, i++);
              }
          }
  
          return sql.toString();
      }
  
      public List<Pair<Attribute, Object>> getValues() {
          ArrayList<Pair<Attribute, Object>> params = new ArrayList<Pair<Attribute, Object>>(_params.size());
          for (Condition condition : _conditions) {
              Object[] objs = _params.get(condition.name);
              if (condition.op != null && condition.op.params != 0 && objs != null) {
                  getParams(params, condition, objs);
              }
          }
  
          for (Condition condition : _additionals) {
              Object[] objs = _params.get(condition.name);
              if ((condition.op.params == 0) || (objs != null)) {
                  getParams(params, condition, objs);
              }
          }
  
          return params;
      }
  
      public Collection<JoinBuilder<SearchCriteria<?>>> getJoins() {
          return _joins != null ? _joins.values() : null;
      }
  
      private void getParams(ArrayList<Pair<Attribute, Object>> params, Condition condition, Object[] objs) {
          if (condition.op == Op.SC) {
              assert (objs != null && objs.length > 0) : " Where's your search criteria object? " + condition.name;
              params.addAll(((SearchCriteria<?>)objs[0]).getValues());
              return;
          }
  
          if (objs != null && objs.length > 0) {
              for (Object obj : objs) {
                  if ((condition.op != Op.EQ && condition.op != Op.NEQ) || (obj != null)) {
                      params.add(new Pair<Attribute, Object>(condition.attr, obj));
                  }
              }
          }
      }
  
      public Pair<String, ArrayList<Object>> toSql() {
          StringBuilder sql = new StringBuilder();
  
          return new Pair<String, ArrayList<Object>>(sql.toString(), null);
      }
  }
  