org.eclipse.jetty.http.MutableHttpFields Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
/**
* HTTP Fields. A collection of HTTP header and or Trailer fields.
*
* This class is not synchronized as it is expected that modifications will only be performed by a
* single thread.
*
*
The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
*/
class MutableHttpFields implements HttpFields.Mutable
{
private static final int INITIAL_SIZE = 16;
private static final int SIZE_INCREMENT = 4;
private HttpField[] _fields;
private boolean _immutable;
private int _size;
/**
* Initialize an empty HttpFields.
*/
protected MutableHttpFields()
{
this(INITIAL_SIZE); // Based on small sample of Chrome requests.
}
/**
* Initialize an empty HttpFields.
*
* @param capacity the capacity of the http fields
*/
protected MutableHttpFields(int capacity)
{
_fields = new HttpField[capacity];
}
/**
* Initialize HttpFields from another.
*
* @param fields the fields to copy data from
*/
protected MutableHttpFields(HttpFields fields)
{
if (fields instanceof org.eclipse.jetty.http.ImmutableHttpFields immutable)
{
_immutable = true;
_fields = immutable._fields;
_size = immutable._size;
}
else if (fields != null)
{
_fields = new HttpField[fields.size() + SIZE_INCREMENT];
add(fields);
}
else
{
_fields = new HttpField[INITIAL_SIZE];
}
}
/**
* Initialize HttpFields from another and replace a field
*
* @param fields the fields to copy data from
* @param replaceField the replacement field
*/
protected MutableHttpFields(HttpFields fields, HttpField replaceField)
{
_fields = new HttpField[fields.size() + SIZE_INCREMENT];
_size = 0;
boolean put = false;
for (HttpField f : fields)
{
if (replaceField.isSameName(f))
{
if (!put)
_fields[_size++] = replaceField;
put = true;
}
else
{
_fields[_size++] = f;
}
}
if (!put)
_fields[_size++] = replaceField;
}
/**
* Initialize HttpFields from another and remove fields
*
* @param fields the fields to copy data from
* @param removeFields the the fields to remove
*/
protected MutableHttpFields(HttpFields fields, EnumSet removeFields)
{
_fields = new HttpField[fields.size() + SIZE_INCREMENT];
_size = 0;
for (HttpField f : fields)
{
if (f.getHeader() == null || !removeFields.contains(f.getHeader()))
_fields[_size++] = f;
}
}
@Override
public Mutable add(HttpField field)
{
if (field != null)
{
if (_immutable || _size == _fields.length)
{
_immutable = false;
_fields = Arrays.copyOf(_fields, _size + SIZE_INCREMENT);
}
_fields[_size++] = field;
}
return this;
}
@Override
public Mutable add(HttpFields fields)
{
if (fields.size() == 0)
return this;
if (_immutable || _size + fields.size() >= _fields.length)
{
_immutable = false;
_fields = Arrays.copyOf(_fields, _size + fields.size() + SIZE_INCREMENT);
}
if (fields instanceof org.eclipse.jetty.http.ImmutableHttpFields immutable)
{
System.arraycopy(immutable._fields, 0, _fields, _size, immutable._size);
_size += immutable._size;
}
else if (fields instanceof org.eclipse.jetty.http.MutableHttpFields mutable)
{
System.arraycopy(mutable._fields, 0, _fields, _size, mutable._size);
_size += mutable._size;
}
else
{
for (HttpField f : fields)
{
_fields[_size++] = f;
}
}
return this;
}
@Override
public HttpFields asImmutable()
{
_immutable = true;
return new org.eclipse.jetty.http.ImmutableHttpFields(_fields, _size);
}
private void copyImmutable()
{
if (_immutable)
{
_immutable = false;
_fields = Arrays.copyOf(_fields, _fields.length);
}
}
@Override
public Mutable clear()
{
if (_immutable)
{
_fields = new HttpField[_fields.length];
_immutable = false;
}
_size = 0;
return this;
}
@Override
public int hashCode()
{
int hash = 2099; // prime
for (int i = _size; i-- > 0; )
{
HttpField field = _fields[i];
if (field != null)
hash ^= field.hashCode();
}
return hash;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof org.eclipse.jetty.http.MutableHttpFields))
return false;
return isEqualTo((HttpFields)o);
}
/**
* Get a Field by index.
*
* @param index the field index
* @return A Field value or null if the Field value has not been set
*/
@Override
public HttpField getField(int index)
{
if (index >= _size || index < 0)
throw new NoSuchElementException();
return _fields[index];
}
@Override
public HttpField getField(HttpHeader header)
{
// default impl overridden for efficiency
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (f != null && f.getHeader() == header)
return f;
}
return null;
}
@Override
public HttpField getField(String name)
{
// default impl overridden for efficiency
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (f != null && f.is(name))
return f;
}
return null;
}
@Override
public Iterator iterator()
{
return new Iterator<>()
{
int _index = 0;
@Override
public boolean hasNext()
{
return _index < _size;
}
@Override
public HttpField next()
{
return _fields[_index++];
}
@Override
public void remove()
{
if (_size == 0)
throw new IllegalStateException();
org.eclipse.jetty.http.MutableHttpFields.this.remove(--_index);
}
};
}
@Override
public ListIterator listIterator()
{
return listIterator(0);
}
@Override
public ListIterator listIterator(int index)
{
copyImmutable();
return new Listerator(index);
}
@Override
public Mutable put(HttpField field)
{
copyImmutable();
boolean put = false;
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (f.isSameName(field))
{
if (put)
System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1);
else
{
_fields[i] = field;
put = true;
}
}
}
if (!put)
add(field);
return this;
}
@Override
public Mutable put(String name, String value)
{
return (value == null)
? remove(name)
: put(new HttpField(name, value));
}
@Override
public Mutable put(HttpHeader header, HttpHeaderValue value)
{
return put(header, value.toString());
}
@Override
public Mutable put(HttpHeader header, String value)
{
return (value == null)
? remove(header)
: put(new HttpField(header, value));
}
@Override
public Mutable computeField(HttpHeader header, BiFunction, HttpField> computeFn)
{
return computeField(header, computeFn, (f, h) -> f.getHeader() == h);
}
@Override
public Mutable computeField(String name, BiFunction, HttpField> computeFn)
{
return computeField(name, computeFn, HttpField::is);
}
public Mutable computeField(T header, BiFunction, HttpField> computeFn, BiPredicate matcher)
{
copyImmutable();
// Look for first occurrence
int first = -1;
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (matcher.test(f, header))
{
first = i;
break;
}
}
// If the header is not found, add a new one;
if (first < 0)
{
HttpField newField = computeFn.apply(header, null);
if (newField != null)
add(newField);
return this;
}
// Are there any more occurrences?
List found = null;
for (int i = first + 1; i < _size; i++)
{
HttpField f = _fields[i];
if (matcher.test(f, header))
{
if (found == null)
{
found = new ArrayList<>();
found.add(_fields[first]);
}
// Remember and remove additional fields
found.add(f);
remove(i--);
}
}
// If no additional fields were found, handle singleton case
if (found == null)
found = Collections.singletonList(_fields[first]);
else
found = Collections.unmodifiableList(found);
HttpField newField = computeFn.apply(header, found);
if (newField == null)
remove(first);
else
_fields[first] = newField;
return this;
}
@Override
public Mutable remove(HttpHeader name)
{
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (f.getHeader() == name)
remove(i--);
}
return this;
}
@Override
public Mutable remove(EnumSet headers)
{
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (headers.contains(f.getHeader()))
remove(i--);
}
return this;
}
@Override
public Mutable remove(String name)
{
for (int i = 0; i < _size; i++)
{
HttpField f = _fields[i];
if (f.is(name))
remove(i--);
}
return this;
}
private void remove(int i)
{
_size--;
if (_immutable)
{
_immutable = false;
HttpField[] fields = _fields;
_fields = new HttpField[fields.length];
System.arraycopy(fields, 0, _fields, 0, i);
System.arraycopy(fields, i + 1, _fields, i, _size - i);
}
else
{
System.arraycopy(_fields, i + 1, _fields, i, _size - i);
}
_fields[_size] = null;
}
public int size()
{
return _size;
}
@Override
public Stream stream()
{
return Arrays.stream(_fields, 0, _size);
}
@Override
public String toString()
{
return asString();
}
private class Listerator implements ListIterator
{
private int _index;
private int _last = -1;
Listerator(int index)
{
if (index < 0 || index > _size)
throw new NoSuchElementException(Integer.toString(index));
_index = index;
}
@Override
public void add(HttpField field)
{
if (field == null)
return;
int last = _size++;
if (_fields.length < _size)
_fields = Arrays.copyOf(_fields, _fields.length + SIZE_INCREMENT);
System.arraycopy(_fields, _index, _fields, _index + 1, last - _index);
_fields[_index++] = field;
_last = -1;
}
@Override
public boolean hasNext()
{
return _index < _size;
}
@Override
public boolean hasPrevious()
{
return _index > 0;
}
@Override
public HttpField next()
{
if (_index >= _size)
throw new NoSuchElementException(Integer.toString(_index));
_last = _index++;
return _fields[_last];
}
@Override
public int nextIndex()
{
return _index + 1;
}
@Override
public HttpField previous()
{
if (_index <= 0)
throw new NoSuchElementException(Integer.toString(_index - 1));
_last = --_index;
return _fields[_last];
}
@Override
public int previousIndex()
{
return _index - 1;
}
@Override
public void remove()
{
if (_last < 0)
throw new IllegalStateException();
org.eclipse.jetty.http.MutableHttpFields.this.remove(_last);
_index = _last;
_last = -1;
}
@Override
public void set(HttpField field)
{
if (_last < 0)
throw new IllegalStateException();
if (field == null)
remove();
else
_fields[_last] = field;
}
}
}