001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.javamail.store.nntp.newsrc;
021    
022    import java.io.IOException;
023    import java.io.Writer;
024    
025    /**
026     * Represent a single Range in a newsrc file. A Range can be either a single
027     * number (start == end) or a span of article numbers.
028     */
029    public class Range {
030        // the low end of the range
031        int start;
032    
033        // the high end of the range (start and end are inclusive);
034        int end;
035    
036        /**
037         * Construct a Range item for a single digit range.
038         * 
039         * @param spot
040         *            The location of the singleton.
041         */
042        public Range(int spot) {
043            this(spot, spot);
044        }
045    
046        /**
047         * Construct a Range item.
048         * 
049         * @param start
050         *            The starting point of the Range.
051         * @param end
052         *            The Range end point (which may be equal to the starting
053         *            point).
054         */
055        public Range(int start, int end) {
056            this.start = start;
057            this.end = end;
058        }
059    
060        /**
061         * Parse a section of a .newsrc range string into a single Range item. The
062         * range is either a single number, or a pair of numbers separated by a
063         * hyphen.
064         * 
065         * @param range
066         *            The range string.
067         * 
068         * @return A constructed Range item, or null if there is a parsing error.
069         */
070        static public Range parse(String range) {
071            // a range from a newsrc file is either a single number or in the format
072            // 'nnnn-mmmm'. We need
073            // to figure out which type this is.
074            int marker = range.indexOf('-');
075    
076            try {
077                if (marker != -1) {
078                    String rangeStart = range.substring(0, marker).trim();
079                    String rangeEnd = range.substring(marker + 1).trim();
080    
081                    int start = Integer.parseInt(rangeStart);
082                    int end = Integer.parseInt(rangeEnd);
083    
084                    if (start >= 0 && end >= 0) {
085                        return new Range(start, end);
086                    }
087                } else {
088                    // use the entire token
089                    int start = Integer.parseInt(range);
090                    // and start and the end are the same
091                    return new Range(start, start);
092    
093                }
094            } catch (NumberFormatException e) {
095            }
096            // return null for any bad values
097            return null;
098        }
099    
100        /**
101         * Get the starting point for the Range.
102         * 
103         * @return The beginning of the mark range.
104         */
105        public int getStart() {
106            return start;
107        }
108    
109        /**
110         * Set the starting point for a Range.
111         * 
112         * @param start
113         *            The new start value.
114         */
115        public void setStart(int start) {
116            this.start = start;
117        }
118    
119        /**
120         * Get the ending point for the Range.
121         * 
122         * @return The end of the mark range.
123         */
124        public int getEnd() {
125            return end;
126        }
127    
128        /**
129         * Set the ending point for a Range.
130         * 
131         * @param end
132         *            The new end value.
133         */
134        public void setEnd(int end) {
135            this.end = end;
136        }
137    
138        /**
139         * Test if a range contains a point value.
140         * 
141         * @param target
142         *            The article location to test.
143         * 
144         * @return True if the target is between the start and end values,
145         *         inclusive.
146         */
147        public boolean contains(int target) {
148            return target >= start && target <= end;
149        }
150    
151        /**
152         * Test if one range is completely contained within another Range.
153         * 
154         * @param other
155         *            The other test range.
156         * 
157         * @return true if the other start and end points are contained within this
158         *         range.
159         */
160        public boolean contains(Range other) {
161            return contains(other.getStart()) && contains(other.getEnd());
162        }
163    
164        /**
165         * Tests if two ranges overlap
166         * 
167         * @param other
168         *            The other test range.
169         * 
170         * @return true if the start or end points of either range are contained
171         *         within the range of the other.
172         */
173        public boolean overlaps(Range other) {
174            return other.contains(start) || other.contains(end) || contains(other.getStart()) || contains(other.getEnd());
175        }
176    
177        /**
178         * Test if two ranges exactly abutt each other.
179         * 
180         * @param other
181         *            The other Range to test.
182         * 
183         * @return true if the end of one range abutts the start of the other range.
184         */
185        public boolean abutts(Range other) {
186            return other.getStart() == end + 1 || other.getEnd() == start - 1;
187        }
188    
189        /**
190         * Tests if a single point abutts either the start or end of this Range.
191         * 
192         * @param article
193         *            The point to test.
194         * 
195         * @return true if test point is equal to start - 1 or end + 1.
196         */
197        public boolean abutts(int article) {
198            return article == start - 1 || article == end + 1;
199        }
200    
201        /**
202         * Test if a point is below the test Range.
203         * 
204         * @param article
205         *            The point to test.
206         * 
207         * @return true if the entire range is less than the test point.
208         */
209        public boolean lessThan(int article) {
210            return end < article;
211        }
212    
213        /**
214         * Test if another Range is less than this Range.
215         * 
216         * @param other
217         *            The other Range to test.
218         * 
219         * @return true if the other Range lies completely below this Range.
220         */
221        public boolean lessThan(Range other) {
222            return end < other.start;
223        }
224    
225        /**
226         * Test if a point is above the test Range.
227         * 
228         * @param article
229         *            The point to test.
230         * 
231         * @return true if the entire range is greater than the test point.
232         */
233        public boolean greaterThan(int article) {
234            return start > article;
235        }
236    
237        /**
238         * Test if another Range is greater than this Range.
239         * 
240         * @param other
241         *            The other Range to test.
242         * 
243         * @return true if the other Range lies completely below this Range.
244         */
245        public boolean greaterThan(Range other) {
246            return start > other.end;
247        }
248    
249        /**
250         * Merge another Range into this one. Merging will increase the bounds of
251         * this Range to encompass the entire span of the two. If the Ranges do not
252         * overlap, the newly created range will include the gap between the two.
253         * 
254         * @param other
255         *            The Range to merge.
256         */
257        public void merge(Range other) {
258            if (other.start < start) {
259                start = other.start;
260            }
261    
262            if (other.end > end) {
263                end = other.end;
264            }
265        }
266    
267        /**
268         * Split a range at a given split point. Splitting will truncate at the
269         * split location - 1 and return a new range beginning at location + 1; This
270         * code assumes that the split location is at neither end poing.
271         * 
272         * @param location
273         *            The split location. Location must be in the range start <
274         *            location < end.
275         * 
276         * @return A new Range object for the split portion of the range.
277         */
278        public Range split(int location) {
279            int newEnd = end;
280    
281            end = location - 1;
282    
283            return new Range(location + 1, newEnd);
284        }
285    
286        /**
287         * Save an individual range element to a newsrc file. The range is expressed
288         * either as a single number, or a hypenated pair of numbers.
289         * 
290         * @param out
291         *            The output writer used to save the data.
292         * 
293         * @exception IOException
294         */
295        public void save(Writer out) throws IOException {
296            // do we have a single data point range?
297            if (start == end) {
298                out.write(Integer.toString(start));
299            } else {
300                out.write(Integer.toString(start));
301                out.write("-");
302                out.write(Integer.toString(end));
303            }
304        }
305    
306        /**
307         * Convert a Range into String form. Used mostly for debugging.
308         * 
309         * @return The String representation of the Range.
310         */
311        public String toString() {
312            if (start == end) {
313                return Integer.toString(start);
314            } else {
315                return Integer.toString(start) + "-" + Integer.toString(end);
316            }
317        }
318    }