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.LinkedList;
027    import java.util.List;
028    import org.jboss.dna.common.text.Inflector;
029    import org.jboss.dna.common.util.CheckArg;
030    import org.jboss.dna.common.util.HashCode;
031    import org.jboss.dna.graph.GraphI18n;
032    import org.jboss.dna.graph.Location;
033    import org.jboss.dna.graph.connector.RepositoryConnection;
034    import org.jboss.dna.graph.property.Path;
035    import org.jboss.dna.graph.property.Property;
036    
037    /**
038     * Instruction to read a block of the children of a node, where the block is dictated by the {@link #startingAtIndex() starting
039     * index} and the {@link #count() maximum number of children} to include in the block. This command is useful when paging through
040     * a large number of children.
041     * 
042     * @see ReadNextBlockOfChildrenRequest
043     * @author Randall Hauch
044     */
045    public class ReadBlockOfChildrenRequest extends CacheableRequest {
046    
047        public static final int INDEX_NOT_USED = -1;
048    
049        private static final long serialVersionUID = 1L;
050    
051        private final Location of;
052        private final String workspaceName;
053        private final List<Location> children = new LinkedList<Location>();
054        private final int startingAtIndex;
055        private final int count;
056        private Location actualLocation;
057    
058        /**
059         * Create a request to read a block of the children of a node at the supplied location. The block is defined by the starting
060         * index of the first child and the number of children to include. Note that this index is <i>not</i> the
061         * {@link Path.Segment#getIndex() same-name-sibiling index}, but rather is the index of the child as if the children were in
062         * an array.
063         * 
064         * @param of the location of the node whose children are to be read
065         * @param workspaceName the name of the workspace containing the parent
066         * @param startingIndex the zero-based index of the first child to be included in the block
067         * @param count the maximum number of children that should be included in the block
068         * @throws IllegalArgumentException if the location or workspace name is null, if <code>startingIndex</code> is negative, or
069         *         if <code>count</count> is less than 1.
070         */
071        public ReadBlockOfChildrenRequest( Location of,
072                                           String workspaceName,
073                                           int startingIndex,
074                                           int count ) {
075            CheckArg.isNotNull(of, "of");
076            CheckArg.isNonNegative(startingIndex, "startingIndex");
077            CheckArg.isPositive(count, "count");
078            CheckArg.isNotNull(workspaceName, "workspaceName");
079            this.workspaceName = workspaceName;
080            this.of = of;
081            this.startingAtIndex = startingIndex;
082            this.count = count;
083        }
084    
085        /**
086         * {@inheritDoc}
087         * 
088         * @see org.jboss.dna.graph.request.Request#isReadOnly()
089         */
090        @Override
091        public boolean isReadOnly() {
092            return true;
093        }
094    
095        /**
096         * Get the location defining the node whose children are to be read.
097         * 
098         * @return the location of the parent node; never null
099         */
100        public Location of() {
101            return of;
102        }
103    
104        /**
105         * Get the name of the workspace in which the parent and children exist.
106         * 
107         * @return the name of the workspace; never null
108         */
109        public String inWorkspace() {
110            return workspaceName;
111        }
112    
113        /**
114         * Get the maximum number of children that may be returned in the block.
115         * 
116         * @return the block's maximum count
117         * @see #startingAtIndex()
118         * @see #endingBefore()
119         */
120        public int count() {
121            return this.count;
122        }
123    
124        /**
125         * Get the starting index of the block, which is the index of the first child to include. This index corresponds to the index
126         * of all children in the list, not the {@link Path.Segment#getIndex() same-name-sibiling index}.
127         * 
128         * @return the (zero-based) child index at which this block starts; never negative and always less than
129         *         {@link #endingBefore()}
130         * @see #endingBefore()
131         * @see #count()
132         */
133        public int startingAtIndex() {
134            return this.startingAtIndex;
135        }
136    
137        /**
138         * Get the index past the last child that is to be included in the block. This index corresponds to the index of all children
139         * in the list, not the {@link Path.Segment#getIndex() same-name-sibiling index}.
140         * 
141         * @return the index just past the last child included in the block; always positive and always greater than
142         *         {@link #startingAtIndex()}.
143         * @see #startingAtIndex()
144         * @see #count()
145         */
146        public int endingBefore() {
147            return this.startingAtIndex + this.count;
148        }
149    
150        /**
151         * Get the children that were read from the {@link RepositoryConnection} after the request was processed. Each child is
152         * represented by a location.
153         * 
154         * @return the children that were read; never null
155         */
156        public List<Location> getChildren() {
157            return children;
158        }
159    
160        /**
161         * Add to the list of children that has been read the supplied children with the given path and identification properties. The
162         * children are added in order.
163         * 
164         * @param children the locations of the children that were read
165         * @throws IllegalArgumentException if the parameter is null
166         * @throws IllegalStateException if the request is frozen
167         * @see #addChild(Location)
168         * @see #addChild(Path, Property)
169         * @see #addChild(Path, Property, Property...)
170         */
171        public void addChildren( Iterable<Location> children ) {
172            checkNotFrozen();
173            CheckArg.isNotNull(children, "children");
174            for (Location child : children) {
175                if (child != null) this.children.add(child);
176            }
177        }
178    
179        /**
180         * Add to the list of children that has been read the child with the given path and identification properties. The children
181         * should be added in order.
182         * 
183         * @param child the location of the child that was read
184         * @throws IllegalArgumentException if the location is null
185         * @throws IllegalStateException if the request is frozen
186         * @see #addChild(Path, Property)
187         * @see #addChild(Path, Property, Property...)
188         */
189        public void addChild( Location child ) {
190            checkNotFrozen();
191            CheckArg.isNotNull(child, "child");
192            this.children.add(child);
193        }
194    
195        /**
196         * Add to the list of children that has been read the child with the given path and identification properties. The children
197         * should be added in order.
198         * 
199         * @param pathToChild the path of the child that was just read
200         * @param firstIdProperty the first identification property of the child that was just read
201         * @param remainingIdProperties the remaining identification properties of the child that was just read
202         * @throws IllegalArgumentException if the path or identification properties are null
203         * @throws IllegalStateException if the request is frozen
204         * @see #addChild(Location)
205         * @see #addChild(Path, Property)
206         */
207        public void addChild( Path pathToChild,
208                              Property firstIdProperty,
209                              Property... remainingIdProperties ) {
210            checkNotFrozen();
211            Location child = Location.create(pathToChild, firstIdProperty, remainingIdProperties);
212            this.children.add(child);
213        }
214    
215        /**
216         * Add to the list of children that has been read the child with the given path and identification property. The children
217         * should be added in order.
218         * 
219         * @param pathToChild the path of the child that was just read
220         * @param idProperty the identification property of the child that was just read
221         * @throws IllegalArgumentException if the path or identification properties are null
222         * @throws IllegalStateException if the request is frozen
223         * @see #addChild(Location)
224         * @see #addChild(Path, Property, Property...)
225         */
226        public void addChild( Path pathToChild,
227                              Property idProperty ) {
228            checkNotFrozen();
229            Location child = Location.create(pathToChild, idProperty);
230            this.children.add(child);
231        }
232    
233        /**
234         * Sets the actual and complete location of the node whose children have been read. This method must be called when processing
235         * the request, and the actual location must have a {@link Location#getPath() path}.
236         * 
237         * @param actual the actual location of the node being read, or null if the {@link #of() current location} should be used
238         * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same
239         *         location} as the {@link #of() current location}, or if the actual location does not have a path.
240         * @throws IllegalStateException if the request is frozen
241         */
242        public void setActualLocationOfNode( Location actual ) {
243            checkNotFrozen();
244            if (!of.isSame(actual)) { // not same if actual is null
245                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, of));
246            }
247            assert actual != null;
248            if (!actual.hasPath()) {
249                throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
250            }
251            this.actualLocation = actual;
252        }
253    
254        /**
255         * Get the actual location of the node whose children were read.
256         * 
257         * @return the actual location, or null if the actual location was not set
258         */
259        public Location getActualLocationOfNode() {
260            return actualLocation;
261        }
262    
263        /**
264         * {@inheritDoc}
265         * 
266         * @see org.jboss.dna.graph.request.Request#cancel()
267         */
268        @Override
269        public void cancel() {
270            super.cancel();
271            this.actualLocation = null;
272            this.children.clear();
273        }
274    
275        /**
276         * {@inheritDoc}
277         * 
278         * @see java.lang.Object#hashCode()
279         */
280        @Override
281        public int hashCode() {
282            return HashCode.compute(of, workspaceName);
283        }
284    
285        /**
286         * {@inheritDoc}
287         * 
288         * @see java.lang.Object#equals(java.lang.Object)
289         */
290        @Override
291        public boolean equals( Object obj ) {
292            if (obj == this) return true;
293            if (this.getClass().isInstance(obj)) {
294                ReadBlockOfChildrenRequest that = (ReadBlockOfChildrenRequest)obj;
295                if (!this.of().equals(that.of())) return false;
296                if (this.startingAtIndex() != that.startingAtIndex()) return false;
297                if (this.count() != that.count()) return false;
298                if (!this.inWorkspace().equals(that.inWorkspace())) return false;
299                return true;
300            }
301            return false;
302        }
303    
304        /**
305         * {@inheritDoc}
306         * 
307         * @see java.lang.Object#toString()
308         */
309        @Override
310        public String toString() {
311            Inflector inflector = Inflector.getInstance();
312            if (count() == 1) {
313                return "read " + inflector.ordinalize(startingAtIndex()) + " thru " + inflector.ordinalize(endingBefore() - 1)
314                       + " children of " + of() + " in the \"" + workspaceName + "\" workspace";
315            }
316            return "read " + inflector.ordinalize(startingAtIndex()) + " child of " + of() + " in the \"" + workspaceName
317                   + "\" workspace";
318        }
319    
320    }