View Javadoc

1   /**
2    *
3    * Copyright 2003-2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.nntp.newsrc;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.util.ArrayList;
23  import java.util.StringTokenizer;
24  
25  /**
26   * Manage a list of ranges values from a newsrc file.
27   */
28  public class RangeList {
29      boolean dirty = false;
30  
31      ArrayList ranges = new ArrayList();
32  
33      /**
34       * Create a RangeList instance from a newsrc range line. Values are saved as
35       * a comma separated set of range values. A range value is either a single
36       * number or a hypenated pair of numbers.
37       * 
38       * @param line
39       *            The newsrc range line.
40       */
41      public RangeList(String line) {
42  
43          // we might be creating an first time list, so nothing to parse.
44          if (line != null) {
45              // ranges are comma delimited tokens
46              StringTokenizer tokenizer = new StringTokenizer(line, ",");
47  
48              while (tokenizer.hasMoreTokens()) {
49                  String rangeString = (String) tokenizer.nextToken();
50                  rangeString = rangeString.trim();
51                  if (rangeString.length() != 0) {
52                      Range range = Range.parse(rangeString);
53                      if (range != null) {
54                          insert(range);
55                      }
56                  }
57              }
58          }
59          // make sure we start out in a clean state. Any changes from this point
60          // will flip on the dirty flat.
61          dirty = false;
62      }
63  
64      /**
65       * Insert a range item into our list. If possible, the inserted range will
66       * be merged with existing ranges.
67       * 
68       * @param newRange
69       *            The new range item.
70       */
71      public void insert(Range newRange) {
72          // first find the insertion point
73          for (int i = 0; i < ranges.size(); i++) {
74              Range range = (Range) ranges.get(i);
75              // does an existing range fully contain the new range, we don't need
76              // to insert anything.
77              if (range.contains(newRange)) {
78                  return;
79              }
80  
81              // does the current one abutt or overlap with the inserted range?
82              if (range.abutts(newRange) || range.overlaps(newRange)) {
83                  // rats, we have an overlap...and it is possible that we could
84                  // overlap with
85                  // the next range after this one too. Therefore, we merge these
86                  // two ranges together,
87                  // remove the place where we're at, and then recursively insert
88                  // the larger range into
89                  // the list.
90                  dirty = true;
91                  newRange.merge(range);
92                  ranges.remove(i);
93                  insert(newRange);
94                  return;
95              }
96  
97              // ok, we don't touch the current one at all. If it is completely
98              // above
99              // range we're adding, we can just poke this into the list here.
100             if (newRange.lessThan(range)) {
101                 dirty = true;
102                 ranges.add(i, newRange);
103                 return;
104             }
105         }
106         dirty = true;
107         // this is easy (and fairly common)...we just tack this on to the end.
108         ranges.add(newRange);
109     }
110 
111     /**
112      * Test if a given article point falls within one of the contained Ranges.
113      * 
114      * @param article
115      *            The test point.
116      * 
117      * @return true if this falls within one of our current mark Ranges, false
118      *         otherwise.
119      */
120     public boolean isMarked(int article) {
121         for (int i = 0; i < ranges.size(); i++) {
122             Range range = (Range) ranges.get(i);
123             if (range.contains(article)) {
124                 return true;
125             }
126             // we've passed the point where a match is possible.
127             if (range.greaterThan(article)) {
128                 return false;
129             }
130         }
131         return false;
132     }
133 
134     /**
135      * Mark a target article as having been seen.
136      * 
137      * @param article
138      *            The target article number.
139      */
140     public void setMarked(int article) {
141         // go through the insertion logic.
142         insert(new Range(article, article));
143     }
144 
145     /**
146      * Clear the seen mark for a target article.
147      * 
148      * @param article
149      *            The target article number.
150      */
151     public void setUnmarked(int article) {
152         for (int i = 0; i < ranges.size(); i++) {
153             Range range = (Range) ranges.get(i);
154             // does this fall within an existing range? We don't need to do
155             // anything here.
156             if (range.contains(article)) {
157                 // ok, we've found where to insert, now to figure out how to
158                 // insert
159                 // article is at the beginning of the range. We can just
160                 // increment the lower
161                 // bound, or if this is a single element range, we can remove it
162                 // entirely.
163                 if (range.getStart() == article) {
164                     if (range.getEnd() == article) {
165                         // piece of cake!
166                         ranges.remove(i);
167                     } else {
168                         // still pretty easy.
169                         range.setStart(article + 1);
170                     }
171                 } else if (range.getEnd() == article) {
172                     // pretty easy also
173                     range.setEnd(article - 1);
174                 } else {
175                     // split this into two ranges and insert the trailing piece
176                     // after this.
177                     Range section = range.split(article);
178                     ranges.add(i + 1, section);
179                 }
180                 dirty = true;
181                 return;
182             }
183             // did we find a point where any articles are greater?
184             if (range.greaterThan(article)) {
185                 // nothing to do
186                 return;
187             }
188         }
189         // didn't find it at all. That was easy!
190     }
191 
192     /**
193      * Save this List of Ranges out to a .newsrc file. This creates a
194      * comma-separated list of range values from each of the Ranges.
195      * 
196      * @param out
197      *            The target output stream.
198      * 
199      * @exception IOException
200      */
201     public void save(Writer out) throws IOException {
202         // we have an empty list
203         if (ranges.size() == 0) {
204             return;
205         }
206 
207         Range range = (Range) ranges.get(0);
208         range.save(out);
209 
210         for (int i = 1; i < ranges.size(); i++) {
211             out.write(",");
212             range = (Range) ranges.get(i);
213             range.save(out);
214         }
215     }
216 
217     /**
218      * Return the state of the dirty flag.
219      * 
220      * @return True if the range list information has changed, false otherwise.
221      */
222     public boolean isDirty() {
223         return dirty;
224     }
225 }