1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.luni.internal.net.www.protocol.http;
19
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Map.Entry;
27
28/**
29 * The general structure for request / response header. It is essentially
30 * constructed by hashtable with key indexed in a vector for position lookup.
31 */
32public class Header implements Cloneable {
33    /*
34     * we use the non-synchronized ArrayList and HashMap instead of the
35     * synchronized Vector and Hashtable
36     */
37    private ArrayList<String> props;
38
39    private HashMap<String, LinkedList<String>> keyTable;
40
41    private String statusLine;
42
43    /**
44     * A generic header structure. Used mostly for request / response header.
45     * The key/value pair of the header may be inserted for later use. The key
46     * is stored in an array for indexed slot access.
47     */
48    public Header() {
49        super();
50        this.props = new ArrayList<String>(20);
51        this.keyTable = new HashMap<String, LinkedList<String>>(20);
52    }
53
54    /**
55     * The alternative constructor which sets the input map as its initial
56     * keyTable.
57     *
58     * @param map
59     *            the initial keyTable as a map
60     */
61    public Header(Map<String, List<String>> map) {
62        this(); // initialize fields
63        for (Entry<String, List<String>> next : map.entrySet()) {
64            String key = next.getKey();
65            props.add(key);
66            List<String> value = next.getValue();
67            LinkedList<String> linkedList = new LinkedList<String>();
68            for (String element : value) {
69                linkedList.add(element);
70                props.add(element);
71            }
72            keyTable.put(key, linkedList);
73        }
74    }
75
76    @SuppressWarnings("unchecked")
77    @Override
78    public Object clone() {
79        try {
80            Header clone = (Header) super.clone();
81            clone.props = (ArrayList<String>) props.clone();
82            clone.keyTable = new HashMap<String, LinkedList<String>>(20);
83            for (Map.Entry<String, LinkedList<String>> next : this.keyTable
84                    .entrySet()) {
85                LinkedList<String> v = (LinkedList<String>) next.getValue()
86                        .clone();
87                clone.keyTable.put(next.getKey(), v);
88            }
89            return clone;
90        } catch (CloneNotSupportedException e) {
91            throw new AssertionError(e); // android-changed
92        }
93    }
94
95    /**
96     * Add a field with the specified value.
97     *
98     * @param key
99     * @param value
100     */
101    public void add(String key, String value) {
102        if (key == null) {
103            throw new NullPointerException();
104        }
105        // BEGIN android-changed
106        key = key.toLowerCase();
107        LinkedList<String> list = keyTable.get(key);
108        if (list == null) {
109            list = new LinkedList<String>();
110            keyTable.put(key, list);
111        }
112        // END android-changed
113        list.add(value);
114        props.add(key);
115        props.add(value);
116    }
117
118    /**
119     * Set a field with the specified value. If the field is not found, it is
120     * added. If the field is found, the existing value(s) are overwritten.
121     *
122     * @param key
123     * @param value
124     */
125    public void set(String key, String value) {
126        if (key == null) {
127            throw new NullPointerException();
128        }
129        // BEGIN android-added
130        key = key.toLowerCase();
131        // END android-added
132        LinkedList<String> list = keyTable.get(key);
133        if (list == null) {
134            add(key, value);
135        } else {
136            list.clear();
137            list.add(value);
138            for (int i = 0; i < props.size(); i += 2) {
139                String propKey = props.get(i);
140                if (propKey != null && key.equals(propKey)) {
141                    props.set(i + 1, value);
142                }
143            }
144        }
145    }
146
147    /**
148     * Provides an unmodifiable map with all String header names mapped to their
149     * String values. The map keys are Strings and the values are unmodifiable
150     * Lists of Strings.
151     *
152     * @return an unmodifiable map of the headers
153     *
154     * @since 1.4
155     */
156    public Map<String, List<String>> getFieldMap() {
157        Map<String, List<String>> result = new HashMap<String, List<String>>(
158                keyTable.size());
159        for (Map.Entry<String, LinkedList<String>> next : keyTable.entrySet()) {
160            List<String> v = next.getValue();
161            result.put(next.getKey(), Collections.unmodifiableList(v));
162        }
163        return Collections.unmodifiableMap(result);
164    }
165
166    /**
167     * Returns the element at <code>pos</code>, null if no such element
168     * exist.
169     *
170     * @return java.lang.String the value of the key
171     * @param pos
172     *            int the position to look for
173     */
174    public String get(int pos) {
175        if (pos >= 0 && pos < props.size() / 2) {
176            return props.get(pos * 2 + 1);
177        }
178        return null;
179    }
180
181    /**
182     * Returns the key of this header at <code>pos</code>, null if there are
183     * fewer keys in the header
184     *
185     *
186     * @return the key the desired position
187     * @param pos
188     *            the position to look for
189     */
190    public String getKey(int pos) {
191        if (pos >= 0 && pos < props.size() / 2) {
192            return props.get(pos * 2);
193        }
194        return null;
195    }
196
197    /**
198     * Returns the value corresponding to the specified key.
199     *
200     * @param key
201     *            the key to look up.
202     * @return Answers the value for the given key, or <code>null</code> if no
203     *         such key exists.
204     */
205    public String get(String key) {
206        LinkedList<String> result = keyTable.get(key.toLowerCase());
207        if (result == null) {
208            return null;
209        }
210        return result.getLast();
211    }
212
213    /**
214     * Returns the number of keys stored in this header
215     *
216     * @return the number of keys.
217     */
218    public int length() {
219        return props.size() / 2;
220    }
221
222    /**
223     * Sets the status line in the header request example: GET / HTTP/1.1
224     * response example: HTTP/1.1 200 OK
225     *
226     * @param statusLine
227     */
228    public void setStatusLine(String statusLine) {
229        this.statusLine = statusLine;
230        /*
231         * we add the status line to the list of headers so that it is
232         * accessible from java.net.HttpURLConnection.getResponseCode() which
233         * calls
234         * org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnection.getHeaderField(0)
235         * to get it
236         */
237        props.add(0, null);
238        props.add(1, statusLine);
239    }
240
241    /**
242     * Gets the status line in the header request example: GET / HTTP/1.1
243     * response example: HTTP/1.1 200 OK
244     *
245     * @return the status line
246     */
247    public String getStatusLine() {
248        return statusLine;
249    }
250}
251