1 #region MIT license
2 //
3 // MIT license
4 //
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
8 // of this software and associated documentation files (the "Software"), to deal
9 // in the Software without restriction, including without limitation the rights
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 // copies of the Software, and to permit persons to whom the Software is
12 // furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 // THE SOFTWARE.
24 //
25 #endregion
26 
27 using System;
28 using System.Collections.Generic;
29 using System.Globalization;
30 using System.Text;
31 using DbLinq.Factory;
32 using DbLinq.Language;
33 
34 namespace DbLinq.Schema.Implementation
35 {
36     /// <summary>
37     /// INameFormatter default implementation
38     /// </summary>
39     internal class NameFormatter : INameFormatter
40     {
41         /// <summary>
42         /// Singularization type
43         /// </summary>
44         internal enum Singularization
45         {
46             /// <summary>
47             /// The word plural doesn't change
48             /// </summary>
49             DontChange,
50             /// <summary>
51             /// Singularize the word
52             /// </summary>
53             Singular,
54             /// <summary>
55             /// Pluralize the word
56             /// </summary>
57             Plural,
58         }
59 
60         /// <summary>
61         /// Indicates the word position. Internally used for capitalization
62         /// </summary>
63         [Flags]
64         protected enum Position
65         {
66             /// <summary>
67             /// Word is first in sentence
68             /// </summary>
69             First = 0x01,
70             /// <summary>
71             /// Word is last in sentence
72             /// </summary>
73             Last = 0x02,
74         }
75 
76         /// <summary>
77         /// ILanguageWords by culture info name
78         /// </summary>
79         private readonly IDictionary<string, ILanguageWords> languageWords = new Dictionary<string, ILanguageWords>();
80 
81         /// <summary>
82         /// Substitution char for invalid characters
83         /// </summary>
84         private const char SubstitutionChar = '_';
85 
86         /// <summary>
87         /// Gets the ILanguageWords by CultureInfo.
88         /// </summary>
89         /// <param name="cultureInfo">The culture info.</param>
90         /// <returns></returns>
GetLanguageWords(CultureInfo cultureInfo)91         protected virtual ILanguageWords GetLanguageWords(CultureInfo cultureInfo)
92         {
93             lock (languageWords)
94             {
95                 ILanguageWords words;
96                 if (!languageWords.TryGetValue(cultureInfo.Name, out words))
97                 {
98                     var languages = ObjectFactory.Get<ILanguages>();
99                     words = languages.Load(cultureInfo);
100                     languageWords[cultureInfo.Name] = words;
101                 }
102                 return words;
103             }
104         }
105 
106         /// <summary>
107         /// Formats the specified words.
108         /// </summary>
109         /// <param name="words">The words.</param>
110         /// <param name="oldName">The old name.</param>
111         /// <param name="newCase">The new case.</param>
112         /// <param name="singularization">The singularization.</param>
113         /// <returns></returns>
Format(ILanguageWords words, string oldName, Case newCase, Singularization singularization)114         public virtual string Format(ILanguageWords words, string oldName, Case newCase, Singularization singularization)
115         {
116             var parts = ExtractWordsFromCaseAndLanguage(words, oldName);
117             return Format(words, parts, newCase, singularization);
118         }
119 
120         /// <summary>
121         /// Formats the specified words.
122         /// </summary>
123         /// <param name="words">The words.</param>
124         /// <param name="parts">The parts.</param>
125         /// <param name="newCase">The new case.</param>
126         /// <param name="singularization">The singularization.</param>
127         /// <returns></returns>
Format(ILanguageWords words, IList<string> parts, Case newCase, Singularization singularization)128         private string Format(ILanguageWords words, IList<string> parts, Case newCase, Singularization singularization)
129         {
130             var result = new StringBuilder();
131             for (int partIndex = 0; partIndex < parts.Count; partIndex++)
132             {
133                 Position position = 0;
134                 if (partIndex == 0)
135                     position |= Position.First;
136                 if (partIndex == parts.Count - 1)
137                     position |= Position.Last;
138                 result.Append(AdjustPart(words, parts[partIndex], position, newCase, singularization));
139             }
140             return result.ToString();
141         }
142 
143         /// <summary>
144         /// Toes the camel case.
145         /// </summary>
146         /// <param name="part">The part.</param>
147         /// <returns></returns>
ToCamelCase(string part)148         public string ToCamelCase(string part)
149         {
150             return part.ToLower();
151         }
152 
153         /// <summary>
154         /// Toes the pascal case.
155         /// </summary>
156         /// <param name="part">The part.</param>
157         /// <returns></returns>
ToPascalCase(string part)158         public string ToPascalCase(string part)
159         {
160             // we have a very special case here, for "ID" that goes to full uppercase even in PascalCase mode
161             if (string.Compare(part, "id", true) == 0)
162                 return "ID";
163             part = part.Substring(0, 1).ToUpper() + part.Substring(1).ToLower();
164             return part;
165         }
166 
167         /// <summary>
168         /// Toes the net case.
169         /// </summary>
170         /// <param name="part">The part.</param>
171         /// <returns></returns>
ToNetCase(string part)172         public string ToNetCase(string part)
173         {
174             return ToPascalCase(part);
175         }
176 
177         /// <summary>
178         /// Adjusts the part.
179         /// </summary>
180         /// <param name="words">The words.</param>
181         /// <param name="part">The part.</param>
182         /// <param name="position">The position.</param>
183         /// <param name="newCase">The new case.</param>
184         /// <param name="singularization">The singularization.</param>
185         /// <returns></returns>
AdjustPart(ILanguageWords words, string part, Position position, Case newCase, Singularization singularization)186         protected virtual string AdjustPart(ILanguageWords words, string part, Position position, Case newCase, Singularization singularization)
187         {
188             if (singularization != Singularization.DontChange && (position & Position.Last) != 0)
189             {
190                 if (singularization == Singularization.Singular)
191                     part = words.Singularize(part);
192                 else
193                     part = words.Pluralize(part);
194             }
195             Case applyCase = newCase;
196             if (applyCase == Case.camelCase && (position & Position.First) == 0)
197                 applyCase = Case.PascalCase;
198             switch (applyCase)
199             {
200             case Case.Leave:
201                 break;
202             case Case.camelCase:
203                 part = ToCamelCase(part);
204                 break;
205             case Case.PascalCase:
206                 part = ToPascalCase(part);
207                 break;
208             case Case.NetCase:
209                 part = ToNetCase(part);
210                 break;
211             default:
212                 throw new ArgumentOutOfRangeException();
213             }
214             return part;
215         }
216 
217         /// <summary>
218         /// Pushes the word on a collection
219         /// </summary>
220         /// <param name="words">The words.</param>
221         /// <param name="currentWord">The current word.</param>
PushWord(ICollection<string> words, StringBuilder currentWord)222         private static void PushWord(ICollection<string> words, StringBuilder currentWord)
223         {
224             if (currentWord.Length > 0)
225             {
226                 words.Add(currentWord.ToString());
227                 currentWord.Remove(0, currentWord.Length);
228             }
229         }
230 
231         /// <summary>
232         /// Extracts words from uppercase and _
233         /// A word can also be composed of several uppercase letters
234         /// </summary>
235         /// <param name="name"></param>
236         /// <param name="substitutionChar"></param>
237         /// <returns></returns>
ExtractWordsFromCase(string name, char substitutionChar)238         protected virtual IList<string> ExtractWordsFromCase(string name, char substitutionChar)
239         {
240             var words = new List<string>();
241             bool currentLowerCase = true;
242             var currentWord = new StringBuilder();
243             for (int charIndex = 0; charIndex < name.Length; charIndex++)
244             {
245                 char currentChar = name[charIndex];
246                 bool isLower = char.IsLower(currentChar);
247                 // we switched to uppercase
248                 if (!isLower && currentLowerCase)
249                 {
250                     PushWord(words, currentWord);
251                 }
252                 else if (isLower && !currentLowerCase)
253                 {
254                     // if the current word has several uppercase letters, it is one unique word
255                     if (currentWord.Length > 1)
256                         PushWord(words, currentWord);
257                 }
258                 // only letters or digits are allowed
259                 if (char.IsLetterOrDigit(currentChar))
260                     currentWord.Append(currentChar);
261                 // _ is the separator character, but all other characters will be kept
262                 else if (currentChar != '_' && !char.IsSeparator(currentChar))
263                     currentWord.Append(substitutionChar);
264                 currentLowerCase = isLower;
265             }
266             PushWord(words, currentWord);
267 
268             return words;
269         }
270 
271         /// <summary>
272         /// Extracts the words from case and language.
273         /// </summary>
274         /// <param name="words">The words.</param>
275         /// <param name="dbName">Name of the db.</param>
276         /// <returns></returns>
ExtractWordsFromCaseAndLanguage(ILanguageWords words, string dbName)277         protected virtual IList<string> ExtractWordsFromCaseAndLanguage(ILanguageWords words, string dbName)
278         {
279             var extractedWords = new List<string>();
280             foreach (var wordsMagma in ExtractWordsFromCase(dbName, SubstitutionChar))
281             {
282                 extractedWords.AddRange(words.GetWords(wordsMagma));
283             }
284             return extractedWords;
285         }
286 
287         /// <summary>
288         /// Extracts the words from given text.
289         /// </summary>
290         /// <param name="words">The words.</param>
291         /// <param name="dbName">Name of the db.</param>
292         /// <param name="extraction">The extraction type (case or language identification).</param>
293         /// <returns></returns>
ExtractWords(ILanguageWords words, string dbName, WordsExtraction extraction)294         protected virtual IList<string> ExtractWords(ILanguageWords words, string dbName, WordsExtraction extraction)
295         {
296             switch (extraction)
297             {
298             case WordsExtraction.None:
299                 return new[] { dbName };
300             case WordsExtraction.FromCase:
301                 return ExtractWordsFromCase(dbName, SubstitutionChar);
302             case WordsExtraction.FromDictionary:
303                 return ExtractWordsFromCaseAndLanguage(words, dbName);
304             default:
305                 throw new ArgumentOutOfRangeException("extraction");
306             }
307         }
308 
309         /// <summary>
310         /// Gets the singularization.
311         /// </summary>
312         /// <param name="singularization">The singularization.</param>
313         /// <param name="nameFormat">The name format.</param>
314         /// <returns></returns>
GetSingularization(Singularization singularization, NameFormat nameFormat)315         protected virtual Singularization GetSingularization(Singularization singularization, NameFormat nameFormat)
316         {
317             if (!nameFormat.Pluralize)
318                 return Singularization.DontChange;
319             return singularization;
320         }
321 
322         /// <summary>
323         /// Reformats a name by adjusting its case.
324         /// </summary>
325         /// <param name="words">The words.</param>
326         /// <param name="newCase">The new case.</param>
327         /// <returns></returns>
Format(string words, Case newCase)328         public string Format(string words, Case newCase)
329         {
330             return Format(null, ExtractWordsFromCase(words, SubstitutionChar), newCase, Singularization.DontChange);
331         }
332 
333         /// <summary>
334         /// Gets the name of the schema.
335         /// </summary>
336         /// <param name="dbName">Name of the db.</param>
337         /// <param name="extraction">The extraction.</param>
338         /// <param name="nameFormat">The name format.</param>
339         /// <returns></returns>
GetSchemaName(string dbName, WordsExtraction extraction, NameFormat nameFormat)340         public SchemaName GetSchemaName(string dbName, WordsExtraction extraction, NameFormat nameFormat)
341         {
342             var words = GetLanguageWords(nameFormat.Culture);
343             var schemaName = new SchemaName { DbName = dbName };
344             schemaName.NameWords = ExtractWords(words, dbName, extraction);
345             schemaName.ClassName = Format(words, schemaName.NameWords, nameFormat.Case, Singularization.DontChange);
346             return schemaName;
347         }
348 
349         /// <summary>
350         /// Gets the name of the procedure.
351         /// </summary>
352         /// <param name="dbName">Name of the db.</param>
353         /// <param name="extraction">The extraction.</param>
354         /// <param name="nameFormat">The name format.</param>
355         /// <returns></returns>
GetProcedureName(string dbName, WordsExtraction extraction, NameFormat nameFormat)356         public ProcedureName GetProcedureName(string dbName, WordsExtraction extraction, NameFormat nameFormat)
357         {
358             var words = GetLanguageWords(nameFormat.Culture);
359             var procedureName = new ProcedureName { DbName = dbName };
360             procedureName.NameWords = ExtractWords(words, dbName, extraction);
361             procedureName.MethodName = Format(words, procedureName.NameWords, nameFormat.Case, Singularization.DontChange);
362             return procedureName;
363         }
364 
365         /// <summary>
366         /// Gets the name of the parameter.
367         /// </summary>
368         /// <param name="dbName">Name of the db.</param>
369         /// <param name="extraction">The extraction.</param>
370         /// <param name="nameFormat">The name format.</param>
371         /// <returns></returns>
GetParameterName(string dbName, WordsExtraction extraction, NameFormat nameFormat)372         public ParameterName GetParameterName(string dbName, WordsExtraction extraction, NameFormat nameFormat)
373         {
374             var words = GetLanguageWords(nameFormat.Culture);
375             var parameterName = new ParameterName { DbName = dbName };
376             parameterName.NameWords = ExtractWords(words, dbName, extraction);
377             parameterName.CallName = Format(words, parameterName.NameWords, Case.camelCase, Singularization.DontChange);
378             return parameterName;
379         }
380 
381         /// <summary>
382         /// Gets the name of the table.
383         /// </summary>
384         /// <param name="dbName">Name of the db.</param>
385         /// <param name="extraction">The extraction.</param>
386         /// <param name="nameFormat">The name format.</param>
387         /// <returns></returns>
GetTableName(string dbName, WordsExtraction extraction, NameFormat nameFormat)388         public TableName GetTableName(string dbName, WordsExtraction extraction, NameFormat nameFormat)
389         {
390             var words = GetLanguageWords(nameFormat.Culture);
391             var tableName = new TableName { DbName = dbName };
392             tableName.NameWords = ExtractWords(words, dbName, extraction);
393             // if no extraction (preset name, just copy it)
394             if (extraction == WordsExtraction.None)
395                 tableName.ClassName = tableName.DbName;
396             else
397                 tableName.ClassName = Format(words, tableName.NameWords, nameFormat.Case, GetSingularization(Singularization.Singular, nameFormat));
398             tableName.MemberName = Format(words, tableName.NameWords, nameFormat.Case, GetSingularization(Singularization.Plural, nameFormat));
399             return tableName;
400         }
401 
402         /// <summary>
403         /// Gets the name of the column.
404         /// </summary>
405         /// <param name="dbName">Name of the db.</param>
406         /// <param name="extraction">The extraction.</param>
407         /// <param name="nameFormat">The name format.</param>
408         /// <returns></returns>
GetColumnName(string dbName, WordsExtraction extraction, NameFormat nameFormat)409         public ColumnName GetColumnName(string dbName, WordsExtraction extraction, NameFormat nameFormat)
410         {
411             var words = GetLanguageWords(nameFormat.Culture);
412             var columnName = new ColumnName { DbName = dbName };
413             columnName.NameWords = ExtractWords(words, dbName, extraction);
414             // if no extraction (preset name, just copy it)
415             if (extraction == WordsExtraction.None)
416                 columnName.PropertyName = dbName;
417             else
418                 columnName.PropertyName = Format(words, columnName.NameWords, nameFormat.Case, Singularization.DontChange);
419             return columnName;
420         }
421 
422         /// <summary>
423         /// Gets the name of the association.
424         /// </summary>
425         /// <param name="dbManyName">Name of the db many.</param>
426         /// <param name="dbOneName">Name of the db one.</param>
427         /// <param name="dbConstraintName">Name of the db constraint.</param>
428         /// <param name="foreignKeyName">Name of the foreign key.</param>
429         /// <param name="extraction">The extraction.</param>
430         /// <param name="nameFormat">The name format.</param>
431         /// <returns></returns>
GetAssociationName(string dbManyName, string dbOneName, string dbConstraintName, string foreignKeyName, WordsExtraction extraction, NameFormat nameFormat)432         public AssociationName GetAssociationName(string dbManyName, string dbOneName, string dbConstraintName,
433             string foreignKeyName, WordsExtraction extraction, NameFormat nameFormat)
434         {
435             var words = GetLanguageWords(nameFormat.Culture);
436             var associationName = new AssociationName { DbName = dbManyName };
437             associationName.NameWords = ExtractWords(words, dbManyName, extraction);
438             associationName.ManyToOneMemberName = Format(words, dbOneName, nameFormat.Case, GetSingularization(Singularization.Singular, nameFormat));
439             // TODO: this works only for PascalCase
440             if (dbManyName == dbOneName)
441                 associationName.ManyToOneMemberName = foreignKeyName.Replace(',', '_') + associationName.ManyToOneMemberName;
442             // TODO: support new extraction
443             associationName.OneToManyMemberName = Format(words, dbManyName, nameFormat.Case, GetSingularization(Singularization.Plural, nameFormat));
444             return associationName;
445         }
446     }
447 }
448