Clear_();
}
+ _finline operator Type_ *() const {
+ return value_;
+ }
+
_finline This_ &operator =(Type_ *value) {
if (value_ != value) {
- Clear_();
+ Type_ *old(value_);
value_ = value;
Retain_();
- } return this;
+ if (old != nil)
+ [old release];
+ } return *this;
}
};
/* }}} */
#define lprintf(args...) fprintf(stderr, args)
#define ForRelease 0
+#define TraceLogging (1 && !ForRelease)
+#define HistogramInsertionSort (0 && !ForRelease)
+#define ProfileTimes (1 && !ForRelease)
#define ForSaurik (0 && !ForRelease)
#define LogBrowser (0 && !ForRelease)
#define TrackResize (0 && !ForRelease)
#define RecycleWebViews 0
#define AlwaysReload (1 && !ForRelease)
-#if ForRelease
+#if !TraceLogging
#undef _trace
#define _trace(args...)
+#endif
+
+#if !ProfileTimes
#undef _profile
#define _profile(name) {
#undef _end
#endif
/* Radix Sort {{{ */
+typedef uint32_t (*SKRadixFunction)(id, void *);
+
@interface NSMutableArray (Radix)
- (void) radixSortUsingSelector:(SEL)selector withObject:(id)object;
-- (void) radixSortUsingFunction:(uint32_t (*)(id, void *))function withArgument:(void *)argument;
+- (void) radixSortUsingFunction:(SKRadixFunction)function withContext:(void *)argument;
@end
struct RadixItem_ {
RadixSort_(self, count, swap);
}
-- (void) radixSortUsingFunction:(uint32_t (*)(id, void *))function withArgument:(void *)argument {
+- (void) radixSortUsingFunction:(SKRadixFunction)function withContext:(void *)argument {
size_t count([self count]);
struct RadixItem_ *swap(new RadixItem_[count * 2]);
/* }}} */
/* Insertion Sort {{{ */
+CFIndex SKBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
+ const char *ptr = (const char *)list;
+ while (0 < count) {
+ CFIndex half = count / 2;
+ const char *probe = ptr + elementSize * half;
+ CFComparisonResult cr = comparator(element, probe, context);
+ if (0 == cr) return (probe - (const char *)list) / elementSize;
+ ptr = (cr < 0) ? ptr : probe + elementSize;
+ count = (cr < 0) ? half : (half + (count & 1) - 1);
+ }
+ return (ptr - (const char *)list) / elementSize;
+}
+
CFIndex CFBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
const char *ptr = (const char *)list;
while (0 < count) {
const void **values(new const void *[range.length]);
CFArrayGetValues(array, range, values);
+#if HistogramInsertionSort
+ uint32_t total(0), *offsets(new uint32_t[range.length]);
+#endif
+
for (CFIndex index(1); index != range.length; ++index) {
const void *value(values[index]);
- CFIndex correct(CFBSearch_(&value, sizeof(const void *), values, index, comparator, context));
- //NSLog(@"%u %u", index, correct);
+ //CFIndex correct(SKBSearch_(&value, sizeof(const void *), values, index, comparator, context));
+ CFIndex correct(index);
+ while (comparator(value, values[correct - 1], context) == kCFCompareLessThan)
+ if (--correct == 0)
+ break;
if (correct != index) {
- memmove(values + correct + 1, values + correct, sizeof(const void *) * (index - correct));
+ size_t offset(index - correct);
+#if HistogramInsertionSort
+ total += offset;
+ ++offsets[offset];
+ if (offset > 10)
+ NSLog(@"Heavy Insertion Displacement: %u = %@", offset, value);
+#endif
+ memmove(values + correct + 1, values + correct, sizeof(const void *) * offset);
values[correct] = value;
}
}
CFArrayReplaceValues(array, range, values, range.length);
delete [] values;
+
+#if HistogramInsertionSort
+ for (CFIndex index(0); index != range.length; ++index)
+ if (offsets[index] != 0)
+ NSLog(@"Insertion Displacement [%u]: %u", index, offsets[index]);
+ NSLog(@"Average Insertion Displacement: %f", double(total) / range.length);
+ delete [] offsets;
+#endif
}
/* }}} */
return size_ == rhs.size_ && memcmp(data_, rhs.data_, size_) == 0;
}
- operator id() {
+ operator CFStringRef() {
if (cache_ == NULL) {
if (size_ == 0)
return nil;
cache_ = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<uint8_t *>(data_), size_, kCFStringEncodingUTF8, NO, kCFAllocatorNull);
- } return (id) cache_;
+ } return cache_;
+ }
+
+ _finline operator id() {
+ return (NSString *) static_cast<CFStringRef>(*this);
}
};
return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
}
-NSString *StripVersion(const char *version) {
+static _finline CFStringRef CFCString(const char *value) {
+ return CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const uint8_t *>(value), strlen(value), kCFStringEncodingUTF8, NO, kCFAllocatorNull);
+}
+
+CFStringRef StripVersion(const char *version) {
const char *colon(strchr(version, ':'));
if (colon != NULL)
version = colon + 1;
- return [NSString stringWithUTF8String:version];
-}
-
-NSString *StripVersion(NSString *version) {
- NSRange colon = [version rangeOfString:@":"];
- if (colon.location != NSNotFound)
- version = [version substringFromIndex:(colon.location + 1)];
- return version;
+ return CFCString(version);
}
NSString *LocalizeSection(NSString *section) {
return title;
}
-
-_finline static void Stifle(char &value) {
- value = (value & 0xdf) ^ 0x40;
-}
/* }}} */
bool isSectionVisible(NSString *section) {
/* }}} */
/* Database Interface {{{ */
+typedef std::map< unsigned long, _H<Source> > SourceMap;
+
@interface Database : NSObject {
NSZone *zone_;
apr_pool_t *pool_;
SPtr<pkgPackageManager> manager_;
pkgSourceList *list_;
- NSMutableDictionary *sources_;
+ SourceMap sources_;
NSMutableArray *packages_;
_transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
/* Package Class {{{ */
@interface Package : NSObject {
unsigned era_;
+ apr_pool_t *pool_;
pkgCache::VerIterator version_;
pkgCache::PkgIterator iterator_;
Source *source_;
bool cached_;
+ bool parsed_;
CYString section_;
NSString *section$_;
Address *author$_;
CYString support_;
- NSArray *tags_;
+ NSMutableArray *tags_;
NSString *role_;
NSArray *relationships_;
+ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
- (pkgCache::PkgIterator) iterator;
+- (void) parse;
- (NSString *) section;
- (NSString *) simpleSection;
- (NSArray *) purposes;
- (bool) isCommercial;
-- (uint32_t) compareByPrefix;
+- (CYString &) cyname;
+
- (uint32_t) compareBySection:(NSArray *)sections;
- (uint32_t) compareForChanges;
return _not(uint32_t) - value.key;
}
-CFStringRef (*PackageName)(Package *self, SEL sel);
+_finline static void Stifle(uint8_t &value) {
+}
-CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
- _profile(PackageNameCompare)
- CFStringRef lhn, rhn;
- CFIndex length;
+uint32_t PackagePrefixRadix(Package *self, void *context) {
+ size_t offset(reinterpret_cast<size_t>(context));
+ CYString &name([self cyname]);
- _profile(PackageNameCompare$Setup)
- lhn = PackageName(lhs, @selector(name));
- rhn = PackageName(rhs, @selector(name));
- _end
+ size_t size(name.size());
+ if (size == 0)
+ return 0;
+ char *text(name.data());
- _profile(PackageNameCompare$Nothing)
- _end
+ size_t zeros;
+ if (!isdigit(text[0]))
+ zeros = 0;
+ else {
+ size_t digits(1);
+ while (size != digits && isdigit(text[digits]))
+ if (++digits == 4)
+ break;
+ zeros = 4 - digits;
+ }
- _profile(PackageNameCompare$Length)
- length = CFStringGetLength(lhn);
- _end
+ uint8_t data[4];
+
+ // 0.607997
+
+ if (offset == 0 && zeros != 0) {
+ memset(data, '0', zeros);
+ memcpy(data + zeros, text, 4 - zeros);
+ } else {
+ /* XXX: there's some danger here if you request a non-zero offset < 4 and it gets zero padded */
+ if (size <= offset - zeros)
+ return 0;
+
+ text += offset - zeros;
+ size -= offset - zeros;
+
+ if (size >= 4)
+ memcpy(data, text, 4);
+ else {
+ memcpy(data, text, size);
+ memset(data + size, 0, 4 - size);
+ }
+
+ for (size_t i(0); i != 4; ++i)
+ if (isalpha(data[i]))
+ data[i] &= 0xdf;
+ }
+
+ if (offset == 0)
+ data[0] = (data[0] & 0x3f) | "\x80\x00\xc0\x40"[data[0] >> 6];
+
+ /* XXX: ntohl may be more honest */
+ return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
+}
+
+CYString &(*PackageName)(Package *self, SEL sel);
+
+CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
+ _profile(PackageNameCompare)
+ CYString &lhi(PackageName(lhs, @selector(cyname)));
+ CYString &rhi(PackageName(rhs, @selector(cyname)));
+ CFStringRef lhn(lhi), rhn(rhi);
_profile(PackageNameCompare$NumbersLast)
- if (length != 0 && CFStringGetLength(rhn) != 0) {
+ if (!lhi.empty() && !rhi.empty()) {
UniChar lhc(CFStringGetCharacterAtIndex(lhn, 0));
UniChar rhc(CFStringGetCharacterAtIndex(rhn, 0));
bool lha(CFUniCharIsMemberOf(lhc, kCFUniCharLetterCharacterSet));
}
_end
+ CFIndex length = CFStringGetLength(lhn);
+
_profile(PackageNameCompare$Compare)
return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, Locale_);
_end
@implementation Package
+- (NSString *) description {
+ return [NSString stringWithFormat:@"<Package:%@>", static_cast<NSString *>(name_)];
+}
+
- (void) dealloc {
if (source_ != nil)
[source_ release];
return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
}
+- (void) parse {
+ if (parsed_)
+ return;
+ parsed_ = true;
+ if (file_.end())
+ return;
+
+ _profile(Package$parse)
+ pkgRecords::Parser *parser;
+
+ _profile(Package$parse$Lookup)
+ parser = &[database_ records]->Lookup(file_);
+ _end
+
+ CYString website;
+
+ _profile(Package$parse$Find)
+ struct {
+ const char *name_;
+ CYString *value_;
+ } names[] = {
+ {"icon", &icon_},
+ {"depiction", &depiction_},
+ {"homepage", &homepage_},
+ {"website", &website},
+ {"support", &support_},
+ {"sponsor", &sponsor_},
+ {"author", &author_},
+ };
+
+ for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
+ const char *start, *end;
+
+ if (parser->Find(names[i].name_, start, end)) {
+ CYString &value(*names[i].value_);
+ _profile(Package$parse$Value)
+ value.set(pool_, start, end - start);
+ _end
+ }
+ }
+ _end
+
+ _profile(Package$parse$Tagline)
+ const char *start, *end;
+ if (parser->ShortDesc(start, end)) {
+ const char *stop(reinterpret_cast<const char *>(memchr(start, '\n', end - start)));
+ if (stop == NULL)
+ stop = end;
+ while (stop != start && stop[-1] == '\r')
+ --stop;
+ tagline_.set(pool_, start, stop - start);
+ }
+ _end
+
+ _profile(Package$parse$Retain)
+ if (!homepage_.empty())
+ homepage_ = website;
+ if (homepage_ == depiction_)
+ homepage_.clear();
+ _end
+ _end
+}
+
- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
if ((self = [super init]) != nil) {
_profile(Package$initWithVersion)
@synchronized (database) {
era_ = [database era];
+ pool_ = pool;
version_ = version;
iterator_ = version.ParentPkg();
database_ = database;
_profile(Package$initWithVersion$Latest)
- latest_ = [StripVersion(version_.VerStr()) retain];
+ latest_ = (NSString *) StripVersion(version_.VerStr());
_end
- pkgCache::VerIterator current(iterator_.CurrentVer());
- if (!current.end())
- installed_ = [StripVersion(current.VerStr()) retain];
+ pkgCache::VerIterator current;
+ _profile(Package$initWithVersion$Versions)
+ current = iterator_.CurrentVer();
+ if (!current.end())
+ installed_ = (NSString *) StripVersion(current.VerStr());
- if (!version_.end())
- file_ = version_.FileList();
- else {
- pkgCache &cache([database_ cache]);
- file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
- }
+ if (!version_.end())
+ file_ = version_.FileList();
+ else {
+ pkgCache &cache([database_ cache]);
+ file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
+ }
+ _end
_profile(Package$initWithVersion$Name)
- id_.set(pool, iterator_.Name());
+ id_.set(pool_, iterator_.Name());
+ name_.set(pool, iterator_.Display());
_end
- if (!file_.end())
- source_ = [database_ getSource:file_.File()];
- if (source_ != nil)
- [source_ retain];
- cached_ = true;
-
- _profile(Package$initWithVersion$Parse)
- pkgRecords::Parser *parser;
-
- _profile(Package$initWithVersion$Parse$Lookup)
- parser = &[database_ records]->Lookup(file_);
- _end
-
- CYString website;
- CYString tag;
-
- _profile(Package$initWithVersion$Parse$Find)
- struct {
- const char *name_;
- CYString *value_;
- } names[] = {
- {"name", &name_},
- {"icon", &icon_},
- {"depiction", &depiction_},
- {"homepage", &homepage_},
- {"website", &website},
- {"support", &support_},
- {"sponsor", &sponsor_},
- {"author", &author_},
- {"tag", &tag},
- };
-
- for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
- const char *start, *end;
-
- if (parser->Find(names[i].name_, start, end)) {
- CYString &value(*names[i].value_);
- _profile(Package$initWithVersion$Parse$Value)
- value.set(pool, start, end - start);
- _end
- }
- }
- _end
-
- _profile(Package$initWithVersion$Parse$Tagline)
- tagline_.set(pool, parser->ShortDesc());
- _end
-
- _profile(Package$initWithVersion$Parse$Retain)
- if (!homepage_.empty())
- homepage_ = website;
- if (homepage_ == depiction_)
- homepage_.clear();
- if (!tag.empty())
- tags_ = [[tag componentsSeparatedByString:@", "] retain];
- _end
+ if (!file_.end()) {
+ _profile(Package$initWithVersion$Source)
+ source_ = [database_ getSource:file_.File()];
+ if (source_ != nil)
+ [source_ retain];
+ cached_ = true;
_end
+ }
_profile(Package$initWithVersion$Tags)
- if (tags_ != nil)
- for (NSString *tag in tags_)
- if ([tag hasPrefix:@"role::"]) {
- role_ = [[tag substringFromIndex:6] retain];
- break;
- }
+ pkgCache::TagIterator tag(iterator_.TagList());
+ if (!tag.end()) {
+ tags_ = [[NSMutableArray alloc] initWithCapacity:8];
+ do {
+ const char *name(tag.Name());
+ [tags_ addObject:(NSString *)CFCString(name)];
+ if (role_ == nil && strncmp(name, "role::", 6) == 0)
+ role_ = (NSString *) CFCString(name + 6);
+ ++tag;
+ } while (!tag.end());
+ }
_end
bool changed(false);
metadata_ = [Packages_ objectForKey:key];
if (metadata_ == nil) {
- firstSeen_ = [now_ retain];
+ firstSeen_ = now_;
metadata_ = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
firstSeen_, @"FirstSeen",
_end
_profile(Package$initWithVersion$Section)
- section_.set(pool, iterator_.Section());
+ section_.set(pool_, iterator_.Section());
_end
essential_ = ((iterator_->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES) || [self hasTag:@"cydia::essential"];
NSString *section = [self simpleSection];
UIImage *icon(nil);
- if (icon_ != nil)
+ if (!icon_.empty())
if ([icon_ hasPrefix:@"file:///"])
icon = [UIImage imageAtPath:[icon_ substringFromIndex:7]];
if (icon == nil) if (section != nil)
}
- (NSString *) support {
- return support_ != nil ? support_ : [[self source] supportForPackage:id_];
+ return !support_.empty() ? support_ : [[self source] supportForPackage:id_];
}
- (NSArray *) files {
return [self hasTag:@"cydia::commercial"];
}
-- (uint32_t) compareByPrefix {
- size_t offset(0);
-
- CYString &name(name_.empty() ? id_ : name_);
- if (name.size() <= offset)
- return 0;
- size_t size(name.size() - offset);
-
- char data[4];
- if (size >= 4)
- memcpy(data, name.data() + offset, 4);
- else {
- memcpy(data, name.data() + offset, size);
- memset(data + size, 0, 4 - size);
- }
-
- Stifle(data[0]);
- Stifle(data[1]);
- Stifle(data[2]);
- Stifle(data[3]);
-
- /* XXX: ntohl may be more honest */
- return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
+- (CYString &) cyname {
+ return name_.empty() ? id_ : name_;
}
- (uint32_t) compareBySection:(NSArray *)sections {
zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
apr_pool_create(&pool_, NULL);
- sources_ = [[NSMutableDictionary dictionaryWithCapacity:16] retain];
packages_ = [[NSMutableArray alloc] init];
int fds[2];
}
- (NSArray *) sources {
- return [sources_ allValues];
+ NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]);
+ for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i)
+ [sources addObject:i->second];
+ return sources;
}
- (NSArray *) issues {
++era_;
}
+ [packages_ removeAllObjects];
+ sources_.clear();
+
_error->Discard();
delete list_;
delete policy_;
policy_ = NULL;
+ if (now_ != nil) {
+ [now_ release];
+ now_ = nil;
+ }
+
cache_.Close();
apr_pool_clear(pool_);
}
_trace();
- {
- std::string lists(_config->FindDir("Dir::State::lists"));
-
- [sources_ removeAllObjects];
- for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
- std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
- for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
- if (debPackagesIndex *packages = dynamic_cast<debPackagesIndex *>(*index))
- [sources_
- setObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]
- forKey:[NSString stringWithFormat:@"%s%s",
- lists.c_str(),
- URItoFileName(packages->IndexURI("Packages")).c_str()
- ]
- ];
- }
+
+ for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
+ std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
+ for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
+ // XXX: this could be more intelligent
+ if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
+ pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
+ if (!cached.end())
+ sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source] autorelease];
+ }
}
+
_trace();
{
[packages_ release];
packages_ = nil;*/
- [packages_ removeAllObjects];
-
_trace();
for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
packages_ = [[NSArray alloc] initWithObjects:&packages.front() count:packages.size()];
_trace();*/
- [packages_ radixSortUsingSelector:@selector(compareByPrefix) withObject:NULL];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(16)];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(4)];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
/*_trace();
PrintTimes();
//CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
- CFArrayInsertionSortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);
+ CFArrayInsertionSortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
//[packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
}
- (Source *) getSource:(pkgCache::PkgFileIterator)file {
- if (const char *name = file.FileName())
- if (Source *source = [sources_ objectForKey:[NSString stringWithUTF8String:name]])
- return source;
- return nil;
+ return sources_[file->ID];
}
@end
- (void) setPackage:(Package *)package {
[self clearPackage];
+ [package parse];
Source *source = [package source];
}
+ (int) heightForPackage:(Package *)package {
- NSString *tagline([package shortDescription]);
- int height = tagline == nil || [tagline length] == 0 ? -17 : 0;
-#ifdef USE_BADGES
- if ([package hasMode] || [package half])
- return height + 96;
- else
-#endif
- return height + 73;
+ return 73;
}
@end
[buttons_ removeAllObjects];
if (package != nil) {
+ [package parse];
+
package_ = [package retain];
name_ = [[package id] retain];
commercial_ = [package isCommercial];
[packages_ addObject:package];
_trace();
- [packages_ radixSortUsingFunction:reinterpret_cast<uint32_t (*)(id, void *)>(&PackageChangesRadix) withArgument:NULL];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
_trace();
Section *upgradable = [[[Section alloc] initWithName:CYLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
int main(int argc, char *argv[]) { _pooled
_trace();
- PackageName = reinterpret_cast<CFStringRef (*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(name))));
+ PackageName = reinterpret_cast<CYString &(*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname))));
/* Library Hacks {{{ */
class_addMethod(objc_getClass("DOMNodeList"), @selector(countByEnumeratingWithState:objects:count:), (IMP) &DOMNodeList$countByEnumeratingWithState$objects$count$, "I20@0:4^{NSFastEnumerationState}8^@12I16");