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