001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.util;
020:
021: import java.io.IOException;
022: import java.util.Iterator;
023:
024: import org.restlet.data.CharacterSet;
025: import org.restlet.data.Encoding;
026: import org.restlet.data.Form;
027: import org.restlet.data.Language;
028: import org.restlet.data.MediaType;
029: import org.restlet.data.Metadata;
030: import org.restlet.data.Parameter;
031: import org.restlet.data.Preference;
032: import org.restlet.util.Series;
033:
034: import com.noelios.restlet.http.HttpUtils;
035:
036: /**
037: * Preference header reader. Works for character sets, encodings, languages or
038: * media types.
039: *
040: * @author Jerome Louvel (contact@noelios.com)
041: */
042: public class PreferenceReader<T extends Metadata> extends HeaderReader {
043: public static final int TYPE_CHARACTER_SET = 1;
044:
045: public static final int TYPE_ENCODING = 2;
046:
047: public static final int TYPE_LANGUAGE = 3;
048:
049: public static final int TYPE_MEDIA_TYPE = 4;
050:
051: /** The type of metadata read. */
052: private int type;
053:
054: /**
055: * Constructor.
056: *
057: * @param type
058: * The type of metadata read.
059: * @param header
060: * The header to read.
061: */
062: public PreferenceReader(int type, String header) {
063: super (header);
064: this .type = type;
065: }
066:
067: /**
068: * Read the next preference.
069: *
070: * @return The next preference.
071: */
072: public Preference<T> readPreference() throws IOException {
073: Preference<T> result = null;
074:
075: boolean readingMetadata = true;
076: boolean readingParamName = false;
077: boolean readingParamValue = false;
078:
079: StringBuilder metadataBuffer = new StringBuilder();
080: StringBuilder paramNameBuffer = null;
081: StringBuilder paramValueBuffer = null;
082:
083: Series<Parameter> parameters = null;
084:
085: String nextValue = readValue();
086: int nextIndex = 0;
087:
088: if (nextValue != null) {
089: int nextChar = nextValue.charAt(nextIndex++);
090:
091: while (result == null) {
092: if (readingMetadata) {
093: if (nextChar == -1) {
094: if (metadataBuffer.length() > 0) {
095: // End of metadata section
096: // No parameters detected
097: result = createPreference(metadataBuffer,
098: null);
099: paramNameBuffer = new StringBuilder();
100: } else {
101: // Ignore empty metadata name
102: }
103: } else if (nextChar == ';') {
104: if (metadataBuffer.length() > 0) {
105: // End of metadata section
106: // Parameters detected
107: readingMetadata = false;
108: readingParamName = true;
109: paramNameBuffer = new StringBuilder();
110: parameters = new Form();
111: } else {
112: throw new IOException(
113: "Empty metadata name detected.");
114: }
115: } else if (HttpUtils.isSpace(nextChar)) {
116: // Ignore spaces
117: } else if (HttpUtils.isText(nextChar)) {
118: metadataBuffer.append((char) nextChar);
119: } else {
120: throw new IOException(
121: "Control characters are not allowed within a metadata name.");
122: }
123: } else if (readingParamName) {
124: if (nextChar == '=') {
125: if (paramNameBuffer.length() > 0) {
126: // End of parameter name section
127: readingParamName = false;
128: readingParamValue = true;
129: paramValueBuffer = new StringBuilder();
130: } else {
131: throw new IOException(
132: "Empty parameter name detected.");
133: }
134: } else if (nextChar == -1) {
135: if (paramNameBuffer.length() > 0) {
136: // End of parameters section
137: parameters.add(HttpUtils.createParameter(
138: paramNameBuffer, null));
139: result = createPreference(metadataBuffer,
140: parameters);
141: } else {
142: throw new IOException(
143: "Empty parameter name detected.");
144: }
145: } else if (nextChar == ';') {
146: // End of parameter
147: parameters.add(HttpUtils.createParameter(
148: paramNameBuffer, null));
149: paramNameBuffer = new StringBuilder();
150: readingParamName = true;
151: readingParamValue = false;
152: } else if (HttpUtils.isSpace(nextChar)
153: && (paramNameBuffer.length() == 0)) {
154: // Ignore white spaces
155: } else if (HttpUtils.isTokenChar(nextChar)) {
156: paramNameBuffer.append((char) nextChar);
157: } else {
158: throw new IOException(
159: "Separator and control characters are not allowed within a token.");
160: }
161: } else if (readingParamValue) {
162: if (nextChar == -1) {
163: if (paramValueBuffer.length() > 0) {
164: // End of parameters section
165: parameters.add(HttpUtils.createParameter(
166: paramNameBuffer, paramValueBuffer));
167: result = createPreference(metadataBuffer,
168: parameters);
169: } else {
170: throw new IOException(
171: "Empty parameter value detected");
172: }
173: } else if (nextChar == ';') {
174: // End of parameter
175: parameters.add(HttpUtils.createParameter(
176: paramNameBuffer, paramValueBuffer));
177: paramNameBuffer = new StringBuilder();
178: readingParamName = true;
179: readingParamValue = false;
180: } else if ((nextChar == '"')
181: && (paramValueBuffer.length() == 0)) {
182: // Parse the quoted string
183: boolean done = false;
184: boolean quotedPair = false;
185:
186: while ((!done) && (nextChar != -1)) {
187: nextChar = (nextIndex < nextValue.length()) ? nextValue
188: .charAt(nextIndex++)
189: : -1;
190:
191: if (quotedPair) {
192: // End of quoted pair (escape sequence)
193: if (HttpUtils.isText(nextChar)) {
194: paramValueBuffer
195: .append((char) nextChar);
196: quotedPair = false;
197: } else {
198: throw new IOException(
199: "Invalid character detected in quoted string. Please check your value");
200: }
201: } else if (HttpUtils
202: .isDoubleQuote(nextChar)) {
203: // End of quoted string
204: done = true;
205: } else if (nextChar == '\\') {
206: // Begin of quoted pair (escape sequence)
207: quotedPair = true;
208: } else if (HttpUtils.isText(nextChar)) {
209: paramValueBuffer
210: .append((char) nextChar);
211: } else {
212: throw new IOException(
213: "Invalid character detected in quoted string. Please check your value");
214: }
215: }
216: } else if (HttpUtils.isTokenChar(nextChar)) {
217: paramValueBuffer.append((char) nextChar);
218: } else {
219: throw new IOException(
220: "Separator and control characters are not allowed within a token");
221: }
222: }
223:
224: nextChar = (nextIndex < nextValue.length()) ? nextValue
225: .charAt(nextIndex++) : -1;
226: }
227: }
228:
229: return result;
230: }
231:
232: /**
233: * Extract the media parameters. Only leave as the quality parameter if
234: * found. Modifies the parameters list.
235: *
236: * @param parameters
237: * All the preference parameters.
238: * @return The media parameters.
239: */
240: protected Series<Parameter> extractMediaParams(
241: Series<Parameter> parameters) {
242: Series<Parameter> result = null;
243: boolean qualityFound = false;
244: Parameter param = null;
245:
246: if (parameters != null) {
247: result = new Form();
248:
249: for (Iterator<Parameter> iter = parameters.iterator(); !qualityFound
250: && iter.hasNext();) {
251: param = iter.next();
252:
253: if (param.getName().equals("q")) {
254: qualityFound = true;
255: } else {
256: iter.remove();
257: result.add(param);
258: }
259: }
260: }
261:
262: return result;
263: }
264:
265: /**
266: * Extract the quality value. If the value is not found, 1 is returned.
267: *
268: * @param parameters
269: * The preference parameters.
270: * @return The quality value.
271: */
272: protected float extractQuality(Series<Parameter> parameters) {
273: float result = 1F;
274: boolean found = false;
275:
276: if (parameters != null) {
277: Parameter param = null;
278: for (Iterator<Parameter> iter = parameters.iterator(); !found
279: && iter.hasNext();) {
280: param = iter.next();
281: if (param.getName().equals("q")) {
282: result = PreferenceUtils.parseQuality(param
283: .getValue());
284: found = true;
285:
286: // Remove the quality parameter as we will directly store it
287: // in the Preference object
288: iter.remove();
289: }
290: }
291: }
292:
293: return result;
294: }
295:
296: /**
297: * Creates a new preference.
298: *
299: * @param metadata
300: * The metadata name.
301: * @param parameters
302: * The parameters list.
303: * @return The new preference.
304: */
305: @SuppressWarnings("unchecked")
306: protected Preference<T> createPreference(CharSequence metadata,
307: Series<Parameter> parameters) {
308: Preference<T> result;
309:
310: if (parameters == null) {
311: result = new Preference<T>();
312:
313: switch (type) {
314: case TYPE_CHARACTER_SET:
315: result.setMetadata((T) CharacterSet.valueOf(metadata
316: .toString()));
317: break;
318:
319: case TYPE_ENCODING:
320: result.setMetadata((T) Encoding.valueOf(metadata
321: .toString()));
322: break;
323:
324: case TYPE_LANGUAGE:
325: result.setMetadata((T) Language.valueOf(metadata
326: .toString()));
327: break;
328:
329: case TYPE_MEDIA_TYPE:
330: result.setMetadata((T) MediaType.valueOf(metadata
331: .toString()));
332: break;
333: }
334: } else {
335: Series<Parameter> mediaParams = extractMediaParams(parameters);
336: float quality = extractQuality(parameters);
337: result = new Preference<T>(null, quality, parameters);
338:
339: switch (type) {
340: case TYPE_CHARACTER_SET:
341: result.setMetadata((T) new CharacterSet(metadata
342: .toString()));
343: break;
344:
345: case TYPE_ENCODING:
346: result
347: .setMetadata((T) new Encoding(metadata
348: .toString()));
349: break;
350:
351: case TYPE_LANGUAGE:
352: result
353: .setMetadata((T) new Language(metadata
354: .toString()));
355: break;
356:
357: case TYPE_MEDIA_TYPE:
358: result.setMetadata((T) new MediaType(metadata
359: .toString(), mediaParams));
360: break;
361: }
362: }
363:
364: return result;
365: }
366:
367: }
|