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