2 * Manager.cs - Implementation of the "I18N.Common.Manager" class.
4 * Copyright (c) 2002 Southern Storm Software, Pty Ltd
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
31 using System.Globalization;
32 using System.Collections;
33 using System.Reflection;
34 using System.Security;
36 // This class provides the primary entry point into the I18N
37 // library. Users of the library start by getting the value
38 // of the "PrimaryManager" property. They then invoke methods
39 // on the manager to obtain further I18N information.
43 // The primary I18N manager.
44 private static Manager manager;
47 private Hashtable handlers; // List of all handler classes.
48 private Hashtable active; // Currently active handlers.
49 private Hashtable assemblies; // Currently loaded region assemblies.
54 handlers = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
55 CaseInsensitiveComparer.Default);
56 active = new Hashtable(16);
57 assemblies = new Hashtable(8);
61 // Get the primary I18N manager instance.
62 public static Manager PrimaryManager
70 manager = new Manager();
78 // FIXME: This means, we accept invalid names such as "euc_jp"
79 private static String Normalize(String name)
82 return (name.ToLower()).Replace('-', '_');
84 return (name.ToLower(CultureInfo.InvariantCulture))
89 // Get an encoding object for a specific code page.
90 // Returns NULL if the code page is not available.
91 public Encoding GetEncoding(int codePage)
93 return (Instantiate("CP" + codePage.ToString()) as Encoding);
96 // Get an encoding object for a specific Web encoding.
97 // Returns NULL if the encoding is not available.
98 public Encoding GetEncoding(String name)
100 // Validate the parameter.
106 // Normalize the encoding name.
107 name = Normalize(name);
109 // Try to find a class called "ENCname".
110 Encoding e = Instantiate ("ENC" + name) as Encoding;
112 e = Instantiate (name) as Encoding;
115 // Try windows aliases
116 string alias = Handlers.GetAlias (name);
118 e = Instantiate ("ENC" + alias) as Encoding;
120 e = Instantiate (alias) as Encoding;
127 // List of hex digits for use by "GetCulture".
128 private const String hex = "0123456789abcdef";
130 // Get a specific culture by identifier. Returns NULL
131 // if the culture information is not available.
132 public CultureInfo GetCulture(int culture, bool useUserOverride)
134 // Create the hex version of the culture identifier.
135 StringBuilder builder = new StringBuilder();
136 builder.Append(hex[(culture >> 12) & 0x0F]);
137 builder.Append(hex[(culture >> 8) & 0x0F]);
138 builder.Append(hex[(culture >> 4) & 0x0F]);
139 builder.Append(hex[culture & 0x0F]);
140 String name = builder.ToString();
142 // Try looking for an override culture handler.
145 Object obj = Instantiate("CIDO" + name);
148 return (obj as CultureInfo);
152 // Look for the generic non-override culture.
153 return (Instantiate("CID" + name) as CultureInfo);
156 // Get a specific culture by name. Returns NULL if the
157 // culture informaion is not available.
158 public CultureInfo GetCulture(String name, bool useUserOverride)
160 // Validate the parameter.
166 // Normalize the culture name.
167 name = Normalize(name);
169 // Try looking for an override culture handler.
172 Object obj = Instantiate("CNO" + name.ToString());
175 return (obj as CultureInfo);
179 // Look for the generic non-override culture.
180 return (Instantiate("CN" + name.ToString()) as CultureInfo);
183 // Instantiate a handler class. Returns null if it is not
184 // possible to instantiate the class.
185 internal Object Instantiate(String name)
194 // See if we already have an active handler by this name.
195 handler = active[name];
201 // Determine which region assembly handles the class.
202 region = (String)(handlers[name]);
205 // The class does not exist in any region assembly.
209 // Find the region-specific assembly and load it.
210 assembly = (Assembly)(assemblies[region]);
215 // we use the same strong name as I18N.dll except the assembly name
216 AssemblyName myName = typeof(Manager).Assembly.GetName();
217 myName.Name = region;
218 assembly = Assembly.Load(myName);
220 catch(SystemException)
228 assemblies[region] = assembly;
231 // Look for the class within the region-specific assembly.
232 type = assembly.GetType(region + "." + name, false, true);
238 // Invoke the constructor, which we assume is public
239 // and has zero arguments.
242 handler = type.InvokeMember
244 BindingFlags.CreateInstance |
245 BindingFlags.Public |
246 BindingFlags.NonPublic |
247 BindingFlags.Instance,
248 null, null, null, null, null, null);
250 catch(MissingMethodException)
252 // The constructor was not present.
255 catch(SecurityException)
257 // The constructor was inaccessible.
261 // Add the handler to the active handlers cache.
262 active.Add(name, handler);
264 // Return the handler to the caller.
269 // Load the list of classes that are present in all region assemblies.
270 private void LoadClassList()
274 // Look for "I18N-handlers.def" in the same directory
275 // as this assembly. Note: this assumes that the
276 // "Assembly.GetFile" method can access files that
277 // aren't explicitly part of the assembly manifest.
279 // This is necessary because the "I18N-handlers.def"
280 // file is generated after the "i18n" assembly is
281 // compiled and linked. So it cannot be embedded
282 // directly into the assembly manifest.
285 stream = Assembly.GetExecutingAssembly()
286 .GetFile("I18N-handlers.def");
289 LoadInternalClasses();
293 catch(FileLoadException)
295 // The file does not exist, or the runtime engine
296 // refuses to implement the necessary semantics.
297 // Fall back to an internal list, which must be
298 // kept up to date manually.
299 LoadInternalClasses();
303 // Load the class list from the stream.
304 StreamReader reader = new StreamReader(stream);
307 while((line = reader.ReadLine()) != null)
309 // Skip comment lines in the input.
310 if(line.Length == 0 || line[0] == '#')
315 // Split the line into namespace and name. We assume
316 // that the line has the form "I18N.<Region>.<Name>".
317 posn = line.LastIndexOf('.');
320 // Add the namespace to the "handlers" hash,
321 // attached to the name of the handler class.
322 String name = line.Substring(posn + 1);
323 if(!handlers.Contains(name))
325 handlers.Add(name, line.Substring(0, posn));
332 // Load the list of classes from the internal list.
333 private void LoadInternalClasses()
336 foreach(String line in Handlers.List)
338 // Split the line into namespace and name. We assume
339 // that the line has the form "I18N.<Region>.<Name>".
340 posn = line.LastIndexOf('.');
343 // Add the namespace to the "handlers" hash,
344 // attached to the name of the handler class.
345 String name = line.Substring(posn + 1);
346 if(!handlers.Contains(name))
348 handlers.Add(name, line.Substring(0, posn));
356 }; // namespace I18N.Common