1 // Created on: 2001-04-26
2 // Created by: OCC Team
3 // Copyright (c) 2001-2014 OPEN CASCADE SAS
5 // This file is part of Open CASCADE Technology software library.
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.
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
16 #include <Message_MsgFile.hxx>
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>
29 typedef NCollection_DataMap<TCollection_AsciiString,TCollection_ExtendedString> Message_DataMapOfExtendedString;
31 static Message_DataMapOfExtendedString& msgsDataMap ()
33 static Message_DataMapOfExtendedString aDataMap;
37 // mutex used to prevent concurrent access to message registry
38 static Standard_Mutex theMutex;
42 MsgFile_WaitingKeyword,
43 MsgFile_WaitingMessage,
44 MsgFile_WaitingMoreMessage,
48 //=======================================================================
49 //function : Message_MsgFile
50 //purpose : Load file from given directories
51 // theDirName may be represented as list: "/dirA/dirB /dirA/dirC"
52 //=======================================================================
54 Standard_Boolean Message_MsgFile::Load (const Standard_CString theDirName,
55 const Standard_CString theFileName)
57 if ( ! theDirName || ! theFileName ) return Standard_False;
59 Standard_Boolean ret = Standard_True;
60 TCollection_AsciiString aDirList (theDirName);
61 // Try to load from all consecutive directories in list
64 TCollection_AsciiString aFileName = aDirList.Token (" \t\n", i);
65 if (aFileName.IsEmpty()) break;
71 aFileName += theFileName;
72 if ( ! LoadFile (aFileName.ToCString()) )
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 //=======================================================================
85 template <typename CharType> struct TCollection_String;
86 template <> struct TCollection_String <Standard_Character> { typedef TCollection_AsciiString type; };
87 template <> struct TCollection_String <Standard_ExtCharacter> { typedef TCollection_ExtendedString type; };
89 template <class CharType> static inline Standard_Boolean
90 getString (CharType *& thePtr,
91 TCollection_ExtendedString& theString,
92 Standard_Integer& theLeftSpaces)
94 CharType * anEndPtr = thePtr;
96 Standard_Integer aLeftSpaces;
100 // Skip whitespaces in the beginning of the string
105 CharType aChar = * aPtr;
106 if (aChar == ' ') aLeftSpaces++;
107 else if (aChar == '\t') aLeftSpaces += 8;
108 else if (aChar == '\r' || * aPtr == '\n') aLeftSpaces = 0;
113 // Find the end of the string
114 for (anEndPtr = aPtr; * anEndPtr; anEndPtr++)
115 if (anEndPtr[0] == '\n')
117 if (anEndPtr[-1] == '\r') anEndPtr--;
121 } while (aPtr[0] == '!');
124 if (aPtr == anEndPtr) return Standard_False;
128 theString = typename TCollection_String<CharType>::type (aPtr);
129 theLeftSpaces = aLeftSpaces;
130 return Standard_True;
133 //=======================================================================
134 //function : loadFile
135 //purpose : Static function, fills the DataMap of Messages from Ascii or Unicode
136 //Called : from LoadFile()
137 //=======================================================================
139 template <class _Char> static inline Standard_Boolean loadFile (_Char * theBuffer)
141 TCollection_AsciiString aKeyword;
142 TCollection_ExtendedString aMessage, aString;
143 LoadingState aState = MsgFile_WaitingKeyword;
144 _Char * sCurrentString = theBuffer;
145 Standard_Integer aLeftSpaces=0, aFirstLeftSpaces = 0;
147 // Take strings one-by-one; comments already screened
148 while (::getString (sCurrentString, aString, aLeftSpaces))
150 Standard_Boolean isKeyword = (aString.Value(1) == '.');
153 case MsgFile_WaitingMoreMessage:
155 Message_MsgFile::AddMsg (aKeyword, aMessage); // terminate the previous one
156 // Pass from here to 'case MsgFile_WaitingKeyword'
159 // Add another line to the message already in the buffer 'aMessage'
161 aLeftSpaces -= aFirstLeftSpaces;
162 if (aLeftSpaces > 0) aMessage += TCollection_ExtendedString (aLeftSpaces, ' ');
167 case MsgFile_WaitingMessage:
168 if (isKeyword == Standard_False)
171 aFirstLeftSpaces = aLeftSpaces; // remember the starting position
172 aState = MsgFile_WaitingMoreMessage;
175 // Pass from here to 'case MsgFile_WaitingKeyword'
177 case MsgFile_WaitingKeyword:
180 // remove the first dot character and all subsequent spaces + right-trim
181 aKeyword = TCollection_AsciiString (aString.Split(1));
182 aKeyword.LeftAdjust();
183 aKeyword.RightAdjust();
184 aState = MsgFile_WaitingMessage;
191 // Process the last string still remaining in the buffer
192 if (aState == MsgFile_WaitingMoreMessage)
193 Message_MsgFile::AddMsg (aKeyword, aMessage);
194 return Standard_True;
197 //=======================================================================
198 //function : GetFileSize
200 //=======================================================================
202 static Standard_Integer GetFileSize (FILE *theFile)
204 if ( !theFile ) return -1;
206 // get real file size
207 long nRealFileSize = 0;
208 if ( fseek(theFile, 0, SEEK_END) != 0 ) return -1;
209 nRealFileSize = ftell(theFile);
210 if ( fseek(theFile, 0, SEEK_SET) != 0 ) return -1;
212 return (Standard_Integer) nRealFileSize;
215 //=======================================================================
216 //function : LoadFile
217 //purpose : Load the list of messages from a file
218 //=======================================================================
220 Standard_Boolean Message_MsgFile::LoadFile (const Standard_CString theFileName)
222 if (theFileName == NULL || * theFileName == '\0') return Standard_False;
225 FILE *anMsgFile = OSD_OpenFile(theFileName,"rb");
227 return Standard_False;
229 const Standard_Integer aFileSize = GetFileSize (anMsgFile);
230 NCollection_Buffer aBuffer(NCollection_BaseAllocator::CommonBaseAllocator());
231 if (aFileSize <= 0 || !aBuffer.Allocate(aFileSize + 2))
234 return Standard_False;
237 char* anMsgBuffer = reinterpret_cast<char*>(aBuffer.ChangeData());
238 const Standard_Integer nbRead =
239 static_cast<Standard_Integer>( fread(anMsgBuffer, 1, aFileSize, anMsgFile) );
242 if (nbRead != aFileSize)
243 return Standard_False;
245 anMsgBuffer[aFileSize] = 0;
246 anMsgBuffer[aFileSize + 1] = 0;
248 // Read the messages in the file and append them to the global DataMap
249 Standard_Boolean isLittleEndian = (anMsgBuffer[0] == '\xff' && anMsgBuffer[1] == '\xfe');
250 Standard_Boolean isBigEndian = (anMsgBuffer[0] == '\xfe' && anMsgBuffer[1] == '\xff');
251 if ( isLittleEndian || isBigEndian )
253 Standard_ExtCharacter* aUnicodeBuffer =
254 reinterpret_cast<Standard_ExtCharacter*>(&anMsgBuffer[2]);
255 // Convert Unicode representation to order adopted on current platform
256 #if defined(__sparc) && defined(__sun)
257 if ( isLittleEndian )
262 // Reverse the bytes throughout the buffer
263 const Standard_ExtCharacter* const anEnd =
264 reinterpret_cast<const Standard_ExtCharacter*>(&anMsgBuffer[aFileSize]);
266 for (Standard_ExtCharacter* aPtr = aUnicodeBuffer; aPtr < anEnd; aPtr++)
268 unsigned short aWord = *aPtr;
269 *aPtr = (aWord & 0x00ff) << 8 | (aWord & 0xff00) >> 8;
272 return ::loadFile (aUnicodeBuffer);
275 return ::loadFile (anMsgBuffer);
278 //=======================================================================
279 //function : LoadFromEnv
281 //=======================================================================
282 Standard_Boolean Message_MsgFile::LoadFromEnv (const Standard_CString theEnvName,
283 const Standard_CString theFileName,
284 const Standard_CString theLangExt)
286 TCollection_AsciiString aLangExt (theLangExt != NULL ? theLangExt : "");
287 if (aLangExt.IsEmpty())
289 OSD_Environment aLangEnv ("CSF_LANGUAGE");
290 aLangExt = aLangEnv.Value();
291 if (aLangExt.IsEmpty())
297 TCollection_AsciiString aFilePath (theFileName);
298 if (theEnvName != NULL
299 && theEnvName[0] != '\0')
301 OSD_Environment aNameEnv (theEnvName);
302 TCollection_AsciiString aNameEnvStr = aNameEnv.Value();
303 if (!aNameEnvStr.IsEmpty())
305 if (aNameEnvStr.Value (aNameEnvStr.Length()) != '/')
307 aFilePath.Insert (1, '/');
309 aFilePath.Insert (1, aNameEnvStr);
313 if (aLangExt.Value (1) != '.')
315 aFilePath.AssignCat ('.');
317 aFilePath.AssignCat (aLangExt);
319 return Message_MsgFile::LoadFile (aFilePath.ToCString());
322 //=======================================================================
323 //function : LoadFromString
325 //=======================================================================
326 Standard_Boolean Message_MsgFile::LoadFromString (const Standard_CString theContent,
327 const Standard_Integer theLength)
329 Standard_Integer aStringSize = theLength >= 0 ? theLength : (Standard_Integer )strlen (theContent);
330 NCollection_Buffer aBuffer (NCollection_BaseAllocator::CommonBaseAllocator());
331 if (aStringSize <= 0 || !aBuffer.Allocate (aStringSize + 2))
333 return Standard_False;
336 memcpy (aBuffer.ChangeData(), theContent, aStringSize);
337 aBuffer.ChangeData()[aStringSize + 0] = '\0';
338 aBuffer.ChangeData()[aStringSize + 1] = '\0';
339 char* anMsgBuffer = reinterpret_cast<char*>(aBuffer.ChangeData());
340 return ::loadFile (anMsgBuffer);
343 //=======================================================================
345 //purpose : Add one message to the global table. Fails if the same keyword
346 // already exists in the table
347 //=======================================================================
349 Standard_Boolean Message_MsgFile::AddMsg (const TCollection_AsciiString& theKeyword,
350 const TCollection_ExtendedString& theMessage)
352 Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
354 Standard_Mutex::Sentry aSentry (theMutex);
355 aDataMap.Bind (theKeyword, theMessage);
356 return Standard_True;
359 //=======================================================================
361 //purpose : retrieve the message previously defined for the given keyword
362 //=======================================================================
364 const TCollection_ExtendedString &Message_MsgFile::Msg (const Standard_CString theKeyword)
366 TCollection_AsciiString aKey((char*)theKeyword);
370 //=======================================================================
373 //=======================================================================
375 Standard_Boolean Message_MsgFile::HasMsg (const TCollection_AsciiString& theKeyword)
377 Standard_Mutex::Sentry aSentry (theMutex);
378 return ::msgsDataMap().IsBound (theKeyword);
381 //=======================================================================
383 //purpose : retrieve the message previously defined for the given keyword
384 //=======================================================================
386 const TCollection_ExtendedString &Message_MsgFile::Msg (const TCollection_AsciiString& theKeyword)
388 // find message in the map
389 Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
390 Standard_Mutex::Sentry aSentry (theMutex);
392 // if message is not found, generate error message and add it to the map to minimize overhead
393 // on consequent calls with the same key
394 if (! aDataMap.IsBound(theKeyword))
396 // text of the error message can be itself defined in the map
397 static const TCollection_AsciiString aPrefixCode("Message_Msg_BadKeyword");
398 static const TCollection_ExtendedString aDefPrefix("Unknown message invoked with the keyword ");
399 TCollection_AsciiString aErrorMessage = (aDataMap.IsBound(aPrefixCode) ? aDataMap(aPrefixCode) : aDefPrefix);
400 aErrorMessage += theKeyword;
401 aDataMap.Bind (theKeyword, aErrorMessage); // do not use AddMsg() here to avoid mutex deadlock
404 return aDataMap (theKeyword);