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 &amp; end inclusive so 0-0 is size 1
224         */
225        public long size() {
226            return end - start + 1;
227        }
228    }
229}