<commit_before>package com.github.earchitecture.reuse.model.specification;

import com.github.earchitecture.reuse.model.annotation.GreaterThan;
import com.github.earchitecture.reuse.model.annotation.GreaterThanOrEqualTo;
import com.github.earchitecture.reuse.model.annotation.Ignore;
import com.github.earchitecture.reuse.model.annotation.LessThan;
import com.github.earchitecture.reuse.model.annotation.LessThanOrEqualTo;
import com.github.earchitecture.reuse.model.annotation.StartsWith;
import com.github.earchitecture.reuse.model.annotation.UseForeignKeySearch;
import com.github.earchitecture.reuse.model.annotation.UseLikeForSearch;
import com.github.earchitecture.reuse.model.entity.Identifiable;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.jpa.domain.Specification;

/**
 * Classe que descreve implementação de pesquisa generica para consultas com criteria
 * 
 * @author <a href="<EMAIL>"><NAME></a>
 */
public class Specs {
  private final Log logger = LogFactory.getLog(getClass());
  protected static Specs specs = new Specs();

  public Specs() {
  }

  /**
   * Recupera por atributos da classe.
   * 
   * @param clazz
   *          clazz
   * @param entity
   *          entity
   * @return ret {@link Specification}
   */
  public static <T, B> Specification<T> byExample(final Class<T> clazz, final B entity) {
    return specs.new ByExampleSpecification<T, B>(entity);
  }

  /**
   * Implementação de specificarion por exemplo.
   *
   * @param <T>
   *          t
   * @param <B>
   *          b
   */
  public class ByExampleSpecification<T, B> implements Specification<T> {
    private final B entity;

    public ByExampleSpecification(B entity) {
      this.entity = entity;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
      List<Predicate> predicates = this.findPredicate(root, query, cb, this.entity, null);
      Predicate ret = null;
      if (predicates != null && !predicates.isEmpty()) {
        for (Predicate predicate : predicates) {
          if (ret == null) {
            ret = cb.and(predicate);
          } else {
            ret = cb.and(ret, predicate);
          }
        }
      }
      return ret;
    }

