package sample.context.orm;
import java.time.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.impl.factory.Lists;
import org.hibernate.criterion.MatchMode;
/**
* 簡易にJPQLを生成するためのビルダー。
* <p>条件句の動的条件生成に特化させています。
*/
public class JpqlBuilder {
private final StringBuilder jpql;
private final AtomicInteger index;
private final MutableList<String> conditions = Lists.mutable.empty();
private final MutableList<Object> reservedArgs = Lists.mutable.empty();
private final MutableList<Object> args = Lists.mutable.empty();
private Optional<String> orderBy = Optional.empty();
public JpqlBuilder(String baseJpql, int fromIndex) {
this.jpql = new StringBuilder(baseJpql);
this.index = new AtomicInteger(fromIndex);
}
public JpqlBuilder(String baseJpql, String staticCondition, int fromIndex) {
this(baseJpql, fromIndex);
add(staticCondition);
}
private JpqlBuilder add(String condition) {
if (StringUtils.isNotBlank(condition)) {
this.conditions.add(condition);
}
return this;
}
private JpqlBuilder reservedArgs(Object... args) {
if (args != null) {
this.reservedArgs.addAll(Arrays.asList(args));
}
return this;
}
/** 一致条件を付与します。(値がnullの時は無視されます) */
public JpqlBuilder equal(String field, Object value) {
return ifValid(value, () -> {
conditions.add(String.format("%s = ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
private JpqlBuilder ifValid(Object value, Runnable command) {
if (isValid(value)) {
command.run();
}
return this;
}
private boolean isValid(Object value) {
if (value instanceof String) {
return StringUtils.isNotBlank((String) value);
} else if (value instanceof Optional) {
return ((Optional<?>) value).isPresent();
} else if (value instanceof Object[]) {
return value != null && 0 < ((Object[]) value).length;
} else if (value instanceof Collection) {
return value != null && 0 < ((Collection<?>) value).size();
} else {
return value != null;
}
}
/** 不一致条件を付与します。(値がnullの時は無視されます) */
public JpqlBuilder equalNot(String field, Object value) {
return ifValid(value, () -> {
conditions.add(String.format("%s != ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
/** like条件を付与します。(値がnullの時は無視されます) */
public JpqlBuilder like(String field, String value, MatchMode mode) {
return ifValid(value, () -> {
conditions.add(String.format("%s like ?%d", field, index.getAndIncrement()));
args.add(mode.toMatchString(value));
});
}
/** like条件を付与します。[複数フィールドに対するOR結合](値がnullの時は無視されます) */
public JpqlBuilder like(List<String> fields, String value, MatchMode mode) {
return ifValid(value, () -> {
StringBuilder condition = new StringBuilder("(");
for (String field : fields) {
if (condition.length() != 1) {
condition.append(" or ");
}
condition.append(String.format("(%s like ?%d)", field, index.getAndIncrement()));
args.add(mode.toMatchString(value));
}
condition.append(")");
conditions.add(condition.toString());
});
}
/** in条件を付与します。 */
public JpqlBuilder in(String field, List<Object> values) {
return ifValid(values, () -> {
conditions.add(String.format("%s in ?%d", field, index.getAndIncrement()));
args.add(values);
});
}
/** between条件を付与します。 */
public JpqlBuilder between(String field, Date from, Date to) {
if (from != null && to != null) {
conditions.add(String.format(
"%s between ?%d and ?%d", field, index.getAndIncrement(), index.getAndIncrement()));
args.add(from);
args.add(to);
}
return this;
}
/** between条件を付与します。 */
public JpqlBuilder between(String field, LocalDate from, LocalDate to) {
if (from != null && to != null) {
conditions.add(String.format(
"%s between ?%d and ?%d", field, index.getAndIncrement(), index.getAndIncrement()));
args.add(from);
args.add(to);
}
return this;
}
/** between条件を付与します。 */
public JpqlBuilder between(String field, LocalDateTime from, LocalDateTime to) {
if (from != null && to != null) {
conditions.add(String.format(
"%s between ?%d and ?%d", field, index.getAndIncrement(), index.getAndIncrement()));
args.add(from);
args.add(to);
}
return this;
}
/** between条件を付与します。 */
public JpqlBuilder between(String field, String from, String to) {
if (isValid(from) && isValid(to)) {
conditions.add(String.format(
"%s between ?%d and ?%d", field, index.getAndIncrement(), index.getAndIncrement()));
args.add(from);
args.add(to);
}
return this;
}
/** [フィールド]>=[値] 条件を付与します。(値がnullの時は無視されます) */
public <Y extends Comparable<? super Y>> JpqlBuilder gte(String field, final Y value) {
return ifValid(value, () -> {
conditions.add(String.format("%s >= ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
/** [フィールド]>[値] 条件を付与します。(値がnullの時は無視されます) */
public <Y extends Comparable<? super Y>> JpqlBuilder gt(String field, final Y value) {
return ifValid(value, () -> {
conditions.add(String.format("%s > ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
/** [フィールド]<=[値] 条件を付与します。 */
public <Y extends Comparable<? super Y>> JpqlBuilder lte(String field, final Y value) {
return ifValid(value, () -> {
conditions.add(String.format("%s <= ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
/** [フィールド]<[値] 条件を付与します。 */
public <Y extends Comparable<? super Y>> JpqlBuilder lt(String field, final Y value) {
return ifValid(value, () -> {
conditions.add(String.format("%s < ?%d", field, index.getAndIncrement()));
args.add(value);
});
}
/** order by 条件句を付与します。 */
public JpqlBuilder orderBy(String orderBy) {
this.orderBy = Optional.ofNullable(orderBy);
return this;
}
/** JPQLを生成します。 */
public String build() {
StringBuilder jpql = new StringBuilder(this.jpql.toString());
if (!conditions.isEmpty()) {
jpql.append(" where ");
AtomicBoolean first = new AtomicBoolean(true);
conditions.each(condition -> {
if (!first.getAndSet(false)) {
jpql.append(" and ");
}
jpql.append(condition);
});
}
orderBy.ifPresent(v -> jpql.append(" order by " + v));
return jpql.toString();
}
/** JPQLに紐付く実行引数を返します。 */
public Object[] args() {
return Lists.mutable.ofAll(reservedArgs).withAll(args).toArray();
}
/**
* ビルダーを生成します。
* @param baseJpql 基点となるJPQL (where / order by は含めない)
* @return ビルダー情報
*/
public static JpqlBuilder of(String baseJpql) {
return new JpqlBuilder(baseJpql, 1);
}
/**
* ビルダーを生成します。
* @param baseJpql 基点となるJPQL (where / order by は含めない)
* @param fromIndex 動的に付与する条件句の開始インデックス(1開始)。
* 既に「field=?1」等で置換連番を付与しているときはその次番号。
* @param args 既に付与済みの置換連番に紐づく引数
* @return ビルダー情報
*/
public static JpqlBuilder of(String baseJpql, int fromIndex, Object... args) {
return new JpqlBuilder(baseJpql, fromIndex).reservedArgs(args);
}
/**
* ビルダーを生成します。
* @param baseJpql 基点となるJPQL (where / order by は含めない)
* @param staticCondition 条件指定無しに確定する where 条件句 (field is null 等)
* @return ビルダー情報
*/
public static JpqlBuilder of(String baseJpql, String staticCondition) {
return new JpqlBuilder(baseJpql, staticCondition, 1);
}
/**
* ビルダーを生成します。
* @param baseJpql 基点となるJPQL (where / order by は含めない)
* @param staticCondition 条件指定無しに確定する where 条件句 (field is null 等)
* @param fromIndex 動的に付与する条件句の開始インデックス(1開始)。
* 既に「field=?1」等で置換連番を付与しているときはその次番号。
* @param args 既に付与済みの置換連番に紐づく引数
* @return ビルダー情報
*/
public static JpqlBuilder of(String baseJpql, String staticCondition, int fromIndex, Object... args) {
return new JpqlBuilder(baseJpql, staticCondition, fromIndex).reservedArgs(args);
}
}