1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "wtf/unicode/Collator.h"
31
32#include "wtf/Assertions.h"
33#include "wtf/StringExtras.h"
34#include "wtf/Threading.h"
35#include "wtf/ThreadingPrimitives.h"
36#include <unicode/ucol.h>
37#include <string.h>
38
39#if OS(DARWIN)
40#include "wtf/RetainPtr.h"
41#include <CoreFoundation/CoreFoundation.h>
42#endif
43
44namespace WTF {
45
46static UCollator* cachedCollator;
47static Mutex& cachedCollatorMutex()
48{
49    AtomicallyInitializedStatic(Mutex&, mutex = *new Mutex);
50    return mutex;
51}
52
53Collator::Collator(const char* locale)
54    : m_collator(0)
55    , m_locale(locale ? strdup(locale) : 0)
56    , m_lowerFirst(false)
57{
58}
59
60PassOwnPtr<Collator> Collator::userDefault()
61{
62#if OS(DARWIN) && USE(CF)
63    // Mac OS X doesn't set UNIX locale to match user-selected one, so ICU default doesn't work.
64    RetainPtr<CFLocaleRef> currentLocale(AdoptCF, CFLocaleCopyCurrent());
65    CFStringRef collationOrder = (CFStringRef)CFLocaleGetValue(currentLocale.get(), kCFLocaleCollatorIdentifier);
66    char buf[256];
67    if (!collationOrder)
68        return adoptPtr(new Collator(""));
69    CFStringGetCString(collationOrder, buf, sizeof(buf), kCFStringEncodingASCII);
70    return adoptPtr(new Collator(buf));
71#else
72    return adoptPtr(new Collator(0));
73#endif
74}
75
76Collator::~Collator()
77{
78    releaseCollator();
79    free(m_locale);
80}
81
82void Collator::setOrderLowerFirst(bool lowerFirst)
83{
84    m_lowerFirst = lowerFirst;
85}
86
87Collator::Result Collator::collate(const UChar* lhs, size_t lhsLength, const UChar* rhs, size_t rhsLength) const
88{
89    if (!m_collator)
90        createCollator();
91
92    return static_cast<Result>(ucol_strcoll(m_collator, lhs, lhsLength, rhs, rhsLength));
93}
94
95void Collator::createCollator() const
96{
97    ASSERT(!m_collator);
98    UErrorCode status = U_ZERO_ERROR;
99
100    {
101        Locker<Mutex> lock(cachedCollatorMutex());
102        if (cachedCollator) {
103            const char* cachedCollatorLocale = ucol_getLocaleByType(cachedCollator, ULOC_REQUESTED_LOCALE, &status);
104            ASSERT(U_SUCCESS(status));
105            ASSERT(cachedCollatorLocale);
106
107            UColAttributeValue cachedCollatorLowerFirst = ucol_getAttribute(cachedCollator, UCOL_CASE_FIRST, &status);
108            ASSERT(U_SUCCESS(status));
109
110            // FIXME: default locale is never matched, because ucol_getLocaleByType returns the actual one used, not 0.
111            if (m_locale && 0 == strcmp(cachedCollatorLocale, m_locale)
112                && ((UCOL_LOWER_FIRST == cachedCollatorLowerFirst && m_lowerFirst) || (UCOL_UPPER_FIRST == cachedCollatorLowerFirst && !m_lowerFirst))) {
113                m_collator = cachedCollator;
114                cachedCollator = 0;
115                return;
116            }
117        }
118    }
119
120    m_collator = ucol_open(m_locale, &status);
121    if (U_FAILURE(status)) {
122        status = U_ZERO_ERROR;
123        m_collator = ucol_open("", &status); // Fallback to Unicode Collation Algorithm.
124    }
125    ASSERT(U_SUCCESS(status));
126
127    ucol_setAttribute(m_collator, UCOL_CASE_FIRST, m_lowerFirst ? UCOL_LOWER_FIRST : UCOL_UPPER_FIRST, &status);
128    ASSERT(U_SUCCESS(status));
129
130    ucol_setAttribute(m_collator, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
131    ASSERT(U_SUCCESS(status));
132}
133
134void Collator::releaseCollator()
135{
136    {
137        Locker<Mutex> lock(cachedCollatorMutex());
138        if (cachedCollator)
139            ucol_close(cachedCollator);
140        cachedCollator = m_collator;
141        m_collator  = 0;
142    }
143}
144
145} // namespace WTF
146