Backed out changeset db55605b2a4c (relanding bug 121341)
[wine-gecko.git] / xpcom / ds / nsPersistentProperties.cpp
blob09acc56e98e3a3fd81c748784537d9b88df665bc
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsID.h"
40 #include "nsCRT.h"
41 #include "nsReadableUtils.h"
42 #include "nsIInputStream.h"
43 #include "nsUnicharInputStream.h"
44 #include "pratom.h"
45 #include "nsEnumeratorUtils.h"
46 #include "nsReadableUtils.h"
47 #include "nsPrintfCString.h"
48 #include "nsDependentString.h"
50 #define PL_ARENA_CONST_ALIGN_MASK 3
51 #include "nsPersistentProperties.h"
52 #include "nsIProperties.h"
53 #include "nsISupportsArray.h"
54 #include "nsProperties.h"
56 struct PropertyTableEntry : public PLDHashEntryHdr
58 // both of these are arena-allocated
59 const char *mKey;
60 const PRUnichar *mValue;
63 static PRUnichar*
64 ArenaStrdup(const nsAFlatString& aString, PLArenaPool* aArena)
66 void *mem;
67 // add one to include the null terminator
68 PRInt32 len = (aString.Length()+1) * sizeof(PRUnichar);
69 PL_ARENA_ALLOCATE(mem, aArena, len);
70 NS_ASSERTION(mem, "Couldn't allocate space!\n");
71 if (mem) {
72 memcpy(mem, aString.get(), len);
74 return static_cast<PRUnichar*>(mem);
77 static char*
78 ArenaStrdup(const nsAFlatCString& aString, PLArenaPool* aArena)
80 void *mem;
81 // add one to include the null terminator
82 PRInt32 len = (aString.Length()+1) * sizeof(char);
83 PL_ARENA_ALLOCATE(mem, aArena, len);
84 NS_ASSERTION(mem, "Couldn't allocate space!\n");
85 if (mem)
86 memcpy(mem, aString.get(), len);
87 return static_cast<char*>(mem);
90 static const struct PLDHashTableOps property_HashTableOps = {
91 PL_DHashAllocTable,
92 PL_DHashFreeTable,
93 PL_DHashStringKey,
94 PL_DHashMatchStringKey,
95 PL_DHashMoveEntryStub,
96 PL_DHashClearEntryStub,
97 PL_DHashFinalizeStub,
98 nsnull,
102 // parser stuff
104 enum EParserState {
105 eParserState_AwaitingKey,
106 eParserState_Key,
107 eParserState_AwaitingValue,
108 eParserState_Value,
109 eParserState_Comment
112 enum EParserSpecial {
113 eParserSpecial_None, // not parsing a special character
114 eParserSpecial_Escaped, // awaiting a special character
115 eParserSpecial_Unicode // parsing a \Uxxx value
118 class nsPropertiesParser
120 public:
121 nsPropertiesParser(nsIPersistentProperties* aProps) :
122 mHaveMultiLine(PR_FALSE), mState(eParserState_AwaitingKey),
123 mProps(aProps) {}
125 void FinishValueState(nsAString& aOldValue) {
126 static const char trimThese[] = " \t";
127 mKey.Trim(trimThese, PR_FALSE, PR_TRUE);
129 // This is really ugly hack but it should be fast
130 PRUnichar backup_char;
131 if (mMinLength)
133 backup_char = mValue[mMinLength-1];
134 mValue.SetCharAt('x', mMinLength-1);
136 mValue.Trim(trimThese, PR_FALSE, PR_TRUE);
137 if (mMinLength)
138 mValue.SetCharAt(backup_char, mMinLength-1);
140 mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
141 mSpecialState = eParserSpecial_None;
142 WaitForKey();
145 EParserState GetState() { return mState; }
147 static NS_METHOD SegmentWriter(nsIUnicharInputStream* aStream,
148 void* aClosure,
149 const PRUnichar *aFromSegment,
150 PRUint32 aToOffset,
151 PRUint32 aCount,
152 PRUint32 *aWriteCount);
154 nsresult ParseBuffer(const PRUnichar* aBuffer, PRUint32 aBufferLength);
156 private:
157 PRBool ParseValueCharacter(
158 PRUnichar c, // character that is just being parsed
159 const PRUnichar* cur, // pointer to character c in the buffer
160 const PRUnichar* &tokenStart, // string copying is done in blocks as big as
161 // possible, tokenStart points to the beginning
162 // of this block
163 nsAString& oldValue); // when duplicate property is found, new value
164 // is stored into hashtable and the old one is
165 // placed in this variable
167 void WaitForKey() {
168 mState = eParserState_AwaitingKey;
171 void EnterKeyState() {
172 mKey.Truncate();
173 mState = eParserState_Key;
176 void WaitForValue() {
177 mState = eParserState_AwaitingValue;
180 void EnterValueState() {
181 mValue.Truncate();
182 mMinLength = 0;
183 mState = eParserState_Value;
184 mSpecialState = eParserSpecial_None;
187 void EnterCommentState() {
188 mState = eParserState_Comment;
191 nsAutoString mKey;
192 nsAutoString mValue;
194 PRUint32 mUnicodeValuesRead; // should be 4!
195 PRUnichar mUnicodeValue; // currently parsed unicode value
196 PRBool mHaveMultiLine; // is TRUE when last processed characters form
197 // any of following sequences:
198 // - "\\\r"
199 // - "\\\n"
200 // - "\\\r\n"
201 // - any sequence above followed by any
202 // combination of ' ' and '\t'
203 PRBool mMultiLineCanSkipN; // TRUE if "\\\r" was detected
204 PRUint32 mMinLength; // limit right trimming at the end to not trim
205 // escaped whitespaces
206 EParserState mState;
207 // if we see a '\' then we enter this special state
208 EParserSpecial mSpecialState;
209 nsIPersistentProperties* mProps;
212 inline PRBool IsWhiteSpace(PRUnichar aChar)
214 return (aChar == ' ') || (aChar == '\t') ||
215 (aChar == '\r') || (aChar == '\n');
218 inline PRBool IsEOL(PRUnichar aChar)
220 return (aChar == '\r') || (aChar == '\n');
224 PRBool nsPropertiesParser::ParseValueCharacter(
225 PRUnichar c, const PRUnichar* cur, const PRUnichar* &tokenStart,
226 nsAString& oldValue)
228 switch (mSpecialState) {
230 // the normal state - look for special characters
231 case eParserSpecial_None:
232 switch (c) {
233 case '\\':
234 if (mHaveMultiLine)
235 // there is nothing to append to mValue yet
236 mHaveMultiLine = PR_FALSE;
237 else
238 mValue += Substring(tokenStart, cur);
240 mSpecialState = eParserSpecial_Escaped;
241 break;
243 case '\n':
244 // if we detected multiline and got only "\\\r" ignore next "\n" if any
245 if (mHaveMultiLine && mMultiLineCanSkipN) {
246 // but don't allow another '\n' to be skipped
247 mMultiLineCanSkipN = PR_FALSE;
248 // Now there is nothing to append to the mValue since we are skipping
249 // whitespaces at the beginning of the new line of the multiline
250 // property. Set tokenStart properly to ensure that nothing is appended
251 // if we find regular line-end or the end of the buffer.
252 tokenStart = cur+1;
253 break;
255 // no break
257 case '\r':
258 // we're done! We have a key and value
259 mValue += Substring(tokenStart, cur);
260 FinishValueState(oldValue);
261 mHaveMultiLine = PR_FALSE;
262 break;
264 default:
265 // there is nothing to do with normal characters,
266 // but handle multilines correctly
267 if (mHaveMultiLine) {
268 if (c == ' ' || c == '\t') {
269 // don't allow another '\n' to be skipped
270 mMultiLineCanSkipN = PR_FALSE;
271 // Now there is nothing to append to the mValue since we are skipping
272 // whitespaces at the beginning of the new line of the multiline
273 // property. Set tokenStart properly to ensure that nothing is appended
274 // if we find regular line-end or the end of the buffer.
275 tokenStart = cur+1;
276 break;
278 mHaveMultiLine = PR_FALSE;
279 tokenStart = cur;
281 break; // from switch on (c)
283 break; // from switch on (mSpecialState)
285 // saw a \ character, so parse the character after that
286 case eParserSpecial_Escaped:
287 // probably want to start parsing at the next token
288 // other characters, like 'u' might override this
289 tokenStart = cur+1;
290 mSpecialState = eParserSpecial_None;
292 switch (c) {
294 // the easy characters - \t, \n, and so forth
295 case 't':
296 mValue += PRUnichar('\t');
297 mMinLength = mValue.Length();
298 break;
299 case 'n':
300 mValue += PRUnichar('\n');
301 mMinLength = mValue.Length();
302 break;
303 case 'r':
304 mValue += PRUnichar('\r');
305 mMinLength = mValue.Length();
306 break;
307 case '\\':
308 mValue += PRUnichar('\\');
309 break;
311 // switch to unicode mode!
312 case 'u':
313 case 'U':
314 mSpecialState = eParserSpecial_Unicode;
315 mUnicodeValuesRead = 0;
316 mUnicodeValue = 0;
317 break;
319 // a \ immediately followed by a newline means we're going multiline
320 case '\r':
321 case '\n':
322 mHaveMultiLine = PR_TRUE;
323 mMultiLineCanSkipN = (c == '\r');
324 mSpecialState = eParserSpecial_None;
325 break;
327 default:
328 // don't recognize the character, so just append it
329 mValue += c;
330 break;
332 break;
334 // we're in the middle of parsing a 4-character unicode value
335 // like \u5f39
336 case eParserSpecial_Unicode:
338 if(('0' <= c) && (c <= '9'))
339 mUnicodeValue =
340 (mUnicodeValue << 4) | (c - '0');
341 else if(('a' <= c) && (c <= 'f'))
342 mUnicodeValue =
343 (mUnicodeValue << 4) | (c - 'a' + 0x0a);
344 else if(('A' <= c) && (c <= 'F'))
345 mUnicodeValue =
346 (mUnicodeValue << 4) | (c - 'A' + 0x0a);
347 else {
348 // non-hex character. Append what we have, and move on.
349 mValue += mUnicodeValue;
350 mMinLength = mValue.Length();
351 mSpecialState = eParserSpecial_None;
353 // leave tokenStart at this unknown character, so it gets appended
354 tokenStart = cur;
356 // ensure parsing this non-hex character again
357 return PR_FALSE;
360 if (++mUnicodeValuesRead >= 4) {
361 tokenStart = cur+1;
362 mSpecialState = eParserSpecial_None;
363 mValue += mUnicodeValue;
364 mMinLength = mValue.Length();
367 break;
370 return PR_TRUE;
373 NS_METHOD nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
374 void* aClosure,
375 const PRUnichar *aFromSegment,
376 PRUint32 aToOffset,
377 PRUint32 aCount,
378 PRUint32 *aWriteCount)
380 nsPropertiesParser *parser =
381 static_cast<nsPropertiesParser *>(aClosure);
383 parser->ParseBuffer(aFromSegment, aCount);
385 *aWriteCount = aCount;
386 return NS_OK;
389 nsresult nsPropertiesParser::ParseBuffer(const PRUnichar* aBuffer,
390 PRUint32 aBufferLength)
392 const PRUnichar* cur = aBuffer;
393 const PRUnichar* end = aBuffer + aBufferLength;
395 // points to the start/end of the current key or value
396 const PRUnichar* tokenStart = nsnull;
398 // if we're in the middle of parsing a key or value, make sure
399 // the current token points to the beginning of the current buffer
400 if (mState == eParserState_Key ||
401 mState == eParserState_Value) {
402 tokenStart = aBuffer;
405 nsAutoString oldValue;
407 while (cur != end) {
409 PRUnichar c = *cur;
411 switch (mState) {
412 case eParserState_AwaitingKey:
413 if (c == '#' || c == '!')
414 EnterCommentState();
416 else if (!IsWhiteSpace(c)) {
417 // not a comment, not whitespace, we must have found a key!
418 EnterKeyState();
419 tokenStart = cur;
421 break;
423 case eParserState_Key:
424 if (c == '=' || c == ':') {
425 mKey += Substring(tokenStart, cur);
426 WaitForValue();
428 break;
430 case eParserState_AwaitingValue:
431 if (IsEOL(c)) {
432 // no value at all! mimic the normal value-ending
433 EnterValueState();
434 FinishValueState(oldValue);
437 // ignore white space leading up to the value
438 else if (!IsWhiteSpace(c)) {
439 tokenStart = cur;
440 EnterValueState();
442 // make sure to handle this first character
443 if (ParseValueCharacter(c, cur, tokenStart, oldValue))
444 cur++;
445 // If the character isn't consumed, don't do cur++ and parse
446 // the character again. This can happen f.e. for char 'X' in sequence
447 // "\u00X". This character can be control character and must be
448 // processed again.
449 continue;
451 break;
453 case eParserState_Value:
454 if (ParseValueCharacter(c, cur, tokenStart, oldValue))
455 cur++;
456 // See few lines above for reason of doing this
457 continue;
459 case eParserState_Comment:
460 // stay in this state till we hit EOL
461 if (c == '\r' || c== '\n')
462 WaitForKey();
463 break;
466 // finally, advance to the next character
467 cur++;
470 // if we're still parsing the value and are in eParserSpecial_None, then
471 // append whatever we have..
472 if (mState == eParserState_Value && tokenStart &&
473 mSpecialState == eParserSpecial_None) {
474 mValue += Substring(tokenStart, cur);
476 // if we're still parsing the key, then append whatever we have..
477 else if (mState == eParserState_Key && tokenStart) {
478 mKey += Substring(tokenStart, cur);
481 return NS_OK;
484 nsPersistentProperties::nsPersistentProperties()
485 : mIn(nsnull)
487 mSubclass = static_cast<nsIPersistentProperties*>(this);
488 mTable.ops = nsnull;
489 PL_INIT_ARENA_POOL(&mArena, "PersistentPropertyArena", 2048);
492 nsPersistentProperties::~nsPersistentProperties()
494 PL_FinishArenaPool(&mArena);
495 if (mTable.ops)
496 PL_DHashTableFinish(&mTable);
499 nsresult
500 nsPersistentProperties::Init()
502 if (!PL_DHashTableInit(&mTable, &property_HashTableOps, nsnull,
503 sizeof(PropertyTableEntry), 20)) {
504 mTable.ops = nsnull;
505 return NS_ERROR_OUT_OF_MEMORY;
507 return NS_OK;
510 NS_METHOD
511 nsPersistentProperties::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
513 if (aOuter)
514 return NS_ERROR_NO_AGGREGATION;
515 nsPersistentProperties* props = new nsPersistentProperties();
516 if (props == nsnull)
517 return NS_ERROR_OUT_OF_MEMORY;
519 NS_ADDREF(props);
520 nsresult rv = props->Init();
521 if (NS_SUCCEEDED(rv))
522 rv = props->QueryInterface(aIID, aResult);
524 NS_RELEASE(props);
525 return rv;
528 NS_IMPL_THREADSAFE_ISUPPORTS2(nsPersistentProperties, nsIPersistentProperties, nsIProperties)
530 NS_IMETHODIMP
531 nsPersistentProperties::Load(nsIInputStream *aIn)
533 nsresult rv = nsSimpleUnicharStreamFactory::GetInstance()->
534 CreateInstanceFromUTF8Stream(aIn, getter_AddRefs(mIn));
536 if (rv != NS_OK) {
537 NS_WARNING("Error creating UnicharInputStream");
538 return NS_ERROR_FAILURE;
541 nsPropertiesParser parser(mSubclass);
543 PRUint32 nProcessed;
544 // If this 4096 is changed to some other value, make sure to adjust
545 // the bug121341.properties test file accordingly.
546 while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter, &parser, 4096, &nProcessed)) &&
547 nProcessed != 0);
548 mIn = nsnull;
549 if (NS_FAILED(rv))
550 return rv;
552 // We may have an unprocessed value at this point
553 // if the last line did not have a proper line ending.
554 if (parser.GetState() == eParserState_Value) {
555 nsAutoString oldValue;
556 parser.FinishValueState(oldValue);
559 return NS_OK;
562 NS_IMETHODIMP
563 nsPersistentProperties::SetStringProperty(const nsACString& aKey,
564 const nsAString& aNewValue,
565 nsAString& aOldValue)
567 const nsAFlatCString& flatKey = PromiseFlatCString(aKey);
568 PropertyTableEntry *entry =
569 static_cast<PropertyTableEntry*>
570 (PL_DHashTableOperate(&mTable, flatKey.get(), PL_DHASH_ADD));
572 if (entry->mKey) {
573 aOldValue = entry->mValue;
574 NS_WARNING(nsPrintfCString(aKey.Length() + 30,
575 "the property %s already exists\n",
576 flatKey.get()).get());
579 entry->mKey = ArenaStrdup(flatKey, &mArena);
580 entry->mValue = ArenaStrdup(PromiseFlatString(aNewValue), &mArena);
582 return NS_OK;
585 NS_IMETHODIMP
586 nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader)
588 return NS_ERROR_NOT_IMPLEMENTED;
591 NS_IMETHODIMP
592 nsPersistentProperties::Subclass(nsIPersistentProperties* aSubclass)
594 if (aSubclass) {
595 mSubclass = aSubclass;
598 return NS_OK;
601 NS_IMETHODIMP
602 nsPersistentProperties::GetStringProperty(const nsACString& aKey,
603 nsAString& aValue)
605 const nsAFlatCString& flatKey = PromiseFlatCString(aKey);
607 PropertyTableEntry *entry =
608 static_cast<PropertyTableEntry*>
609 (PL_DHashTableOperate(&mTable, flatKey.get(), PL_DHASH_LOOKUP));
611 if (PL_DHASH_ENTRY_IS_FREE(entry))
612 return NS_ERROR_FAILURE;
614 aValue = entry->mValue;
615 return NS_OK;
618 PR_STATIC_CALLBACK(PLDHashOperator)
619 AddElemToArray(PLDHashTable* table, PLDHashEntryHdr *hdr,
620 PRUint32 i, void *arg)
622 nsISupportsArray *propArray = (nsISupportsArray *) arg;
623 PropertyTableEntry* entry =
624 static_cast<PropertyTableEntry*>(hdr);
626 nsPropertyElement *element =
627 new nsPropertyElement(nsDependentCString(entry->mKey),
628 nsDependentString(entry->mValue));
629 if (!element)
630 return PL_DHASH_STOP;
632 propArray->InsertElementAt(element, i);
634 return PL_DHASH_NEXT;
638 NS_IMETHODIMP
639 nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult)
641 nsCOMPtr<nsISupportsArray> propArray;
642 nsresult rv = NS_NewISupportsArray(getter_AddRefs(propArray));
643 if (NS_FAILED(rv))
644 return rv;
646 // We know the necessary size; we can avoid growing it while adding elements
647 if (!propArray->SizeTo(mTable.entryCount))
648 return NS_ERROR_OUT_OF_MEMORY;
650 // Step through hash entries populating a transient array
651 PRUint32 n =
652 PL_DHashTableEnumerate(&mTable, AddElemToArray, (void *)propArray);
653 if (n < mTable.entryCount)
654 return NS_ERROR_OUT_OF_MEMORY;
656 return NS_NewArrayEnumerator(aResult, propArray);
659 ////////////////////////////////////////////////////////////////////////////////
660 // XXX Some day we'll unify the nsIPersistentProperties interface with
661 // nsIProperties, but until now...
663 NS_IMETHODIMP
664 nsPersistentProperties::Get(const char* prop, const nsIID & uuid, void* *result)
666 return NS_ERROR_NOT_IMPLEMENTED;
669 NS_IMETHODIMP
670 nsPersistentProperties::Set(const char* prop, nsISupports* value)
672 return NS_ERROR_NOT_IMPLEMENTED;
674 NS_IMETHODIMP
675 nsPersistentProperties::Undefine(const char* prop)
677 return NS_ERROR_NOT_IMPLEMENTED;
680 NS_IMETHODIMP
681 nsPersistentProperties::Has(const char* prop, PRBool *result)
683 PropertyTableEntry *entry =
684 static_cast<PropertyTableEntry*>
685 (PL_DHashTableOperate(&mTable, prop, PL_DHASH_LOOKUP));
687 *result = (entry && PL_DHASH_ENTRY_IS_BUSY(entry));
689 return NS_OK;
692 NS_IMETHODIMP
693 nsPersistentProperties::GetKeys(PRUint32 *count, char ***keys)
695 return NS_ERROR_NOT_IMPLEMENTED;
698 ////////////////////////////////////////////////////////////////////////////////
699 // PropertyElement
700 ////////////////////////////////////////////////////////////////////////////////
702 NS_METHOD
703 nsPropertyElement::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
705 if (aOuter)
706 return NS_ERROR_NO_AGGREGATION;
707 nsPropertyElement* propElem = new nsPropertyElement();
708 if (propElem == nsnull)
709 return NS_ERROR_OUT_OF_MEMORY;
710 NS_ADDREF(propElem);
711 nsresult rv = propElem->QueryInterface(aIID, aResult);
712 NS_RELEASE(propElem);
713 return rv;
716 NS_IMPL_ISUPPORTS1(nsPropertyElement, nsIPropertyElement)
718 NS_IMETHODIMP
719 nsPropertyElement::GetKey(nsACString& aReturnKey)
721 aReturnKey = mKey;
722 return NS_OK;
725 NS_IMETHODIMP
726 nsPropertyElement::GetValue(nsAString& aReturnValue)
728 aReturnValue = mValue;
729 return NS_OK;
732 NS_IMETHODIMP
733 nsPropertyElement::SetKey(const nsACString& aKey)
735 mKey = aKey;
736 return NS_OK;
739 NS_IMETHODIMP
740 nsPropertyElement::SetValue(const nsAString& aValue)
742 mValue = aValue;
743 return NS_OK;
746 ////////////////////////////////////////////////////////////////////////////////