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 import java.util.ArrayList;
025 import java.util.StringTokenizer;
026
027 /**
028 * Manage a list of ranges values from a newsrc file.
029 */
030 public class RangeList {
031 boolean dirty = false;
032
033 ArrayList ranges = new ArrayList();
034
035 /**
036 * Create a RangeList instance from a newsrc range line. Values are saved as
037 * a comma separated set of range values. A range value is either a single
038 * number or a hypenated pair of numbers.
039 *
040 * @param line
041 * The newsrc range line.
042 */
043 public RangeList(String line) {
044
045 // we might be creating an first time list, so nothing to parse.
046 if (line != null) {
047 // ranges are comma delimited tokens
048 StringTokenizer tokenizer = new StringTokenizer(line, ",");
049
050 while (tokenizer.hasMoreTokens()) {
051 String rangeString = (String) tokenizer.nextToken();
052 rangeString = rangeString.trim();
053 if (rangeString.length() != 0) {
054 Range range = Range.parse(rangeString);
055 if (range != null) {
056 insert(range);
057 }
058 }
059 }
060 }
061 // make sure we start out in a clean state. Any changes from this point
062 // will flip on the dirty flat.
063 dirty = false;
064 }
065
066 /**
067 * Insert a range item into our list. If possible, the inserted range will
068 * be merged with existing ranges.
069 *
070 * @param newRange
071 * The new range item.
072 */
073 public void insert(Range newRange) {
074 // first find the insertion point
075 for (int i = 0; i < ranges.size(); i++) {
076 Range range = (Range) ranges.get(i);
077 // does an existing range fully contain the new range, we don't need
078 // to insert anything.
079 if (range.contains(newRange)) {
080 return;
081 }
082
083 // does the current one abutt or overlap with the inserted range?
084 if (range.abutts(newRange) || range.overlaps(newRange)) {
085 // rats, we have an overlap...and it is possible that we could
086 // overlap with
087 // the next range after this one too. Therefore, we merge these
088 // two ranges together,
089 // remove the place where we're at, and then recursively insert
090 // the larger range into
091 // the list.
092 dirty = true;
093 newRange.merge(range);
094 ranges.remove(i);
095 insert(newRange);
096 return;
097 }
098
099 // ok, we don't touch the current one at all. If it is completely
100 // above
101 // range we're adding, we can just poke this into the list here.
102 if (newRange.lessThan(range)) {
103 dirty = true;
104 ranges.add(i, newRange);
105 return;
106 }
107 }
108 dirty = true;
109 // this is easy (and fairly common)...we just tack this on to the end.
110 ranges.add(newRange);
111 }
112
113 /**
114 * Test if a given article point falls within one of the contained Ranges.
115 *
116 * @param article
117 * The test point.
118 *
119 * @return true if this falls within one of our current mark Ranges, false
120 * otherwise.
121 */
122 public boolean isMarked(int article) {
123 for (int i = 0; i < ranges.size(); i++) {
124 Range range = (Range) ranges.get(i);
125 if (range.contains(article)) {
126 return true;
127 }
128 // we've passed the point where a match is possible.
129 if (range.greaterThan(article)) {
130 return false;
131 }
132 }
133 return false;
134 }
135
136 /**
137 * Mark a target article as having been seen.
138 *
139 * @param article
140 * The target article number.
141 */
142 public void setMarked(int article) {
143 // go through the insertion logic.
144 insert(new Range(article, article));
145 }
146
147 /**
148 * Clear the seen mark for a target article.
149 *
150 * @param article
151 * The target article number.
152 */
153 public void setUnmarked(int article) {
154 for (int i = 0; i < ranges.size(); i++) {
155 Range range = (Range) ranges.get(i);
156 // does this fall within an existing range? We don't need to do
157 // anything here.
158 if (range.contains(article)) {
159 // ok, we've found where to insert, now to figure out how to
160 // insert
161 // article is at the beginning of the range. We can just
162 // increment the lower
163 // bound, or if this is a single element range, we can remove it
164 // entirely.
165 if (range.getStart() == article) {
166 if (range.getEnd() == article) {
167 // piece of cake!
168 ranges.remove(i);
169 } else {
170 // still pretty easy.
171 range.setStart(article + 1);
172 }
173 } else if (range.getEnd() == article) {
174 // pretty easy also
175 range.setEnd(article - 1);
176 } else {
177 // split this into two ranges and insert the trailing piece
178 // after this.
179 Range section = range.split(article);
180 ranges.add(i + 1, section);
181 }
182 dirty = true;
183 return;
184 }
185 // did we find a point where any articles are greater?
186 if (range.greaterThan(article)) {
187 // nothing to do
188 return;
189 }
190 }
191 // didn't find it at all. That was easy!
192 }
193
194 /**
195 * Save this List of Ranges out to a .newsrc file. This creates a
196 * comma-separated list of range values from each of the Ranges.
197 *
198 * @param out
199 * The target output stream.
200 *
201 * @exception IOException
202 */
203 public void save(Writer out) throws IOException {
204 // we have an empty list
205 if (ranges.size() == 0) {
206 return;
207 }
208
209 Range range = (Range) ranges.get(0);
210 range.save(out);
211
212 for (int i = 1; i < ranges.size(); i++) {
213 out.write(",");
214 range = (Range) ranges.get(i);
215 range.save(out);
216 }
217 }
218
219 /**
220 * Return the state of the dirty flag.
221 *
222 * @return True if the range list information has changed, false otherwise.
223 */
224 public boolean isDirty() {
225 return dirty;
226 }
227 }