Added a utility class for parsing Accept* headers.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 13 Dec 2009 20:14:31 +0000 (21:14 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 13 Dec 2009 20:14:31 +0000 (21:14 +0100)
src/dolda/jsvc/util/AcceptMap.java [new file with mode: 0644]

diff --git a/src/dolda/jsvc/util/AcceptMap.java b/src/dolda/jsvc/util/AcceptMap.java
new file mode 100644 (file)
index 0000000..b74cace
--- /dev/null
@@ -0,0 +1,157 @@
+package dolda.jsvc.util;
+
+import java.util.*;
+import java.io.*;
+
+public class AcceptMap implements Iterable<AcceptMap.Entry> {
+    private static final Entry[] typehint = new Entry[0];
+    private final Entry[] list;
+    private static final Comparator<Entry> lcmp = new Comparator<Entry>() {
+       public int compare(Entry a, Entry b) {
+           if(a.q > b.q)
+               return(-1);
+           else if(a.q < b.q)
+               return(1);
+           return(0);
+       }
+    };
+    
+    public static class Entry {
+       public String type;
+       public double q = 1.0;
+       public Map<String, String> pars = new HashMap<String, String>();
+    }
+    
+    private AcceptMap(Entry[] list) {
+       this.list = list;
+    }
+    
+    private static String token(PushbackReader in) throws IOException {
+       Misc.eatws(in);
+       StringBuilder buf = new StringBuilder();
+       while(true) {
+           int c = in.read();
+           if((c < 0) || Character.isWhitespace((char)c) || (",;=:\\\"?".indexOf((char)c) >= 0)) {
+               if(c >= 0)
+                   in.unread(c);
+               if(buf.length() == 0)
+                   return(null);
+               return(buf.toString());
+           } else {
+               buf.append((char)c);
+           }
+       }
+    }
+
+    public static AcceptMap parse(Reader in) throws IOException {
+       PushbackReader pin = new PushbackReader(in);
+       List<Entry> lbuf = new LinkedList<Entry>();
+       List<Entry> ebuf = new LinkedList<Entry>();
+       while(true) {
+           Entry e = new Entry();
+           if((e.type = token(pin)) == null)
+               throw(new Http.EncodingException("Illegal format for Accept* header (expected type)"));
+           ebuf.add(e);
+           Misc.eatws(pin);
+           int sep = pin.read();
+           if(sep < 0) {
+               lbuf.addAll(ebuf);
+               break;
+           } else if(sep == ';') {
+               String st = "tp";
+               boolean flush = false;
+               boolean end = false;
+               while(true) {
+                   String key = Http.tokenunquote(pin);
+                   Misc.eatws(pin);
+                   if(pin.read() != '=')
+                       throw(new Http.EncodingException("Illegal format for Accept* header (expected `=' in parameter)"));
+                   String val = Http.tokenunquote(pin);
+                   Misc.eatws(pin);
+                   int psep = pin.read();
+                   if(st == "tp") {
+                       if(key.equals("q")) {
+                           double q;
+                           try {
+                               q = Double.parseDouble(val);
+                           } catch(NumberFormatException exc) {
+                               throw(new Http.EncodingException(exc.getMessage()));
+                           }
+                           for(Entry e2 : ebuf)
+                               e2.q = q;
+                           flush = true;
+                           st = "ap";
+                       } else if(st == "ap") {
+                           e.pars.put(key, val);
+                       }
+                   } else {
+                       /* No known accept-params */
+                   }
+                   if(psep < 0) {
+                       end = true;
+                       flush = true;
+                       break;
+                   } else if(psep == ';') {
+                   } else if(psep == ',') {
+                       break;
+                   } else {
+                       throw(new Http.EncodingException("Illegal format for Accept* header (expected `;', `,' or end of parameters)"));
+                   }
+               }
+               if(flush) {
+                   lbuf.addAll(ebuf);
+                   ebuf = new LinkedList<Entry>();
+               }
+               if(end)
+                   break;
+           } else if(sep == ',') {
+           } else {
+               throw(new Http.EncodingException("Illegal format for Accept* header (expected `;', `,' or end of list)"));
+           }
+       }
+       Entry[] list = lbuf.toArray(typehint);
+       Arrays.sort(list, lcmp);
+       return(new AcceptMap(list));
+    }
+    
+    public static AcceptMap parse(String input) {
+       try {
+           return(parse(new StringReader(input)));
+       } catch(IOException e) {
+           throw(new Error(e));
+       }
+    }
+
+    public Iterator<Entry> iterator() {
+       return(new Iterator<Entry>() {
+               private int i = 0;
+               
+               public Entry next() {
+                   return(list[i++]);
+               }
+               
+               public boolean hasNext() {
+                   return(i < list.length);
+               }
+               
+               public void remove() {
+                   throw(new UnsupportedOperationException());
+               }
+           });
+    }
+    
+    public Entry accepts(String type) {
+       for(Entry e : list) {
+           if(e.type.equals(type))
+               return(e);
+       }
+       return(null);
+    }
+    
+    public String toString() {
+       StringBuilder buf = new StringBuilder();
+       for(Entry e : list)
+           buf.append(String.format("%s %f %s\n", e.type, e.q, e.pars));
+       return(buf.toString());
+    }
+}