/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* Licensed 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.seasar.extension.jdbc.where;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.seasar.extension.jdbc.ConditionType;
import org.seasar.extension.jdbc.Where;
import org.seasar.extension.jdbc.util.LikeUtil;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;
/**
* 検索条件を組み立てる抽象クラスです。
*
* @author higa
* @param <T>
* このクラスのサブクラス
*/
public class AbstractWhere<T extends AbstractWhere<T>> implements Where {
/** 現在のクライテリアを保持する文字列バッファ */
protected StringBuilder criteriaSb = new StringBuilder(100);
/** バインド変数のリスト */
protected List<Object> paramList = new ArrayList<Object>();
/** バインド変数に対応するプロパティ名のリスト */
protected List<String> propertyNameList = new ArrayList<String>();
/**
* {@link #eq(String, Object)}等で渡されたパラメータ値が空文字列または空白のみの文字列なら
* <code>null</code>として扱い、 条件に加えない場合は<code>true</code>
*/
protected boolean excludesWhitespace;
/**
* インスタンスを構築します。
*/
public AbstractWhere() {
}
/**
* {@link #eq(String, Object)}等で渡されたパラメータ値が空文字列または空白のみの文字列なら
* <code>null</code>として扱い、条件に加えないことを指定します。
*
* @return このインスタンス自身
* @see #ignoreWhitespace()
*/
@SuppressWarnings("unchecked")
public T excludesWhitespace() {
excludesWhitespace = true;
return (T) this;
}
/**
* {@link #eq(String, Object)}等で渡されたパラメータ値が空文字列または空白のみの文字列なら
* <code>null</code>として扱い、条件に加えないことを指定します。
*
* @return このインスタンス自身
*
*/
@Deprecated
public T ignoreWhitespace() {
return excludesWhitespace();
}
/**
* <code>=</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T eq(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.EQ.isTarget(value)) {
addCondition(ConditionType.EQ, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code><></code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T ne(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.NE.isTarget(value)) {
addCondition(ConditionType.NE, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code><</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T lt(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.LT.isTarget(value)) {
addCondition(ConditionType.LT, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code><=</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T le(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.LE.isTarget(value)) {
addCondition(ConditionType.LE, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code>></code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T gt(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.GT.isTarget(value)) {
addCondition(ConditionType.GT, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code>>=</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T ge(final CharSequence propertyName, Object value) {
assertPropertyName(propertyName);
value = normalize(value);
if (ConditionType.GE.isTarget(value)) {
addCondition(ConditionType.GE, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code>in</code>の条件を追加します。
*
* @param propertyName
* @param values
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T in(final CharSequence propertyName, Object... values) {
assertPropertyName(propertyName);
values = normalizeArray(values);
if (ConditionType.IN.isTarget(values)) {
addCondition(ConditionType.IN, propertyName.toString(), values);
}
return (T) this;
}
/**
* <code>in</code>の条件を追加します。
*
* @param propertyName
* @param values
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T in(final CharSequence propertyName, Collection<?> values) {
assertPropertyName(propertyName);
values = normalizeList(values);
if (ConditionType.IN.isTarget(values)) {
addCondition(ConditionType.IN, propertyName.toString(), values);
}
return (T) this;
}
/**
* <code>not in</code>の条件を追加します。
*
* @param propertyName
* @param values
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notIn(final CharSequence propertyName, Object... values) {
assertPropertyName(propertyName);
values = normalizeArray(values);
if (ConditionType.NOT_IN.isTarget(values)) {
addCondition(ConditionType.NOT_IN, propertyName.toString(), values);
}
return (T) this;
}
/**
* <code>not in</code>の条件を追加します。
*
* @param propertyName
* @param values
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notIn(final CharSequence propertyName, Collection<?> values) {
assertPropertyName(propertyName);
values = normalizeList(values);
if (ConditionType.NOT_IN.isTarget(values)) {
addCondition(ConditionType.NOT_IN, propertyName.toString(), values);
}
return (T) this;
}
/**
* <code>like</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T like(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final Object normalizedValue = normalize(value);
if (ConditionType.LIKE.isTarget(normalizedValue)) {
addCondition(ConditionType.LIKE, propertyName.toString(),
normalizedValue);
}
return (T) this;
}
/**
* <code>like</code>の条件を追加します。
*
* @param propertyName
* @param value
* @param escape
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T like(final CharSequence propertyName, final String value,
final char escape) {
assertPropertyName(propertyName);
final Object normalizedValue = normalize(value);
if (ConditionType.LIKE_ESCAPE.isTarget(normalizedValue)) {
addCondition(ConditionType.LIKE_ESCAPE, propertyName.toString(),
new Object[] { normalizedValue, escape });
}
return (T) this;
}
/**
* <code>not like</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notLike(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final Object normalizedValue = normalize(value);
if (ConditionType.NOT_LIKE.isTarget(normalizedValue)) {
addCondition(ConditionType.NOT_LIKE, propertyName.toString(),
normalizedValue);
}
return (T) this;
}
/**
* <code>not like</code>の条件を追加します。
*
* @param propertyName
* @param value
* @param escape
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notLike(final CharSequence propertyName, final String value,
final char escape) {
assertPropertyName(propertyName);
final Object normalizedValue = normalize(value);
if (ConditionType.NOT_LIKE_ESCAPE.isTarget(normalizedValue)) {
addCondition(ConditionType.NOT_LIKE_ESCAPE,
propertyName.toString(), new Object[] { normalizedValue,
escape });
}
return (T) this;
}
/**
* <code>like '?%'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T starts(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.STARTS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.STARTS, propertyName.toString(),
normalizedValue);
} else {
addCondition(ConditionType.STARTS_ESCAPE, propertyName
.toString(), LikeUtil.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>not like '?%'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notStarts(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.NOT_STARTS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.NOT_STARTS, propertyName.toString(),
normalizedValue);
} else {
addCondition(ConditionType.NOT_STARTS_ESCAPE, propertyName
.toString(), LikeUtil.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>like '%?'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T ends(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.ENDS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.ENDS, propertyName.toString(),
normalizedValue);
} else {
addCondition(ConditionType.ENDS_ESCAPE,
propertyName.toString(), LikeUtil
.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>not like '%?'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notEnds(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.NOT_ENDS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.NOT_ENDS, propertyName.toString(),
normalizedValue);
} else {
addCondition(ConditionType.NOT_ENDS_ESCAPE, propertyName
.toString(), LikeUtil.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>like '%?%'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T contains(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.CONTAINS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.CONTAINS, propertyName.toString(),
normalizedValue);
} else {
addCondition(ConditionType.CONTAINS_ESCAPE, propertyName
.toString(), LikeUtil.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>not like '%?%'</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T notContains(final CharSequence propertyName, final String value) {
assertPropertyName(propertyName);
final String normalizedValue = String.class.cast(normalize(value));
if (ConditionType.NOT_CONTAINS.isTarget(normalizedValue)) {
if (!LikeUtil.containsWildcard(normalizedValue)) {
addCondition(ConditionType.NOT_CONTAINS, propertyName
.toString(), normalizedValue);
} else {
addCondition(ConditionType.NOT_CONTAINS_ESCAPE, propertyName
.toString(), LikeUtil.escapeWildcard(normalizedValue));
}
}
return (T) this;
}
/**
* <code>is null</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T isNull(final CharSequence propertyName, final Boolean value) {
assertPropertyName(propertyName);
if (ConditionType.IS_NULL.isTarget(value)) {
addCondition(ConditionType.IS_NULL, propertyName.toString(), value);
}
return (T) this;
}
/**
* <code>is not null</code>の条件を追加します。
*
* @param propertyName
* @param value
* @return このインスタンス自身
*/
@SuppressWarnings("unchecked")
public T isNotNull(final CharSequence propertyName, final Boolean value) {
assertPropertyName(propertyName);
if (ConditionType.IS_NOT_NULL.isTarget(value)) {
addCondition(ConditionType.IS_NOT_NULL, propertyName.toString(),
value);
}
return (T) this;
}
public String getCriteria() {
return new String(criteriaSb);
}
public Object[] getParams() {
return paramList.toArray();
}
public String[] getPropertyNames() {
return propertyNameList.toArray(new String[propertyNameList.size()]);
}
/**
* 条件を追加します。
*
* @param conditionType
* 条件タイプ
* @param propertyName
* プロパティ名
* @param value
* 値
*/
protected void addCondition(final ConditionType conditionType,
final String propertyName, final Object value) {
if (criteriaSb.length() > 0) {
criteriaSb.append(" and ");
}
criteriaSb.append(conditionType.getCondition(propertyName, value));
int size = conditionType.addValue(paramList, value);
for (int i = 0; i < size; i++) {
propertyNameList.add(propertyName);
}
}
/**
* {@link #ignoreWhitespace()}が呼び出された場合でパラメータ値が空文字列または空白のみの文字列なら
* <code>null</code>を、 それ以外なら元の値をそのまま返します。
*
* @param value
* パラメータ値
* @return {@link #ignoreWhitespace()}が呼び出された場合でパラメータ値が空文字列または空白のみの文字列なら
* <code>null</code>、 それ以外なら元の値
*/
protected Object normalize(final Object value) {
if (excludesWhitespace && value instanceof String) {
if (StringUtil.isEmpty(String.class.cast(value).trim())) {
return null;
}
}
return value;
}
/**
* {@link #ignoreWhitespace()}が呼び出された場合で パラメータ値の要素が空文字列または空白のみの文字列なら
* <code>null</code>、 それ以外なら元の値からなる配列を返します。
*
* @param values
* パラメータ値の配列
* @return {@link #ignoreWhitespace()}が呼び出された場合でパラメータ値の要素が空文字列または空白のみの文字列なら
* <code>null</code>、 それ以外なら元の値からなる配列
*/
protected Object[] normalizeArray(final Object... values) {
if (!excludesWhitespace || values == null) {
return values;
}
final List<Object> list = CollectionsUtil.newArrayList(values.length);
for (int i = 0; i < values.length; ++i) {
final Object normalizedValue = normalize(values[i]);
if (normalizedValue != null) {
list.add(normalizedValue);
}
}
return list.toArray(new Object[list.size()]);
}
/**
* {@link #ignoreWhitespace()}が呼び出された場合で パラメータ値の要素が空文字列または空白のみの文字列なら
* <code>null</code>、 それ以外なら元の値からなるリストを返します。
*
* @param values
* パラメータ値のコレクション
* @return {@link #ignoreWhitespace()}が呼び出された場合でパラメータ値の要素が空文字列または空白のみの文字列なら
* <code>null</code>、 それ以外なら元の値からなるリスト
*/
protected Collection<?> normalizeList(final Collection<?> values) {
if (!excludesWhitespace || values == null) {
return values;
}
final List<Object> list = CollectionsUtil.newArrayList(values.size());
for (final Object value : values) {
final Object normalizedValue = normalize(value);
if (normalizedValue != null) {
list.add(normalizedValue);
}
}
return list;
}
/**
* プロパティ名が{@literal null}でないことを確認します。
*
* @param s
* 文字の列
*/
protected void assertPropertyName(final CharSequence s) {
if (s == null) {
throw new NullPointerException("propertyName");
}
}
}