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 net.jcip.annotations.Immutable;
033    import org.jboss.dna.common.util.CheckArg;
034    
035    /**
036     * A request that wraps multiple other requests, allowing multiple requests to be treated as a single request.
037     * <p>
038     * Note that {@link #isCancelled()} and {@link #cancel()} apply to all requests contained by the composite request. In other
039     * words, cancelling this request immediately marks all contained requests as cancelled. However, cancelling any request in the
040     * request has the effect of cancelling all other requests in the composite, including the composite. (This is implemented by
041     * having all {@link Request} objects in the composite share the same cancelled flag object.)
042     * </p>
043     * 
044     * @author Randall Hauch
045     */
046    @Immutable
047    public class CompositeRequest extends Request implements Iterable<Request> {
048    
049        private static final long serialVersionUID = 1L;
050    
051        public static final int UNKNOWN_NUMBER_OF_REQUESTS = Integer.MAX_VALUE;
052    
053        /**
054         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
055         * 
056         * @param requests the requests to wrap
057         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
058         * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
059         */
060        public static Request with( Request... requests ) {
061            CheckArg.isNotEmpty(requests, "requests");
062            if (requests.length == 1) {
063                CheckArg.isNotNull(requests[0], "requests[0]");
064                return requests[0];
065            }
066            boolean readOnly = true;
067            List<Request> list = new ArrayList<Request>(requests.length);
068            for (Request request : requests) {
069                if (request == null) continue;
070                if (request instanceof CompositeRequest) {
071                    CompositeRequest composite = (CompositeRequest)request;
072                    list.addAll(composite.getRequests());
073                    if (!composite.isReadOnly()) readOnly = false;
074                } else {
075                    list.add(request);
076                    if (!request.isReadOnly()) readOnly = false;
077                }
078            }
079            CheckArg.isNotEmpty(list, "requests");
080            return new CompositeRequest(list, readOnly);
081        }
082    
083        /**
084         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
085         * 
086         * @param requests the requests to wrap
087         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
088         * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
089         */
090        public static Request with( Iterator<? extends Request> requests ) {
091            CheckArg.isNotNull(requests, "requests");
092            boolean readOnly = true;
093            List<Request> list = new LinkedList<Request>();
094            while (requests.hasNext()) {
095                Request request = requests.next();
096                if (request == null) continue;
097                if (request instanceof CompositeRequest) {
098                    CompositeRequest composite = (CompositeRequest)request;
099                    list.addAll(composite.getRequests());
100                    if (!composite.isReadOnly()) readOnly = false;
101                } else {
102                    list.add(request);
103                    if (!request.isReadOnly()) readOnly = false;
104                }
105            }
106            if (list.size() == 1) {
107                return list.get(0);
108            }
109            CheckArg.isNotEmpty(list, "requests");
110            return new CompositeRequest(list, readOnly);
111        }
112    
113        /**
114         * Return a request that either wraps multiple requests, or the single request if only one is supplied.
115         * 
116         * @param requests the requests to wrap
117         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
118         * @throws IllegalArgumentException if there requests are null or empty
119         */
120        public static Request with( List<? extends Request> requests ) {
121            CheckArg.isNotEmpty(requests, "requests");
122            if (requests.size() == 1) {
123                return requests.get(0);
124            }
125            boolean readOnly = true;
126            for (Request request : requests) {
127                readOnly = request.isReadOnly();
128                if (!readOnly) break;
129            }
130            return new CompositeRequest(requests, readOnly);
131        }
132    
133        /**
134         * Add requests to the supplied composite request.
135         * 
136         * @param composite the composite request to which the requests are to be added
137         * @param requests the requests to wrap
138         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
139         *         there are no request
140         * @throws IllegalArgumentException if the composite request is null
141         */
142        public static CompositeRequest add( CompositeRequest composite,
143                                            Request... requests ) {
144            CheckArg.isNotNull(composite, "composite");
145            if (requests == null || requests.length == 0) return composite;
146            List<Request> list = new ArrayList<Request>(requests.length + composite.size());
147            boolean readOnly = composite.isReadOnly();
148            if (composite.size() != 0) list.addAll(composite.getRequests());
149            for (Request request : requests) {
150                if (request == null) continue;
151                if (request instanceof CompositeRequest) {
152                    CompositeRequest compositeRequest = (CompositeRequest)request;
153                    list.addAll(compositeRequest.getRequests());
154                    if (!compositeRequest.isReadOnly()) readOnly = false;
155                } else {
156                    list.add(request);
157                    if (!request.isReadOnly()) readOnly = false;
158                }
159            }
160            return new CompositeRequest(list, readOnly);
161        }
162    
163        /**
164         * Add requests to the supplied composite request.
165         * 
166         * @param composite the composite request to which the requests are to be added
167         * @param requests the requests to wrap
168         * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
169         *         there are no request
170         * @throws IllegalArgumentException if the composite request is null
171         */
172        public static CompositeRequest add( CompositeRequest composite,
173                                            Iterator<? extends Request> requests ) {
174            CheckArg.isNotNull(composite, "composite");
175            List<Request> list = new LinkedList<Request>();
176            boolean readOnly = composite.isReadOnly();
177            if (composite.size() != 0) list.addAll(composite.getRequests());
178            while (requests.hasNext()) {
179                Request request = requests.next();
180                if (request == null) continue;
181                if (request instanceof CompositeRequest) {
182                    CompositeRequest compositeRequest = (CompositeRequest)request;
183                    list.addAll(compositeRequest.getRequests());
184                    if (!compositeRequest.isReadOnly()) readOnly = false;
185                } else {
186                    list.add(request);
187                    if (!request.isReadOnly()) readOnly = false;
188                }
189            }
190            return new CompositeRequest(list, readOnly);
191        }
192    
193        private final List<Request> requests;
194        private final boolean readOnly;
195    
196        /**
197         * Create a composite request from the supplied list of requests.
198         * 
199         * @param requests the modifiable list of requests; may not be null
200         * @param readOnly true if all of the requests are {@link Request#isReadOnly() read-only}
201         */
202        protected CompositeRequest( List<? extends Request> requests,
203                                    boolean readOnly ) {
204            // Iterate through the requests and set the cancelled flag of each request to this object's flag ...
205            final AtomicBoolean flag = super.getCancelledFlag();
206            for (Request request : requests) {
207                request.setCancelledFlag(flag);
208            }
209            this.requests = Collections.unmodifiableList(requests);
210            this.readOnly = readOnly;
211        }
212    
213        /**
214         * Create a composite request from the supplied list of requests. This is useful only for subclasses.
215         * 
216         * @param readOnly true if all of the requests are {@link Request#isReadOnly() read-only}
217         */
218        protected CompositeRequest( boolean readOnly ) {
219            this.requests = Collections.emptyList();
220            this.readOnly = false;
221        }
222    
223        /**
224         * Return the unmodifiable requests contained in this composite request.
225         * 
226         * @return requests
227         */
228        public List<Request> getRequests() {
229            return requests;
230        }
231    
232        /**
233         * Get the number of requests.
234         * 
235         * @return the number of requests
236         */
237        public int size() {
238            return requests.size();
239        }
240    
241        /**
242         * {@inheritDoc}
243         * 
244         * @see java.lang.Iterable#iterator()
245         */
246        public Iterator<Request> iterator() {
247            return requests.iterator();
248        }
249    
250        /**
251         * {@inheritDoc}
252         * 
253         * @see org.jboss.dna.graph.request.Request#isReadOnly()
254         */
255        @Override
256        public boolean isReadOnly() {
257            return readOnly;
258        }
259    
260        /**
261         * {@inheritDoc}
262         * 
263         * @see java.lang.Object#equals(java.lang.Object)
264         */
265        @Override
266        public boolean equals( Object obj ) {
267            if (obj == this) return true;
268            if (obj instanceof CompositeRequest) {
269                CompositeRequest that = (CompositeRequest)obj;
270                if (this.size() != that.size()) return false;
271                Iterator<Request> thisIter = this.iterator();
272                Iterator<Request> thatIter = that.iterator();
273                while (thisIter.hasNext()) {
274                    Request thisRequest = thisIter.next();
275                    Request thatRequest = thatIter.next();
276                    if (thisRequest == null) {
277                        if (thatRequest != null) return false;
278                    } else {
279                        if (!thisRequest.equals(thatRequest)) return false;
280                    }
281                }
282                return true;
283            }
284            return false;
285        }
286    
287    }