001/*
002 * Copyright 2006-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.core.step.item;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024
025/**
026 * Encapsulation of a list of items to be processed and possibly a list of
027 * failed items to be skipped. To mark an item as skipped clients should iterate
028 * over the chunk using the {@link #iterator()} method, and if there is a
029 * failure call {@link org.springframework.batch.core.step.item.Chunk.ChunkIterator#remove()} on the iterator.
030 * The skipped items are then available through the chunk.
031 *
032 * @author Dave Syer
033 * @since 2.0
034 */
035public class Chunk<W> implements Iterable<W> {
036
037        private List<W> items = new ArrayList<W>();
038
039        private List<SkipWrapper<W>> skips = new ArrayList<SkipWrapper<W>>();
040
041        private List<Exception> errors = new ArrayList<Exception>();
042
043        private Object userData;
044
045        private boolean end;
046
047        private boolean busy;
048
049        public Chunk() {
050                this(null, null);
051        }
052
053        public Chunk(Collection<? extends W> items) {
054                this(items, null);
055        }
056
057        public Chunk(Collection<? extends W> items, List<SkipWrapper<W>> skips) {
058                super();
059                if (items != null) {
060                        this.items = new ArrayList<W>(items);
061                }
062                if (skips != null) {
063                        this.skips = new ArrayList<SkipWrapper<W>>(skips);
064                }
065        }
066
067        /**
068         * Add the item to the chunk.
069         * @param item the item to add
070         */
071        public void add(W item) {
072                items.add(item);
073        }
074
075        /**
076         * Clear the items down to signal that we are done.
077         */
078        public void clear() {
079                items.clear();
080                skips.clear();
081                userData = null;
082        }
083
084        /**
085         * @return a copy of the items to be processed as an unmodifiable list
086         */
087        public List<W> getItems() {
088                return Collections.unmodifiableList(new ArrayList<W>(items));
089        }
090
091        /**
092         * @return a copy of the skips as an unmodifiable list
093         */
094        public List<SkipWrapper<W>> getSkips() {
095                return Collections.unmodifiableList(skips);
096        }
097
098        /**
099         * @return a copy of the anonymous errors as an unmodifiable list
100         */
101        public List<Exception> getErrors() {
102                return Collections.unmodifiableList(errors);
103        }
104
105        /**
106         * Register an anonymous skip. To skip an individual item, use
107         * {@link ChunkIterator#remove()}.
108         *
109         * @param e the exception that caused the skip
110         */
111        public void skip(Exception e) {
112                errors.add(e);
113        }
114
115        /**
116         * @return true if there are no items in the chunk
117         */
118        public boolean isEmpty() {
119                return items.isEmpty();
120        }
121
122        /**
123         * Get an unmodifiable iterator for the underlying items.
124         * @see java.lang.Iterable#iterator()
125         */
126        @Override
127        public ChunkIterator iterator() {
128                return new ChunkIterator(items);
129        }
130
131        /**
132         * @return the number of items (excluding skips)
133         */
134        public int size() {
135                return items.size();
136        }
137
138        /**
139         * Flag to indicate if the source data is exhausted.
140         *
141         * @return true if there is no more data to process
142         */
143        public boolean isEnd() {
144                return end;
145        }
146
147        /**
148         * Set the flag to say that this chunk represents an end of stream (there is
149         * no more data to process).
150         */
151        public void setEnd() {
152                this.end = true;
153        }
154
155        /**
156         * Query the chunk to see if anyone has registered an interest in keeping a
157         * reference to it.
158         *
159         * @return the busy flag
160         */
161        public boolean isBusy() {
162                return busy;
163        }
164
165        /**
166         * Register an interest in the chunk to prevent it from being cleaned up
167         * before the flag is reset to false.
168         *
169         * @param busy the flag to set
170         */
171        public void setBusy(boolean busy) {
172                this.busy = busy;
173        }
174
175        /**
176         * Clear only the skips list.
177         */
178        public void clearSkips() {
179                skips.clear();
180        }
181
182        public Object getUserData() {
183                return userData;
184        }
185
186        public void setUserData(Object userData) {
187                this.userData = userData;
188        }
189
190        /*
191         * (non-Javadoc)
192         *
193         * @see java.lang.Object#toString()
194         */
195        @Override
196        public String toString() {
197                return String.format("[items=%s, skips=%s]", items, skips);
198        }
199
200        /**
201         * Special iterator for a chunk providing the {@link #remove(Throwable)}
202         * method for dynamically removing an item and adding it to the skips.
203         *
204         * @author Dave Syer
205         *
206         */
207        public class ChunkIterator implements Iterator<W> {
208
209                final private Iterator<W> iterator;
210
211                private W next;
212
213                public ChunkIterator(List<W> items) {
214                        iterator = items.iterator();
215                }
216
217                @Override
218                public boolean hasNext() {
219                        return iterator.hasNext();
220                }
221
222                @Override
223                public W next() {
224                        next = iterator.next();
225                        return next;
226                }
227
228                public void remove(Throwable e) {
229                        remove();
230                        skips.add(new SkipWrapper<W>(next, e));
231                }
232
233                @Override
234                public void remove() {
235                        if (next == null) {
236                                if (iterator.hasNext()) {
237                                        next = iterator.next();
238                                }
239                                else {
240                                        return;
241                                }
242                        }
243                        iterator.remove();
244                }
245
246                @Override
247                public String toString() {
248                        return String.format("[items=%s, skips=%s]", items, skips);
249                }
250
251        }
252
253}