001/*
002 *  Copyright 2001-2013 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.beans.query;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.List;
021
022import org.joda.beans.Bean;
023import org.joda.beans.BeanQuery;
024import org.joda.beans.MetaProperty;
025
026/**
027 * A chained query, that allows two or more queries to be joined.
028 * <p>
029 * For example, consider a structure where class A has a property b of type B,
030 * and class B has a property c of type C. The compound query allows property
031 * c to be accessed directly from an instance of A.
032 * 
033 * @param <P>  the type of the result of the query
034 * @author Stephen Colebourne
035 */
036public final class ChainedBeanQuery<P> implements BeanQuery<P> {
037
038    /**
039     * The list of queries.
040     */
041    private final List<BeanQuery<? extends Bean>> chain;
042    /**
043     * The last query.
044     */
045    private final BeanQuery<P> last;
046
047    /**
048     * Obtains a chained query from two other queries.
049     * <p>
050     * {@link MetaProperty} implements {@link BeanQuery}, so typically the parameters
051     * are in fact meta-properties.
052     * 
053     * @param <P>  the result type
054     * @param prop1  the first query, not null
055     * @param prop2  the second query, not null
056     * @return the compound query, not null
057     * @throws IllegalArgumentException if unable to obtain the meta-bean
058     */
059    public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<P> prop2) {
060        if (prop1 == null || prop2 == null) {
061            throw new NullPointerException("BeanQuery must not be null");
062        }
063        List<BeanQuery<? extends Bean>> list = Collections.<BeanQuery<? extends Bean>>singletonList(prop1);
064        return new ChainedBeanQuery<P>(list, prop2);
065    }
066
067    /**
068     * Obtains a chained query from three queries.
069     * <p>
070     * {@link MetaProperty} implements {@link BeanQuery}, so typically the parameters
071     * are in fact meta-properties.
072     * 
073     * @param <P>  the result type
074     * @param prop1  the first query, not null
075     * @param prop2  the second query, not null
076     * @param prop3  the third query, not null
077     * @return the compound query, not null
078     * @throws IllegalArgumentException if unable to obtain the meta-bean
079     */
080    public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<? extends Bean> prop2, BeanQuery<P> prop3) {
081        if (prop1 == null || prop2 == null || prop3 == null) {
082            throw new NullPointerException("BeanQuery must not be null");
083        }
084        List<BeanQuery<? extends Bean>> list = new ArrayList<BeanQuery<? extends Bean>>();
085        list.add(prop1);
086        list.add(prop2);
087        return new ChainedBeanQuery<P>(list, prop3);
088    }
089
090    /**
091     * Obtains a chained query from four queries.
092     * <p>
093     * {@link MetaProperty} implements {@link BeanQuery}, so typically the parameters
094     * are in fact meta-properties.
095     * 
096     * @param <P>  the result type
097     * @param prop1  the first query, not null
098     * @param prop2  the second query, not null
099     * @param prop3  the third query, not null
100     * @param prop4  the fourth query, not null
101     * @return the compound query, not null
102     * @throws IllegalArgumentException if unable to obtain the meta-bean
103     */
104    public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<? extends Bean> prop2, BeanQuery<? extends Bean> prop3, BeanQuery<P> prop4) {
105        if (prop1 == null || prop2 == null || prop3 == null || prop4 == null) {
106            throw new NullPointerException("BeanQuery must not be null");
107        }
108        List<BeanQuery<? extends Bean>> list = new ArrayList<BeanQuery<? extends Bean>>();
109        list.add(prop1);
110        list.add(prop2);
111        list.add(prop3);
112        return new ChainedBeanQuery<P>(list, prop4);
113    }
114
115    //-------------------------------------------------------------------------
116    /**
117     * Restricted constructor.
118     */
119    private ChainedBeanQuery(List<BeanQuery<? extends Bean>> metaProperties, BeanQuery<P> last) {
120        this.chain = metaProperties;
121        this.last = last;
122    }
123
124    //-----------------------------------------------------------------------
125    /**
126     * Gets the list of queries being chained.
127     * <p>
128     * {@link MetaProperty} implements {@link BeanQuery}, so typically the chain
129     * is formed from meta-properties.
130     * 
131     * @return the list of all meta-properties being chained, not null
132     */
133    public List<BeanQuery<?>> getChain() {
134        List<BeanQuery<?>> list = new ArrayList<BeanQuery<?>>(chain);
135        list.add(last);
136        return list;
137    }
138
139    //-------------------------------------------------------------------------
140    @Override
141    public P get(Bean bean) {
142        for (BeanQuery<? extends Bean> mp : chain) {
143            bean = mp.get(bean);
144        }
145        return last.get(bean);
146    }
147
148    //-------------------------------------------------------------------------
149    @Override
150    public String toString() {
151        StringBuilder buf = new StringBuilder(64);
152        for (BeanQuery<? extends Bean> mp : chain) {
153            buf.append(mp).append('.');
154        }
155        buf.append(last);
156        return buf.toString();
157    }
158
159}