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