1    /*
2     * Copyright 2010 :torweg free software group
3     * 
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    * 
17    */
18   package org.torweg.pulse.service.request;
19   
20   import java.util.Collections;
21   import java.util.Locale;
22   import java.util.SortedSet;
23   import java.util.TreeSet;
24   
25   import javax.servlet.http.HttpServletRequest;
26   
27   import org.slf4j.Logger;
28   import org.slf4j.LoggerFactory;
29   
30   /**
31    * The parsed accept-languages header.
32    * 
33    * @author Thomas Weber
34    * @version $Revision: 1477 $
35    * 
36    */
37   public final class AcceptLanguages {
38   
39       /**
40        * the logger.
41        */
42       private static final Logger LOGGER = LoggerFactory
43               .getLogger(AcceptLanguages.class);
44   
45       /**
46        * the sorted acceptable languages.
47        */
48       private SortedSet<AcceptLanguages.Value> values;
49   
50       /**
51        * creates empty accept lanuages.
52        */
53       public AcceptLanguages() {
54           super();
55           this.values = new TreeSet<AcceptLanguages.Value>();
56       }
57   
58       /**
59        * creates the accept-languages.
60        * 
61        * @param req
62        *            the request
63        */
64       AcceptLanguages(final HttpServletRequest req) {
65           super();
66           parseHeader(req.getHeader("Accept-Language"));
67       }
68   
69       /**
70        * actually does the parsing.
71        * 
72        * @param header
73        *            the header
74        */
75       private void parseHeader(final String header) {
76           SortedSet<AcceptLanguages.Value> parsedLanguagesByQuality = new TreeSet<AcceptLanguages.Value>();
77           if (header == null) {
78               this.values = Collections
79                       .unmodifiableSortedSet(parsedLanguagesByQuality);
80               return;
81           }
82           int position = 0;
83           String[] languages = header.split(",");
84           for (String language : languages) {
85               String[] langQuality = language.split(";q=");
86               if (langQuality.length == 1) {
87                   parsedLanguagesByQuality.add(new Value(langQuality[0],
88                           position++));
89               } else {
90                   double q = Double.valueOf(langQuality[1].trim());
91                   if ((q > 0) && (q <= 1)) {
92                       parsedLanguagesByQuality.add(new Value(langQuality[0], q,
93                               position++));
94                   }
95               }
96           }
97           this.values = Collections
98                   .unmodifiableSortedSet(parsedLanguagesByQuality);
99   
100          // debug
101          if (LOGGER.isDebugEnabled()) {
102              StringBuilder builder = new StringBuilder();
103              for (AcceptLanguages.Value value : this.values) {
104                  builder.append(value.toString());
105                  builder.append(';');
106              }
107              LOGGER.debug(builder.toString());
108          }
109      }
110  
111      /**
112       * @return the values as an unmodifiable sorted set
113       */
114      public SortedSet<AcceptLanguages.Value> getValues() {
115          return this.values;
116      }
117  
118      /**
119       * a single comparable accept-language value.
120       */
121      public static final class Value implements
122              Comparable<AcceptLanguages.Value> {
123  
124          /**
125           * the locale.
126           */
127          private Locale locale;
128  
129          /**
130           * the quality.
131           */
132          private final double quality;
133  
134          /**
135           * the position in the header.
136           */
137          private final int position;
138  
139          /**
140           * creates a new value with the default quality 1.
141           * 
142           * @param lang
143           *            the language
144           * @param p
145           *            the position
146           */
147          protected Value(final String lang, final int p) {
148              super();
149              parseLocale(lang);
150              this.quality = 1;
151              this.position = p;
152          }
153  
154          /**
155           * creates a new value with the given quality 1.
156           * 
157           * @param lang
158           *            the language
159           * @param q
160           *            the quality
161           * @param p
162           *            the position
163           */
164          protected Value(final String lang, final double q, final int p) {
165              super();
166              parseLocale(lang);
167              if ((q <= 0) || (q > 1)) {
168                  throw new IllegalArgumentException("Quality must be between 0 "
169                          + "(excluded) and 1 (included).");
170              }
171              this.quality = q;
172              this.position = p;
173          }
174  
175          /**
176           * @param lang
177           *            the language
178           */
179          private void parseLocale(final String lang) {
180              String[] parts = lang.trim().split("-");
181              if (parts.length == 1) {
182                  this.locale = new Locale(parts[0]);
183              } else {
184                  this.locale = new Locale(parts[0], parts[1]);
185              }
186          }
187  
188          /**
189           * @return the locale
190           */
191          public Locale getLocale() {
192              return this.locale;
193          }
194  
195          /**
196           * @return the quality
197           */
198          public double getQuality() {
199              return this.quality;
200          }
201  
202          /**
203           * @param o
204           *            the value to compare with
205           * @return a negative integer, zero, or a positive integer as this
206           *         object is less than, equal to, or greater than the specified
207           *         object.
208           * @see java.lang.Comparable#compareTo(java.lang.Object)
209           */
210          public int compareTo(final AcceptLanguages.Value o) {
211              if (this.quality == o.quality) {
212                  if (this.position < o.position) {
213                      return -1;
214                  }
215                  return 1;
216              } else if (this.quality > o.quality) {
217                  return -1;
218              }
219              return 1;
220          }
221  
222          /**
223           * @return the hash code for the value
224           */
225          @Override
226          // CHECKSTYLE:OFF
227          public int hashCode() {
228              final int prime = 31; // NOPMD
229              int result = 1;
230              result = prime * result
231                      + ((this.locale == null) ? 0 : this.locale.hashCode());
232              long temp;
233              temp = Double.doubleToLongBits(this.quality);
234              result = prime * result + (int) (temp ^ (temp >>> 32));
235              return result;
236          } // CHECKSTYLE:ON
237  
238          /**
239           * @param obj
240           *            the object to check for equality
241           * @return whether the values are equal
242           */
243          @Override
244          public boolean equals(final Object obj) {
245              if (this == obj) {
246                  return true;
247              }
248              if (obj == null) {
249                  return false;
250              }
251              if (getClass() != obj.getClass()) {
252                  return false;
253              }
254              AcceptLanguages.Value other = (AcceptLanguages.Value) obj;
255              if (this.locale == null) {
256                  if (other.locale != null) {
257                      return false;
258                  }
259              } else if (!this.locale.equals(other.locale)) {
260                  return false;
261              }
262              if (Double.doubleToLongBits(this.quality) != Double
263                      .doubleToLongBits(other.quality)) {
264                  return false;
265              }
266              if (this.position != other.position) {
267                  return false;
268              }
269              return true;
270          }
271  
272          /**
273           * @return a string representation of the value
274           */
275          @Override
276          public String toString() {
277              StringBuilder builder = new StringBuilder("Value[");
278              builder.append(locale.toString()).append(",q:").append(quality)
279                      .append(",p:").append(position).append(']');
280              return builder.toString();
281          }
282  
283      }
284  
285  }
286