    /**
     * Recupera predicate e sub predicate.
     * 
     * @param root
     *          root
     * @param query
     *          query
     * @param cb
     *          cb
     * @param entity
     *          entity
     * @return List de predicate
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<Predicate> findPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb, Object entity, Path<Object> expression) {
      List<Predicate> predicates = new ArrayList<Predicate>();
      PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(entity.getClass());
      for (PropertyDescriptor pd : pds) {
        try {
          /* Caso seja Transient ou Ignore não vai ser considerado para pesquisa de banco de dados. */
          if (pd.getReadMethod().isAnnotationPresent(Transient.class) || pd.getReadMethod().isAnnotationPresent(Ignore.class)) {
            continue;
          }
          /* Caso seja getClass, ignora a leitura */
          if (pd.getReadMethod() != null && !pd.getName().equalsIgnoreCase("class")) {
            Object val = pd.getReadMethod().invoke(entity);
            if ((val != null && !(val instanceof ArrayList)) || (val instanceof ArrayList && !((ArrayList<?>) val).isEmpty())) {
              Field field = this.getField(entity.getClass(), pd.getName());
              /* Trata regra para enum */
              if (val.getClass().isEnum()) {
                Expression exp = this.getExpressionParam(root, expression, pd.getName());
                predicates.add(cb.equal(exp, val));
                continue;
                /* Trata regra para Foreign Key */
              } else if (expression == null
                  && (pd.getReadMethod().isAnnotationPresent(UseForeignKeySearch.class) || field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class))) {
                List<Predicate> predis = this.findPredicate(root, query, cb, val, root.get(pd.getName()));
                if (predis != null && !predis.isEmpty()) {
                  predicates.addAll(predis);
                }
                continue;
              } else if (val instanceof String) {
                /* Se for string e for em branco e para desconsiderar esta condição */
                if (StringUtils.isNotBlank((CharSequence) val)) {
                  this.stringProcessor(root, cb, pd, (String) val, predicates);
                }
                continue;
              } else if (val instanceof Comparable) {
                Class<? extends Comparable> clazz = (Class<? extends Comparable>) val.getClass();
                if (this.comparableProcessor(root, cb, predicates, root.getModel(), pd, val, clazz)) {
                  continue;
                }
              }
              if (!(val instanceof Identifiable) && !(val instanceof Collection)) {
                predicates.add(cb.equal(this.getExpressionParam(root, expression, pd.getName()), val));
              }
            }
          }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
          logger.warn("Erro ao montar clausula para: " + pd.getName(), exception);
        } catch (NoSuchFieldException | SecurityException exception) {
          logger.warn("Erro ao montar clausula para: " + pd.getName(), exception);
        }
      }
      return predicates;
    }

    private Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
      Field field = null;
      try {
        field = clazz.getDeclaredField(fieldName);
      } catch (NoSuchFieldException exp) {
        field = this.getField(clazz.getSuperclass(), fieldName);
      }
      return field;
    }

    /**
     * Trata expressão de path, para considerar hierarquia de objeto.
     * 
     * @param root
     *          Root element
     * @param path
     *          path de subobjetos
     * @param name
     *          nome da propriedade
     * @return path da propriedade
     */
    private Path<Object> getExpressionParam(Root<T> root, Path<Object> path, String name) {<commit_msg>#1 Correção do build da aplicação.<commit_after>package com.github.earchitecture.reuse.model.specification;

import com.github.earchitecture.reuse.model.annotation.GreaterThan;
import com.github.earchitecture.reuse.model.annotation.GreaterThanOrEqualTo;
import com.github.earchitecture.reuse.model.annotation.Ignore;
import com.github.earchitecture.reuse.model.annotation.LessThan;
import com.github.earchitecture.reuse.model.annotation.LessThanOrEqualTo;
import com.github.earchitecture.reuse.model.annotation.StartsWith;
import com.github.earchitecture.reuse.model.annotation.UseForeignKeySearch;
import com.github.earchitecture.reuse.model.annotation.UseLikeForSearch;
import com.github.earchitecture.reuse.model.entity.Identifiable;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.jpa.domain.Specification;

/**
 * Classe que descreve implementação de pesquisa generica para consultas com criteria
 * 
 * @author <a href="<EMAIL>"><NAME></a>
 */
public class Specs {
  protected static Specs specs = new Specs();

  public Specs() {
  }

  /**
   * Recupera por atributos da classe.
   * 
   * @param clazz
   *          clazz
   * @param entity
   *          entity
   * @return ret {@link Specification}
   */
  public static <T, B> Specification<T> byExample(final Class<T> clazz, final B entity) {
    return specs.new ByExampleSpecification<T, B>(entity);
  }

  /**
   * Implementação de specificarion por exemplo.
   *
   * @param <T>
   *          t
   * @param <B>
   *          b
   */
  public class ByExampleSpecification<T, B> implements Specification<T> {
    private final B entity;

    public ByExampleSpecification(B entity) {
      this.entity = entity;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
      List<Predicate> predicates = this.findPredicate(root, query, cb, this.entity, null);
      Predicate ret = null;
      if (predicates != null && !predicates.isEmpty()) {
        for (Predicate predicate : predicates) {
          if (ret == null) {
            ret = cb.and(predicate);
          } else {
            ret = cb.and(ret, predicate);
          }
        }
      }
      return ret;
    }

    /**
     * Recupera predicate e sub predicate.
     * 
     * @param root
     *          root
     * @param query
     *          query
     * @param cb
     *          cb
     * @param entity
     *          entity
     * @return List de predicate
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<Predicate> findPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb, Object entity, Path<Object> expression) {
      List<Predicate> predicates = new ArrayList<Predicate>();
      PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(entity.getClass());
      for (PropertyDescriptor pd : pds) {
        try {
          /* Caso seja Transient ou Ignore não vai ser considerado para pesquisa de banco de dados. */
          if (pd.getReadMethod().isAnnotationPresent(Transient.class) || pd.getReadMethod().isAnnotationPresent(Ignore.class)) {
            continue;
          }
          /* Caso seja getClass, ignora a leitura */
          if (pd.getReadMethod() != null && !pd.getName().equalsIgnoreCase("class")) {
            Object val = pd.getReadMethod().invoke(entity);
            if ((val != null && !(val instanceof ArrayList)) || (val instanceof ArrayList && !((ArrayList<?>) val).isEmpty())) {
              Field field = this.getField(entity.getClass(), pd.getName());
              /* Trata regra para enum */
              if (val.getClass().isEnum()) {
                Expression exp = this.getExpressionParam(root, expression, pd.getName());
                predicates.add(cb.equal(exp, val));
                continue;
                /* Trata regra para Foreign Key */
              } else if (expression == null
                  && (pd.getReadMethod().isAnnotationPresent(UseForeignKeySearch.class) || field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class))) {
                List<Predicate> predis = this.findPredicate(root, query, cb, val, root.get(pd.getName()));
                if (predis != null && !predis.isEmpty()) {
                  predicates.addAll(predis);
                }
                continue;
              } else if (val instanceof String) {
                /* Se for string e for em branco e para desconsiderar esta condição */
                if (StringUtils.isNotBlank((CharSequence) val)) {
                  this.stringProcessor(root, cb, pd, (String) val, predicates);
                }
                continue;
              } else if (val instanceof Comparable) {
                Class<? extends Comparable> clazz = (Class<? extends Comparable>) val.getClass();
                if (this.comparableProcessor(root, cb, predicates, root.getModel(), pd, val, clazz)) {
                  continue;
                }
              }
              if (!(val instanceof Identifiable) && !(val instanceof Collection)) {
                predicates.add(cb.equal(this.getExpressionParam(root, expression, pd.getName()), val));
              }
            }
          }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
          // logger.warn("Erro ao montar clausula para: " + pd.getName(), exception);
        } catch (NoSuchFieldException | SecurityException exception) {
          // logger.warn("Erro ao montar clausula para: " + pd.getName(), exception);
        }
      }
      return predicates;
    }

    private Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
      Field field = null;
      try {
        field = clazz.getDeclaredField(fieldName);
      } catch (NoSuchFieldException exp) {
        field = this.getField(clazz.getSuperclass(), fieldName);
      }
      return field;
    }

    /**
     * Trata expressão de path, para considerar hierarquia de objeto.
     * 
     * @param root
     *          Root element
     * @param path
     *          path de subobjetos
     * @param name
     *          nome da propriedade
     * @return path da propriedade
     */
    private Path<Object> getExpressionParam(Root<T> root, Path<Object> path, String name) {
{'fraction_non_alphanumeric': 0.11080391322841344, 'fraction_numerical': 0.0002126754572522331, 'mean_word_length': 2.5840955284552845, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4158361', 'n_tokens_mistral': 3931, 'n_tokens_neox': 3878, 'n_words': 920}