KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > infohazard > maverick > shunt > LanguageShuntFactory


1 /*
2  * $Id: LanguageShuntFactory.java,v 1.8 2004/06/07 20:39:02 eelco12 Exp $
3  * $Source: /cvsroot/mav/maverick/src/java/org/infohazard/maverick/shunt/LanguageShuntFactory.java,v $
4  */

5
6 package org.infohazard.maverick.shunt;
7
8 import java.util.ArrayList JavaDoc;
9 import java.util.Collections JavaDoc;
10 import java.util.Enumeration JavaDoc;
11 import java.util.HashMap JavaDoc;
12 import java.util.List JavaDoc;
13 import java.util.Map JavaDoc;
14 import java.util.StringTokenizer JavaDoc;
15
16 import javax.servlet.ServletConfig JavaDoc;
17 import javax.servlet.http.HttpServletRequest JavaDoc;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.infohazard.maverick.flow.ConfigException;
22 import org.infohazard.maverick.flow.NoSuitableModeException;
23 import org.infohazard.maverick.flow.Shunt;
24 import org.infohazard.maverick.flow.ShuntFactory;
25 import org.infohazard.maverick.flow.View;
26 import org.jdom.Element;
27
28 /**
29  * <p>LanguageShuntFactory produces Shunts which determine mode based
30  * on the Accept-Language header submitted by the user agent.
31  *
32  * <p>Modes can be specified as "en", "fr", "zh-hk", and the like.
33  * Multiple modes can be assigned to the same view by comma-delimiting
34  * them ("en,fr"). In addition, a view can leave its mode unspecified
35  * to be a "default" view which will apply when no other mode is
36  * appropriate.</p>
37  *
38  * <p>Choosing mode from the Accept-Language header follows the way
39  * browsers actually work rather than the HTTP spec. Go figure. There
40  * is no support for quality levels and preference is determined by
41  * simple order in the string. Furthermore, there is some magic regarding
42  * prefixed languages: After trying each of the languages specified,
43  * any prefixed languages are chopped one level and tried again. This
44  * process is repeated until nothing is left to try but the null mode.</p>
45  *
46  * <p>For an example Accept-Language header "fr,zh-tw,zh-hk,no-nynorsk",
47  * the Shunt will check for modes in this order:</p>
48  *
49  * <ol>
50  * <li> fr </li>
51  * <li> zh-tw </li>
52  * <li> zh-hk </li>
53  * <li> no-nynorsk </li>
54  * <li> zh </li>
55  * <li> no </li>
56  * <li> the "null" mode </li>
57  * </ol>
58  *
59  * <p>Hopefully this produces useful behavior.</p>
60  */

61 public class LanguageShuntFactory implements ShuntFactory
62 {
63     /**
64      */

65     class LanguageShunt implements Shunt
66     {
67         /**
68          */

69         protected Map JavaDoc modes = new HashMap JavaDoc();
70
71         /**
72          * All modes are converted to lower case and trimmed.
73          * Note that multiple modes can be specified as a
74          * comma-delimited sequence, all aliased to the same view.
75          */

76         public void defineMode(String JavaDoc mode, View v) throws ConfigException
77         {
78             if (mode == null)
79                 this.reallyDefineMode(null, v);
80             else
81             {
82                 mode = mode.toLowerCase();
83
84                 StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(mode, ",");
85                 while (tokens.hasMoreTokens())
86                 {
87                     mode = tokens.nextToken().trim();
88
89                     this.reallyDefineMode(mode, v);
90                 }
91             }
92         }
93
94         /**
95          * Does not attempt to interpret mode, but checks for duplicates.
96          */

97         protected void reallyDefineMode(String JavaDoc mode, View v) throws ConfigException
98         {
99             if (this.modes.containsKey(mode))
100                 throw new ConfigException("A duplicate mode (" + mode + ") was"
101                                             + " specified in a view. Note that language modes"
102                                             + " are case insensitive.");
103
104             this.modes.put(mode, v);
105         }
106
107         /**
108          */

109         public View getView(HttpServletRequest JavaDoc request) throws NoSuitableModeException
110         {
111             // Must be made lowercase to be case insensitive.
112
String JavaDoc wholeHeader = request.getHeader("Accept-Language");
113             if (wholeHeader == null) // Can happen with unusual browsers (or telnet, for that matter)
114
wholeHeader = "";
115             else
116                 wholeHeader = wholeHeader.toLowerCase();
117
118             // Languages are separated by commas.
119
Enumeration JavaDoc tokens = new StringTokenizer JavaDoc(wholeHeader, ",");
120
121             // We may need multiple passes to process the base prefixes of languages
122
// with dashes in them.
123
List JavaDoc nextPass = null;
124
125             do
126             {
127                 while (tokens.hasMoreElements())
128                 {
129                     String JavaDoc lang = (String JavaDoc)tokens.nextElement();
130                     lang = lang.trim();
131
132                     View theView = (View)modes.get(lang);
133                     if (theView != null)
134                     {
135                         log.debug("Using mode: " + lang);
136
137                         return theView;
138                     }
139
140                     int dash = lang.lastIndexOf("-");
141                     if (dash >= 0)
142                     {
143                         // Make sure we create the list
144
if (nextPass == null)
145                             nextPass = new ArrayList JavaDoc();
146
147                         // Keep it for later!
148
nextPass.add(lang.substring(0, dash));
149                     }
150                 }
151
152                 // If there is more to process, use it as the token stream and start over.
153
if (nextPass != null)
154                 {
155                     tokens = Collections.enumeration(nextPass);
156                     nextPass = null;
157                 }
158             }
159             while (tokens.hasMoreElements());
160
161
162             // Try the default view if nothing else was found
163
View defaultView = (View)modes.get(null);
164             if (defaultView != null)
165                 return defaultView;
166
167
168             // Wow, after all that, we come up empty handed.
169
throw new NoSuitableModeException("No appropriate mode could be found for Accepts-Language \""
170                                         + wholeHeader + "\". Possibilities were: "
171                                         + modes.keySet());
172         }
173     }
174
175     /**
176      * Logger.
177      */

178     private static Log log = LogFactory.getLog(LanguageShuntFactory.class);
179
180     /**
181      * Does nothing.
182      */

183     public void init(Element factoryNode, ServletConfig JavaDoc servletCfg)
184     {
185     }
186
187     /**
188      * Merely creates a shunt object.
189      */

190     public Shunt createShunt() throws ConfigException
191     {
192         log.debug("Creating language shunt");
193
194         return new LanguageShunt();
195     }
196 }
Popular Tags