1 /*
   2  * Copyright (c) 2014, 2017, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 
  33 
  34 #include "Platform.h"
  35 
  36 #ifdef MAC
  37 
  38 #include "MacPlatform.h"
  39 #include "Helpers.h"
  40 #include "Package.h"
  41 #include "PropertyFile.h"
  42 #include "IniFile.h"
  43 
  44 #include <sys/sysctl.h>
  45 #include <pthread.h>
  46 #include <vector>
  47 
  48 #import <Foundation/Foundation.h>
  49 #import <AppKit/NSRunningApplication.h>
  50 
  51 #include <CoreFoundation/CoreFoundation.h>
  52 #include <CoreFoundation/CFString.h>
  53 
  54 #ifdef __OBJC__
  55 #import <Cocoa/Cocoa.h>
  56 #endif //__OBJC__
  57 
  58 #define MAC_PACKAGER_TMP_DIR "/Library/Application Support/Oracle/Java/Packager/tmp"
  59 
  60 //--------------------------------------------------------------------------------------------------
  61 
  62 NSString* StringToNSString(TString Value) {
  63     NSString* result = [NSString stringWithCString:Value.c_str()
  64                                           encoding:[NSString defaultCStringEncoding]];
  65     return result;
  66 }
  67 
  68 //--------------------------------------------------------------------------------------------------
  69 
  70 MacPlatform::MacPlatform(void) : Platform(), GenericPlatform(), PosixPlatform() {
  71 }
  72 
  73 MacPlatform::~MacPlatform(void) {
  74 }
  75 
  76 bool MacPlatform::UsePListForConfigFile() {
  77     return FilePath::FileExists(GetConfigFileName()) == false;
  78 }
  79 
  80 void MacPlatform::ShowMessage(TString Title, TString Description) {
  81     NSString *ltitle = StringToNSString(Title);
  82     NSString *ldescription = StringToNSString(Description);
  83 
  84     NSLog(@"%@:%@", ltitle, ldescription);
  85 }
  86 
  87 void MacPlatform::ShowMessage(TString Description) {
  88     TString appname = GetModuleFileName();
  89     appname = FilePath::ExtractFileName(appname);
  90     ShowMessage(appname, Description);
  91 }
  92 
  93 const char* MacPlatform::getTmpDirString() {
  94     return MAC_PACKAGER_TMP_DIR;
  95 }
  96 
  97 void MacPlatform::reactivateAnotherInstance() {
  98     if (singleInstanceProcessId == 0) {
  99         printf("Unable to reactivate another instance, PID is undefined");
 100         return;
 101     } 
 102     NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: singleInstanceProcessId];
 103     if (app != nil) {
 104         [app activateWithOptions: NSApplicationActivateIgnoringOtherApps];
 105     } else {
 106         printf("Unable to reactivate another instance PID: %d", singleInstanceProcessId);
 107     }
 108 }
 109 
 110 TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source, bool &release) {
 111     TCHAR* result = NULL;
 112     release = false;
 113     CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault, Source, kCFStringEncodingUTF8);
 114 
 115     if (StringRef != NULL) {
 116         @try {
 117             CFIndex length = CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef);
 118             result = new char[length + 1];
 119 
 120             if (CFStringGetFileSystemRepresentation(StringRef, result, length)) {
 121                 release = true;
 122             }
 123             else {
 124                 delete[] result;
 125                 result = NULL;
 126             }
 127         }
 128         @finally {
 129             CFRelease(StringRef);
 130         }
 131     }
 132 
 133     return result;
 134 }
 135 
 136 TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source, bool &release) {
 137     TCHAR* result = NULL;
 138     release = false;
 139     CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, Source);
 140 
 141     if (StringRef != NULL) {
 142         @try {
 143             CFIndex length = CFStringGetLength(StringRef);
 144 
 145             if (length > 0) {
 146                 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
 147 
 148                 result = new char[maxSize + 1];
 149 
 150                 if (CFStringGetCString(StringRef, result, maxSize, kCFStringEncodingUTF8) == true) {
 151                     release = true;
 152                 }
 153                 else {
 154                     delete[] result;
 155                     result = NULL;
 156                 }
 157             }
 158         }
 159         @finally {
 160             CFRelease(StringRef);
 161         }
 162     }
 163 
 164     return result;
 165 }
 166 
 167 void MacPlatform::SetCurrentDirectory(TString Value) {
 168     chdir(PlatformString(Value).toPlatformString());
 169 }
 170 
 171 TString MacPlatform::GetPackageRootDirectory() {
 172     NSBundle *mainBundle = [NSBundle mainBundle];
 173     NSString *mainBundlePath = [mainBundle bundlePath];
 174     NSString *contentsPath = [mainBundlePath stringByAppendingString:@"/Contents"];
 175     TString result = [contentsPath UTF8String];
 176     return result;
 177 }
 178 
 179 TString MacPlatform::GetAppDataDirectory() {
 180     TString result;
 181     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
 182     NSString *applicationSupportDirectory = [paths firstObject];
 183     result = [applicationSupportDirectory UTF8String];
 184     return result;
 185 }
 186 
 187 TString MacPlatform::GetBundledJVMLibraryFileName(TString RuntimePath) {
 188     TString result;
 189 
 190     result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/jre/lib/jli/libjli.dylib");
 191 
 192     if (FilePath::FileExists(result) == false) {
 193         result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/lib/jli/libjli.dylib");
 194 
 195         if (FilePath::FileExists(result) == false) {
 196             result = _T("");
 197         }
 198     }
 199 
 200     return result;
 201 }
 202 
 203 
 204 TString MacPlatform::GetSystemJRE() {
 205     if (GetAppCDSState() != cdsDisabled) {
 206         //TODO throw exception
 207         return _T("");
 208     }
 209 
 210     return _T("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib");
 211 }
 212 
 213 TString MacPlatform::GetSystemJVMLibraryFileName() {
 214     TString result = GetSystemJRE();
 215 
 216     if (FilePath::FileExists(result) == false) {
 217         result = _T("");
 218     }
 219 
 220     return result;
 221 }
 222 
 223 TString MacPlatform::GetAppName() {
 224     NSString *appName = [[NSProcessInfo processInfo] processName];
 225     TString result = [appName UTF8String];
 226     return result;
 227 }
 228 
 229 void AppendPListArrayToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section) {
 230     NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()];
 231     NSDictionary *array = [infoDictionary objectForKey:sectionKey];
 232 
 233     for (id option in array) {
 234         if ([option isKindOfClass:[NSString class]]) {
 235             TString arg = [option UTF8String];
 236 
 237             TString name;
 238             TString value;
 239 
 240             if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
 241                 result->Append(Section, name, value);
 242             }
 243         }
 244     }
 245 }
 246 
 247 void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section, bool FollowSection = true) {
 248     NSDictionary *dictionary = NULL;
 249 
 250     if (FollowSection == true) {
 251         NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()];
 252         dictionary = [infoDictionary objectForKey:sectionKey];
 253     }
 254     else {
 255         dictionary = infoDictionary;
 256     }
 257 
 258     for (id key in dictionary) {
 259         id option = [dictionary valueForKey:key];
 260 
 261         if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) {
 262             TString name = [key UTF8String];
 263             TString value = [option UTF8String];
 264             result->Append(Section, name, value);
 265         }
 266     }
 267 }
 268 
 269 // Convert parts of the info.plist to the INI format the rest of the packager uses unless
 270 // a packager config file exists.
 271 ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
 272     IniFile* result = new IniFile();
 273 
 274     if (UsePListForConfigFile() == false) {
 275         if (result->LoadFromFile(FileName) == false) {
 276             // New property file format was not found, attempt to load old property file format.
 277             Helpers::LoadOldConfigFile(FileName, result);
 278         }
 279     }
 280     else {
 281         NSBundle *mainBundle = [NSBundle mainBundle];
 282         NSDictionary *infoDictionary = [mainBundle infoDictionary];
 283         std::map<TString, TString> keys = GetKeys();
 284 
 285         // Packager options.
 286         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPLICATION], false);
 287 
 288         // jvmargs
 289         AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMOPTIONS]);
 290 
 291         // jvmuserargs
 292         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMUSEROPTIONS]);
 293 
 294         // Generate AppCDS Cache
 295         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSJVMOPTIONS]);
 296         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS]);
 297 
 298         // args
 299         AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_ARGOPTIONS]);
 300     }
 301 
 302     return result;
 303 }
 304 
 305 TString GetModuleFileNameOSX() {
 306     Dl_info module_info;
 307     if (dladdr(reinterpret_cast<void*>(GetModuleFileNameOSX), &module_info) == 0) {
 308         // Failed to find the symbol we asked for.
 309         return std::string();
 310     }
 311     return TString(module_info.dli_fname);
 312 }
 313 
 314 #include <mach-o/dyld.h>
 315 
 316 TString MacPlatform::GetModuleFileName() {
 317     //return GetModuleFileNameOSX();
 318 
 319     TString result;
 320     DynamicBuffer<TCHAR> buffer(MAX_PATH);
 321     uint32_t size = buffer.GetSize();
 322 
 323     if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
 324         result = FileSystemStringToString(buffer.GetData());
 325     }
 326 
 327     return result;
 328 }
 329 
 330 bool MacPlatform::IsMainThread() {
 331     bool result = (pthread_main_np() == 1);
 332     return result;
 333 }
 334 
 335 TPlatformNumber MacPlatform::GetMemorySize() {
 336     unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
 337     TPlatformNumber result = memory / 1048576; // Convert from bytes to megabytes.
 338     return result;
 339 }
 340 
 341 std::map<TString, TString> MacPlatform::GetKeys() {
 342     std::map<TString, TString> keys;
 343 
 344     if (UsePListForConfigFile() == false) {
 345         return GenericPlatform::GetKeys();
 346     }
 347     else {
 348         keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,            _T("app.version")));
 349         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,        _T("JVMMainJarName")));
 350         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY,     _T("JVMMainModuleName")));
 351         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINCLASSNAME_KEY,  _T("JVMMainClassName")));
 352         keys.insert(std::map<TString, TString>::value_type(CONFIG_CLASSPATH_KEY,      _T("JVMAppClasspath")));
 353         keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,              _T("CFBundleName")));
 354         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_ID_KEY,         _T("JVMPreferencesID")));
 355         keys.insert(std::map<TString, TString>::value_type(JVM_RUNTIME_KEY,           _T("JVMRuntime")));
 356         keys.insert(std::map<TString, TString>::value_type(PACKAGER_APP_DATA_DIR,     _T("CFBundleIdentifier")));
 357 
 358         keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,         _T("app.splash")));
 359         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,         _T("app.memory")));
 360         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,          _T("app.debug")));
 361         keys.insert(std::map<TString, TString>::value_type(CONFIG_APPLICATION_INSTANCE,   _T("app.application.instance")));
 362 
 363         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPLICATION,    _T("Application")));
 364         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMOPTIONS,     _T("JVMOptions")));
 365         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROPTIONS, _T("JVMUserOptions")));
 366         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROVERRIDESOPTIONS, _T("JVMUserOverrideOptions")));
 367         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSJVMOPTIONS, _T("AppCDSJVMOptions")));
 368         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS, _T("AppCDSGenerateCacheJVMOptions")));
 369         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_ARGOPTIONS,     _T("ArgOptions")));
 370     }
 371 
 372     return keys;
 373 }
 374 
 375 #ifdef DEBUG
 376 bool MacPlatform::IsNativeDebuggerPresent() {
 377     int state;
 378     int mib[4];
 379     struct kinfo_proc info;
 380     size_t size;
 381 
 382     info.kp_proc.p_flag = 0;
 383 
 384     mib[0] = CTL_KERN;
 385     mib[1] = KERN_PROC;
 386     mib[2] = KERN_PROC_PID;
 387     mib[3] = getpid();
 388 
 389     size = sizeof(info);
 390     state = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
 391     assert(state == 0);
 392     return ((info.kp_proc.p_flag & P_TRACED) != 0);
 393 }
 394 
 395 int MacPlatform::GetProcessID() {
 396     int pid = [[NSProcessInfo processInfo] processIdentifier];
 397     return pid;
 398 }
 399 #endif //DEBUG
 400 
 401 //--------------------------------------------------------------------------------------------------
 402 
 403 class UserDefaults {
 404 private:
 405     OrderedMap<TString, TString> FData;
 406     TString FDomainName;
 407 
 408     bool ReadDictionary(NSDictionary *Items, OrderedMap<TString, TString> &Data) {
 409         bool result = false;
 410 
 411         for (id key in Items) {
 412             id option = [Items valueForKey:key];
 413 
 414             if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) {
 415                 TString name = [key UTF8String];
 416                 TString value = [option UTF8String];
 417 
 418                 if (name.empty() == false) {
 419                     Data.Append(name, value);
 420                 }
 421             }
 422         }
 423 
 424         return result;
 425     }
 426 
 427     // Open and read the defaults file specified by domain.
 428     bool ReadPreferences(NSDictionary *Defaults, std::list<TString> Keys, OrderedMap<TString, TString> &Data) {
 429         bool result = false;
 430 
 431         if (Keys.size() > 0 && Defaults != NULL) {
 432             NSDictionary *node = Defaults;
 433 
 434             while (Keys.size() > 0 && node != NULL) {
 435                 TString key = Keys.front();
 436                 Keys.pop_front();
 437                 NSString *tempKey = StringToNSString(key);
 438                 node = [node valueForKey:tempKey];
 439 
 440                 if (Keys.size() == 0) {
 441                     break;
 442                 }
 443             }
 444 
 445             if (node != NULL) {
 446                 result = ReadDictionary(node, Data);
 447             }
 448         }
 449 
 450         return result;
 451     }
 452 
 453     NSDictionary* LoadPreferences(TString DomainName) {
 454         NSDictionary *result = NULL;
 455 
 456         if (DomainName.empty() == false) {
 457             NSUserDefaults *prefs = [[NSUserDefaults alloc] init];
 458 
 459             if (prefs != NULL) {
 460                 NSString *lDomainName = StringToNSString(DomainName);
 461                 result = [prefs persistentDomainForName: lDomainName];
 462             }
 463         }
 464 
 465         return result;
 466     }
 467 
 468 public:
 469     UserDefaults(TString DomainName) {
 470         FDomainName = DomainName;
 471     }
 472 
 473     bool Read(std::list<TString> Keys) {
 474         NSDictionary *defaults = LoadPreferences(FDomainName);
 475         return ReadPreferences(defaults, Keys, FData);
 476     }
 477 
 478     OrderedMap<TString, TString> GetData() {
 479         return FData;
 480     }
 481 };
 482 
 483 //--------------------------------------------------------------------------------------------------
 484 
 485 MacJavaUserPreferences::MacJavaUserPreferences(void) : JavaUserPreferences() {
 486 }
 487 
 488 TString toLowerCase(TString Value) {
 489     // Use Cocoa's lowercase method because it is better than the ones provided by C/C++.
 490     NSString *temp = StringToNSString(Value);
 491     temp = [temp lowercaseString];
 492     TString result = [temp UTF8String];
 493     return result;
 494 }
 495 
 496 // Split the string Value into using Delimiter.
 497 std::list<TString> Split(TString Value, TString Delimiter) {
 498     std::list<TString> result;
 499     std::vector<char> buffer(Value.c_str(), Value.c_str() + Value.size() + 1);
 500     char *p = strtok(&buffer[0], Delimiter.data());
 501 
 502     while (p != NULL) {
 503         TString token = p;
 504         result.push_back(token);
 505         p = strtok(NULL, Delimiter.data());
 506     }
 507 
 508     return result;
 509 }
 510 
 511 // 1. If the path is fewer than three components (Example: one/two/three) then the domain is the
 512 //    default domain "com.apple.java.util.prefs" stored in the plist file
 513 //    ~/Library/Preferences/com.apple.java.util.prefs.plist
 514 //
 515 //    For example: If AppID = "hello", the path is "hello/JVMUserOptions and the
 516 //    plist file is ~/Library/Preferences/com.apple.java.util.prefs.plist containing the contents:
 517 //
 518 //    <?xml version="1.0" encoding="UTF-8"?>
 519 //    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 520 //    <plist version="1.0">
 521 //    <dict>
 522 //      <key>/</key>
 523 //      <dict>
 524 //        <key>hello/</key>
 525 //        <dict>
 526 //          <key>JVMUserOptions/</key>
 527 //          <dict>
 528 //            <key>-DXmx</key>
 529 //            <string>512m</string>
 530 //          </dict>
 531 //        </dict>
 532 //      </dict>
 533 //    </dict>
 534 //    </plist>
 535 //
 536 // 2. If the path is three or more, the first three become the domain name (even
 537 //    if shared across applicaitons) and the remaining become individual keys.
 538 //
 539 //    For example: If AppID = "com/hello/foo", the path is "hello/JVMUserOptions and the
 540 //    domain is "com.hello.foo" stored in the plist file ~/Library/Preferences/com.hello.foo.plist
 541 //    containing the contents:
 542 //
 543 //    <?xml version="1.0" encoding="UTF-8"?>
 544 //    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 545 //    <plist version="1.0">
 546 //    <dict>
 547 //      <key>/com/hello/foo/</key>
 548 //      <dict>
 549 //        <key>JVMUserOptions/</key>
 550 //        <dict>
 551 //          <key>-DXmx</key>
 552 //          <string>512m</string>
 553 //        </dict>
 554 //      </dict>
 555 //    </dict>
 556 //    </plist>
 557 //
 558 // NOTE: To change these values use the command line utility "defaults":
 559 // Example: defaults read com.apple.java.util.prefs /
 560 // Since OS 10.9 Mavericks the defaults are cashed so directly modifying the files is not recommended.
 561 bool MacJavaUserPreferences::Load(TString Appid) {
 562     bool result = false;
 563 
 564     if (Appid.empty() == false) {
 565         // This is for backwards compatability. Older packaged applications have an
 566         // app.preferences.id that is delimited by period (".") rather than
 567         // slash ("/") so convert to newer style.
 568         TString path = Helpers::ReplaceString(Appid, _T("."), _T("/"));
 569 
 570         path = path + _T("/JVMUserOptions");
 571         TString domainName;
 572         std::list<TString> keys = Split(path, _T("/"));
 573 
 574         // If there are less than three parts to the path then use the default preferences file.
 575         if (keys.size() < 3) {
 576             domainName = _T("com.apple.java.util.prefs");
 577 
 578             // Append slash to the end of each key.
 579             for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) {
 580                 TString item = *iterator;
 581                 item = item + _T("/");
 582                 *iterator = item;
 583             }
 584 
 585             // The root key is /.
 586             keys.push_front(_T("/"));
 587         }
 588         else {
 589             // Remove the first three keys and use them for the root key and the preferencesID.
 590             TString one = keys.front();
 591             keys.pop_front();
 592             TString two = keys.front();
 593             keys.pop_front();
 594             TString three = keys.front();
 595             keys.pop_front();
 596             domainName = one + TString(".") + two + TString(".") + three;
 597             domainName = toLowerCase(domainName);
 598 
 599             // Append slash to the end of each key.
 600             for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) {
 601                 TString item = *iterator;
 602                 item = item + _T("/");
 603                 *iterator = item;
 604             }
 605 
 606             // The root key is /one/two/three/
 607             TString key = TString("/") + one + TString("/") + two + TString("/") + three + TString("/");
 608             keys.push_front(key);
 609         }
 610 
 611         UserDefaults userDefaults(domainName);
 612 
 613         if (userDefaults.Read(keys) == true) {
 614             result = true;
 615             FMap = userDefaults.GetData();
 616         }
 617     }
 618 
 619     return result;
 620 }
 621 
 622 //--------------------------------------------------------------------------------------------------
 623 
 624 #endif //MAC