Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / System.Design / System.Resources.Tools / StronglyTypedResourceBuilder.cs
1 //
2 // StronglyTypedResourceBuilder.cs
3 //
4 // Author:
5 //      Atsushi Enomoto (atsushi@ximian.com)
6 //      Gary Barnett (gary.barnett.mono@gmail.com)
7 // 
8 // Copyright (C) 2007 Novell, Inc.
9 //
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if NET_2_0
31
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.ComponentModel.Design;
36 using System.Reflection;
37 using System.Collections.Generic;
38 using System.IO;
39 using System.Resources;
40
41 namespace System.Resources.Tools
42 {
43         public static class StronglyTypedResourceBuilder
44         {
45
46                 static char [] specialChars = { ' ', '\u00A0', '.', ',', ';', '|', '~', '@', '#', '%', '^', '&', 
47                                                 '*', '+', '-', '/', '\\', '<', '>', '?', '[', ']', '(', ')', '{', 
48                                                 '}', '\"', '\'', ':', '!'};
49
50                 static char [] specialCharsNameSpace = { ' ', '\u00A0', ',', ';', '|', '~', '@', '#', '%', '^', '&', 
51                                                 '*', '+', '-', '/', '\\', '<', '>', '?', '[', ']', '(', ')', '{', 
52                                                 '}', '\"', '\'', '!'};
53
54                 public static CodeCompileUnit Create (string resxFile,
55                                                       string baseName,
56                                                       string generatedCodeNamespace,
57                                                       CodeDomProvider codeProvider, bool internalClass,
58                                                       out string [] unmatchable)
59                 {
60                         
61                         return Create (resxFile,
62                                        baseName,
63                                        generatedCodeNamespace,
64                                        null,
65                                        codeProvider,
66                                        internalClass,
67                                        out unmatchable);
68                 }
69
70                 public static CodeCompileUnit Create (string resxFile,
71                                                       string baseName,
72                                                       string generatedCodeNamespace,
73                                                       string resourcesNamespace,
74                                                       CodeDomProvider codeProvider, bool internalClass,
75                                                       out string [] unmatchable)
76                 {
77                         // unpack resfile into dictionary, pass to overload
78
79                         // validate resxFile
80                         if (resxFile == null)
81                                 throw new ArgumentNullException ("Parameter resxFile must not be null");
82
83                         List<char> invalidPathChars = new List<char> (Path.GetInvalidPathChars ());
84                         foreach (char c in resxFile.ToCharArray ()) {
85                                 if (invalidPathChars.Contains (c))
86                                         throw new ArgumentException ("Invalid character in resxFileName");
87                         }
88
89                         Dictionary<string,object> resourcesList = new Dictionary<string,object> ();
90
91                         using (ResXResourceReader reader = new ResXResourceReader (resxFile)) {
92
93                                 foreach (DictionaryEntry d in reader)
94                                         resourcesList.Add ((string) d.Key, d.Value);    
95                         }
96
97                         return Create (resourcesList,
98                                        baseName,
99                                        generatedCodeNamespace,
100                                        resourcesNamespace,
101                                        codeProvider,
102                                        internalClass,
103                                        out unmatchable);
104
105                 }
106
107                 public static CodeCompileUnit Create (IDictionary resourceList,
108                                                       string baseName,
109                                                       string generatedCodeNamespace,
110                                                       CodeDomProvider codeProvider, bool internalClass,
111                                                       out string [] unmatchable)
112                 {
113                         
114                         return Create (resourceList, 
115                                        baseName, 
116                                        generatedCodeNamespace, 
117                                        null, 
118                                        codeProvider, 
119                                        internalClass, 
120                                        out unmatchable);
121                 }
122
123                 public static CodeCompileUnit Create (IDictionary resourceList, 
124                                                       string baseName,
125                                                       string generatedCodeNamespace,
126                                                       string resourcesNamespace,
127                                                       CodeDomProvider codeProvider, bool internalClass,
128                                                       out string [] unmatchable)
129                 {
130                         string baseNameToUse, generatedCodeNamespaceToUse;
131                         string resourcesToUse;
132                         
133                         // validate parameters, convert into useable form where necessary / possible
134                         if (resourceList == null)
135                                 throw new ArgumentNullException ("Parameter resourceList must not be null");
136                         
137                         if (codeProvider == null)
138                                 throw new ArgumentNullException ("Parameter: codeProvider must not be null");
139                         
140                         if (baseName == null)
141                                 throw new ArgumentNullException ("Parameter: baseName must not be null");
142
143                         baseNameToUse = VerifyResourceName (baseName, codeProvider);
144                         
145                         if (baseNameToUse == null)
146                                 throw new ArgumentException ("Parameter: baseName is invalid");
147                         
148                         if (generatedCodeNamespace == null) {
149                                 generatedCodeNamespaceToUse = "";
150                         } else {
151                                 generatedCodeNamespaceToUse = CleanNamespaceChars (generatedCodeNamespace);
152                                 generatedCodeNamespaceToUse = codeProvider.CreateValidIdentifier (
153                                                                                         generatedCodeNamespaceToUse);
154                         }
155
156                         if (resourcesNamespace == null)
157                                 resourcesToUse = generatedCodeNamespaceToUse + "." + baseNameToUse;
158                         else if (resourcesNamespace == String.Empty)
159                                 resourcesToUse = baseNameToUse;
160                         else
161                                 resourcesToUse = resourcesNamespace + "." + baseNameToUse;
162
163                         // validate ResourceList IDictionary
164
165                         Dictionary<string,ResourceItem> resourceItemDict;
166                         resourceItemDict = new Dictionary<string,ResourceItem> (StringComparer.OrdinalIgnoreCase);
167
168                         //allow ArgumentException to be raised on case insensitive dupes,InvalidCastException on key not being string
169                         foreach (DictionaryEntry de in resourceList)
170                                 resourceItemDict.Add ((string) de.Key, new ResourceItem (de.Value));
171
172                         ProcessResourceList (resourceItemDict, codeProvider);
173
174                         // Generate CodeDOM
175                         CodeCompileUnit ccu = GenerateCodeDOMBase (baseNameToUse, generatedCodeNamespaceToUse, 
176                                                                    resourcesToUse, internalClass);
177
178                         // add properties for resources
179                         unmatchable = ResourcePropertyGeneration (ccu.Namespaces [0].Types [0], 
180                                                                   resourceItemDict, internalClass);
181
182                         return ccu;
183                 }
184
185                 static string[] ResourcePropertyGeneration (CodeTypeDeclaration resType, 
186                                                             Dictionary<string, ResourceItem> resourceItemDict, 
187                                                             bool internalClass)
188                 {
189                         // either create properties for resources, ignore or add to unmatchableList
190                         List<string> unmatchableList = new List<string> ();
191
192                         foreach (KeyValuePair<string, ResourceItem> kvp in resourceItemDict) {
193                                 if (kvp.Value.isUnmatchable)
194                                         unmatchableList.Add (kvp.Key); // orig key
195                                 else if (!kvp.Value.toIgnore) {
196                                                 if (kvp.Value.Resource is Stream)
197                                                         resType.Members.Add (GenerateStreamResourceProp (kvp.Value.VerifiedKey,
198                                                                                                         kvp.Key,
199                                                                                                         internalClass));
200                                                 else if (kvp.Value.Resource is String)
201                                                         resType.Members.Add (GenerateStringResourceProp (kvp.Value.VerifiedKey,
202                                                                                                         kvp.Key,
203                                                                                                         internalClass));
204                                                 else
205                                                         resType.Members.Add (GenerateStandardResourceProp (kvp.Value.VerifiedKey,
206                                                                                                         kvp.Key,
207                                                                                                         kvp.Value.Resource.GetType (),
208                                                                                                         internalClass));
209                                 }
210                         }
211
212                         return unmatchableList.ToArray ();
213                 }
214
215                 static CodeCompileUnit GenerateCodeDOMBase (string baseNameToUse, string generatedCodeNamespaceToUse, 
216                                                             string resourcesToUse, bool internalClass)
217                 {
218                         CodeCompileUnit ccu = new CodeCompileUnit ();
219                         ccu.ReferencedAssemblies.Add ("System.dll");
220                         CodeNamespace nsMain = new CodeNamespace (generatedCodeNamespaceToUse);
221                         ccu.Namespaces.Add (nsMain);
222                         nsMain.Imports.Add (new CodeNamespaceImport ("System"));
223                         
224                         //class
225                         CodeTypeDeclaration resType = GenerateBaseType (baseNameToUse, internalClass);
226                         nsMain.Types.Add (resType);
227
228                         GenerateFields (resType);
229
230                         resType.Members.Add (GenerateConstructor ());
231
232                         // Default Properties
233                         resType.Members.Add (GenerateResourceManagerProp (baseNameToUse, resourcesToUse, internalClass));
234                         resType.Members.Add (GenerateCultureProp (internalClass));
235
236                         return ccu;
237                 }
238
239                 static void ProcessResourceList (Dictionary<string, ResourceItem> resourceItemDict, 
240                                                  CodeDomProvider codeProvider)
241                 {
242                         foreach (KeyValuePair<string, ResourceItem> kvp in resourceItemDict) {
243                                 //deal with ignored keys
244                                 if (kvp.Key.StartsWith (">>") || kvp.Key.StartsWith ("$")) {
245                                         kvp.Value.toIgnore = true;
246                                         continue;
247                                 }
248                                 //deal with specified invalid names (case sensitive)
249                                 if (kvp.Key == "ResourceManager" || kvp.Key == "Culture") {
250                                         kvp.Value.isUnmatchable = true;
251                                         continue;
252                                 }
253
254                                 kvp.Value.VerifiedKey = VerifyResourceName (kvp.Key, codeProvider);
255                                 // will be null if codeProvider deems invalid
256                                 if (kvp.Value.VerifiedKey == null) {
257                                         kvp.Value.isUnmatchable = true;
258                                         continue;
259                                 }
260                                 //dupe check
261                                 foreach (KeyValuePair<string, ResourceItem> item in resourceItemDict) {
262                                         // skip on encountering kvp or if VerifiedKey on object null (ie hasnt been processed yet)
263                                         if (Object.ReferenceEquals (item.Value, kvp.Value)
264                                             || item.Value.VerifiedKey == null)
265                                             continue;
266                                         // if case insensitive dupe found mark both
267                                         if (String.Equals (item.Value.VerifiedKey, kvp.Value.VerifiedKey, 
268                                                            StringComparison.OrdinalIgnoreCase)) {
269                                                 item.Value.isUnmatchable = true;
270                                                 kvp.Value.isUnmatchable = true;
271                                         }
272                                 }
273                         }
274                 }
275
276                 static CodeTypeDeclaration GenerateBaseType (string baseNameToUse, bool internalClass)
277                 {
278                         CodeTypeDeclaration resType = new CodeTypeDeclaration (baseNameToUse);
279                         resType.IsClass = true;
280                         // set access modifier for class
281                         if (internalClass)
282                                 resType.TypeAttributes =  TypeAttributes.NotPublic;
283                         else
284                                 resType.TypeAttributes =  TypeAttributes.Public;
285                         
286                         //class CustomAttributes
287                         resType.CustomAttributes.Add (new CodeAttributeDeclaration (
288                                                         "System.CodeDom.Compiler.GeneratedCodeAttribute",
289                                                         new CodeAttributeArgument (
290                                                         new CodePrimitiveExpression (
291                                                         "System.Resources.Tools.StronglyTypedResourceBuilder")),
292                                                         new CodeAttributeArgument (
293                                                         new CodePrimitiveExpression ("4.0.0.0"))));
294
295
296                         resType.CustomAttributes.Add (new CodeAttributeDeclaration (
297                                                         "System.Diagnostics.DebuggerNonUserCodeAttribute"));
298
299                         resType.CustomAttributes.Add (new CodeAttributeDeclaration (
300                                                         "System.Runtime.CompilerServices.CompilerGeneratedAttribute"));
301
302                         return resType;
303                 }
304                 static void GenerateFields (CodeTypeDeclaration resType)
305                 {
306                         //resourceMan field
307                         CodeMemberField resourceManField = new CodeMemberField ();
308                         resourceManField.Attributes = (MemberAttributes.Abstract
309                                                         | MemberAttributes.Final
310                                                         | MemberAttributes.Assembly
311                                                         | MemberAttributes.FamilyOrAssembly);
312                         resourceManField.Name = "resourceMan";
313                         resourceManField.Type = new CodeTypeReference (typeof (System.Resources.ResourceManager));
314                         resType.Members.Add (resourceManField);
315                         
316                         //resourceCulture field
317                         CodeMemberField resourceCultureField = new CodeMemberField ();
318                         resourceCultureField.Attributes = (MemberAttributes.Abstract
319                                                         | MemberAttributes.Final
320                                                         | MemberAttributes.Assembly
321                                                         | MemberAttributes.FamilyOrAssembly);
322                         resourceCultureField.Name = "resourceCulture";
323                         resourceCultureField.Type = new CodeTypeReference (typeof (System.Globalization.CultureInfo));
324                         resType.Members.Add (resourceCultureField);
325                 }
326
327                 static CodeConstructor GenerateConstructor ()
328                 {
329                         CodeConstructor ctor = new CodeConstructor ();
330                         ctor.Attributes = MemberAttributes.FamilyAndAssembly; // always internal
331
332                         ctor.CustomAttributes.Add (new CodeAttributeDeclaration (
333                                                 "System.Diagnostics.CodeAnalysis.SuppressMessageAttribute",
334                                                 new CodeAttributeArgument (
335                                                 new CodePrimitiveExpression ("Microsoft.Performance")),
336                                                 new CodeAttributeArgument (
337                                                 new CodePrimitiveExpression ("CA1811:AvoidUncalledPrivateCode"))));
338
339                         return ctor;
340                 }
341
342                 static CodeAttributeDeclaration DefaultPropertyAttribute ()
343                 {
344                         // CustomAttributes for ResourceManager and Culture 
345                         return new CodeAttributeDeclaration ("System.ComponentModel.EditorBrowsableAttribute",
346                                                                 new CodeAttributeArgument (
347                                                                 new CodeFieldReferenceExpression (
348                                                                 new CodeTypeReferenceExpression (
349                                                                 "System.ComponentModel.EditorBrowsableState"),
350                                                                 "Advanced")));
351                 }
352
353                 static CodeMemberProperty GenerateCultureProp (bool internalClass)
354                 {
355                         // Culture property
356                         CodeMemberProperty cultureProp = GeneratePropertyBase ("Culture",
357                                                                        typeof (System.Globalization.CultureInfo),
358                                                                        internalClass,
359                                                                        true,
360                                                                        true);
361                         
362                         // attributes - same as ResourceManager
363                         cultureProp.CustomAttributes.Add (DefaultPropertyAttribute ());
364
365                         // getter
366                         cultureProp.GetStatements.Add (new CodeMethodReturnStatement (
367                                                         new CodeFieldReferenceExpression (
368                                                         null,"resourceCulture")));
369
370                         // setter
371                         cultureProp.SetStatements.Add (new CodeAssignStatement (
372                                 new CodeFieldReferenceExpression (
373                                 null,"resourceCulture"),
374                                 new CodePropertySetValueReferenceExpression ()));
375
376                         return cultureProp;
377                 }
378
379                 static CodeMemberProperty GenerateResourceManagerProp (string baseNameToUse, string resourcesToUse, 
380                                                                        bool internalClass)
381                 {
382                         // ResourceManager property
383                         CodeMemberProperty resourceManagerProp = GeneratePropertyBase ("ResourceManager",
384                                                                                typeof (System.Resources.ResourceManager),
385                                                                                internalClass,
386                                                                                true,
387                                                                                false);
388
389                         resourceManagerProp.CustomAttributes.Add (DefaultPropertyAttribute ());
390                         // getter
391                         
392                         // true statments for check if resourceMan null to go inside getter
393                         CodeStatement [] trueStatements = new CodeStatement [2];
394                         
395                         trueStatements [0] = new CodeVariableDeclarationStatement (
396                                                                                 new CodeTypeReference (
397                                                                                 "System.Resources.ResourceManager"),
398                                                                                 "temp", new CodeObjectCreateExpression (
399                                                                                 new CodeTypeReference (
400                                                                                 "System.Resources.ResourceManager"),
401                                                                                 new CodePrimitiveExpression (resourcesToUse),
402                                                                                 new CodePropertyReferenceExpression (
403                                                                                 new CodeTypeOfExpression (baseNameToUse),
404                                                                                 "Assembly")));
405
406                         trueStatements [1] = new CodeAssignStatement (new CodeFieldReferenceExpression (null, "resourceMan"),
407                                                                       new CodeVariableReferenceExpression ("temp"));
408                         
409                         resourceManagerProp.GetStatements.Add (new CodeConditionStatement (
410                                                                 new CodeMethodInvokeExpression (
411                                                                 new CodeMethodReferenceExpression (
412                                                                 new CodeTypeReferenceExpression ("System.Object"), "Equals"),
413                                                                 new CodePrimitiveExpression(null),
414                                                                 new CodeFieldReferenceExpression (
415                                                                 null,"resourceMan")),trueStatements));
416                         
417                         resourceManagerProp.GetStatements.Add (new CodeMethodReturnStatement ( 
418                                                                new CodeFieldReferenceExpression ( null,"resourceMan")));
419
420                         return resourceManagerProp;
421
422                 }
423
424                 static CodeMemberProperty GenerateStandardResourceProp (string propName, string resName, 
425                                                                         Type propertyType, bool isInternal)
426                 {
427
428                         CodeMemberProperty prop = GeneratePropertyBase (propName, propertyType, isInternal, true, false);
429
430                         prop.GetStatements.Add (new CodeVariableDeclarationStatement (
431                                                 new CodeTypeReference ("System.Object"),
432                                                 "obj",
433                                                 new CodeMethodInvokeExpression (
434                                                 new CodePropertyReferenceExpression (null,"ResourceManager"),
435                                                 "GetObject",
436                                                 new CodePrimitiveExpression (resName),
437                                                 new CodeFieldReferenceExpression (null,"resourceCulture"))));
438
439                         prop.GetStatements.Add (new CodeMethodReturnStatement (
440                                                 new CodeCastExpression (
441                                                 new CodeTypeReference (propertyType),
442                                                 new CodeVariableReferenceExpression ("obj"))));
443
444                         return prop;
445                 }
446
447                 static CodeMemberProperty GenerateStringResourceProp (string propName, string resName, bool isInternal)
448                 {
449                         CodeMemberProperty prop = GeneratePropertyBase (propName, typeof (String), isInternal, true, false);
450
451                         prop.GetStatements.Add (new CodeMethodReturnStatement (
452                                                 new CodeMethodInvokeExpression (
453                                                 new CodeMethodReferenceExpression (
454                                                 new CodePropertyReferenceExpression (null,"ResourceManager"),
455                                                 "GetString"),
456                                                 new CodePrimitiveExpression (resName),
457                                                 new CodeFieldReferenceExpression (null,"resourceCulture"))));
458                                                 
459                         return prop;
460                 }
461
462                 static CodeMemberProperty GenerateStreamResourceProp (string propName, string resName, bool isInternal)
463                 {
464                         CodeMemberProperty prop = GeneratePropertyBase (propName, typeof (UnmanagedMemoryStream), 
465                                                                         isInternal, true, false);
466
467                         prop.GetStatements.Add (new CodeMethodReturnStatement (
468                                                 new CodeMethodInvokeExpression (
469                                                 new CodeMethodReferenceExpression (
470                                                 new CodePropertyReferenceExpression (null,"ResourceManager"),
471                                                 "GetStream"),
472                                                 new CodePrimitiveExpression (resName),
473                                                 new CodeFieldReferenceExpression (null,"resourceCulture"))));
474
475                         return prop;
476                 }
477
478                 static CodeMemberProperty GeneratePropertyBase (string name, Type propertyType, bool isInternal, 
479                                                                 bool hasGet, bool hasSet)
480                 {
481                         CodeMemberProperty prop = new CodeMemberProperty ();
482
483                         prop.Name = name;
484                         prop.Type = new CodeTypeReference (propertyType);
485                         
486                         // accessor
487                         if (isInternal)
488                                 prop.Attributes = (MemberAttributes.Abstract
489                                                         | MemberAttributes.Final
490                                                         | MemberAttributes.Assembly);
491                         else
492                                 prop.Attributes = (MemberAttributes.Abstract
493                                                         | MemberAttributes.Final
494                                                         | MemberAttributes.FamilyAndAssembly
495                                                         | MemberAttributes.FamilyOrAssembly);
496                         
497                         prop.HasGet = hasGet;
498                         prop.HasSet = hasSet;
499                         return prop;
500                 }
501
502                 public static string VerifyResourceName (string key, CodeDomProvider provider)
503                 {
504                         string keyToUse;
505                         char [] charKey;
506
507                         // check params
508                         if (key == null)
509                                 throw new ArgumentNullException ("Parameter: key must not be null");
510                         if (provider == null)
511                                 throw new ArgumentNullException ("Parameter: provider must not be null");
512
513                         if (key == String.Empty) {
514                                 keyToUse = "_";
515                         } else {
516                                 // replaces special chars
517                                 charKey = key.ToCharArray ();
518                                 for (int i = 0; i < charKey.Length; i++)
519                                         charKey [i] = VerifySpecialChar (charKey [i]);
520                                 keyToUse = new string(charKey);
521                         }
522                         // resolve if keyword
523                         keyToUse = provider.CreateValidIdentifier (keyToUse);
524                         // check if still not valid for provider
525                         if (provider.IsValidIdentifier (keyToUse))
526                                 return keyToUse;
527                         else
528                                 return null;
529                 }
530
531                 static char VerifySpecialChar (char ch)
532                 {
533                         for (int i = 0; i < specialChars.Length; i++) {
534                                 if (specialChars [i] == ch)
535                                         return '_';
536                         }
537                         return ch;
538                 }
539
540                 static string CleanNamespaceChars (string name)
541                 {
542                         char [] nameChars = name.ToCharArray ();
543                         for (int i = 0; i < nameChars.Length ;i++) {
544                                 foreach (char c in specialCharsNameSpace) {
545                                         if (nameChars [i] == c)
546                                                 nameChars [i] = '_';
547                                 }
548                         }
549                         return new string (nameChars);
550                 }
551
552                 class ResourceItem {
553                         public string VerifiedKey { get;set; }
554                         public object Resource { get;set; }
555                         public bool isUnmatchable { get;set; }
556                         public bool toIgnore { get;set; }
557
558                         public ResourceItem (object value)
559                         {
560                                 Resource = value;
561                         }
562                 }
563
564         }
565 }
566
567 #endif