View Javadoc

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 &quot;xxx-yyy&quot;, &quot;xxx-&quot; or &quot;-yyy&quot;
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 &quot;Content-Range&quot;
368      * @return value of this ranges object as specified in &quot;Content-Range&quot;
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 }