blob: 845f54a873c3f970f99a80028db1ce5cee8505e1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.geronimo.javamail.store.nntp.newsrc;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.StringTokenizer;
/**
* Manage a list of ranges values from a newsrc file.
*/
public class RangeList {
boolean dirty = false;
ArrayList ranges = new ArrayList();
/**
* Create a RangeList instance from a newsrc range line. Values are saved as
* a comma separated set of range values. A range value is either a single
* number or a hypenated pair of numbers.
*
* @param line
* The newsrc range line.
*/
public RangeList(String line) {
// we might be creating an first time list, so nothing to parse.
if (line != null) {
// ranges are comma delimited tokens
StringTokenizer tokenizer = new StringTokenizer(line, ",");
while (tokenizer.hasMoreTokens()) {
String rangeString = (String) tokenizer.nextToken();
rangeString = rangeString.trim();
if (rangeString.length() != 0) {
Range range = Range.parse(rangeString);
if (range != null) {
insert(range);
}
}
}
}
// make sure we start out in a clean state. Any changes from this point
// will flip on the dirty flat.
dirty = false;
}
/**
* Insert a range item into our list. If possible, the inserted range will
* be merged with existing ranges.
*
* @param newRange
* The new range item.
*/
public void insert(Range newRange) {
// first find the insertion point
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
// does an existing range fully contain the new range, we don't need
// to insert anything.
if (range.contains(newRange)) {
return;
}
// does the current one abutt or overlap with the inserted range?
if (range.abutts(newRange) || range.overlaps(newRange)) {
// rats, we have an overlap...and it is possible that we could
// overlap with
// the next range after this one too. Therefore, we merge these
// two ranges together,
// remove the place where we're at, and then recursively insert
// the larger range into
// the list.
dirty = true;
newRange.merge(range);
ranges.remove(i);
insert(newRange);
return;
}
// ok, we don't touch the current one at all. If it is completely
// above
// range we're adding, we can just poke this into the list here.
if (newRange.lessThan(range)) {
dirty = true;
ranges.add(i, newRange);
return;
}
}
dirty = true;
// this is easy (and fairly common)...we just tack this on to the end.
ranges.add(newRange);
}
/**
* Test if a given article point falls within one of the contained Ranges.
*
* @param article
* The test point.
*
* @return true if this falls within one of our current mark Ranges, false
* otherwise.
*/
public boolean isMarked(int article) {
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
if (range.contains(article)) {
return true;
}
// we've passed the point where a match is possible.
if (range.greaterThan(article)) {
return false;
}
}
return false;
}
/**
* Mark a target article as having been seen.
*
* @param article
* The target article number.
*/
public void setMarked(int article) {
// go through the insertion logic.
insert(new Range(article, article));
}
/**
* Clear the seen mark for a target article.
*
* @param article
* The target article number.
*/
public void setUnmarked(int article) {
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
// does this fall within an existing range? We don't need to do
// anything here.
if (range.contains(article)) {
// ok, we've found where to insert, now to figure out how to
// insert
// article is at the beginning of the range. We can just
// increment the lower
// bound, or if this is a single element range, we can remove it
// entirely.
if (range.getStart() == article) {
if (range.getEnd() == article) {
// piece of cake!
ranges.remove(i);
} else {
// still pretty easy.
range.setStart(article + 1);
}
} else if (range.getEnd() == article) {
// pretty easy also
range.setEnd(article - 1);
} else {
// split this into two ranges and insert the trailing piece
// after this.
Range section = range.split(article);
ranges.add(i + 1, section);
}
dirty = true;
return;
}
// did we find a point where any articles are greater?
if (range.greaterThan(article)) {
// nothing to do
return;
}
}
// didn't find it at all. That was easy!
}
/**
* Save this List of Ranges out to a .newsrc file. This creates a
* comma-separated list of range values from each of the Ranges.
*
* @param out
* The target output stream.
*
* @exception IOException
*/
public void save(Writer out) throws IOException {
// we have an empty list
if (ranges.size() == 0) {
return;
}
Range range = (Range) ranges.get(0);
range.save(out);
for (int i = 1; i < ranges.size(); i++) {
out.write(",");
range = (Range) ranges.get(i);
range.save(out);
}
}
/**
* Return the state of the dirty flag.
*
* @return True if the range list information has changed, false otherwise.
*/
public boolean isDirty() {
return dirty;
}
}