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     */
016    package org.joda.beans.query;
017    
018    import java.util.ArrayList;
019    import java.util.Collections;
020    import java.util.List;
021    
022    import org.joda.beans.Bean;
023    import org.joda.beans.BeanQuery;
024    import 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     */
036    public 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 prop1  the first query, not null
054         * @param prop2  the second query, not null
055         * @return the compound query, not null
056         * @throws IllegalArgumentException if unable to obtain the meta-bean
057         */
058        public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<P> prop2) {
059            if (prop1 == null || prop2 == null) {
060                throw new NullPointerException("BeanQuery must not be null");
061            }
062            List<BeanQuery<? extends Bean>> list = Collections.<BeanQuery<? extends Bean>>singletonList(prop1);
063            return new ChainedBeanQuery<P>(list, prop2);
064        }
065    
066        /**
067         * Obtains a chained query from three queries.
068         * <p>
069         * {@link MetaProperty} implements {@link BeanQuery}, so typically the parameters
070         * are in fact meta-properties.
071         * 
072         * @param prop1  the first query, not null
073         * @param prop2  the second query, not null
074         * @param prop3  the third query, not null
075         * @return the compound query, not null
076         * @throws IllegalArgumentException if unable to obtain the meta-bean
077         */
078        public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<? extends Bean> prop2, BeanQuery<P> prop3) {
079            if (prop1 == null || prop2 == null || prop3 == null) {
080                throw new NullPointerException("BeanQuery must not be null");
081            }
082            List<BeanQuery<? extends Bean>> list = new ArrayList<BeanQuery<? extends Bean>>();
083            list.add(prop1);
084            list.add(prop2);
085            return new ChainedBeanQuery<P>(list, prop3);
086        }
087    
088        /**
089         * Obtains a chained query from four queries.
090         * <p>
091         * {@link MetaProperty} implements {@link BeanQuery}, so typically the parameters
092         * are in fact meta-properties.
093         * 
094         * @param prop1  the first query, not null
095         * @param prop2  the second query, not null
096         * @param prop3  the third query, not null
097         * @param prop4  the fourth query, not null
098         * @return the compound query, not null
099         * @throws IllegalArgumentException if unable to obtain the meta-bean
100         */
101        public static <P> ChainedBeanQuery<P> of(BeanQuery<? extends Bean> prop1, BeanQuery<? extends Bean> prop2, BeanQuery<? extends Bean> prop3, BeanQuery<P> prop4) {
102            if (prop1 == null || prop2 == null || prop3 == null || prop4 == null) {
103                throw new NullPointerException("BeanQuery must not be null");
104            }
105            List<BeanQuery<? extends Bean>> list = new ArrayList<BeanQuery<? extends Bean>>();
106            list.add(prop1);
107            list.add(prop2);
108            list.add(prop3);
109            return new ChainedBeanQuery<P>(list, prop4);
110        }
111    
112        //-------------------------------------------------------------------------
113        /**
114         * Restricted constructor.
115         */
116        private ChainedBeanQuery(List<BeanQuery<? extends Bean>> metaProperties, BeanQuery<P> last) {
117            this.chain = metaProperties;
118            this.last = last;
119        }
120    
121        //-----------------------------------------------------------------------
122        /**
123         * Gets the list of queries being chained.
124         * <p>
125         * {@link MetaProperty} implements {@link BeanQuery}, so typically the chain
126         * is formed from meta-properties.
127         * 
128         * @return the list of all meta-properties being chained, not null
129         */
130        public List<BeanQuery<?>> getChain() {
131            List<BeanQuery<?>> list = new ArrayList<BeanQuery<?>>(chain);
132            list.add(last);
133            return list;
134        }
135    
136        //-------------------------------------------------------------------------
137        @Override
138        public P get(Bean bean) {
139            for (BeanQuery<? extends Bean> mp : chain) {
140                bean = mp.get(bean);
141            }
142            return last.get(bean);
143        }
144    
145        //-------------------------------------------------------------------------
146        @Override
147        public String toString() {
148            StringBuilder buf = new StringBuilder(64);
149            for (BeanQuery<? extends Bean> mp : chain) {
150                buf.append(mp).append('.');
151            }
152            buf.append(last);
153            return buf.toString();
154        }
155    
156    }