/Objective-J/CFBundle.js

http://github.com/cacaodev/cappuccino · JavaScript · 740 lines · 537 code · 173 blank · 30 comment · 72 complexity · 76bbe0c73e1883f32fd44d74f6d38d64 MD5 · raw file

  1. /*
  2. * CFBundle.js
  3. * Objective-J
  4. *
  5. * Created by Francisco Tolmasky.
  6. * Copyright 2008-2010, 280 North, Inc.
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this library; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. */
  22. var CFBundleUnloaded = 0,
  23. CFBundleLoading = 1 << 0,
  24. CFBundleLoadingInfoPlist = 1 << 1,
  25. CFBundleLoadingExecutable = 1 << 2,
  26. CFBundleLoadingSpritedImages = 1 << 3,
  27. CFBundleLoaded = 1 << 4;
  28. var CFBundlesForURLStrings = { },
  29. CFBundlesForClasses = { },
  30. CFBundlesWithIdentifiers = { },
  31. CFCacheBuster = new Date().getTime(),
  32. CFTotalBytesLoaded = 0,
  33. CPApplicationSizeInBytes = 0;
  34. GLOBAL(CFBundle) = function(/*CFURL|String*/ aURL)
  35. {
  36. aURL = makeAbsoluteURL(aURL).asDirectoryPathURL();
  37. var URLString = aURL.absoluteString(),
  38. existingBundle = CFBundlesForURLStrings[URLString];
  39. if (existingBundle)
  40. return existingBundle;
  41. CFBundlesForURLStrings[URLString] = this;
  42. this._bundleURL = aURL;
  43. this._resourcesDirectoryURL = new CFURL("Resources/", aURL);
  44. this._staticResource = NULL;
  45. this._isValid = NO;
  46. this._loadStatus = CFBundleUnloaded;
  47. this._loadRequests = [];
  48. this._infoDictionary = new CFDictionary();
  49. this._eventDispatcher = new EventDispatcher(this);
  50. }
  51. DISPLAY_NAME(CFBundle);
  52. CFBundle.environments = function()
  53. {
  54. // Passed in by GCC.
  55. return ENVIRONMENTS;
  56. };
  57. DISPLAY_NAME(CFBundle.environments);
  58. CFBundle.bundleContainingURL = function(/*CFURL|String*/ aURL)
  59. {
  60. aURL = new CFURL(".", makeAbsoluteURL(aURL));
  61. var previousURLString,
  62. URLString = aURL.absoluteString();
  63. while (!previousURLString || previousURLString !== URLString)
  64. {
  65. var bundle = CFBundlesForURLStrings[URLString];
  66. if (bundle && bundle._isValid)
  67. return bundle;
  68. aURL = new CFURL("..", aURL);
  69. previousURLString = URLString;
  70. URLString = aURL.absoluteString();
  71. }
  72. return NULL;
  73. };
  74. DISPLAY_NAME(CFBundle.bundleContainingURL);
  75. CFBundle.mainBundle = function()
  76. {
  77. return new CFBundle(mainBundleURL);
  78. };
  79. DISPLAY_NAME(CFBundle.mainBundle);
  80. function addClassToBundle(aClass, aBundle)
  81. {
  82. if (aBundle)
  83. CFBundlesForClasses[aClass.name] = aBundle;
  84. }
  85. function resetBundle()
  86. {
  87. CFBundlesForURLStrings = { };
  88. CFBundlesForClasses = { };
  89. CFBundlesWithIdentifiers = { };
  90. //CFCacheBuster = new Date().getTime(),
  91. CFTotalBytesLoaded = 0;
  92. CPApplicationSizeInBytes = 0;
  93. }
  94. CFBundle.bundleForClass = function(/*Class*/ aClass)
  95. {
  96. return CFBundlesForClasses[aClass.name] || CFBundle.mainBundle();
  97. };
  98. DISPLAY_NAME(CFBundle.bundleForClass);
  99. CFBundle.bundleWithIdentifier = function(/*String*/ bundleID)
  100. {
  101. return CFBundlesWithIdentifiers[bundleID] || NULL;
  102. };
  103. DISPLAY_NAME(CFBundle.bundleWithIdentifier);
  104. CFBundle.prototype.bundleURL = function()
  105. {
  106. return this._bundleURL.absoluteURL();
  107. };
  108. DISPLAY_NAME(CFBundle.prototype.bundleURL);
  109. CFBundle.prototype.resourcesDirectoryURL = function()
  110. {
  111. return this._resourcesDirectoryURL;
  112. };
  113. DISPLAY_NAME(CFBundle.prototype.resourcesDirectoryURL);
  114. CFBundle.prototype.resourceURL = function(/*String*/ aResourceName, /*String*/ aType, /*String*/ aSubDirectory)
  115. {
  116. if (aType)
  117. aResourceName = aResourceName + "." + aType;
  118. if (aSubDirectory)
  119. aResourceName = aSubDirectory + "/" + aResourceName;
  120. var resourceURL = (new CFURL(aResourceName, this.resourcesDirectoryURL())).mappedURL();
  121. return resourceURL.absoluteURL();
  122. };
  123. DISPLAY_NAME(CFBundle.prototype.resourceURL);
  124. CFBundle.prototype.mostEligibleEnvironmentURL = function()
  125. {
  126. if (this._mostEligibleEnvironmentURL === undefined)
  127. this._mostEligibleEnvironmentURL = new CFURL(this.mostEligibleEnvironment() + ".environment/", this.bundleURL());
  128. return this._mostEligibleEnvironmentURL;
  129. }
  130. DISPLAY_NAME(CFBundle.prototype.mostEligibleEnvironmentURL);
  131. CFBundle.prototype.executableURL = function()
  132. {
  133. if (this._executableURL === undefined)
  134. {
  135. var executableSubPath = this.valueForInfoDictionaryKey("CPBundleExecutable");
  136. if (!executableSubPath)
  137. this._executableURL = NULL;
  138. else
  139. this._executableURL = new CFURL(executableSubPath, this.mostEligibleEnvironmentURL());
  140. }
  141. return this._executableURL;
  142. };
  143. DISPLAY_NAME(CFBundle.prototype.executableURL);
  144. CFBundle.prototype.infoDictionary = function()
  145. {
  146. return this._infoDictionary;
  147. };
  148. DISPLAY_NAME(CFBundle.prototype.infoDictionary);
  149. CFBundle.prototype.valueForInfoDictionaryKey = function(/*String*/ aKey)
  150. {
  151. return this._infoDictionary.valueForKey(aKey);
  152. };
  153. DISPLAY_NAME(CFBundle.prototype.valueForInfoDictionaryKey);
  154. CFBundle.prototype.identifier = function()
  155. {
  156. return this._infoDictionary.valueForKey("CPBundleIdentifier");
  157. };
  158. DISPLAY_NAME(CFBundle.prototype.identifier);
  159. CFBundle.prototype.hasSpritedImages = function()
  160. {
  161. var environments = this._infoDictionary.valueForKey("CPBundleEnvironmentsWithImageSprites") || [],
  162. index = environments.length,
  163. mostEligibleEnvironment = this.mostEligibleEnvironment();
  164. while (index--)
  165. if (environments[index] === mostEligibleEnvironment)
  166. return YES;
  167. return NO;
  168. };
  169. DISPLAY_NAME(CFBundle.prototype.hasSpritedImages);
  170. CFBundle.prototype.environments = function()
  171. {
  172. return this._infoDictionary.valueForKey("CPBundleEnvironments") || ["ObjJ"];
  173. };
  174. DISPLAY_NAME(CFBundle.prototype.environments);
  175. CFBundle.prototype.mostEligibleEnvironment = function(/*Array*/ environments)
  176. {
  177. environments = environments || this.environments();
  178. var objj_environments = CFBundle.environments(),
  179. index = 0,
  180. count = objj_environments.length,
  181. innerCount = environments.length;
  182. // Ugh, no indexOf, no objects-in-common.
  183. for(; index < count; ++index)
  184. {
  185. var innerIndex = 0,
  186. environment = objj_environments[index];
  187. for (; innerIndex < innerCount; ++innerIndex)
  188. if(environment === environments[innerIndex])
  189. return environment;
  190. }
  191. return NULL;
  192. };
  193. DISPLAY_NAME(CFBundle.prototype.mostEligibleEnvironment);
  194. CFBundle.prototype.isLoading = function()
  195. {
  196. return this._loadStatus & CFBundleLoading;
  197. };
  198. DISPLAY_NAME(CFBundle.prototype.isLoading);
  199. CFBundle.prototype.isLoaded = function()
  200. {
  201. return !!(this._loadStatus & CFBundleLoaded);
  202. };
  203. DISPLAY_NAME(CFBundle.prototype.isLoaded);
  204. CFBundle.prototype.load = function(/*BOOL*/ shouldExecute)
  205. {
  206. if (this._loadStatus !== CFBundleUnloaded)
  207. return;
  208. this._loadStatus = CFBundleLoading | CFBundleLoadingInfoPlist;
  209. var self = this,
  210. bundleURL = this.bundleURL(),
  211. parentURL = new CFURL("..", bundleURL);
  212. if (parentURL.absoluteString() === bundleURL.absoluteString())
  213. parentURL = parentURL.schemeAndAuthority();
  214. StaticResource.resolveResourceAtURL(parentURL, YES, function(aStaticResource)
  215. {
  216. var resourceName = bundleURL.lastPathComponent();
  217. self._staticResource = aStaticResource._children[resourceName] ||
  218. new StaticResource(bundleURL, aStaticResource, YES, NO);
  219. function onsuccess(/*Event*/ anEvent)
  220. {
  221. self._loadStatus &= ~CFBundleLoadingInfoPlist;
  222. var infoDictionary = anEvent.request.responsePropertyList();
  223. self._isValid = !!infoDictionary || CFBundle.mainBundle() === self;
  224. if (infoDictionary)
  225. {
  226. self._infoDictionary = infoDictionary;
  227. var identifier = self._infoDictionary.valueForKey("CPBundleIdentifier");
  228. if (identifier)
  229. CFBundlesWithIdentifiers[identifier] = self;
  230. }
  231. if (!self._infoDictionary)
  232. {
  233. finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + path + "\""));
  234. return;
  235. }
  236. if (self === CFBundle.mainBundle() && self.valueForInfoDictionaryKey("CPApplicationSize"))
  237. CPApplicationSizeInBytes = self.valueForInfoDictionaryKey("CPApplicationSize").valueForKey("executable") || 0;
  238. loadExecutableAndResources(self, shouldExecute);
  239. }
  240. function onfailure()
  241. {
  242. self._isValid = CFBundle.mainBundle() === self;
  243. self._loadStatus = CFBundleUnloaded;
  244. finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + self.bundleURL() + "\""));
  245. }
  246. new FileRequest(new CFURL("Info.plist", self.bundleURL()), onsuccess, onfailure);
  247. });
  248. };
  249. DISPLAY_NAME(CFBundle.prototype.load);
  250. function finishBundleLoadingWithError(/*CFBundle*/ aBundle, /*Event*/ anError)
  251. {
  252. resolveStaticResource(aBundle._staticResource);
  253. aBundle._eventDispatcher.dispatchEvent(
  254. {
  255. type:"error",
  256. error:anError,
  257. bundle:aBundle
  258. });
  259. }
  260. function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute)
  261. {
  262. if (!aBundle.mostEligibleEnvironment())
  263. return failure();
  264. loadExecutableForBundle(aBundle, success, failure, progress);
  265. loadSpritedImagesForBundle(aBundle, success, failure, progress);
  266. if (aBundle._loadStatus === CFBundleLoading)
  267. return success();
  268. function failure(/*Error*/ anError)
  269. {
  270. var loadRequests = aBundle._loadRequests,
  271. count = loadRequests.length;
  272. while (count--)
  273. loadRequests[count].abort();
  274. this._loadRequests = [];
  275. aBundle._loadStatus = CFBundleUnloaded;
  276. finishBundleLoadingWithError(aBundle, anError || new Error("Could not recognize executable code format in Bundle " + aBundle));
  277. }
  278. function progress(bytesLoaded)
  279. {
  280. if ((typeof CPApp === "undefined" || !CPApp || !CPApp._finishedLaunching) &&
  281. typeof OBJJ_PROGRESS_CALLBACK === "function")
  282. {
  283. CFTotalBytesLoaded += bytesLoaded;
  284. var percent = CPApplicationSizeInBytes ? MAX(MIN(1.0, CFTotalBytesLoaded / CPApplicationSizeInBytes), 0.0) : 0;
  285. OBJJ_PROGRESS_CALLBACK(percent, CPApplicationSizeInBytes, aBundle.bundlePath());
  286. }
  287. }
  288. function success()
  289. {
  290. if (aBundle._loadStatus === CFBundleLoading)
  291. aBundle._loadStatus = CFBundleLoaded;
  292. else
  293. return;
  294. // Set resolved to true here in case during evaluation this bundle
  295. // needs to resolve another bundle which in turn needs it to be resolved (cycle).
  296. resolveStaticResource(aBundle._staticResource);
  297. function complete()
  298. {
  299. aBundle._eventDispatcher.dispatchEvent(
  300. {
  301. type:"load",
  302. bundle:aBundle
  303. });
  304. }
  305. if (shouldExecute)
  306. executeBundle(aBundle, complete);
  307. else
  308. complete();
  309. }
  310. }
  311. function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure, progress)
  312. {
  313. var executableURL = aBundle.executableURL();
  314. if (!executableURL)
  315. return;
  316. aBundle._loadStatus |= CFBundleLoadingExecutable;
  317. new FileRequest(executableURL, function(/*Event*/ anEvent)
  318. {
  319. try
  320. {
  321. decompileStaticFile(aBundle, anEvent.request.responseText(), executableURL);
  322. aBundle._loadStatus &= ~CFBundleLoadingExecutable;
  323. success();
  324. }
  325. catch(anException)
  326. {
  327. failure(anException);
  328. }
  329. }, failure, progress);
  330. }
  331. function spritedImagesTestURLStringForBundle(/*Bundle*/ aBundle)
  332. {
  333. return "mhtml:" + new CFURL("MHTMLTest.txt", aBundle.mostEligibleEnvironmentURL());
  334. }
  335. function spritedImagesURLForBundle(/*Bundle*/ aBundle)
  336. {
  337. if (CFBundleSupportedSpriteType === CFBundleDataURLSpriteType)
  338. return new CFURL("dataURLs.txt", aBundle.mostEligibleEnvironmentURL());
  339. if (CFBundleSupportedSpriteType === CFBundleMHTMLSpriteType ||
  340. CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType)
  341. return new CFURL("MHTMLPaths.txt", aBundle.mostEligibleEnvironmentURL());
  342. return NULL;
  343. }
  344. function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure, progress)
  345. {
  346. if (!aBundle.hasSpritedImages())
  347. return;
  348. aBundle._loadStatus |= CFBundleLoadingSpritedImages;
  349. if (!CFBundleHasTestedSpriteSupport())
  350. return CFBundleTestSpriteSupport(spritedImagesTestURLStringForBundle(aBundle), function()
  351. {
  352. loadSpritedImagesForBundle(aBundle, success, failure, progress);
  353. });
  354. var spritedImagesURL = spritedImagesURLForBundle(aBundle);
  355. if (!spritedImagesURL)
  356. {
  357. aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
  358. return success();
  359. }
  360. new FileRequest(spritedImagesURL, function(/*Event*/ anEvent)
  361. {
  362. try
  363. {
  364. decompileStaticFile(aBundle, anEvent.request.responseText(), spritedImagesURL);
  365. aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
  366. success();
  367. }
  368. catch(anException)
  369. {
  370. failure(anException);
  371. }
  372. }, failure, progress);
  373. }
  374. var CFBundleSpriteSupportListeners = [],
  375. CFBundleSupportedSpriteType = -1,
  376. CFBundleNoSpriteType = 0,
  377. CFBundleDataURLSpriteType = 1,
  378. CFBundleMHTMLSpriteType = 2,
  379. CFBundleMHTMLUncachedSpriteType = 3;
  380. function CFBundleHasTestedSpriteSupport()
  381. {
  382. return CFBundleSupportedSpriteType !== -1;
  383. }
  384. function CFBundleTestSpriteSupport(/*String*/ MHTMLPath, /*Function*/ aCallback)
  385. {
  386. if (CFBundleHasTestedSpriteSupport())
  387. return;
  388. CFBundleSpriteSupportListeners.push(aCallback);
  389. if (CFBundleSpriteSupportListeners.length > 1)
  390. return;
  391. CFBundleSpriteSupportListeners.push(function()
  392. {
  393. var size = 0,
  394. sizeDictionary = CFBundle.mainBundle().valueForInfoDictionaryKey("CPApplicationSize");
  395. if (!sizeDictionary)
  396. return;
  397. switch (CFBundleSupportedSpriteType)
  398. {
  399. case CFBundleDataURLSpriteType:
  400. size = sizeDictionary.valueForKey("data");
  401. break;
  402. case CFBundleMHTMLSpriteType:
  403. case CFBundleMHTMLUncachedSpriteType:
  404. size = sizeDictionary.valueForKey("mhtml");
  405. break;
  406. }
  407. CPApplicationSizeInBytes += size;
  408. });
  409. CFBundleTestSpriteTypes([
  410. CFBundleDataURLSpriteType,
  411. "data:image/gif;base64,R0lGODlhAQABAIAAAMc9BQAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==",
  412. CFBundleMHTMLSpriteType,
  413. MHTMLPath+"!test",
  414. CFBundleMHTMLUncachedSpriteType,
  415. MHTMLPath+"?"+CFCacheBuster+"!test"
  416. ]);
  417. }
  418. function CFBundleNotifySpriteSupportListeners()
  419. {
  420. var count = CFBundleSpriteSupportListeners.length;
  421. while (count--)
  422. CFBundleSpriteSupportListeners[count]();
  423. }
  424. function CFBundleTestSpriteTypes(/*Array*/ spriteTypes)
  425. {
  426. // If we don't support Images, then clearly we don't support sprites.
  427. if (!("Image" in global) || spriteTypes.length < 2)
  428. {
  429. CFBundleSupportedSpriteType = CFBundleNoSpriteType;
  430. CFBundleNotifySpriteSupportListeners();
  431. return;
  432. }
  433. var image = new Image();
  434. image.onload = function()
  435. {
  436. if (image.width === 1 && image.height === 1)
  437. {
  438. CFBundleSupportedSpriteType = spriteTypes[0];
  439. CFBundleNotifySpriteSupportListeners();
  440. }
  441. else
  442. image.onerror();
  443. };
  444. image.onerror = function()
  445. {
  446. CFBundleTestSpriteTypes(spriteTypes.slice(2));
  447. };
  448. image.src = spriteTypes[1];
  449. }
  450. function executeBundle(/*Bundle*/ aBundle, /*Function*/ aCallback)
  451. {
  452. var staticResources = [aBundle._staticResource];
  453. function executeStaticResources(index)
  454. {
  455. for (; index < staticResources.length; ++index)
  456. {
  457. var staticResource = staticResources[index];
  458. if (staticResource.isNotFound())
  459. continue;
  460. if (staticResource.isFile())
  461. {
  462. var executable = new FileExecutable(staticResource.URL());
  463. if (executable.hasLoadedFileDependencies())
  464. executable.execute();
  465. else
  466. {
  467. executable.loadFileDependencies(function()
  468. {
  469. executeStaticResources(index);
  470. });
  471. return;
  472. }
  473. }
  474. else //if (staticResource.isDirectory())
  475. {
  476. // We don't want to execute resources.
  477. if (staticResource.URL().absoluteString() === aBundle.resourcesDirectoryURL().absoluteString())
  478. continue;
  479. var children = staticResource.children();
  480. for (var name in children)
  481. if (hasOwnProperty.call(children, name))
  482. staticResources.push(children[name]);
  483. }
  484. }
  485. aCallback();
  486. }
  487. executeStaticResources(0);
  488. }
  489. var STATIC_MAGIC_NUMBER = "@STATIC",
  490. MARKER_PATH = "p",
  491. MARKER_URI = "u",
  492. MARKER_CODE = "c",
  493. MARKER_TEXT = "t",
  494. MARKER_IMPORT_STD = 'I',
  495. MARKER_IMPORT_LOCAL = 'i';
  496. function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString, /*String*/ aPath)
  497. {
  498. var stream = new MarkedStream(aString);
  499. if (stream.magicNumber() !== STATIC_MAGIC_NUMBER)
  500. throw new Error("Could not read static file: " + aPath);
  501. if (stream.version() !== "1.0")
  502. throw new Error("Could not read static file: " + aPath);
  503. var marker,
  504. bundleURL = aBundle.bundleURL(),
  505. file = NULL;
  506. while (marker = stream.getMarker())
  507. {
  508. var text = stream.getString();
  509. if (marker === MARKER_PATH)
  510. {
  511. var fileURL = new CFURL(text, bundleURL),
  512. parent = StaticResource.resourceAtURL(new CFURL(".", fileURL), YES);
  513. file = new StaticResource(fileURL, parent, NO, YES);
  514. }
  515. else if (marker === MARKER_URI)
  516. {
  517. var URL = new CFURL(text, bundleURL),
  518. mappedURLString = stream.getString();
  519. if (mappedURLString.indexOf("mhtml:") === 0)
  520. {
  521. mappedURLString = "mhtml:" + new CFURL(mappedURLString.substr("mhtml:".length), bundleURL);
  522. if (CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType)
  523. {
  524. var exclamationIndex = mappedURLString.indexOf("!"),
  525. firstPart = mappedURLString.substring(0, exclamationIndex),
  526. lastPart = mappedURLString.substring(exclamationIndex);
  527. mappedURLString = firstPart + "?" + CFCacheBuster + lastPart;
  528. }
  529. }
  530. CFURL.setMappedURLForURL(URL, new CFURL(mappedURLString));
  531. // The unresolved directories must not be bundles.
  532. var parent = StaticResource.resourceAtURL(new CFURL(".", URL), YES);
  533. new StaticResource(URL, parent, NO, YES);
  534. }
  535. else if (marker === MARKER_TEXT)
  536. file.write(text);
  537. }
  538. }
  539. // Event Managament
  540. CFBundle.prototype.addEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
  541. {
  542. this._eventDispatcher.addEventListener(anEventName, anEventListener);
  543. };
  544. DISPLAY_NAME(CFBundle.prototype.addEventListener);
  545. CFBundle.prototype.removeEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
  546. {
  547. this._eventDispatcher.removeEventListener(anEventName, anEventListener);
  548. };
  549. DISPLAY_NAME(CFBundle.prototype.removeEventListener);
  550. CFBundle.prototype.onerror = function(/*Event*/ anEvent)
  551. {
  552. throw anEvent.error;
  553. };
  554. DISPLAY_NAME(CFBundle.prototype.onerror);
  555. CFBundle.prototype.bundlePath = function()
  556. {
  557. return this.bundleURL().path();
  558. };
  559. CFBundle.prototype.path = function()
  560. {
  561. CPLog.warn("CFBundle.prototype.path is deprecated, use CFBundle.prototype.bundlePath instead.");
  562. return this.bundlePath.apply(this, arguments);
  563. };
  564. CFBundle.prototype.pathForResource = function(aResource)
  565. {
  566. return this.resourceURL(aResource).absoluteString();
  567. };