/* * JLibs: Common Utilities for Java * Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package jlibs.nio.http.msg; import jlibs.nio.Reactor; import jlibs.nio.http.expr.ValueMap; import jlibs.nio.http.util.Parser; import jlibs.nio.http.util.USAscii; import java.util.*; import java.util.function.Function; import static jlibs.nio.http.util.USAscii.QUOTE; /** * @author Santhosh Kumar Tekuri */ public final class Headers implements ValueMap{ private Object table[] = new Object[16]; private Header first; /*-------------------------------------------------[ Get ]---------------------------------------------------*/ public Header getFirst(){ return first; } public Header get(AsciiString name){ return entry(name, false); } public Header get(CharSequence name){ return entry(name, USAscii.caseInsensitiveHashCode(name), false); } public String value(AsciiString name){ Header header = get(name); return header==null ? null : header.value; } public String value(CharSequence name){ Header header = get(name); return header==null ? null : header.value; } /*-------------------------------------------------[ Add ]---------------------------------------------------*/ public void add(AsciiString name, String value){ if(name==null || value==null) return; Header head = entry(name, true); if(head.value==null) head.value = value; else{ Header newHeader = newHeader(name); newHeader.value = value; Header tail = head.samePrev; tail.sameNext = newHeader; newHeader.samePrev = tail; head.samePrev = newHeader; } assert validateLinks(); } /*-------------------------------------------------[ Remove ]---------------------------------------------------*/ public Header remove(AsciiString name){ if(name==null || first==null) return null; Header h = null; int idx = name.hashCode() & (table.length-1); Object obj = table[idx]; if(obj==null) return null; else if(obj instanceof Header){ Header header = (Header)obj; if(header.name.equals(name)){ table[idx] = null; h = header; } }else{ Header headers[] = (Header[])obj; for(int i=0; i<headers.length; i++){ Header header = headers[i]; if(header!=null && header.name.equals(name)){ headers[i] = null; h = header; break; } } } if(h!=null) removeSameNext(h); return h; } private void removeSameNext(Header header){ while(header!=null){ if(header==first){ first = header.next; if(first!=null) first.prev = header.prev; }else{ Header before = header.prev; Header after = header.next; before.next = after; if(after==null) first.prev = before; else after.prev = before; } header.prev = header; header.next = null; header = header.sameNext; } assert validateLinks(); } public void clear(){ Arrays.fill(table, null); first = null; } /*-------------------------------------------------[ Set ]---------------------------------------------------*/ public void set(AsciiString name, String value){ if(name==null) return; if(value==null){ remove(name); return; } Header head = entry(name, true); if(head.value==null) head.value = value; else{ head.value = value; Header next = head.sameNext; head.sameNext = null; head.samePrev = head; removeSameNext(next); } assert validateLinks(); } /*-------------------------------------------------[ Internal-Helpers ]---------------------------------------------------*/ private Header newHeader(AsciiString name){ Header header = new Header(name); if(first==null) first = header; else{ header.prev = first.prev; first.prev.next = header; first.prev = header; } return header; } private Header entry(AsciiString name, boolean create){ int idx = name.hashCode() & (table.length-1); Object obj = table[idx]; if(obj==null){ if(create){ Header header = newHeader(name); table[idx] = header; return header; }else return null; }else if(obj instanceof Header){ Header header = (Header)obj; if(header.name.equals(name)) return header; if(create){ Header newHeader = newHeader(name); Header headers[] = { header, newHeader, null, null }; table[idx] = headers; return newHeader; }else return null; }else{ Header headers[] = (Header[])obj; int freeSlot = -1; for(int i=0; i<headers.length; i++){ Header header = headers[i]; if(header==null){ if(freeSlot==-1) freeSlot = i; }else if(header.name.equals(name)) return header; } if(create){ Header newHeader = newHeader(name); if(freeSlot==-1){ int len = headers.length; headers = Arrays.copyOf(headers, len+3); table[idx] = headers; headers[len] = newHeader; }else headers[freeSlot] = newHeader; return newHeader; }else return null; } } Header entry(CharSequence name, int hashCode, boolean create){ int idx = hashCode & (table.length-1); Object obj = table[idx]; if(obj==null){ if(create){ Header header = newHeader(new AsciiString(name, hashCode)); table[idx] = header; return header; }else return null; }else if(obj instanceof Header){ Header header = (Header)obj; if(header.name.hashCode()==hashCode && header.name.equals(name)) return header; if(create){ Header newHeader = newHeader(new AsciiString(name, hashCode)); Header headers[] = { header, newHeader, null, null }; table[idx] = headers; return newHeader; }else return null; }else{ Header headers[] = (Header[])obj; int freeSlot = -1; for(int i=0; i<headers.length; i++){ Header header = headers[i]; if(header==null){ if(freeSlot==-1) freeSlot = i; }else if(header.name.hashCode()==hashCode && header.name.equals(name)) return header; } if(create){ Header newHeader = newHeader(new AsciiString(name, hashCode)); if(freeSlot==-1){ int len = headers.length; headers = Arrays.copyOf(headers, len+3); table[idx] = headers; headers[len] = newHeader; }else headers[freeSlot] = newHeader; return newHeader; }else return null; } } private boolean validateLinks(){ Header h1 = first; int count1 = 0; while(h1!=null){ if(h1.next==null) assert first.prev==h1; else assert h1.next.prev==h1; h1 = h1.next; ++count1; } int count2 = 0; for(Object entry: table){ if(entry!=null){ Header headers[]; if(entry instanceof Header) headers = new Header[]{ (Header)entry }; else headers = (Header[])entry; for(Header h2: headers){ Header t = h2; while(h2!=null){ if(h2.sameNext==null) assert t.samePrev==h2; else assert h2.sameNext.samePrev==h2; h2 = h2.sameNext; ++count2; } } } } return count1==count2; } /*-------------------------------------------------[ Value-Map ]---------------------------------------------------*/ @Override public Object getValue(String name){ return value(name); } /*-------------------------------------------------[ HTTP-Helpers ]---------------------------------------------------*/ @Override public String toString(){ StringBuilder buffer = Reactor.stringBuilder(); Header header = first; while(header!=null){ buffer.append(header.name).append(": ").append(header.value).append("\r\n"); header = header.next; } buffer.append("\r\n"); return Reactor.free(buffer); } public <T> T getSingleValue(AsciiString name, Function<String, T> delegate){ String value = value(name); if(value==null) return null; if(value.length()>0 && value.charAt(0)==QUOTE) value = new Parser(false, value).value(); return delegate.apply(value); } public <T> void setSingleValue(AsciiString name, T value, Function<T, String> delegate){ if(value==null) remove(name); else set(name, delegate==null ? value.toString() : delegate.apply(value)); } public <T> List<T> getListValue(AsciiString name, Function<Parser, T> delegate, boolean foldable){ Parser parser = null; List<T> list = null; Header header = get(name); while(header!=null){ if(parser==null) parser = new Parser(foldable, header.getValue()); else parser.reset(header.getValue()); if(!parser.isEmpty()){ if(list==null) list = new ArrayList<>(); while(true){ list.add(delegate.apply(parser)); if(parser.isEmpty()) break; else parser.skip(); } } header = header.sameNext(); } if(list==null) list = Collections.emptyList(); return list; } public <T> void setListValue(AsciiString name, Collection<T> values, Function<T, String> delegate, boolean foldable){ if(values==null || values.isEmpty()) remove(name); else if(values.size()==1){ T value = values.iterator().next(); String hvalue = delegate==null ? value.toString() : delegate.apply(value); set(name, hvalue); }else if(foldable){ StringBuilder builder = new StringBuilder(); for(T value: values){ if(builder.length()>0) builder.append(',').append(' '); builder.append(delegate==null ? value.toString() : delegate.apply(value)); } set(name, builder.toString()); }else{ remove(name); for(T value: values) add(name, delegate==null ? value.toString() : delegate.apply(value)); } } public <T> Map<String, T> getMapValue(AsciiString name, Function<Parser, T> delegate, Function<T, String> nameFunction, boolean foldable){ Parser parser = null; Map<String, T> map = null; Header header = get(name); while(header!=null){ if(parser==null) parser = new Parser(foldable, header.getValue()); else parser.reset(header.getValue()); if(!parser.isEmpty()){ if(map==null) map = new HashMap<>(); while(true){ T item = delegate.apply(parser); map.put(nameFunction.apply(item), item); if(parser.isEmpty()) break; else parser.skip(); } } header = header.sameNext(); } if(map==null) map = Collections.emptyMap(); return map; } public <T> void setMapValues(AsciiString name, Map<String, T> map, Function<T, String> delegate, boolean foldable){ setListValue(name, map.values(), delegate, foldable); } }