001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.request;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.LinkedList;
030    import java.util.List;
031    import java.util.concurrent.atomic.AtomicBoolean;
032    import org.jboss.dna.common.util.CheckArg;
033    
034    /**
035     * A request that wraps multiple other requests, allowing multiple requests to be treated as a single request.
036     * <p>
037     * Note that {@link #isCancelled()} and {@link #cancel()} apply to all requests contained by the composite request. In other
038     * words, cancelling this request immediately marks all contained requests as cancelled. However, cancelling any request in the
039     * request has the effect of cancelling all other requests in the composite, including the composite. (This is implemented by
040     * having all {@link Request} objects in the composite share the same cancelled flag object.)
041     * </p>
042     * 
043     * @author Randall Hauch
044     */
045    public class CompositeRequest extends Request implements Iterable<Request> {
046    
047        private static final long serialVersionUID = 1L;
048    
049        /**
050         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
051         * 
052         * @param requests the requests to wrap
053         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
054         * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
055         */
056        public static Request with( Request... requests ) {
057            CheckArg.isNotEmpty(requests, "requests");
058            if (requests.length == 1) {
059                CheckArg.isNotNull(requests[0], "requests[0]");
060                return requests[0];
061            }
062            boolean readOnly = true;
063            List<Request> list = new ArrayList<Request>(requests.length);
064            for (Request request : requests) {
065                if (request == null) continue;
066                if (request instanceof CompositeRequest) {
067                    CompositeRequest composite = (CompositeRequest)request;
068                    list.addAll(composite.getRequests());
069                    if (!composite.isReadOnly()) readOnly = false;
070                } else {
071                    list.add(request);
072                    if (!request.isReadOnly()) readOnly = false;
073                }
074            }
075            CheckArg.isNotEmpty(list, "requests");
076            return new CompositeRequest(list, readOnly);
077        }
078    
079        /**
080         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
081         * 
082         * @param requests the requests to wrap
083         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
084         * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
085         */
086        public static Request with( Iterator<? extends Request> requests ) {
087            CheckArg.isNotNull(requests, "requests");
088            boolean readOnly = true;
089            List<Request> list = new LinkedList<Request>();
090            while (requests.hasNext()) {
091                Request request = requests.next();
092                if (request == null) continue;
093                if (request instanceof CompositeRequest) {
094                    CompositeRequest composite = (CompositeRequest)request;
095                    list.addAll(composite.getRequests());
096                    if (!composite.isReadOnly()) readOnly = false;
097                } else {
098                    list.add(request);
099                    if (!request.isReadOnly()) readOnly = false;
100                }
101            }
102            if (list.size() == 1) {
103                return list.get(0);
104            }
105            CheckArg.isNotEmpty(list, "requests");
106            return new CompositeRequest(list, readOnly);
107        }
108    
109        /**
110         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
111         * 
112         * @param requests the requests to wrap
113         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
114         * @throws IllegalArgumentException if there requests are null or empty
115         */
116        public static Request with( List<? extends Request> requests ) {
117            CheckArg.isNotEmpty(requests, "requests");
118            if (requests.size() == 1) {
119                return requests.get(0);
120            }
121            boolean readOnly = true;
122            for (Request request : requests) {
123                readOnly = request.isReadOnly();
124                if (!readOnly) break;
125            }
126            return new CompositeRequest(requests, readOnly);
127        }
128    
129        /**
130         * Add requests to the supplied composite request.
131         * 
132         * @param composite the composite request to which the requests are to be added
133         * @param requests the requests to wrap
134         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
135         *         there are no request
136         * @throws IllegalArgumentException if the composite request is null
137         */
138        public static CompositeRequest add( CompositeRequest composite,
139                                            Request... requests ) {
140            CheckArg.isNotNull(composite, "composite");
141            if (requests == null || requests.length == 0) return composite;
142            List<Request> list = new ArrayList<Request>(requests.length + composite.size());
143            boolean readOnly = composite.isReadOnly();
144            if (composite.size() != 0) list.addAll(composite.getRequests());
145            for (Request request : requests) {
146                if (request == null) continue;
147                if (request instanceof CompositeRequest) {
148                    CompositeRequest compositeRequest = (CompositeRequest)request;
149                    list.addAll(compositeRequest.getRequests());
150                    if (!compositeRequest.isReadOnly()) readOnly = false;
151                } else {
152                    list.add(request);
153                    if (!request.isReadOnly()) readOnly = false;
154                }
155            }
156            return new CompositeRequest(list, readOnly);
157        }
158    
159        /**
160         * Add requests to the supplied composite request.
161         * 
162         * @param composite the composite request to which the requests are to be added
163         * @param requests the requests to wrap
164         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
165         *         there are no request
166         * @throws IllegalArgumentException if the composite request is null
167         */
168        public static CompositeRequest add( CompositeRequest composite,
169                                            Iterator<? extends Request> requests ) {
170            CheckArg.isNotNull(composite, "composite");
171            List<Request> list = new LinkedList<Request>();
172            boolean readOnly = composite.isReadOnly();
173            if (composite.size() != 0) list.addAll(composite.getRequests());
174            while (requests.hasNext()) {
175                Request request = requests.next();
176                if (request == null) continue;
177                if (request instanceof CompositeRequest) {
178                    CompositeRequest compositeRequest = (CompositeRequest)request;
179                    list.addAll(compositeRequest.getRequests());
180                    if (!compositeRequest.isReadOnly()) readOnly = false;
181                } else {
182                    list.add(request);
183                    if (!request.isReadOnly()) readOnly = false;
184                }
185            }
186            return new CompositeRequest(list, readOnly);
187        }
188    
189        private final List<Request> requests;
190        private final boolean readOnly;
191    
192        /**
193         * Create a composite request from the supplied list of requests.
194         * 
195         * @param requests the modifiable list of requests; may not be null
196         * @param readOnly true if all of the requests are {@link Request#isReadOnly() read-only}
197         */
198        /*package*/CompositeRequest( List<? extends Request> requests,
199                                      boolean readOnly ) {
200            // Iterate through the requests and set the cancelled flag of each request to this object's flag ...
201            final AtomicBoolean flag = super.getCancelledFlag();
202            for (Request request : requests) {
203                request.setCancelledFlag(flag);
204            }
205            this.requests = Collections.unmodifiableList(requests);
206            this.readOnly = readOnly;
207        }
208    
209        /**
210         * Return the unmodifiable requests contained in this composite request.
211         * 
212         * @return requests
213         */
214        public List<Request> getRequests() {
215            return requests;
216        }
217    
218        /**
219         * Get the number of requests.
220         * 
221         * @return the number of requests
222         */
223        public int size() {
224            return requests.size();
225        }
226    
227        /**
228         * {@inheritDoc}
229         * 
230         * @see java.lang.Iterable#iterator()
231         */
232        public Iterator<Request> iterator() {
233            return requests.iterator();
234        }
235    
236        /**
237         * {@inheritDoc}
238         * 
239         * @see org.jboss.dna.graph.request.Request#isReadOnly()
240         */
241        @Override
242        public boolean isReadOnly() {
243            return readOnly;
244        }
245    
246        /**
247         * {@inheritDoc}
248         * 
249         * @see java.lang.Object#equals(java.lang.Object)
250         */
251        @Override
252        public boolean equals( Object obj ) {
253            if (obj == this) return true;
254            if (obj instanceof CompositeRequest) {
255                CompositeRequest that = (CompositeRequest)obj;
256                if (this.size() != that.size()) return false;
257                Iterator<Request> thisIter = this.iterator();
258                Iterator<Request> thatIter = that.iterator();
259                while (thisIter.hasNext()) {
260                    Request thisRequest = thisIter.next();
261                    Request thatRequest = thatIter.next();
262                    if (thisRequest == null) {
263                        if (thatRequest != null) return false;
264                    } else {
265                        if (!thisRequest.equals(thatRequest)) return false;
266                    }
267                }
268                return true;
269            }
270            return false;
271        }
272    
273    }