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 }