001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.http.commons.domain; 007 008import static java.lang.Long.parseLong; 009import static java.util.regex.Pattern.compile; 010 011import java.util.regex.Matcher; 012import java.util.regex.Pattern; 013 014/** 015 * Range header parsing logic 016 * 017 * @author awoods 018 * @author whikloj 019 */ 020public class Range { 021 022 private final long start; 023 024 private final long end; 025 026 private static final Pattern rangePattern = 027 compile("^bytes\\s*=\\s*(\\d*)\\s*-\\s*(\\d*)"); 028 029 /** 030 * Unbounded Range 031 */ 032 private Range() { 033 this(-1, -1); 034 } 035 036 /** 037 * Left-bounded range 038 * @param start the start 039 */ 040 protected Range(final long start) { 041 this(start, -1L); 042 } 043 044 /** 045 * Left and right bounded range 046 * @param start the start 047 * @param end the end 048 */ 049 private Range(final long start, final long end) { 050 this.start = start; 051 this.end = end; 052 } 053 054 /** 055 * Does this range actually impose limits 056 * @return true if the range imposes limits 057 */ 058 protected boolean hasRange() { 059 return !(start == -1 && end == -1); 060 } 061 062 /** 063 * Length contained in the range 064 * @return length of the range 065 */ 066 protected long size() { 067 if (end == -1) { 068 return -1; 069 } else if (start == -1) { 070 return end; 071 } 072 return end - start + 1; 073 } 074 075 /** 076 * Start of the range 077 * @return start of the range, or -1 if no start was specified 078 */ 079 protected long start() { 080 return start; 081 } 082 083 /** 084 * End of the range 085 * @return end of the range 086 */ 087 protected long end() { 088 return end; 089 } 090 091 /** 092 * Convert an HTTP Range header to a Range object 093 * @param source the source 094 * @return range object 095 */ 096 public static Range convert(final String source) { 097 098 final Matcher matcher = rangePattern.matcher(source); 099 100 if (!matcher.matches()) { 101 return new Range(); 102 } 103 104 final String from = matcher.group(1); 105 final String to = matcher.group(2); 106 107 final long start; 108 109 if (from.equals("")) { 110 start = -1; 111 } else { 112 start = parseLong(from); 113 } 114 115 final long end; 116 if (to.equals("")) { 117 end = -1; 118 } else { 119 end = parseLong(to); 120 } 121 122 return new Range(start, end); 123 } 124 125 /** 126 * Create a range object with start and end bytes based on the length of the content. 127 * @param length the length of the content 128 * @return a range of length object 129 */ 130 public Range.RangeOfLength rangeOfLength(final long length) { 131 final long end; 132 133 if ( 134 end() == -1 || // Range end is not specified 135 end() >= length || // Range end is too large 136 start() == -1 // Range start is not specified 137 ) { 138 end = length - 1; 139 } else { 140 end = end(); 141 } 142 143 final long start; 144 if (start() == -1) { 145 start = length - size(); 146 } else { 147 start = start(); 148 } 149 return new RangeOfLength(start, end, length); 150 } 151 152 /** 153 * Represents a range object based on length 154 */ 155 public static class RangeOfLength { 156 157 /** 158 * The start of the range 159 */ 160 private final long start; 161 162 /** 163 * The end of the range 164 */ 165 private final long end; 166 167 /** 168 * Is the range satisfiable 169 */ 170 private final boolean satisfiable; 171 172 /** 173 * Create a range object based on length 174 * @param start the start of the range 175 * @param end the end of the range 176 * @param length the length of the content 177 */ 178 protected RangeOfLength(final long start, final long end, final long length) { 179 this.start = start; 180 this.end = end; 181 // If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that is less than 182 // the current length of the representation, or at least one suffix-byte-range-spec with a non-zero 183 // suffix-length, then the byte-range-set is satisfiable. Otherwise, the byte-range-set is unsatisfiable. 184 this.satisfiable = start < length && (end() == -1 || end >= start) && (start != -1 && end != -1); 185 } 186 187 /** 188 * @return the start of the range 189 */ 190 public long start() { 191 return start; 192 } 193 194 /** 195 * @return the start of the range as a string 196 */ 197 public String startAsString() { 198 return Long.toString(start); 199 } 200 201 /** 202 * @return the end of the range 203 */ 204 public long end() { 205 return end; 206 } 207 208 /** 209 * @return the end of the range as a string 210 */ 211 public String endAsString() { 212 return Long.toString(end); 213 } 214 215 /** 216 * @return true if the range is satisfiable 217 */ 218 public boolean isSatisfiable() { 219 return satisfiable; 220 } 221 222 /** 223 * @return the size of the range, start & end inclusive so 0-0 is size 1 224 */ 225 public long size() { 226 return end - start + 1; 227 } 228 } 229}