1 /*
2 * Copyright (c) 2006-2007 Creative Sphere Limited.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *
10 * Creative Sphere - initial API and implementation
11 *
12 */
13 package org.abstracthorizon.danube.http.util;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.List;
19
20 /**
21 * Class keeping from and to pointers in a file
22 *
23 * @author Daniel Sendula
24 */
25 public class Ranges {
26
27 /** If this is a single range then it is stored here */
28 protected Range singleRange;
29
30 /** List of ranges for multi-range */
31 protected List<Range> ranges;
32
33 /** Size of the resource or -1 if unkonwn */
34 protected long size = -1;
35
36 /**
37 * Constructor
38 */
39 public Ranges() {
40 }
41
42 /**
43 * Returns <code>true</code> if this is multi-range
44 * @return <code>true</code> if this is multi-range
45 */
46 public boolean isMultiRange() {
47 return ranges != null;
48 }
49
50 /**
51 * Sets size
52 * @param size size
53 */
54 public void setSize(long size) {
55 this.size = size;
56 if (isMultiRange()) {
57 for (Range range : ranges) {
58 updateSize(range);
59 }
60 makeCanonic();
61 } else if (singleRange != null) {
62 updateSize(singleRange);
63 }
64 }
65
66 /**
67 * Returns size or -1 if size is unknown
68 * @return size or -1 if size is unknown
69 */
70 public long getSize() {
71 return size;
72 }
73
74 /**
75 * Returns list of ranges
76 * @return list of ranges
77 */
78 public List<Range> getRanges() {
79 return ranges;
80 }
81
82 /**
83 * Returns single range or <code>null</code>
84 * @return single range or <code>null</code>
85 */
86 public Range getSingleRange() {
87 return singleRange;
88 }
89
90 /**
91 * Updates size to prefix and suffix ranges
92 * @param range range to be updated
93 */
94 protected void updateSize(Range range) {
95 if (size >= 0) {
96 if (range.from == -1) {
97 range.from = size - range.to;
98 range.to = size - 1;
99 }
100 if (range.to == -1) {
101 range.to = size - 1;
102 }
103 }
104 }
105
106 /**
107 * Adds next range
108 *
109 * @param from from
110 * @param to to
111 */
112 public void addRange(long from, long to) {
113 if ((from != -1) || (to != -1)) {
114 Range range = new Range(from , to);
115 updateSize(range);
116 if (singleRange == null) {
117 if (ranges == null) {
118 singleRange = range;
119 } else {
120 ranges.add(range);
121 makeCanonic();
122 }
123 } else {
124 if (!combine(singleRange, range)) {
125 ranges = new ArrayList<Range>();
126 ranges.add(singleRange);
127 ranges.add(range);
128 singleRange = null;
129 }
130 }
131 }
132 }
133
134 /**
135 * Tries to combine two ranges. If it succeeds result is in
136 * range1 and result is <code>true</code>. Otherwise result is
137 * <code>false</code> and ranges are unchanged.
138 *
139 * @param range1 range one and result in case of success
140 * @param range2 range two
141 * @return <code>true</code> if two ranges can be combined
142 */
143 protected boolean combine(Range range1, Range range2) {
144 if (range1.from == -1) {
145 if (range2.from == -1) {
146 if (range1.to < range2.to) {
147 range1.to = range2.to;
148 }
149 return true;
150 } else {
151 return false;
152 }
153 } else if (range1.to == -1) {
154 if (range2.to == -1) {
155 if (range1.from > range2.from) {
156 range1.from = range2.from;
157 }
158 return true;
159 } else {
160 return false;
161 }
162 } else {
163 if (range1.from <= range2.from) {
164 if (range1.to <= range2.from) {
165 return false;
166 } else {
167 if (range1.to < range2.to) {
168 range1.to = range2.to;
169 }
170 return true;
171 }
172 } else { // if (range1.from >= range2.from) {
173 if (range1.from >= range2.to) {
174 return false;
175 } else {
176 if (range1.to < range2.to) {
177 range1.from = range2.from;
178 range1.to = range2.to;
179 } else {
180 range1.from = range2.from;
181 }
182 return true;
183 }
184 }
185 }
186 }
187
188 /**
189 * Makes this ranges in canonic form (as long as they are sorted
190 */
191 protected void makeCanonic() {
192 if (ranges != null) {
193 Collections.sort(ranges, new Comparator<Range>() {
194 public int compare(Range r1, Range r2) {
195 if (r1.from < r2.from) {
196 return -1;
197 } else if (r1.from > r2.from) {
198 return 1;
199 } else if (r1.to < r2.to) {
200 return -1;
201 } else if (r1.to > r2.to) {
202 return 1;
203 }
204 return 0;
205 }
206 });
207 int i = 0;
208 while (i + 1 < ranges.size()) {
209 Range r1 = ranges.get(i);
210 Range r2 = ranges.get(i + 1);
211 if (combine(r1, r2)) {
212 ranges.remove(i + 1);
213 } else {
214 i = i + 1;
215 }
216 }
217 }
218 }
219
220 /**
221 * Parses input string for ranges format
222 * @param ranges input string
223 * @return parsed ranges object or <code>null</code> if incorrect format
224 */
225 public static Ranges parseRange(String ranges) {
226 if (ranges.startsWith("bytes=")) {
227 Ranges result = new Ranges();
228 int j = 6;
229 int i = ranges.indexOf(',');
230 while (i > 0) {
231 if (i > j) {
232 if (!parseOneRange(ranges, j, i, result)) {
233 return null;
234 }
235 j = i + 1;
236 if (j <= ranges.length()) {
237 i = ranges.indexOf(',', j + 1);
238 } else {
239 return result;
240 }
241 } else {
242 return null;
243 }
244 }
245 if (!parseOneRange(ranges, j, ranges.length(), result)) {
246 return null;
247 }
248 return result;
249 } else {
250 return null;
251 }
252 }
253
254 /**
255 * Parses input string for ranges format
256 * @param ranges input string
257 * @return parsed ranges object or <code>null</code> if incorrect format
258 */
259 public static Ranges parseContentRange(String ranges) {
260 if (ranges.startsWith("bytes ")) {
261 Ranges result = new Ranges();
262 long s = -1;
263 int j = 6;
264 int i = ranges.indexOf('/');
265 if (i >= 0) {
266 if (i + 1 >= ranges.length()) {
267 return null;
268 }
269 if ((i + 2 == ranges.length() && ranges.charAt(i + 1) == '*')) {
270 s = -1;
271 } else {
272 s = parseLong(ranges, i+1, ranges.length());
273 if (s == -1) {
274 return null;
275 }
276 result.setSize(s);
277 }
278 } else {
279 i = ranges.length();
280 }
281 if (!parseOneRange(ranges, j, i, result)) {
282 return null;
283 }
284 return result;
285 } else {
286 return null;
287 }
288 }
289
290 /**
291 * Parses one range in format of "xxx-yyy", "xxx-" or "-yyy"
292 * @param input input string
293 * @param from form index
294 * @param to to index
295 * @param result result object to be populated with newly recognised range
296 * @return <code>true</code> if format is recognised, <code>false</code> otherwise
297 */
298 protected static boolean parseOneRange(String input, int from, int to, Ranges result) {
299 long f = -1;
300 long t = -1;
301 int i = input.indexOf('-', from);
302 if ((i < from) || (i > to)) {
303 return false;
304 }
305 if (i > from) {
306 f = parseLong(input, from, i);
307 if (f < 0) {
308 return false;
309 }
310 }
311 if (i + 1 < to) {
312 t = parseLong(input, i + 1, to);
313 if (t < 0) {
314 return false;
315 }
316 }
317 result.addRange(f, t);
318 return true;
319 }
320
321 /**
322 * Parses input string from given index
323 * @param input input string
324 * @param from index to start parsing from
325 * @return <code>true</code> if there is at least one digit
326 */
327 protected static long parseLong(String input, int from, int to) {
328 long res = 0;
329 int c = input.charAt(from);
330 if (Character.isDigit(c)) {
331 res = c - '0';
332 from = from + 1;
333 while (from < to) {
334 c = input.charAt(from);
335 if (Character.isDigit(c)) {
336 res = res * 10 + c - '0';
337 from = from + 1;
338 } else {
339 return -1;
340 }
341 }
342 return res;
343 } else {
344 return -1;
345 }
346 }
347 /**
348 * Returns string representation
349 * @return string represetnation
350 */
351 public String toString() {
352 StringBuffer res = new StringBuffer("Ranges[");
353 boolean first = true;
354 for (Range range : ranges) {
355 if (first) {
356 first = false;
357 } else {
358 res.append(',');
359 }
360 res.append(range.toString());
361 }
362 res.append(']');
363 return res.toString();
364 }
365
366 /**
367 * Formats value of this ranges object as specified in "Content-Range"
368 * @return value of this ranges object as specified in "Content-Range"
369 * if there is more then one range then return <code>null</code>
370 */
371 public String format() {
372 if (isMultiRange()) {
373 return null;
374 }
375 StringBuffer res = new StringBuffer("bytes ");
376 if (singleRange.to == -1) {
377 singleRange.to = size -1;
378 }
379 if (singleRange.from == -1) {
380 singleRange.from = 0;
381 }
382 res.append(singleRange.from).append('-').append(singleRange.to).append('/').append(size);
383 return res.toString();
384 }
385
386 /**
387 * Single range definition
388 *
389 * @author Daniel Sendula
390 */
391 public static class Range {
392
393 /** From pointer */
394 private long from = -1;
395
396 /** To pointer */
397 private long to = -1;
398
399 /**
400 * Constructor
401 * @param from from
402 * @param to to
403 */
404 public Range(long from, long to) {
405 this.from = from;
406 this.to = to;
407 }
408
409 /**
410 * Returns from pointer
411 * @return from pointer
412 */
413 public long getFrom() {
414 return from;
415 }
416
417 /**
418 * Returns to pointer
419 * @return to pointer
420 */
421 public long getTo() {
422 return to;
423 }
424
425 /**
426 * Returns size of the range
427 * @return size of the range
428 */
429 public long getSize() {
430 return to - from;
431 }
432
433 /**
434 * Is this range a suffix range
435 * @return is this range a suffix range
436 */
437 public boolean isSuffix() {
438 return from == -1;
439 }
440
441 /**
442 * Is this range a prefix range
443 * @return is this range a prefix range
444 */
445 public boolean isPrefix() {
446 return to == -1;
447 }
448
449 /**
450 * Returns string representation of this range
451 * @return string representation
452 */
453 public String toString() {
454 if (from == -1) {
455 return "Range[-" + to + "]";
456 } else if (to == -1) {
457 return "Range[" + from + "-]";
458 } else {
459 return "Range[" + from + "-" + to + "]";
460 }
461 }
462 }
463 }