0024023: Revamp the OCCT Handle -- general
[occt.git] / src / Message / Message_MsgFile.cxx
1 // Created on: 2001-04-26
2 // Created by: OCC Team
3 // Copyright (c) 2001-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <Message_MsgFile.hxx>
17
18 #include <NCollection_Buffer.hxx>
19 #include <NCollection_DataMap.hxx>
20 #include <OSD_Environment.hxx>
21 #include <TCollection_AsciiString.hxx>
22 #include <TCollection_ExtendedString.hxx>
23 #include <Standard_Mutex.hxx>
24 #include <OSD_OpenFile.hxx>
25
26 #include <stdlib.h>
27 #include <stdio.h>
28
29 typedef NCollection_DataMap<TCollection_AsciiString,TCollection_ExtendedString> Message_DataMapOfExtendedString;
30
31 static Message_DataMapOfExtendedString& msgsDataMap ()
32 {
33   static Message_DataMapOfExtendedString aDataMap;
34   return aDataMap;
35 }
36
37 // mutex used to prevent concurrent access to message registry
38 static Standard_Mutex theMutex;
39
40 typedef enum
41 {
42   MsgFile_WaitingKeyword,
43   MsgFile_WaitingMessage,
44   MsgFile_WaitingMoreMessage,
45   MsgFile_Indefinite
46 } LoadingState;
47
48 //=======================================================================
49 //function : Message_MsgFile
50 //purpose  : Load file from given directories
51 //           theDirName may be represented as list: "/dirA/dirB /dirA/dirC"
52 //=======================================================================
53
54 Standard_Boolean Message_MsgFile::Load (const Standard_CString theDirName,
55                                         const Standard_CString theFileName)
56 {
57   if ( ! theDirName || ! theFileName ) return Standard_False;
58
59   Standard_Boolean ret = Standard_True;
60   TCollection_AsciiString aDirList (theDirName);
61   //  Try to load from all consecutive directories in list
62   for (int i = 1;; i++)
63   {
64     TCollection_AsciiString aFileName = aDirList.Token (" \t\n", i);
65     if (aFileName.IsEmpty()) break;
66 #ifdef WNT
67     aFileName += '\\';
68 #else
69     aFileName += '/';
70 #endif
71     aFileName += theFileName;
72     if ( ! LoadFile (aFileName.ToCString()) ) 
73       ret = Standard_False;
74   }
75   return ret;
76 }
77
78 //=======================================================================
79 //function : getString
80 //purpose  : Takes a TCollection_ExtendedString from Ascii or Unicode
81 //           Strings are left-trimmed; those beginning with '!' are omitted
82 //Called   : from loadFile()
83 //=======================================================================
84
85 template <class _Char> static inline Standard_Boolean
86 getString (_Char *&                     thePtr,
87            TCollection_ExtendedString&  theString,
88            Standard_Integer&            theLeftSpaces)
89 {
90   _Char * anEndPtr = thePtr;
91   _Char * aPtr;
92   Standard_Integer aLeftSpaces;
93
94   do 
95   {
96     //    Skip whitespaces in the beginning of the string
97     aPtr = anEndPtr;
98     aLeftSpaces = 0;
99     for (;;)
100     {
101       _Char aChar = * aPtr;
102       if      (aChar == ' ')  aLeftSpaces++;
103       else if (aChar == '\t') aLeftSpaces += 8;
104       else if (aChar == '\r' || * aPtr == '\n') aLeftSpaces = 0;
105       else break;
106       aPtr++;
107     }
108
109     //    Find the end of the string
110     for (anEndPtr = aPtr; * anEndPtr; anEndPtr++)
111       if (anEndPtr[0] == '\n')
112       {
113         if (anEndPtr[-1] == '\r') anEndPtr--;
114         break;
115       }
116
117   } while (aPtr[0] == '!');
118
119   //    form the result
120   if (aPtr == anEndPtr) return Standard_False;
121   thePtr = anEndPtr;
122   if (*thePtr)
123     *thePtr++ = '\0';
124   theString = TCollection_ExtendedString (aPtr);
125   theLeftSpaces = aLeftSpaces;
126   return Standard_True;
127 }
128
129 //=======================================================================
130 //function : loadFile
131 //purpose  : Static function, fills the DataMap of Messages from Ascii or Unicode
132 //Called   : from LoadFile()
133 //=======================================================================
134
135 template <class _Char> static inline Standard_Boolean loadFile (_Char * theBuffer)
136 {
137   TCollection_AsciiString               aKeyword;
138   TCollection_ExtendedString            aMessage, aString;
139   LoadingState                          aState = MsgFile_WaitingKeyword;
140   _Char                                 * sCurrentString = theBuffer;
141   Standard_Integer                      aLeftSpaces=0, aFirstLeftSpaces = 0;
142
143   //    Take strings one-by-one; comments already screened
144   while (::getString (sCurrentString, aString, aLeftSpaces))
145   {
146     Standard_Boolean isKeyword = (aString.Value(1) == '.');
147     switch (aState)
148     {
149     case MsgFile_WaitingMoreMessage:
150       if (isKeyword)
151         Message_MsgFile::AddMsg (aKeyword, aMessage); // terminate the previous one
152         //      Pass from here to 'case MsgFile_WaitingKeyword'
153       else
154       {
155         //      Add another line to the message already in the buffer 'aMessage'
156         aMessage += '\n';
157         aLeftSpaces -= aFirstLeftSpaces;
158         if (aLeftSpaces > 0) aMessage += TCollection_ExtendedString (aLeftSpaces, ' ');
159         aMessage += aString;
160         break;
161       }
162     case MsgFile_WaitingMessage:
163       if (isKeyword == Standard_False)
164       {
165         aMessage         = aString;
166         aFirstLeftSpaces = aLeftSpaces;         // remember the starting position
167         aState = MsgFile_WaitingMoreMessage;
168         break;
169       }
170       //      Pass from here to 'case MsgFile_WaitingKeyword'
171     case MsgFile_WaitingKeyword:
172       if (isKeyword)
173       {
174         // remove the first dot character and all subsequent spaces + right-trim
175         aKeyword = TCollection_AsciiString (aString.Split(1));
176         aKeyword.LeftAdjust();
177         aKeyword.RightAdjust();
178         aState = MsgFile_WaitingMessage;
179       }
180       break;
181     default:
182       break;
183     }
184   }
185   //    Process the last string still remaining in the buffer
186   if (aState == MsgFile_WaitingMoreMessage)
187     Message_MsgFile::AddMsg (aKeyword, aMessage);
188   return Standard_True;
189 }
190
191 //=======================================================================
192 //function : GetFileSize
193 //purpose  : 
194 //=======================================================================
195
196 static Standard_Integer GetFileSize (FILE *theFile)
197 {
198   if ( !theFile ) return -1;
199
200   // get real file size
201   long nRealFileSize = 0;
202   if ( fseek(theFile, 0, SEEK_END) != 0 ) return -1;
203   nRealFileSize = ftell(theFile);
204   if ( fseek(theFile, 0, SEEK_SET) != 0 ) return -1;
205
206   return (Standard_Integer) nRealFileSize;
207 }
208
209 //=======================================================================
210 //function : LoadFile
211 //purpose  : Load the list of messages from a file
212 //=======================================================================
213
214 Standard_Boolean Message_MsgFile::LoadFile (const Standard_CString theFileName)
215 {
216   if (theFileName == NULL || * theFileName == '\0') return Standard_False;
217
218   //    Open the file
219   FILE *anMsgFile = OSD_OpenFile(theFileName,"rb");
220   if (!anMsgFile)
221     return Standard_False;
222
223   const Standard_Integer aFileSize = GetFileSize (anMsgFile);
224   NCollection_Buffer aBuffer(NCollection_BaseAllocator::CommonBaseAllocator());
225   if (aFileSize <= 0 || !aBuffer.Allocate(aFileSize + 2))
226   {
227     fclose (anMsgFile);
228     return Standard_False;
229   }
230
231   char* anMsgBuffer = reinterpret_cast<char*>(aBuffer.ChangeData());
232   const Standard_Integer nbRead =
233     static_cast<Standard_Integer>( fread(anMsgBuffer, 1, aFileSize, anMsgFile) );
234
235   fclose (anMsgFile);
236   if (nbRead != aFileSize)
237     return Standard_False;
238
239   anMsgBuffer[aFileSize] = 0;
240   anMsgBuffer[aFileSize + 1] = 0;
241
242   // Read the messages in the file and append them to the global DataMap
243   Standard_Boolean isLittleEndian = (anMsgBuffer[0] == '\xff' && anMsgBuffer[1] == '\xfe');
244   Standard_Boolean isBigEndian    = (anMsgBuffer[0] == '\xfe' && anMsgBuffer[1] == '\xff');
245   if ( isLittleEndian || isBigEndian )
246   {
247     Standard_ExtCharacter* aUnicodeBuffer =
248       reinterpret_cast<Standard_ExtCharacter*>(&anMsgBuffer[2]);
249     // Convert Unicode representation to order adopted on current platform
250 #if defined(__sparc) && defined(__sun)
251     if ( isLittleEndian ) 
252 #else
253     if ( isBigEndian ) 
254 #endif
255     {
256       // Reverse the bytes throughout the buffer
257       const Standard_ExtCharacter* const anEnd =
258         reinterpret_cast<const Standard_ExtCharacter* const>(&anMsgBuffer[aFileSize]);
259
260       for (Standard_ExtCharacter* aPtr = aUnicodeBuffer; aPtr < anEnd; aPtr++)
261       {
262         unsigned short aWord = *aPtr;
263         *aPtr = (aWord & 0x00ff) << 8 | (aWord & 0xff00) >> 8;
264       }
265     }
266     return ::loadFile (aUnicodeBuffer);
267   }
268   else
269     return ::loadFile (anMsgBuffer);
270 }
271
272 //=======================================================================
273 //function : LoadFromEnv
274 //purpose  : 
275 //=======================================================================
276 void  Message_MsgFile::LoadFromEnv
277   (const Standard_CString envname,
278    const Standard_CString filename,
279    const Standard_CString ext)
280 {
281   Standard_CString extname = ext;
282   TCollection_AsciiString extstr;
283   if (!extname || extname[0] == '\0') {
284     OSD_Environment extenv("CSF_LANGUAGE");
285     extstr  = extenv.Value();
286     extname = extstr.ToCString();
287   }
288   if (!extname || extname[0] == '\0') extname = "us";
289
290   TCollection_AsciiString filestr(filename);
291   if (envname && envname[0] != '\0') {
292     OSD_Environment envenv(envname);
293     TCollection_AsciiString envstr  = envenv.Value();
294     if (envstr.Length() > 0) {
295       if (envstr.Value(envstr.Length()) != '/') filestr.Insert (1,'/');
296       filestr.Insert (1,envstr.ToCString());
297     }
298   }
299   if (extname[0] != '.') filestr.AssignCat ('.');
300   filestr.AssignCat (extname);
301
302   Message_MsgFile::LoadFile (filestr.ToCString());
303 }
304
305 //=======================================================================
306 //function : AddMsg
307 //purpose  : Add one message to the global table. Fails if the same keyword
308 //           already exists in the table
309 //=======================================================================
310
311 Standard_Boolean Message_MsgFile::AddMsg (const TCollection_AsciiString& theKeyword,
312                                           const TCollection_ExtendedString&  theMessage)
313 {
314   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
315
316   Standard_Mutex::Sentry aSentry (theMutex);
317   aDataMap.Bind (theKeyword, theMessage);
318   return Standard_True;
319 }
320
321 //=======================================================================
322 //function : getMsg
323 //purpose  : retrieve the message previously defined for the given keyword
324 //=======================================================================
325
326 const TCollection_ExtendedString &Message_MsgFile::Msg (const Standard_CString theKeyword)
327 {
328   TCollection_AsciiString aKey((char*)theKeyword);
329   return Msg (aKey);
330
331
332 //=======================================================================
333 //function : HasMsg
334 //purpose  : 
335 //=======================================================================
336
337 Standard_Boolean Message_MsgFile::HasMsg (const TCollection_AsciiString& theKeyword)
338 {
339   Standard_Mutex::Sentry aSentry (theMutex);
340   return ::msgsDataMap().IsBound (theKeyword);
341 }
342
343 //=======================================================================
344 //function : Msg
345 //purpose  : retrieve the message previously defined for the given keyword
346 //=======================================================================
347
348 const TCollection_ExtendedString &Message_MsgFile::Msg (const TCollection_AsciiString& theKeyword)
349 {
350   // find message in the map
351   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
352   Standard_Mutex::Sentry aSentry (theMutex);
353
354   // if message is not found, generate error message and add it to the map to minimize overhead
355   // on consequent calls with the same key
356   if (! aDataMap.IsBound(theKeyword))
357   {
358     // text of the error message can be itself defined in the map
359     static const TCollection_AsciiString aPrefixCode("Message_Msg_BadKeyword");
360     static const TCollection_ExtendedString aDefPrefix("Unknown message invoked with the keyword ");
361     TCollection_AsciiString aErrorMessage = (aDataMap.IsBound(aPrefixCode) ? aDataMap(aPrefixCode) : aDefPrefix);
362     aErrorMessage += theKeyword;
363     aDataMap.Bind (theKeyword, aErrorMessage); // do not use AddMsg() here to avoid mutex deadlock
364   }
365
366   return aDataMap (theKeyword);
367 }