GPBStringTests.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. // Protocol Buffers - Google's data interchange format
  2. // Copyright 2008 Google Inc. All rights reserved.
  3. // https://developers.google.com/protocol-buffers/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are
  7. // met:
  8. //
  9. // * Redistributions of source code must retain the above copyright
  10. // notice, this list of conditions and the following disclaimer.
  11. // * Redistributions in binary form must reproduce the above
  12. // copyright notice, this list of conditions and the following disclaimer
  13. // in the documentation and/or other materials provided with the
  14. // distribution.
  15. // * Neither the name of Google Inc. nor the names of its
  16. // contributors may be used to endorse or promote products derived from
  17. // this software without specific prior written permission.
  18. //
  19. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. #import <XCTest/XCTest.h>
  31. #import "GPBCodedInputStream_PackagePrivate.h"
  32. #import "GPBTestUtilities.h"
  33. @interface TestClass : NSObject
  34. @property(nonatomic, retain) NSString *foo;
  35. @end
  36. @implementation TestClass
  37. @synthesize foo;
  38. @end
  39. @interface GPBStringTests : XCTestCase {
  40. NSMutableArray *nsStrings_;
  41. NSMutableArray *gpbStrings_;
  42. }
  43. @end
  44. @implementation GPBStringTests
  45. - (void)setUp {
  46. [super setUp];
  47. const char *strings[] = {
  48. "ascii string",
  49. "non-ascii string \xc3\xa9", // e with acute accent
  50. "\xe2\x99\xa1", // White Heart
  51. "mix \xe2\x99\xa4 string", // White Spade
  52. // Decomposed forms from http://www.unicode.org/reports/tr15/
  53. // 1.2 Singletons
  54. "\xe2\x84\xa8 = A\xcc\x8a = \xc3\x85", "\xe2\x84\xa6 = \xce\xa9",
  55. // 1.2 Canonical Composites
  56. "A\xcc\x8a = \xc3\x85",
  57. "o\xcc\x82 = \xc3\xb4",
  58. // 1.2 Multiple Combining Marks
  59. "s\xcc\xa3\xcc\x87 = \xe1\xb9\xa9",
  60. "\xe1\xb8\x8b\xcc\xa3 = d\xcc\xa3\xcc\x87 = \xe1\xb8\x8d \xcc\x87",
  61. "q\xcc\x87\xcc\xa3 = q\xcc\xa3\xcc\x87",
  62. // BOM
  63. "\xEF\xBB\xBF String with BOM",
  64. "String with \xEF\xBB\xBF in middle",
  65. "String with end bom \xEF\xBB\xBF",
  66. "\xEF\xBB\xBF\xe2\x99\xa1", // BOM White Heart
  67. "\xEF\xBB\xBF\xEF\xBB\xBF String with Two BOM",
  68. // Supplementary Plane
  69. "\xf0\x9d\x84\x9e", // MUSICAL SYMBOL G CLEF
  70. // Tags
  71. "\xf3\xa0\x80\x81", // Language Tag
  72. // Variation Selectors
  73. "\xf3\xa0\x84\x80", // VARIATION SELECTOR-17
  74. // Specials
  75. "\xef\xbb\xbf\xef\xbf\xbd\xef\xbf\xbf",
  76. // Left To Right/Right To Left
  77. // http://unicode.org/reports/tr9/
  78. // Hello! <RTL marker>!Merhaba<LTR marker>
  79. "Hello! \xE2\x80\x8F!\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7\xE2\x80\x8E",
  80. "\xE2\x80\x8E LTR At Start",
  81. "LTR At End\xE2\x80\x8E",
  82. "\xE2\x80\x8F RTL At Start",
  83. "RTL At End\xE2\x80\x8F",
  84. "\xE2\x80\x8E\xE2\x80\x8E Double LTR \xE2\x80\x8E\xE2\x80\x8E",
  85. "\xE2\x80\x8F\xE2\x80\x8F Double RTL \xE2\x80\x8F\xE2\x80\x8F",
  86. "\xE2\x80\x8F\xE2\x80\x8E LTR-RTL LTR-RTL \xE2\x80\x8E\xE2\x80\x8F",
  87. };
  88. size_t stringsSize = GPBARRAYSIZE(strings);
  89. size_t numberUnicodeStrings = 17375;
  90. nsStrings_ = [[NSMutableArray alloc]
  91. initWithCapacity:stringsSize + numberUnicodeStrings];
  92. gpbStrings_ = [[NSMutableArray alloc]
  93. initWithCapacity:stringsSize + numberUnicodeStrings];
  94. for (size_t i = 0; i < stringsSize; ++i) {
  95. size_t length = strlen(strings[i]);
  96. NSString *nsString = [[NSString alloc] initWithBytes:strings[i]
  97. length:length
  98. encoding:NSUTF8StringEncoding];
  99. [nsStrings_ addObject:nsString];
  100. [nsString release];
  101. GPBString *gpbString = GPBCreateGPBStringWithUTF8(strings[i], length);
  102. [gpbStrings_ addObject:gpbString];
  103. [gpbString release];
  104. }
  105. // Generate all UTF8 characters in a variety of strings
  106. // UTF8-1 - 1 Byte UTF 8 chars
  107. int length = 0x7F + 1;
  108. char *buffer = (char *)calloc(length, 1);
  109. for (int i = 0; i < length; ++i) {
  110. buffer[i] = (char)i;
  111. }
  112. NSString *nsString = [[NSString alloc] initWithBytes:buffer
  113. length:length
  114. encoding:NSUTF8StringEncoding];
  115. [nsStrings_ addObject:nsString];
  116. [nsString release];
  117. GPBString *gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  118. [gpbStrings_ addObject:gpbString];
  119. [gpbString release];
  120. // UTF8-2 - 2 Byte UTF 8 chars
  121. int pointLength = 0xbf - 0x80 + 1;
  122. length = pointLength * 2;
  123. buffer = (char *)calloc(length, 1);
  124. for (int i = 0xc2; i <= 0xdf; ++i) {
  125. char *bufferPtr = buffer;
  126. for (int j = 0x80; j <= 0xbf; ++j) {
  127. (*bufferPtr++) = (char)i;
  128. (*bufferPtr++) = (char)j;
  129. }
  130. nsString = [[NSString alloc] initWithBytes:buffer
  131. length:length
  132. encoding:NSUTF8StringEncoding];
  133. [nsStrings_ addObject:nsString];
  134. [nsString release];
  135. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  136. [gpbStrings_ addObject:gpbString];
  137. [gpbString release];
  138. }
  139. free(buffer);
  140. // UTF8-3 - 3 Byte UTF 8 chars
  141. length = pointLength * 3;
  142. buffer = (char *)calloc(length, 1);
  143. for (int i = 0xa0; i <= 0xbf; ++i) {
  144. char *bufferPtr = buffer;
  145. for (int j = 0x80; j <= 0xbf; ++j) {
  146. (*bufferPtr++) = (char)0xE0;
  147. (*bufferPtr++) = (char)i;
  148. (*bufferPtr++) = (char)j;
  149. }
  150. nsString = [[NSString alloc] initWithBytes:buffer
  151. length:length
  152. encoding:NSUTF8StringEncoding];
  153. [nsStrings_ addObject:nsString];
  154. [nsString release];
  155. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  156. [gpbStrings_ addObject:gpbString];
  157. [gpbString release];
  158. }
  159. for (int i = 0xe1; i <= 0xec; ++i) {
  160. for (int j = 0x80; j <= 0xbf; ++j) {
  161. char *bufferPtr = buffer;
  162. for (int k = 0x80; k <= 0xbf; ++k) {
  163. (*bufferPtr++) = (char)i;
  164. (*bufferPtr++) = (char)j;
  165. (*bufferPtr++) = (char)k;
  166. }
  167. nsString = [[NSString alloc] initWithBytes:buffer
  168. length:length
  169. encoding:NSUTF8StringEncoding];
  170. [nsStrings_ addObject:nsString];
  171. [nsString release];
  172. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  173. [gpbStrings_ addObject:gpbString];
  174. [gpbString release];
  175. }
  176. }
  177. for (int i = 0x80; i <= 0x9f; ++i) {
  178. char *bufferPtr = buffer;
  179. for (int j = 0x80; j <= 0xbf; ++j) {
  180. (*bufferPtr++) = (char)0xED;
  181. (*bufferPtr++) = (char)i;
  182. (*bufferPtr++) = (char)j;
  183. }
  184. nsString = [[NSString alloc] initWithBytes:buffer
  185. length:length
  186. encoding:NSUTF8StringEncoding];
  187. [nsStrings_ addObject:nsString];
  188. [nsString release];
  189. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  190. [gpbStrings_ addObject:gpbString];
  191. [gpbString release];
  192. }
  193. for (int i = 0xee; i <= 0xef; ++i) {
  194. for (int j = 0x80; j <= 0xbf; ++j) {
  195. char *bufferPtr = buffer;
  196. for (int k = 0x80; k <= 0xbf; ++k) {
  197. (*bufferPtr++) = (char)i;
  198. (*bufferPtr++) = (char)j;
  199. (*bufferPtr++) = (char)k;
  200. }
  201. nsString = [[NSString alloc] initWithBytes:buffer
  202. length:length
  203. encoding:NSUTF8StringEncoding];
  204. [nsStrings_ addObject:nsString];
  205. [nsString release];
  206. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  207. [gpbStrings_ addObject:gpbString];
  208. [gpbString release];
  209. }
  210. }
  211. free(buffer);
  212. // UTF8-4 - 4 Byte UTF 8 chars
  213. length = pointLength * 4;
  214. buffer = (char *)calloc(length, 1);
  215. for (int i = 0x90; i <= 0xbf; ++i) {
  216. for (int j = 0x80; j <= 0xbf; ++j) {
  217. char *bufferPtr = buffer;
  218. for (int k = 0x80; k <= 0xbf; ++k) {
  219. (*bufferPtr++) = (char)0xF0;
  220. (*bufferPtr++) = (char)i;
  221. (*bufferPtr++) = (char)j;
  222. (*bufferPtr++) = (char)k;
  223. }
  224. nsString = [[NSString alloc] initWithBytes:buffer
  225. length:length
  226. encoding:NSUTF8StringEncoding];
  227. [nsStrings_ addObject:nsString];
  228. [nsString release];
  229. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  230. [gpbStrings_ addObject:gpbString];
  231. [gpbString release];
  232. }
  233. }
  234. for (int i = 0xf1; i <= 0xf3; ++i) {
  235. for (int j = 0x80; j <= 0xbf; ++j) {
  236. for (int k = 0x80; k <= 0xbf; ++k) {
  237. char *bufferPtr = buffer;
  238. for (int m = 0x80; m <= 0xbf; ++m) {
  239. (*bufferPtr++) = (char)i;
  240. (*bufferPtr++) = (char)j;
  241. (*bufferPtr++) = (char)k;
  242. (*bufferPtr++) = (char)m;
  243. }
  244. nsString = [[NSString alloc] initWithBytes:buffer
  245. length:length
  246. encoding:NSUTF8StringEncoding];
  247. [nsStrings_ addObject:nsString];
  248. [nsString release];
  249. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  250. [gpbStrings_ addObject:gpbString];
  251. [gpbString release];
  252. }
  253. }
  254. }
  255. for (int i = 0x80; i <= 0x8f; ++i) {
  256. for (int j = 0x80; j <= 0xbf; ++j) {
  257. char *bufferPtr = buffer;
  258. for (int k = 0x80; k <= 0xbf; ++k) {
  259. (*bufferPtr++) = (char)0xF4;
  260. (*bufferPtr++) = (char)i;
  261. (*bufferPtr++) = (char)j;
  262. (*bufferPtr++) = (char)k;
  263. }
  264. nsString = [[NSString alloc] initWithBytes:buffer
  265. length:length
  266. encoding:NSUTF8StringEncoding];
  267. [nsStrings_ addObject:nsString];
  268. [nsString release];
  269. gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
  270. [gpbStrings_ addObject:gpbString];
  271. [gpbString release];
  272. }
  273. }
  274. free(buffer);
  275. }
  276. - (void)tearDown {
  277. [nsStrings_ release];
  278. [gpbStrings_ release];
  279. [super tearDown];
  280. }
  281. - (void)testLength {
  282. size_t i = 0;
  283. for (NSString *nsString in nsStrings_) {
  284. GPBString *gpbString = gpbStrings_[i];
  285. XCTAssertEqual([nsString length], [gpbString length], @"%@ %@", nsString,
  286. gpbString);
  287. ++i;
  288. }
  289. }
  290. - (void)testLengthOfBytesUsingEncoding {
  291. NSStringEncoding encodings[] = {
  292. NSUTF8StringEncoding,
  293. NSASCIIStringEncoding,
  294. NSISOLatin1StringEncoding,
  295. NSMacOSRomanStringEncoding,
  296. NSUTF16StringEncoding,
  297. NSUTF32StringEncoding,
  298. };
  299. for (size_t j = 0; j < GPBARRAYSIZE(encodings); ++j) {
  300. NSStringEncoding testEncoding = encodings[j];
  301. size_t i = 0;
  302. for (NSString *nsString in nsStrings_) {
  303. GPBString *gpbString = gpbStrings_[i];
  304. XCTAssertEqual([nsString lengthOfBytesUsingEncoding:testEncoding],
  305. [gpbString lengthOfBytesUsingEncoding:testEncoding],
  306. @"%@ %@", nsString, gpbString);
  307. ++i;
  308. }
  309. }
  310. }
  311. - (void)testHash {
  312. size_t i = 0;
  313. for (NSString *nsString in nsStrings_) {
  314. GPBString *gpbString = gpbStrings_[i];
  315. XCTAssertEqual([nsString hash], [gpbString hash], @"%@ %@", nsString,
  316. gpbString);
  317. ++i;
  318. }
  319. }
  320. - (void)testEquality {
  321. size_t i = 0;
  322. for (NSString *nsString in nsStrings_) {
  323. GPBString *gpbString = gpbStrings_[i];
  324. XCTAssertEqualObjects(nsString, gpbString);
  325. ++i;
  326. }
  327. }
  328. - (void)testCharacterAtIndex {
  329. size_t i = 0;
  330. for (NSString *nsString in nsStrings_) {
  331. GPBString *gpbString = gpbStrings_[i];
  332. NSUInteger length = [nsString length];
  333. for (size_t j = 0; j < length; ++j) {
  334. unichar nsChar = [nsString characterAtIndex:j];
  335. unichar pbChar = [gpbString characterAtIndex:j];
  336. XCTAssertEqual(nsChar, pbChar, @"%@ %@ %zu", nsString, gpbString, j);
  337. }
  338. ++i;
  339. }
  340. }
  341. - (void)testCopy {
  342. size_t i = 0;
  343. for (NSString *nsString in nsStrings_) {
  344. GPBString *gpbString = [[gpbStrings_[i] copy] autorelease];
  345. XCTAssertEqualObjects(nsString, gpbString);
  346. ++i;
  347. }
  348. }
  349. - (void)testMutableCopy {
  350. size_t i = 0;
  351. for (NSString *nsString in nsStrings_) {
  352. GPBString *gpbString = [[gpbStrings_[i] mutableCopy] autorelease];
  353. XCTAssertEqualObjects(nsString, gpbString);
  354. ++i;
  355. }
  356. }
  357. - (void)testGetBytes {
  358. // Do an attempt at a reasonably exhaustive test of get bytes.
  359. // Get bytes with options other than 0 should always fall through to Apple
  360. // code so we don't bother testing that path.
  361. size_t i = 0;
  362. char pbBuffer[256];
  363. char nsBuffer[256];
  364. int count = 0;
  365. for (NSString *nsString in nsStrings_) {
  366. GPBString *gpbString = gpbStrings_[i];
  367. for (int j = 0; j < 100; ++j) {
  368. // [NSString getBytes:maxLength:usedLength:encoding:options:range:remainingRange]
  369. // does not return reliable results if the maxLength argument is 0,
  370. // or range is 0,0.
  371. // Radar 16385183
  372. NSUInteger length = [nsString length];
  373. NSUInteger maxBufferCount = (arc4random() % (length + 3)) + 1;
  374. NSUInteger rangeStart = arc4random() % length;
  375. NSUInteger rangeLength = arc4random() % (length - rangeStart);
  376. NSRange range = NSMakeRange(rangeStart, rangeLength);
  377. NSStringEncoding encodings[] = {
  378. NSASCIIStringEncoding,
  379. NSUTF8StringEncoding,
  380. NSUTF16StringEncoding,
  381. };
  382. for (size_t k = 0; k < GPBARRAYSIZE(encodings); ++k) {
  383. NSStringEncoding encoding = encodings[k];
  384. NSUInteger pbUsedBufferCount = 0;
  385. NSUInteger nsUsedBufferCount = 0;
  386. NSRange pbLeftOver = NSMakeRange(0, 0);
  387. NSRange nsLeftOver = NSMakeRange(0, 0);
  388. BOOL pbGotBytes = [gpbString getBytes:pbBuffer
  389. maxLength:maxBufferCount
  390. usedLength:&pbUsedBufferCount
  391. encoding:encoding
  392. options:0
  393. range:range
  394. remainingRange:&pbLeftOver];
  395. BOOL nsGotBytes = [nsString getBytes:nsBuffer
  396. maxLength:maxBufferCount
  397. usedLength:&nsUsedBufferCount
  398. encoding:encoding
  399. options:0
  400. range:range
  401. remainingRange:&nsLeftOver];
  402. XCTAssertEqual(
  403. (bool)pbGotBytes, (bool)nsGotBytes,
  404. @"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
  405. @"Used: %tu, %tu LeftOver %@, %@)",
  406. count, gpbString, nsString, encoding, maxBufferCount,
  407. NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
  408. NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
  409. XCTAssertEqual(
  410. pbUsedBufferCount, nsUsedBufferCount,
  411. @"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
  412. @"Used: %tu, %tu LeftOver %@, %@)",
  413. count, gpbString, nsString, encoding, maxBufferCount,
  414. NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
  415. NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
  416. XCTAssertEqual(
  417. pbLeftOver.location, nsLeftOver.location,
  418. @"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
  419. @"Used: %tu, %tu LeftOver %@, %@)",
  420. count, gpbString, nsString, encoding, maxBufferCount,
  421. NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
  422. NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
  423. XCTAssertEqual(
  424. pbLeftOver.length, nsLeftOver.length,
  425. @"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
  426. @"Used: %tu, %tu LeftOver %@, %@)",
  427. count, gpbString, nsString, encoding, maxBufferCount,
  428. NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
  429. NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
  430. ++count;
  431. }
  432. }
  433. ++i;
  434. }
  435. }
  436. - (void)testLengthAndGetBytes {
  437. // This test exists as an attempt to ferret out a bug.
  438. // http://b/13516532
  439. size_t i = 0;
  440. char pbBuffer[256];
  441. char nsBuffer[256];
  442. for (NSString *nsString in nsStrings_) {
  443. GPBString *gpbString = gpbStrings_[i++];
  444. NSUInteger nsLength =
  445. [nsString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  446. NSUInteger pbLength =
  447. [gpbString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  448. XCTAssertEqual(nsLength, pbLength, @"%@ %@", nsString, gpbString);
  449. NSUInteger pbUsedBufferCount = 0;
  450. NSUInteger nsUsedBufferCount = 0;
  451. NSRange pbLeftOver = NSMakeRange(0, 0);
  452. NSRange nsLeftOver = NSMakeRange(0, 0);
  453. NSRange range = NSMakeRange(0, [gpbString length]);
  454. BOOL pbGotBytes = [gpbString getBytes:pbBuffer
  455. maxLength:sizeof(pbBuffer)
  456. usedLength:&pbUsedBufferCount
  457. encoding:NSUTF8StringEncoding
  458. options:0
  459. range:range
  460. remainingRange:&pbLeftOver];
  461. BOOL nsGotBytes = [nsString getBytes:nsBuffer
  462. maxLength:sizeof(nsBuffer)
  463. usedLength:&nsUsedBufferCount
  464. encoding:NSUTF8StringEncoding
  465. options:0
  466. range:range
  467. remainingRange:&nsLeftOver];
  468. XCTAssertTrue(pbGotBytes, @"%@", gpbString);
  469. XCTAssertTrue(nsGotBytes, @"%@", nsString);
  470. XCTAssertEqual(pbUsedBufferCount, pbLength, @"%@", gpbString);
  471. XCTAssertEqual(nsUsedBufferCount, nsLength, @"%@", nsString);
  472. }
  473. }
  474. @end