/* * 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"); } } }