2004-05-09 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / I18N / Common / Manager.cs
1 /*
2  * Manager.cs - Implementation of the "I18N.Common.Manager" class.
3  *
4  * Copyright (c) 2002  Southern Storm Software, Pty Ltd
5  *
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:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
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.
23  */
24
25 namespace I18N.Common
26 {
27
28 using System;
29 using System.IO;
30 using System.Text;
31 using System.Globalization;
32 using System.Collections;
33 using System.Reflection;
34 using System.Security;
35
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.
40
41 public class Manager
42 {
43         // The primary I18N manager.
44         private static Manager manager;
45
46         // Internal state.
47         private Hashtable handlers;             // List of all handler classes.
48         private Hashtable active;               // Currently active handlers.
49         private Hashtable assemblies;   // Currently loaded region assemblies.
50
51         // Constructor.
52         private Manager()
53                         {
54                                 handlers = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
55                                                           CaseInsensitiveComparer.Default);
56                                 active = new Hashtable(16);
57                                 assemblies = new Hashtable(8);
58                                 LoadClassList();
59                         }
60
61         // Get the primary I18N manager instance.
62         public static Manager PrimaryManager
63                         {
64                                 get
65                                 {
66                                         lock(typeof(Manager))
67                                         {
68                                                 if(manager == null)
69                                                 {
70                                                         manager = new Manager();
71                                                 }
72                                                 return manager;
73                                         }
74                                 }
75                         }
76
77         // Normalize a name.
78         // FIXME: This means, we accept invalid names such as "euc_jp"
79         private static String Normalize(String name)
80                         {
81                         #if ECMA_COMPAT
82                                 return (name.ToLower()).Replace('-', '_');
83                         #else
84                                 return (name.ToLower(CultureInfo.InvariantCulture))
85                                                         .Replace('-', '_');
86                         #endif
87                         }
88
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)
92                         {
93                                 return (Instantiate("CP" + codePage.ToString()) as Encoding);
94                         }
95
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)
99                         {
100                                 // Validate the parameter.
101                                 if(name == null)
102                                 {
103                                         return null;
104                                 }
105
106                                 // Normalize the encoding name.
107                                 name = Normalize(name);
108
109                                 // Try to find a class called "ENCname".
110                                 Encoding e = Instantiate ("ENC" + name) as Encoding;
111                                 if (e == null)
112                                         e = Instantiate (name) as Encoding;
113
114                                 if (e == null) {
115                                         // Try windows aliases
116                                         string alias = Handlers.GetAlias (name);
117                                         if (alias != null) {
118                                                 e = Instantiate ("ENC" + alias) as Encoding;
119                                                 if (e == null)
120                                                         e = Instantiate (alias) as Encoding;
121                                         }
122                                 }
123
124                                 return e;
125                         }
126         
127         // List of hex digits for use by "GetCulture".
128         private const String hex = "0123456789abcdef";
129
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)
133                         {
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();
141
142                                 // Try looking for an override culture handler.
143                                 if(useUserOverride)
144                                 {
145                                         Object obj = Instantiate("CIDO" + name);
146                                         if(obj != null)
147                                         {
148                                                 return (obj as CultureInfo);
149                                         }
150                                 }
151
152                                 // Look for the generic non-override culture.
153                                 return (Instantiate("CID" + name) as CultureInfo);
154                         }
155
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)
159                         {
160                                 // Validate the parameter.
161                                 if(name == null)
162                                 {
163                                         return null;
164                                 }
165
166                                 // Normalize the culture name.
167                                 name = Normalize(name);
168
169                                 // Try looking for an override culture handler.
170                                 if(useUserOverride)
171                                 {
172                                         Object obj = Instantiate("CNO" + name.ToString());
173                                         if(obj != null)
174                                         {
175                                                 return (obj as CultureInfo);
176                                         }
177                                 }
178
179                                 // Look for the generic non-override culture.
180                                 return (Instantiate("CN" + name.ToString()) as CultureInfo);
181                         }
182
183         // Instantiate a handler class.  Returns null if it is not
184         // possible to instantiate the class.
185         internal Object Instantiate(String name)
186                         {
187                                 Object handler;
188                                 String region;
189                                 Assembly assembly;
190                                 Type type;
191
192                                 lock(this)
193                                 {
194                                         // See if we already have an active handler by this name.
195                                         handler = active[name];
196                                         if(handler != null)
197                                         {
198                                                 return handler;
199                                         }
200
201                                         // Determine which region assembly handles the class.
202                                         region = (String)(handlers[name]);
203                                         if(region == null)
204                                         {
205                                                 // The class does not exist in any region assembly.
206                                                 return null;
207                                         }
208
209                                         // Find the region-specific assembly and load it.
210                                         assembly = (Assembly)(assemblies[region]);
211                                         if(assembly == null)
212                                         {
213                                                 try
214                                                 {
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);
219                                                 }
220                                                 catch(SystemException)
221                                                 {
222                                                         assembly = null;
223                                                 }
224                                                 if(assembly == null)
225                                                 {
226                                                         return null;
227                                                 }
228                                                 assemblies[region] = assembly;
229                                         }
230
231                                         // Look for the class within the region-specific assembly.
232                                         type = assembly.GetType(region + "." + name, false, true);
233                                         if(type == null)
234                                         {
235                                                 return null;
236                                         }
237
238                                         // Invoke the constructor, which we assume is public
239                                         // and has zero arguments.
240                                         try
241                                         {
242                                                 handler = type.InvokeMember
243                                                                 (String.Empty,
244                                                                  BindingFlags.CreateInstance |
245                                                                         BindingFlags.Public |
246                                                                         BindingFlags.NonPublic |
247                                                                         BindingFlags.Instance,
248                                                                  null, null, null, null, null, null);
249                                         }
250                                         catch(MissingMethodException)
251                                         {
252                                                 // The constructor was not present.
253                                                 return null;
254                                         }
255                                         catch(SecurityException)
256                                         {
257                                                 // The constructor was inaccessible.
258                                                 return null;
259                                         }
260
261                                         // Add the handler to the active handlers cache.
262                                         active.Add(name, handler);
263
264                                         // Return the handler to the caller.
265                                         return handler;
266                                 }
267                         }
268
269         // Load the list of classes that are present in all region assemblies.
270         private void LoadClassList()
271                         {
272                                 FileStream stream;
273
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.
278                                 //
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.
283                                 try
284                                 {
285                                         stream = Assembly.GetExecutingAssembly()
286                                                                 .GetFile("I18N-handlers.def");
287                                         if(stream == null)
288                                         {
289                                                 LoadInternalClasses();
290                                                 return;
291                                         }
292                                 }
293                                 catch(FileLoadException)
294                                 {
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();
300                                         return;
301                                 }
302
303                                 // Load the class list from the stream.
304                                 StreamReader reader = new StreamReader(stream);
305                                 String line;
306                                 int posn;
307                                 while((line = reader.ReadLine()) != null)
308                                 {
309                                         // Skip comment lines in the input.
310                                         if(line.Length == 0 || line[0] == '#')
311                                         {
312                                                 continue;
313                                         }
314
315                                         // Split the line into namespace and name.  We assume
316                                         // that the line has the form "I18N.<Region>.<Name>".
317                                         posn = line.LastIndexOf('.');
318                                         if(posn != -1)
319                                         {
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))
324                                                 {
325                                                         handlers.Add(name, line.Substring(0, posn));
326                                                 }
327                                         }
328                                 }
329                                 reader.Close();
330                         }
331
332         // Load the list of classes from the internal list.
333         private void LoadInternalClasses()
334                         {
335                                 int posn;
336                                 foreach(String line in Handlers.List)
337                                 {
338                                         // Split the line into namespace and name.  We assume
339                                         // that the line has the form "I18N.<Region>.<Name>".
340                                         posn = line.LastIndexOf('.');
341                                         if(posn != -1)
342                                         {
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))
347                                                 {
348                                                         handlers.Add(name, line.Substring(0, posn));
349                                                 }
350                                         }
351                                 }
352                         }
353
354 }; // class Manager
355
356 }; // namespace I18N.Common