一覧の検索条件を生成するユーティリティ
データストアからモデル一覧を取得する際の検索条件を生成するユーティリティを書いてみました。以下のような感じで使えます。
// ユーティリティをstaticインポート import static Expressions.*; .... // 名前が"mii"のKittenオブジェクト一覧を取得 List<Kitten> kittens = list( eq( KittenProperties.NAME, "mii" ) ); // 年齢が1より大きいのKittenオブジェクト一覧を取得 kittens = list( gt( KittenProperties.AGE, 1 ) ); // 年齢が1以下のKittenオブジェクト一覧を取得 kittens = list( le( KittenProperties.AGE, 1 ) ); // 名前が"mii"でないKittenオブジェクト一覧を取得 kittens = list( ne( KittenProperties.NAME, "mii" ) ); // 年齢が1以下で、かつ、名前が"mii"のKittenオブジェクト一覧を取得 kittens = list( and( le( KittenProperties.AGE, 1 ), eq( KittenProperties.NAME, "mii" )) ); // 名前が"mii"または"tora"のKittenオブジェクト一覧を取得 kittens = list( or( eq( KittenProperties.NAME, "mii" ), eq( KittenProperties.NAME, "tora" )) ); // 名前が"mii"または"tora"で、かつ、年齢が1以下のKittenオブジェクト一覧を取得 kittens = list( and( le( KittenProperties.AGE, 1 ), or( eq( KittenProperties.NAME, "tora" ), eq( KittenProperties.NAME, "mii" ))) );
呼び出している「list()」関数は↓になります。
/** * Kitten一覧を取得する * @param exp 検索条件 * @return Kitten一覧 */ public List<Kitten> list( Expression<Kitten> exp ) { // TODO ソート条件とか。 Query q = Tx.getPersistenceManager().newQuery( Kitten.class ); q.setFilter( exp.createFilter() ); q.declareImports( exp.createImport() ); q.declareParameters( exp.createParameter() ); return new ArrayList<Kitten>( (List<Kitten>) q.executeWithArray( exp.getParameterValues().toArray( new Object[0] ) )); }
オブジェクトの属性は、オブジェクトごとに「XXProperties」を用意してそこに定義する形にしています。
import orz.javaclasses.dao.PropertyImpl; import com.google.appengine.api.datastore.Key; /** * ねこのプロパティ */ public class KittenProperties { /** * ねこのプロパティ */ private static final class KittenProperty<V> extends PropertyImpl<Kitten, V> { KittenProperty( String name ) { super(name); } } /** * ID */ public static final KittenProperty<Key> ID = new KittenProperty<Key>( "id" ); /** * 名前 */ public static final KittenProperty<String> NAME = new KittenProperty<String>( "name" ); /** * 年齢 */ public static final KittenProperty<Integer> AGE = new KittenProperty<Integer>( "age" ); }
永続化するモデルクラスは以下。
import java.util.Arrays; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; import com.google.appengine.api.datastore.Key; /** * ねこ */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Kitten { /** * ID */ @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key id; /** * 名前 */ @Persistent private String name = ""; /** * 年齢 */ @Persistent private int age = 0; /** * コンストラクタ */ public Kitten (){} /** * コンストラクタ * * @param name * 名前 * @param age * 年齢 */ public Kitten ( String name, int age ) { this.name = name; this.age = age; } /** * コンストラクタ * * @param id * ID * @param name * 名前 * @param age * 年齢 */ public Kitten ( Key id, String name, int age ) { this.id = id; this.name = name; this.age = age; } /** * IDを取得する。 * @return ID */ public Key getId () { return id; } /** * IDを設定する。 * @param id ID */ public void setId ( Key id ) { this.id = id; } /** * 名前を取得する。 * @return 名前 */ public String getName () { return name; } /** * 名前を設定する。 * @param name 名前 */ public void setName ( String name ) { this.name = name; } /** * 年齢を取得する。 * @return 年齢 */ public int getAge () { return age; } /** * 年齢を設定する。 * @param age 年齢 */ public void setAge ( int age ) { this.age = age; } public boolean equals ( Object obj ) { if ( obj == null ) { return false; } if ( obj instanceof Kitten ) { Object[] that = getValues((Kitten) obj); return Arrays.deepEquals(that, getValues(this)) ; } return false; } public int hashCode () { return Arrays.deepHashCode(getValues(this)) ; } private static Object[] getValues( Kitten v) { return new Object[] { v.id, v.name, new Integer( v.age ) }; } }
実装
実装は以下のとおり。notとかも用意していますが、現在のデータストアでは未サポートです。
Expressions
import java.util.List; /** * 検索条件を生成するユーティリティ */ public class Expressions { /** * 指定した条件にすべてマッチすることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param expressions * 結合する検索条件 * @return 指定した条件にすべてマッチすることを評価する{@link Expression 条件} */ public static final < X > Expression<X> and( Expression<X>... expressions ) { return new CompositeExpression<X>( "&&", expressions ); } /** * 指定した条件にすべてマッチすることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param expressions * 結合する検索条件 * @return 指定した条件にすべてマッチすることを評価する{@link Expression 条件} */ public static final < X > Expression<X> and( List<? extends Expression<X>> expressions ) { return new CompositeExpression<X>( "&&", expressions ); } /** * 指定した条件のいずれかにマッチすることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param expressions * 結合する検索条件 * @return 指定した条件のいずれかにマッチすることを評価する{@link Expression 条件} */ public static final < X > Expression<X> or( Expression<X>... expressions ) { return new CompositeExpression<X>( "||", expressions ); } /** * 指定した条件のいずれかにマッチすることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param expressions * 結合する検索条件 * @return 指定した条件のいずれかにマッチすることを評価する{@link Expression 条件} */ public static final < X > Expression<X> or( List<? extends Expression<X>> expressions ) { return new CompositeExpression<X>( "||", expressions ); } /** * 指定した条件にマッチしないことを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param expression * 検索条件 * @return 指定した条件にマッチしないことを評価する{@link Expression 条件} */ public static final < X > Expression<X> not( Expression<X> expression ) { return new FilterExpression<X>( "!", expression ); } /** * 指定した比較値との「完全一致」を評価する{@link Expression 条件}を生成します。 * * @param <V> 値の型 * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値との「完全一致」を評価する{@link Expression 条件} */ public static final < V, X > Expression<X> equals( Property<? extends X, V> key, V value ) { return new SimpleExpression<X>( key.getName() + " == %s", value ); } /** * 指定した比較値との「完全一致」を評価する{@link Expression 条件}を生成します。 * <br/>{@link #equals(Property, Object)}の別名です。 * * @param <V> 値の型 * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値との「完全一致」を評価する{@link Expression 条件} */ public static final < V, X > Expression<X> eq( Property<? extends X, V> key, V value ) { return equals( key, value ); } /** * 指定した比較値と「一致しない」ことを評価する{@link Expression 条件}を生成します。 * * @param <V> 値の型 * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と「一致しない」ことを評価する{@link Expression 条件} */ public static final < V, X > Expression<X> notEquals( Property<? extends X, V> key, V value ) { return new SimpleExpression<X>( key.getName() + " != %s", value ); } /** * 指定した比較値と「一致しない」ことを評価する{@link Expression 条件}を生成します。 * <br/>{@link #notEquals(Property, Object)}の別名です。 * * @param <V> 値の型 * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と「一致しない」ことを評価する{@link Expression 条件} */ public static final < V, X > Expression<X> ne( Property<? extends X, V> key, V value ) { return notEquals( key, value ); } /** * 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> greaterThanOrEqual( Property<? extends X, ? extends Number> key, Number value ) { return new SimpleExpression<X>( key.getName() + " >= %s", value ); } /** * 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> greaterThanOrEqual( Property<? extends X, java.util.Date> key, java.util.Date value ) { return new SimpleExpression<X>( key.getName() + " >= %s", value ); } /** * 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件}を生成します。 * <br/>{@link #greaterThanOrEqual(Property, Number)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> ge( Property<? extends X, ? extends Number> key, Number value ) { return greaterThanOrEqual( key, value ); } /** * 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件}を生成します。 * <br/>{@link #greaterThanOrEqual(Property, java.util.Date)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以上」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> ge( Property<? extends X, java.util.Date> key, java.util.Date value ) { return greaterThanOrEqual( key, value ); } /** * 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> lessThanOrEqual( Property<? extends X, ? extends Number> key, Number value ) { return new SimpleExpression<X>( key.getName() + " <= %s", value ); } /** * 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> lessThanOrEqual( Property<? extends X, java.util.Date> key, java.util.Date value ) { return new SimpleExpression<X>( key.getName() + " <= %s", value ); } /** * 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件}を生成します。 * <br/>{@link #lessThanOrEqual(Property, Number)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> le( Property<? extends X, ? extends Number> key, Number value ) { return lessThanOrEqual( key, value ); } /** * 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件}を生成します。 * <br/>{@link #lessThanOrEqual(Property, java.util.Date)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「以下」であることを評価する{@link Expression 条件} */ public static final < X > Expression<X> le( Property<? extends X, java.util.Date> key, java.util.Date value ) { return lessThanOrEqual( key, value ); } /** * 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> greaterThan( Property<? extends X, ? extends Number> key, Number value ) { return new SimpleExpression<X>( key.getName() + " > %s", value ); } /** * 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> greaterThan( Property<? extends X, java.util.Date> key, java.util.Date value ) { return new SimpleExpression<X>( key.getName() + " > %s", value ); } /** * 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件}を生成します。 * <br/>{@link #greaterThan(Property, Number)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> gt( Property<? extends X, ? extends Number> key, Number value ) { return greaterThan( key, value ); } /** * 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件}を生成します。 * <br/>{@link #greaterThan(Property, java.util.Date)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より大きい」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> gt( Property<? extends X, java.util.Date> key, java.util.Date value ) { return greaterThan( key, value ); } /** * 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> lessThan( Property<? extends X, ? extends Number> key, Number value ) { return new SimpleExpression<X>( key.getName() + " < %s", value ); } /** * 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件}を生成します。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> lessThan( Property<? extends X, java.util.Date> key, java.util.Date value ) { return new SimpleExpression<X>( key.getName() + " < %s", value ); } /** * 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件}を生成します。 * <br/>{@link #lessThan(Property, Number)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> lt( Property<? extends X, ? extends Number> key, Number value ) { return lessThan( key, value ); } /** * 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件}を生成します。 * <br/>{@link #lessThan(Property, java.util.Date)}の別名です。 * * @param <X> モデルの型 * * @param key * プロパティ * @param value * 比較値 * @return 指定した比較値と比較して「より小さい(未満)」ことを評価する{@link Expression 条件} */ public static final < X > Expression<X> lt( Property<? extends X, java.util.Date> key, java.util.Date value ) { return lessThan( key, value ); } }
Expression
import java.util.List; /** * 検索条件 * * @param <X> */ public interface Expression<X> { /** * フィルタ文字列を生成する。 * @return フィルタ文字列 */ String createFilter( ); /** * パラメータ文字列を生成する。 * @return パラメータ文字列 */ String createParameter( ); /** * インポート文字列を生成する。 * @return インポート文字列 */ String createImport( ); /** * フィルタ文字列を取得する。 * @return フィルタ文字列 */ String getFilter(); /** * パラメータのリストを取得する。 * @return パラメータのリスト */ List<Object> getParameterValues(); }
AbstractExpression
import java.util.HashSet; import java.util.Set; /** * 検索条件の抽象基底クラス。 * @param <X> モデルの型 */ abstract class AbstractExpression<X> implements Expression<X> { @Override public String createFilter( ) { Object[] strs = new String[getParameterValues().size()]; for ( int i=0; i< strs.length; i++ ) { strs[i] = "arg" + String.valueOf( i ); } return String.format( getFilter(), strs ); } @Override public String createImport() { Set<String> imported = new HashSet<String>(); StringBuilder buff = new StringBuilder(); for ( int i=0;i<getParameterValues().size(); i++ ) { String name = getParameterValues().get(i).getClass().getName(); if (imported.contains(name)) continue; buff.append( "import " ).append( name ).append(";"); imported.add( name ); } return buff.toString(); } @Override public String createParameter() { StringBuilder buff = new StringBuilder(); for ( int i=0;i<getParameterValues().size(); i++ ) { Object arg = getParameterValues().get(i); buff.append( arg.getClass().getName() ); buff.append( " arg" ).append( i ); if ( i != getParameterValues().size()-1 ) buff.append( "," ); } return buff.toString(); } }
SimpleExpression
import java.util.Arrays; import java.util.List; /** * {@link Expression}の実装。 * * @param <X> 条件の型 */ public class SimpleExpression<X> extends AbstractExpression<X> { /** フィルタ */ private String filter; /** 引数 */ private List<Object> args; /** * コンストラクタ */ public SimpleExpression() {} /** * コンストラクタ * @param filter フィルタ * @param args 引数 */ public SimpleExpression( String filter, List<Object> args ) { this.filter = filter; this.args = args; } /** * コンストラクタ * @param filter フィルタ * @param args 引数 */ public SimpleExpression( String filter, Object... args ) { this( filter, Arrays.asList(args) ); } @Override public String getFilter() { return filter; } public void setFilter(String filter) { this.filter = filter; } @Override public List<Object> getParameterValues( ) { return args; } public void setParameterValues(List<Object> args) { this.args = args; } @Override public boolean equals ( Object obj ) { if ( obj == null ) { return false; } if ( obj instanceof SimpleExpression<?> ) { Object[] that = getValues((SimpleExpression<?>) obj); return Arrays.deepEquals(that, getValues(this)); } return false; } @Override public int hashCode () { return Arrays.deepHashCode(getValues(this)); } private static Object[] getValues(SimpleExpression<?> v) { return new Object[] { v.filter, v.args }; } }
FilterExpression
import java.util.Arrays; import java.util.List; /** * 検索条件をフィルタする検索条件 * * @param <X> モデルの型 */ public class FilterExpression<X> extends AbstractExpression<X> { /**結合する検索条件*/ private Expression<X> expression; /**演算子*/ private String operator; /** * コンストラクタ */ public FilterExpression() {} /** * コンストラクタ * @param operator 演算子 * @param expression フィルタする検索条件 */ public FilterExpression( String operator, Expression<X> expression ) { this.operator = operator; this.expression = expression; } @Override public String getFilter() { StringBuilder buff = new StringBuilder(); buff.append( operator ).append( "( " ).append( expression.getFilter() ).append( " )" ); return buff.toString(); } @Override public List<Object> getParameterValues() { return expression.getParameterValues(); } public Expression<X> getExpression() { return expression; } public void setExpression ( Expression<X> expression) { this.expression = expression; } public String getOperator() { return operator; } public void setOperator(String operator) { this.operator = operator; } @Override public boolean equals ( Object obj ) { if ( obj == null ) { return false; } if ( obj instanceof FilterExpression<?> ) { Object[] that = getValues((FilterExpression<?>) obj); return Arrays.deepEquals(that, getValues(this)); } return false; } @Override public int hashCode () { return Arrays.deepHashCode(getValues(this)); } private static Object[] getValues(FilterExpression<?> v) { return new Object[] { v.operator, v.expression }; } }
CompositeExpression
import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 複数の検索条件を結合した検索条件 * * @param <X> モデルの型 */ public class CompositeExpression<X> extends AbstractExpression<X> { /**結合する検索条件*/ private List<? extends Expression<X>> expressions; /**演算子*/ private String operator; /** * コンストラクタ */ public CompositeExpression() {} /** * コンストラクタ * @param operator 演算子 * @param expressions 結合する検索条件 */ public CompositeExpression( String operator, List<? extends Expression<X>> expressions ) { this.operator = operator; this.expressions = expressions; } /** * コンストラクタ * @param operator 演算子 * @param expressions 結合する検索条件 */ public CompositeExpression( String operator, Expression<X>... expressions ) { this( operator, Arrays.asList(expressions) ); } @Override public String getFilter() { StringBuilder buff = new StringBuilder(); buff.append( "( " ); for ( int i=0; i < expressions.size(); i++ ) { buff.append( expressions.get(i).getFilter() ); if ( expressions.size()-1 != i ) { buff.append( " " ).append( operator ).append( " " ); } } buff.append( " )" ); return buff.toString(); } @Override public List<Object> getParameterValues() { List<Object> args = new ArrayList<Object>(); for ( Expression<X> e : expressions ) { args.addAll( e.getParameterValues() ); } return args; } public List<? extends Expression<X>> getExpressions() { return expressions; } public void setExpressions(List<? extends Expression<X>> expressions) { this.expressions = expressions; } public String getOperator() { return operator; } public void setOperator(String operator) { this.operator = operator; } @Override public boolean equals ( Object obj ) { if ( obj == null ) { return false; } if ( obj instanceof CompositeExpression<?> ) { Object[] that = getValues((CompositeExpression<?>) obj); return Arrays.deepEquals(that, getValues(this)); } return false; } @Override public int hashCode () { return Arrays.deepHashCode(getValues(this)); } private static Object[] getValues(CompositeExpression<?> v) { return new Object[] { v.operator, v.expressions }; } }
Property
/** * プロパティ * * @param <X> モデルの型 * @param <V> 値の型 */ public interface Property<X, V> { /** * プロパティ名を取得する。 * @return プロパティ名 */ String getName(); }
PropertyImpl
import java.util.Arrays; /** * {@link Property}の実装。 * * @param <X> モデルの型 * @param <V> 値の型 */ public class PropertyImpl<X, V> implements Property<X, V> { /** プロパティ名 */ private String name; /** * コンストラクタ */ public PropertyImpl() {} /** * コンストラクタ * @param name プロパティ名 */ public PropertyImpl( String name ) { this.name = name; } @Override public String getName() { return this.name; } public void setName(String name) { this.name = name; } @Override public boolean equals ( Object obj ) { if ( obj == null ) { return false; } if ( obj instanceof PropertyImpl<?, ?> ) { Object[] that = getValues((PropertyImpl<?, ?>) obj); return Arrays.deepEquals(that, getValues(this)); } return false; } @Override public int hashCode () { return Arrays.deepHashCode(getValues(this)); } private static Object[] getValues(PropertyImpl<?, ?> v) { return new Object[] { v.name }; } }