1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.util;
20
21 import java.util.AbstractCollection;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Deque;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.NoSuchElementException;
29 import java.util.RandomAccess;
30
31 /**
32  * An array-backed list/deque for header string values.
33  *
34  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
35  */

36 public final class HeaderValues extends AbstractCollection<String> implements Deque<String>, List<String>, RandomAccess {
37
38     private static final String[] NO_STRINGS = new String[0];
39     final HttpString key;
40     byte size;
41     Object value;
42
43     HeaderValues(final HttpString key) {
44         this.key = key;
45     }
46
47     public HttpString getHeaderName() {
48         return key;
49     }
50
51     public int size() {
52         return size;
53     }
54
55     public boolean isEmpty() {
56         return size == 0;
57     }
58
59     public void clear() {
60         final byte size = this.size;
61         if (size == 0) return;
62         clearInternal();
63     }
64
65     private void clearInternal() {
66         final Object value = this.value;
67         if (value instanceof String[]) {
68             final String[] strings = (String[]) value;
69             final int len = strings.length;
70             Arrays.fill(strings, 0, len, null);
71         } else {
72             this.value = null;
73         }
74         this.size = 0;
75     }
76
77     private int index(int idx) {
78         assert idx >= 0;
79         assert idx < size;
80         final int len = ((String[]) value).length;
81         if (idx > len) {
82             idx -= len;
83         }
84         return idx;
85     }
86
87     public ListIterator<String> listIterator() {
88         return iterator(0, true);
89     }
90
91     public ListIterator<String> listIterator(final int index) {
92         return iterator(index, true);
93     }
94
95     public Iterator<String> iterator() {
96         return iterator(0, true);
97     }
98
99     public Iterator<String> descendingIterator() {
100         return iterator(0, false);
101     }
102
103     private ListIterator<String> iterator(final int start, final boolean forwards) {
104         return new ListIterator<String>() {
105             int idx = start;
106             int returned = -1;
107
108             public boolean hasNext() {
109                 return idx < size;
110             }
111
112             public boolean hasPrevious() {
113                 return idx > 0;
114             }
115
116             public String next() {
117                 try {
118                     final String next;
119                     if (forwards) {
120                         int idx = this.idx;
121                         next = get(idx);
122                         returned = idx;
123                         this.idx = idx + 1;
124                         return next;
125                     } else {
126                         int idx = this.idx - 1;
127                         next = get(idx);
128                         this.idx = returned = idx;
129                     }
130                     return next;
131                 } catch (IndexOutOfBoundsException e) {
132                     throw new NoSuchElementException();
133                 }
134             }
135
136             public int nextIndex() {
137                 return idx;
138             }
139
140             public String previous() {
141                 try {
142                     final String prev;
143                     if (forwards) {
144                         int idx = this.idx - 1;
145                         prev = get(idx);
146                         this.idx = returned = idx;
147                     } else {
148                         int idx = this.idx;
149                         prev = get(idx);
150                         returned = idx;
151                         this.idx = idx + 1;
152                         return prev;
153                     }
154                     return prev;
155                 } catch (IndexOutOfBoundsException e) {
156                     throw new NoSuchElementException();
157                 }
158             }
159
160             public int previousIndex() {
161                 return idx - 1;
162             }
163
164             public void remove() {
165                 if (returned == -1) {
166                     throw new IllegalStateException();
167                 }
168                 HeaderValues.this.remove(returned);
169                 returned = -1;
170             }
171
172             public void set(final String headerValue) {
173                 if (returned == -1) {
174                     throw new IllegalStateException();
175                 }
176                 HeaderValues.this.set(returned, headerValue);
177             }
178
179             public void add(final String headerValue) {
180                 if (returned == -1) {
181                     throw new IllegalStateException();
182                 }
183                 final int idx = this.idx;
184                 HeaderValues.this.add(idx, headerValue);
185                 this.idx = idx + 1;
186                 returned = -1;
187             }
188         };
189     }
190
191     public boolean offerFirst(final String headerValue) {
192         int size = this.size;
193         if (headerValue == null || size == Byte.MAX_VALUE) return false;
194         final Object value = this.value;
195         if (value instanceof String[]) {
196             final String[] strings = (String[]) value;
197             final int len = strings.length;
198             if (size == len) {
199                 final String[] newStrings = new String[len + 2];
200                 System.arraycopy(strings, 0, newStrings, 1, len);
201                 newStrings[0] = headerValue;
202                 this.value = newStrings;
203             } else {
204                 System.arraycopy(strings, 0, strings, 1, strings.length - 1);
205                 strings[0] = headerValue;
206             }
207             this.size = (byte) (size + 1);
208         } else {
209             if (size == 0) {
210                 this.value = headerValue;
211                 this.size = (byte) 1;
212             } else {
213                 this.value = new String[] { headerValue, (String) value, nullnull };
214                 this.size = (byte) 2;
215             }
216         }
217         return true;
218     }
219
220     public boolean offerLast(final String headerValue) {
221         int size = this.size;
222         if (headerValue == null || size == Byte.MAX_VALUE) return false;
223         final Object value = this.value;
224         if (value instanceof String[]) {
225             offerLastMultiValue(headerValue, size, (String[]) value);
226         } else {
227             if (size == 0) {
228                 this.value = headerValue;
229                 this.size = (byte) 1;
230             } else {
231                 this.value = new String[] { (String) value, headerValue, nullnull };
232                 this.size = (byte) 2;
233             }
234         }
235         return true;
236     }
237
238     private void offerLastMultiValue(String headerValue, int size, String[] value) {
239         final String[] strings = value;
240         final int len = strings.length;
241         if (size == len) {
242             final String[] newStrings = new String[len + 2];
243             System.arraycopy(strings, 0, newStrings, 0, len);
244             newStrings[len] = headerValue;
245             this.value = newStrings;
246         } else {
247             strings[size] = headerValue;
248         }
249         this.size = (byte) (size + 1);
250     }
251
252     private boolean offer(int idx, final String headerValue) {
253         int size = this.size;
254         if (idx < 0 || idx > size || size == Byte.MAX_VALUE || headerValue == nullreturn false;
255         if (idx == 0) return offerFirst(headerValue);
256         if (idx == size) return offerLast(headerValue);
257         assert size >= 2; // must be >= 2 to pass the last two checks
258         final Object value = this.value;
259         assert value instanceof String[];
260         final String[] strings = (String[]) value;
261         final int len = strings.length;
262         // This stuff is all algebraically derived.
263         if (size == len) {
264             // Grow the list, copy each segment into new spots so that head = 0
265             final int newLen = len + 2;
266             final String[] newStrings = new String[newLen];
267             System.arraycopy(value, 0, newStrings, 0, idx);
268             System.arraycopy(value, idx, newStrings, idx + 1, len - idx);
269
270             // finally fill in the new value
271             newStrings[idx] = headerValue;
272             this.value = newStrings;
273         } else{
274             System.arraycopy(value, idx, value, idx + 1, len - idx);
275
276             // finally fill in the new value
277             strings[idx] = headerValue;
278         }
279         this.size = (byte) (size + 1);
280         return true;
281     }
282
283     public String pollFirst() {
284         final byte size = this.size;
285         if (size == 0) return null;
286
287         final Object value = this.value;
288         if (value instanceof String) {
289             this.size = 0;
290             this.value = null;
291             return (String) value;
292         } else {
293             final String[] strings = (String[]) value;
294             String ret = strings[0];
295             System.arraycopy(strings, 1, strings, 0, strings.length - 1);
296             this.size = (byte) (size - 1);
297             return ret;
298         }
299     }
300
301     public String pollLast() {
302         final byte size = this.size;
303         if (size == 0) return null;
304
305         final Object value = this.value;
306         if (value instanceof String) {
307             this.size = 0;
308             this.value = null;
309             return (String) value;
310         } else {
311             final String[] strings = (String[]) value;
312             int idx = (this.size = (byte) (size - 1));
313             final int len = strings.length;
314             if (idx > len) idx -= len;
315             try {
316                 return strings[idx];
317             } finally {
318                 strings[idx] = null;
319             }
320         }
321     }
322
323     public String remove(int idx) {
324         final int size = this.size;
325         if (idx < 0 || idx >= size) throw new IndexOutOfBoundsException();
326         if (idx == 0) return removeFirst();
327         if (idx == size - 1) return removeLast();
328         assert size > 2; // must be > 2 to pass the last two checks
329         // value must be an array since size > 2
330         final String[] value = (String[]) this.value;
331         final int len = value.length;
332         String ret = value[idx];
333         System.arraycopy(value, idx + 1, value, idx, len - idx - 1);
334         value[len - 1] = null;
335         this.size = (byte) (size - 1);
336         return ret;
337     }
338
339     public String get(int idx) {
340         if (idx > size) {
341             throw new IndexOutOfBoundsException();
342         }
343         Object value = this.value;
344         assert value != null;
345         if (value instanceof String) {
346             assert size == 1;
347             return (String) value;
348         }
349         final String[] a = (String[]) value;
350         return a[index(idx)];
351     }
352
353     public int indexOf(final Object o) {
354         if (o == null || size == 0) return -1;
355         if (value instanceof String[]) {
356             final String[] list = (String[]) value;
357             final int len = list.length;
358             for (int i = 0; i < size; i ++) {
359                 if ((i > len ? list[i - len] : list[i]).equals(o)) {
360                     return i;
361                 }
362             }
363         } else if (o.equals(value)) {
364             return 0;
365         }
366         return -1;
367     }
368
369     public int lastIndexOf(final Object o) {
370         if (o == null || size == 0) return -1;
371         if (value instanceof String[]) {
372             final String[] list = (String[]) value;
373             final int len = list.length;
374             int idx;
375             for (int i = size - 1; i >= 0; i --) {
376                 idx = i;
377                 if ((idx > len ? list[idx - len] : list[idx]).equals(o)) {
378                     return i;
379                 }
380             }
381         } else if (o.equals(value)) {
382             return 0;
383         }
384         return -1;
385     }
386
387     public String set(final int index, final String element) {
388         if (element == nullthrow new IllegalArgumentException();
389
390         final byte size = this.size;
391         if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
392
393         final Object value = this.value;
394         if (size == 1 && value instanceof String) try {
395             return (String) value;
396         } finally {
397             this.value = element;
398         } else {
399             final String[] list = (String[]) value;
400             final int i = index(index);
401             try {
402                 return list[i];
403             } finally {
404                 list[i] = element;
405             }
406         }
407     }
408
409     public boolean addAll(int index, final Collection<? extends String> c) {
410         final int size = this.size;
411         if (index < 0 || index > size) throw new IndexOutOfBoundsException();
412         final Iterator<? extends String> iterator = c.iterator();
413         boolean result = false;
414         while (iterator.hasNext()) {
415             result |= offer(index, iterator.next());
416         }
417         return result;
418     }
419
420     public List<String> subList(final int fromIndex, final int toIndex) {
421         // todo - this is about 75% correct, by spec...
422         if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) throw new IndexOutOfBoundsException();
423         final int len = toIndex - fromIndex;
424         final String[] strings = new String[len];
425         for (int i = 0; i < len; i ++) {
426             strings[i] = get(i + fromIndex);
427         }
428         return Arrays.asList(strings);
429     }
430
431     public String[] toArray() {
432         int size = this.size;
433         if (size == 0) {
434             return NO_STRINGS;
435         }
436         final Object v = this.value;
437         if (v instanceof String) return new String[] { (String) v };
438         final String[] list = (String[]) v;
439         final int len = list.length;
440         final int copyEnd =  size;
441         if (copyEnd < len) {
442             return Arrays.copyOfRange(list, 0, copyEnd);
443         } else {
444             String[] ret = Arrays.copyOfRange(list, 0, copyEnd);
445             System.arraycopy(list, 0, ret, len, copyEnd - len);
446             return ret;
447         }
448     }
449
450     public <T> T[] toArray(final T[] a) {
451         int size = this.size;
452         if (size == 0) return a;
453         final int inLen = a.length;
454         final Object[] target = inLen < size ? Arrays.copyOfRange(a, inLen, inLen + size) : a;
455         final Object v = this.value;
456         if (v instanceof String) {
457             target[0] = v;
458         } else {
459             System.arraycopy(v, 0, target, 0, size);
460         }
461         return (T[]) target;
462     }
463
464     //======================================
465     //
466     // Derived methods
467     //
468     //======================================
469
470     public void addFirst(final String s) {
471         if (s == nullreturn;
472         if (! offerFirst(s)) throw new IllegalStateException();
473     }
474
475     public void addLast(final String s) {
476         if (s == nullreturn;
477         if (! offerLast(s)) throw new IllegalStateException();
478     }
479
480     public void add(final int index, final String s) {
481         if (s == nullreturn;
482         if (! offer(index, s)) throw new IllegalStateException();
483     }
484
485     public boolean contains(final Object o) {
486         return indexOf(o) != -1;
487     }
488
489     public String peekFirst() {
490         return size == 0 ? null : get(0);
491     }
492
493     public String peekLast() {
494         return size == 0 ? null : get(size - 1);
495     }
496
497     public boolean removeFirstOccurrence(final Object o) {
498         int i = indexOf(o);
499         return i != -1 && remove(i) != null;
500     }
501
502     public boolean removeLastOccurrence(final Object o) {
503         int i = lastIndexOf(o);
504         return i != -1 && remove(i) != null;
505     }
506
507     public boolean add(final String s) {
508         addLast(s);
509         return true;
510     }
511
512     public void push(final String s) {
513         addFirst(s);
514     }
515
516     public String pop() {
517         return removeFirst();
518     }
519
520     public boolean offer(final String s) {
521         return offerLast(s);
522     }
523
524     public String poll() {
525         return pollFirst();
526     }
527
528     public String peek() {
529         return peekFirst();
530     }
531
532     public String remove() {
533         return removeFirst();
534     }
535
536     public String removeFirst() {
537         final String s = pollFirst();
538         if (s == null) {
539             throw new NoSuchElementException();
540         }
541         return s;
542     }
543
544     public String removeLast() {
545         final String s = pollLast();
546         if (s == null) {
547             throw new NoSuchElementException();
548         }
549         return s;
550     }
551
552     public String getFirst() {
553         final String s = peekFirst();
554         if (s == null) {
555             throw new NoSuchElementException();
556         }
557         return s;
558     }
559
560     public String getLast() {
561         final String s = peekLast();
562         if (s == null) {
563             throw new NoSuchElementException();
564         }
565         return s;
566     }
567
568     public String element() {
569         return getFirst();
570     }
571
572     public boolean remove(Object obj) {
573         return removeFirstOccurrence(obj);
574     }
575
576     public boolean addAll(final Collection<? extends String> c) {
577         for (String s : c) {
578             add(s);
579         }
580         return !c.isEmpty();
581     }
582 }
583