Added a utility class for parsing Accept* headers.
[jsvc.git] / src / dolda / jsvc / util / AcceptMap.java
1 package dolda.jsvc.util;
2
3 import java.util.*;
4 import java.io.*;
5
6 public class AcceptMap implements Iterable<AcceptMap.Entry> {
7     private static final Entry[] typehint = new Entry[0];
8     private final Entry[] list;
9     private static final Comparator<Entry> lcmp = new Comparator<Entry>() {
10         public int compare(Entry a, Entry b) {
11             if(a.q > b.q)
12                 return(-1);
13             else if(a.q < b.q)
14                 return(1);
15             return(0);
16         }
17     };
18     
19     public static class Entry {
20         public String type;
21         public double q = 1.0;
22         public Map<String, String> pars = new HashMap<String, String>();
23     }
24     
25     private AcceptMap(Entry[] list) {
26         this.list = list;
27     }
28     
29     private static String token(PushbackReader in) throws IOException {
30         Misc.eatws(in);
31         StringBuilder buf = new StringBuilder();
32         while(true) {
33             int c = in.read();
34             if((c < 0) || Character.isWhitespace((char)c) || (",;=:\\\"?".indexOf((char)c) >= 0)) {
35                 if(c >= 0)
36                     in.unread(c);
37                 if(buf.length() == 0)
38                     return(null);
39                 return(buf.toString());
40             } else {
41                 buf.append((char)c);
42             }
43         }
44     }
45
46     public static AcceptMap parse(Reader in) throws IOException {
47         PushbackReader pin = new PushbackReader(in);
48         List<Entry> lbuf = new LinkedList<Entry>();
49         List<Entry> ebuf = new LinkedList<Entry>();
50         while(true) {
51             Entry e = new Entry();
52             if((e.type = token(pin)) == null)
53                 throw(new Http.EncodingException("Illegal format for Accept* header (expected type)"));
54             ebuf.add(e);
55             Misc.eatws(pin);
56             int sep = pin.read();
57             if(sep < 0) {
58                 lbuf.addAll(ebuf);
59                 break;
60             } else if(sep == ';') {
61                 String st = "tp";
62                 boolean flush = false;
63                 boolean end = false;
64                 while(true) {
65                     String key = Http.tokenunquote(pin);
66                     Misc.eatws(pin);
67                     if(pin.read() != '=')
68                         throw(new Http.EncodingException("Illegal format for Accept* header (expected `=' in parameter)"));
69                     String val = Http.tokenunquote(pin);
70                     Misc.eatws(pin);
71                     int psep = pin.read();
72                     if(st == "tp") {
73                         if(key.equals("q")) {
74                             double q;
75                             try {
76                                 q = Double.parseDouble(val);
77                             } catch(NumberFormatException exc) {
78                                 throw(new Http.EncodingException(exc.getMessage()));
79                             }
80                             for(Entry e2 : ebuf)
81                                 e2.q = q;
82                             flush = true;
83                             st = "ap";
84                         } else if(st == "ap") {
85                             e.pars.put(key, val);
86                         }
87                     } else {
88                         /* No known accept-params */
89                     }
90                     if(psep < 0) {
91                         end = true;
92                         flush = true;
93                         break;
94                     } else if(psep == ';') {
95                     } else if(psep == ',') {
96                         break;
97                     } else {
98                         throw(new Http.EncodingException("Illegal format for Accept* header (expected `;', `,' or end of parameters)"));
99                     }
100                 }
101                 if(flush) {
102                     lbuf.addAll(ebuf);
103                     ebuf = new LinkedList<Entry>();
104                 }
105                 if(end)
106                     break;
107             } else if(sep == ',') {
108             } else {
109                 throw(new Http.EncodingException("Illegal format for Accept* header (expected `;', `,' or end of list)"));
110             }
111         }
112         Entry[] list = lbuf.toArray(typehint);
113         Arrays.sort(list, lcmp);
114         return(new AcceptMap(list));
115     }
116     
117     public static AcceptMap parse(String input) {
118         try {
119             return(parse(new StringReader(input)));
120         } catch(IOException e) {
121             throw(new Error(e));
122         }
123     }
124
125     public Iterator<Entry> iterator() {
126         return(new Iterator<Entry>() {
127                 private int i = 0;
128                 
129                 public Entry next() {
130                     return(list[i++]);
131                 }
132                 
133                 public boolean hasNext() {
134                     return(i < list.length);
135                 }
136                 
137                 public void remove() {
138                     throw(new UnsupportedOperationException());
139                 }
140             });
141     }
142     
143     public Entry accepts(String type) {
144         for(Entry e : list) {
145             if(e.type.equals(type))
146                 return(e);
147         }
148         return(null);
149     }
150     
151     public String toString() {
152         StringBuilder buf = new StringBuilder();
153         for(Entry e : list)
154             buf.append(String.format("%s %f %s\n", e.type, e.q, e.pars));
155         return(buf.toString());
156     }
157 }