0023920: Change use of static variables in Message package to prevent data races...
[occt.git] / src / Message / Message_MsgFile.cxx
1 // Created on: 2001-04-26
2 // Created by: OCC Team
3 // Copyright (c) 2001-2012 OPEN CASCADE SAS
4 //
5 // The content of this file is subject to the Open CASCADE Technology Public
6 // License Version 6.5 (the "License"). You may not use the content of this file
7 // except in compliance with the License. Please obtain a copy of the License
8 // at http://www.opencascade.org and read it completely before using this file.
9 //
10 // The Initial Developer of the Original Code is Open CASCADE S.A.S., having its
11 // main offices at: 1, place des Freres Montgolfier, 78280 Guyancourt, France.
12 //
13 // The Original Code and all software distributed under the License is
14 // distributed on an "AS IS" basis, without warranty of any kind, and the
15 // Initial Developer hereby disclaims all such warranties, including without
16 // limitation, any warranties of merchantability, fitness for a particular
17 // purpose or non-infringement. Please see the License for the specific terms
18 // and conditions governing the rights and limitations under the License.
19
20
21 #include <Message_MsgFile.hxx>
22
23 #include <TCollection_AsciiString.hxx>
24 #include <TCollection_ExtendedString.hxx>
25 #include <NCollection_DefineBaseCollection.hxx>
26 #include <NCollection_DefineDataMap.hxx>
27 #include <OSD_Environment.hxx>
28 #include <stdlib.h>
29 #include <stdio.h>
30
31 DEFINE_BASECOLLECTION(Message_CollectionOfExtendedString, TCollection_ExtendedString)
32 DEFINE_DATAMAP(Message_DataMapOfExtendedString,
33                Message_CollectionOfExtendedString,
34                TCollection_AsciiString,
35                TCollection_ExtendedString)
36
37 static Message_DataMapOfExtendedString& msgsDataMap ()
38 {
39   static Message_DataMapOfExtendedString aDataMap;
40   return aDataMap;
41 }
42
43 typedef enum
44 {
45   MsgFile_WaitingKeyword,
46   MsgFile_WaitingMessage,
47   MsgFile_WaitingMoreMessage,
48   MsgFile_Indefinite
49 } LoadingState;
50
51 //=======================================================================
52 //function : Message_MsgFile
53 //purpose  : Load file from given directories
54 //           theDirName may be represented as list: "/dirA/dirB /dirA/dirC"
55 //=======================================================================
56
57 Standard_Boolean Message_MsgFile::Load (const Standard_CString theDirName,
58                                         const Standard_CString theFileName)
59 {
60   if ( ! theDirName || ! theFileName ) return Standard_False;
61
62   Standard_Boolean ret = Standard_True;
63   TCollection_AsciiString aDirList (theDirName);
64   //  Try to load from all consecutive directories in list
65   for (int i = 1;; i++)
66   {
67     TCollection_AsciiString aFileName = aDirList.Token (" \t\n", i);
68     if (aFileName.IsEmpty()) break;
69 #ifdef WNT
70     aFileName += '\\';
71 #else
72     aFileName += '/';
73 #endif
74     aFileName += theFileName;
75     if ( ! LoadFile (aFileName.ToCString()) ) 
76       ret = Standard_False;
77   }
78   return ret;
79 }
80
81 //=======================================================================
82 //function : getString
83 //purpose  : Takes a TCollection_ExtendedString from Ascii or Unicode
84 //           Strings are left-trimmed; those beginning with '!' are omitted
85 //Called   : from loadFile()
86 //=======================================================================
87
88 template <class _Char> static inline Standard_Boolean
89 getString (_Char *&                     thePtr,
90            TCollection_ExtendedString&  theString,
91            Standard_Integer&            theLeftSpaces)
92 {
93   _Char * anEndPtr = thePtr;
94   _Char * aPtr;
95   Standard_Integer aLeftSpaces;
96
97   do 
98   {
99     //    Skip whitespaces in the beginning of the string
100     aPtr = anEndPtr;
101     aLeftSpaces = 0;
102     while (1)
103     {
104       _Char aChar = * aPtr;
105       if      (aChar == ' ')  aLeftSpaces++;
106       else if (aChar == '\t') aLeftSpaces += 8;
107       else if (aChar == '\r' || * aPtr == '\n') aLeftSpaces = 0;
108       else break;
109       aPtr++;
110     }
111
112     //    Find the end of the string
113     for (anEndPtr = aPtr; * anEndPtr; anEndPtr++)
114       if (anEndPtr[0] == '\n')
115       {
116         if (anEndPtr[-1] == '\r') anEndPtr--;
117         break;
118       }
119
120   } while (aPtr[0] == '!');
121
122   //    form the result
123   if (aPtr == anEndPtr) return Standard_False;
124   thePtr = anEndPtr;
125   if (*thePtr)
126     *thePtr++ = '\0';
127   theString = TCollection_ExtendedString (aPtr);
128   theLeftSpaces = aLeftSpaces;
129   return Standard_True;
130 }
131
132 //=======================================================================
133 //function : loadFile
134 //purpose  : Static function, fills the DataMap of Messages from Ascii or Unicode
135 //Called   : from LoadFile()
136 //=======================================================================
137
138 template <class _Char> static inline Standard_Boolean loadFile (_Char * theBuffer)
139 {
140   TCollection_AsciiString               aKeyword;
141   TCollection_ExtendedString            aMessage, aString;
142   LoadingState                          aState = MsgFile_WaitingKeyword;
143   _Char                                 * sCurrentString = theBuffer;
144   Standard_Integer                      aLeftSpaces=0, aFirstLeftSpaces = 0;
145
146   //    Take strings one-by-one; comments already screened
147   while (::getString (sCurrentString, aString, aLeftSpaces))
148   {
149     Standard_Boolean isKeyword = (aString.Value(1) == '.');
150     switch (aState)
151     {
152     case MsgFile_WaitingMoreMessage:
153       if (isKeyword)
154         Message_MsgFile::AddMsg (aKeyword, aMessage); // terminate the previous one
155         //      Pass from here to 'case MsgFile_WaitingKeyword'
156       else
157       {
158         //      Add another line to the message already in the buffer 'aMessage'
159         aMessage += '\n';
160         aLeftSpaces -= aFirstLeftSpaces;
161         if (aLeftSpaces > 0) aMessage += TCollection_ExtendedString (aLeftSpaces, ' ');
162         aMessage += aString;
163         break;
164       }
165     case MsgFile_WaitingMessage:
166       if (isKeyword == Standard_False)
167       {
168         aMessage         = aString;
169         aFirstLeftSpaces = aLeftSpaces;         // remember the starting position
170         aState = MsgFile_WaitingMoreMessage;
171         break;
172       }
173       //      Pass from here to 'case MsgFile_WaitingKeyword'
174     case MsgFile_WaitingKeyword:
175       if (isKeyword)
176       {
177         // remove the first dot character and all subsequent spaces + right-trim
178         aKeyword = TCollection_AsciiString (aString.Split(1));
179         aKeyword.LeftAdjust();
180         aKeyword.RightAdjust();
181         aState = MsgFile_WaitingMessage;
182       }
183       break;
184     default:
185       break;
186     }
187   }
188   //    Process the last string still remaining in the buffer
189   if (aState == MsgFile_WaitingMoreMessage)
190     Message_MsgFile::AddMsg (aKeyword, aMessage);
191   return Standard_True;
192 }
193
194 //=======================================================================
195 //function : GetFileSize
196 //purpose  : 
197 //=======================================================================
198
199 static Standard_Integer GetFileSize (FILE *theFile)
200 {
201   if ( !theFile ) return -1;
202
203   // get real file size
204   long nRealFileSize = 0;
205   if ( fseek(theFile, 0, SEEK_END) != 0 ) return -1;
206   nRealFileSize = ftell(theFile);
207   if ( fseek(theFile, 0, SEEK_SET) != 0 ) return -1;
208
209   return (Standard_Integer) nRealFileSize;
210 }
211
212 //=======================================================================
213 //function : LoadFile
214 //purpose  : Load the list of messages from a file
215 //=======================================================================
216
217 Standard_Boolean Message_MsgFile::LoadFile (const Standard_CString theFileName)
218 {
219   if (theFileName == NULL || * theFileName == '\0') return Standard_False;
220
221   //    Open the file
222   FILE *anMsgFile = fopen (theFileName, "rb");
223   if (!anMsgFile) return Standard_False;
224
225   //    Read the file into memory
226   class Buffer
227   {
228     // self-destructing buffer
229     char *myBuf;
230   public:
231     Buffer (Standard_Integer theSize) : myBuf(new char [theSize]) {}
232     ~Buffer () { delete [] myBuf; }
233     operator char* () const { return myBuf; }
234     char& operator [] (Standard_Integer theInd) { return myBuf[theInd]; }
235   };
236   Standard_Integer aFileSize = GetFileSize (anMsgFile);
237   if (aFileSize <= 0)
238   {
239     fclose (anMsgFile);
240     return Standard_False;
241   }
242   Buffer anMsgBuffer (aFileSize + 2);
243   Standard_Integer nbRead =
244     (Standard_Integer) fread (anMsgBuffer, 1, aFileSize, anMsgFile);
245   fclose (anMsgFile);
246   if (nbRead != aFileSize)
247     return Standard_False;
248   anMsgBuffer[aFileSize] = 0;
249   anMsgBuffer[aFileSize+1] = 0;
250
251   // Read the messages in the file and append them to the global DataMap
252   Standard_Boolean isLittleEndian = (anMsgBuffer[0] == '\xff' && anMsgBuffer[1] == '\xfe');
253   Standard_Boolean isBigEndian    = (anMsgBuffer[0] == '\xfe' && anMsgBuffer[1] == '\xff');
254   if ( isLittleEndian || isBigEndian )
255   {
256     Standard_ExtCharacter * aUnicodeBuffer =
257       (Standard_ExtCharacter *) &anMsgBuffer[2];
258     // Convert Unicode representation to order adopted on current platform
259 #if defined(__sparc) && defined(__sun)
260     if ( isLittleEndian ) 
261 #else
262     if ( isBigEndian ) 
263 #endif
264     {
265       // Reverse the bytes throughout the buffer
266       for (Standard_ExtCharacter * aPtr = aUnicodeBuffer;
267            aPtr < (Standard_ExtCharacter *) &anMsgBuffer[aFileSize]; aPtr++)
268       {
269         unsigned short aWord = *aPtr;
270         *aPtr = (aWord & 0x00ff) << 8 | (aWord & 0xff00) >> 8;
271       }
272     }
273     return ::loadFile (aUnicodeBuffer);
274   }
275   else
276     return ::loadFile ((char*) anMsgBuffer);
277 }
278
279 //=======================================================================
280 //function : LoadFromEnv
281 //purpose  : 
282 //=======================================================================
283 void  Message_MsgFile::LoadFromEnv
284   (const Standard_CString envname,
285    const Standard_CString filename,
286    const Standard_CString ext)
287 {
288   Standard_CString extname = ext;
289   TCollection_AsciiString extstr;
290   if (!extname || extname[0] == '\0') {
291     OSD_Environment extenv("CSF_LANGUAGE");
292     extstr  = extenv.Value();
293     extname = extstr.ToCString();
294   }
295   if (!extname || extname[0] == '\0') extname = "us";
296
297   TCollection_AsciiString filestr(filename);
298   if (envname && envname[0] != '\0') {
299     OSD_Environment envenv(envname);
300     TCollection_AsciiString envstr  = envenv.Value();
301     if (envstr.Length() > 0) {
302       if (envstr.Value(envstr.Length()) != '/') filestr.Insert (1,'/');
303       filestr.Insert (1,envstr.ToCString());
304     }
305   }
306   if (extname[0] != '.') filestr.AssignCat ('.');
307   filestr.AssignCat (extname);
308
309   Message_MsgFile::LoadFile (filestr.ToCString());
310 }
311
312 //=======================================================================
313 //function : AddMsg
314 //purpose  : Add one message to the global table. Fails if the same keyword
315 //           already exists in the table
316 //=======================================================================
317
318 Standard_Boolean Message_MsgFile::AddMsg (const TCollection_AsciiString& theKeyword,
319                                           const TCollection_ExtendedString&  theMessage)
320 {
321   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
322 //  if (aDataMap.IsBound (theKeyword))
323 //    return Standard_False;
324   aDataMap.Bind (theKeyword, theMessage);
325   return Standard_True;
326 }
327
328 //=======================================================================
329 //function : getMsg
330 //purpose  : retrieve the message previously defined for the given keyword
331 //=======================================================================
332
333 const TCollection_ExtendedString &Message_MsgFile::Msg (const Standard_CString theKeyword)
334 {
335   TCollection_AsciiString aKey((char*)theKeyword);
336   return Msg (aKey);
337
338
339 //=======================================================================
340 //function : getMsg
341 //purpose  : retrieve the message previously defined for the given keyword
342 //=======================================================================
343
344 const TCollection_ExtendedString &Message_MsgFile::Msg (const TCollection_AsciiString& theKeyword)
345 {
346   // find message in the map
347   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
348   if (aDataMap.IsBound (theKeyword))
349     return aDataMap.Find (theKeyword);
350
351   // if not found, generate error message
352   // to minimize risk of data races when running concurrently, set the static variables
353   // only if they are empty; this gives a possibility to enforce calling this method
354   // upfront to initialize these variables and only read access them afterwards. However
355   // theKeyword is no longer appended. aDefPrefix remained unchanged to not break some
356   // logs which might expect the previous value
357   static const TCollection_ExtendedString aDefPrefix ("Unknown message invoked with the keyword");
358   static const TCollection_AsciiString aPrefixCode ("Message_Msg_BadKeyword");
359   static TCollection_ExtendedString aFailureMessage;
360   if (aFailureMessage.Length() == 0) {
361      if (aDataMap.IsBound (aPrefixCode))
362        aFailureMessage = aDataMap.Find (aPrefixCode);
363      else
364        aFailureMessage = aDefPrefix;
365   }
366   return aFailureMessage;
367 }