読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

一覧の検索条件を生成するユーティリティ

データストアからモデル一覧を取得する際の検索条件を生成するユーティリティを書いてみました。以下のような感じで使えます。

// ユーティリティを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
        };
    }    
}