8b2c84620c
X-SVN-Rev: 20241
1226 lines
37 KiB
C++
1226 lines
37 KiB
C++
/*
|
|
**********************************************************************
|
|
* Copyright (C) 2002-2006, International Business Machines
|
|
* Corporation and others. All Rights Reserved.
|
|
**********************************************************************
|
|
* Date Name Description
|
|
* 10/11/02 aliu Creation.
|
|
**********************************************************************
|
|
*/
|
|
|
|
#include "unicode/utypes.h"
|
|
#include "unicode/putil.h"
|
|
#include "unicode/uclean.h"
|
|
#include "cmemory.h"
|
|
#include "cstring.h"
|
|
#include "filestrm.h"
|
|
#include "uarrsort.h"
|
|
#include "unewdata.h"
|
|
#include "uoptions.h"
|
|
#include "uprops.h"
|
|
#include "propname.h"
|
|
#include "uassert.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
U_NAMESPACE_USE
|
|
|
|
// TODO: Clean up and comment this code.
|
|
|
|
//----------------------------------------------------------------------
|
|
// BEGIN DATA
|
|
//
|
|
// This is the raw data to be output. We define the data structure,
|
|
// then include a machine-generated header that contains the actual
|
|
// data.
|
|
|
|
#include "unicode/uchar.h"
|
|
#include "unicode/uscript.h"
|
|
#include "unicode/unorm.h"
|
|
|
|
class AliasName {
|
|
public:
|
|
const char* str;
|
|
int32_t index;
|
|
|
|
AliasName(const char* str, int32_t index);
|
|
|
|
int compare(const AliasName& other) const;
|
|
|
|
UBool operator==(const AliasName& other) const {
|
|
return compare(other) == 0;
|
|
}
|
|
|
|
UBool operator!=(const AliasName& other) const {
|
|
return compare(other) != 0;
|
|
}
|
|
};
|
|
|
|
AliasName::AliasName(const char* _str,
|
|
int32_t _index) :
|
|
str(_str),
|
|
index(_index)
|
|
{
|
|
}
|
|
|
|
int AliasName::compare(const AliasName& other) const {
|
|
return uprv_comparePropertyNames(str, other.str);
|
|
}
|
|
|
|
class Alias {
|
|
public:
|
|
int32_t enumValue;
|
|
int32_t nameGroupIndex;
|
|
|
|
Alias(int32_t enumValue,
|
|
int32_t nameGroupIndex);
|
|
|
|
int32_t getUniqueNames(int32_t* nameGroupIndices) const;
|
|
};
|
|
|
|
Alias::Alias(int32_t anEnumValue,
|
|
int32_t aNameGroupIndex) :
|
|
enumValue(anEnumValue),
|
|
nameGroupIndex(aNameGroupIndex)
|
|
{
|
|
}
|
|
|
|
class Property : public Alias {
|
|
public:
|
|
int32_t valueCount;
|
|
const Alias* valueList;
|
|
|
|
Property(int32_t enumValue,
|
|
int32_t nameGroupIndex,
|
|
int32_t valueCount,
|
|
const Alias* valueList);
|
|
};
|
|
|
|
Property::Property(int32_t _enumValue,
|
|
int32_t _nameGroupIndex,
|
|
int32_t _valueCount,
|
|
const Alias* _valueList) :
|
|
Alias(_enumValue, _nameGroupIndex),
|
|
valueCount(_valueCount),
|
|
valueList(_valueList)
|
|
{
|
|
}
|
|
|
|
// *** Include the data header ***
|
|
#include "data.h"
|
|
|
|
/* return a list of unique names, not including "", for this property
|
|
* @param stringIndices array of at least MAX_NAMES_PER_GROUP
|
|
* elements, will be filled with indices into STRING_TABLE
|
|
* @return number of indices, >= 1
|
|
*/
|
|
int32_t Alias::getUniqueNames(int32_t* stringIndices) const {
|
|
int32_t count = 0;
|
|
int32_t i = nameGroupIndex;
|
|
UBool done = FALSE;
|
|
while (!done) {
|
|
int32_t j = NAME_GROUP[i++];
|
|
if (j < 0) {
|
|
done = TRUE;
|
|
j = -j;
|
|
}
|
|
if (j == 0) continue; // omit "" entries
|
|
UBool dupe = FALSE;
|
|
for (int32_t k=0; k<count; ++k) {
|
|
if (stringIndices[k] == j) {
|
|
dupe = TRUE;
|
|
break;
|
|
}
|
|
// also do a string check for things like "age|Age"
|
|
if (STRING_TABLE[stringIndices[k]] == STRING_TABLE[j]) {
|
|
//printf("Found dupe %s|%s\n",
|
|
// STRING_TABLE[stringIndices[k]].str,
|
|
// STRING_TABLE[j].str);
|
|
dupe = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (dupe) continue; // omit duplicates
|
|
stringIndices[count++] = j;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// END DATA
|
|
//----------------------------------------------------------------------
|
|
|
|
#define MALLOC(type, count) \
|
|
(type*) uprv_malloc(sizeof(type) * count)
|
|
|
|
void die(const char* msg) {
|
|
fprintf(stderr, "Error: %s\n", msg);
|
|
exit(1);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
/**
|
|
* A list of Alias objects.
|
|
*/
|
|
class AliasList {
|
|
public:
|
|
virtual ~AliasList();
|
|
virtual const Alias& operator[](int32_t i) const = 0;
|
|
virtual int32_t count() const = 0;
|
|
};
|
|
|
|
AliasList::~AliasList() {}
|
|
|
|
/**
|
|
* A single array.
|
|
*/
|
|
class AliasArrayList : public AliasList {
|
|
const Alias* a;
|
|
int32_t n;
|
|
public:
|
|
AliasArrayList(const Alias* _a, int32_t _n) {
|
|
a = _a;
|
|
n = _n;
|
|
}
|
|
virtual const Alias& operator[](int32_t i) const {
|
|
return a[i];
|
|
}
|
|
virtual int32_t count() const {
|
|
return n;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A single array.
|
|
*/
|
|
class PropertyArrayList : public AliasList {
|
|
const Property* a;
|
|
int32_t n;
|
|
public:
|
|
PropertyArrayList(const Property* _a, int32_t _n) {
|
|
a = _a;
|
|
n = _n;
|
|
}
|
|
virtual const Alias& operator[](int32_t i) const {
|
|
return a[i];
|
|
}
|
|
virtual int32_t count() const {
|
|
return n;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
/**
|
|
* An element in a name index. It maps a name (given by index) into
|
|
* an enum value.
|
|
*/
|
|
class NameToEnumEntry {
|
|
public:
|
|
int32_t nameIndex;
|
|
int32_t enumValue;
|
|
NameToEnumEntry(int32_t a, int32_t b) { nameIndex=a; enumValue=b; }
|
|
};
|
|
|
|
// Sort function for NameToEnumEntry (sort by name)
|
|
U_CFUNC int32_t
|
|
compareNameToEnumEntry(const void * /*context*/, const void* e1, const void* e2) {
|
|
return
|
|
STRING_TABLE[((NameToEnumEntry*)e1)->nameIndex].
|
|
compare(STRING_TABLE[((NameToEnumEntry*)e2)->nameIndex]);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
/**
|
|
* An element in an enum index. It maps an enum into a name group entry
|
|
* (given by index).
|
|
*/
|
|
class EnumToNameGroupEntry {
|
|
public:
|
|
int32_t enumValue;
|
|
int32_t nameGroupIndex;
|
|
EnumToNameGroupEntry(int32_t a, int32_t b) { enumValue=a; nameGroupIndex=b; }
|
|
|
|
// are enumValues contiguous for count entries starting with this one?
|
|
// ***!!!*** we assume we are in an array and look at neighbors ***!!!***
|
|
UBool isContiguous(int32_t count) const {
|
|
const EnumToNameGroupEntry* p = this;
|
|
for (int32_t i=1; i<count; ++i) {
|
|
if (p[i].enumValue != (this->enumValue + i)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
};
|
|
|
|
// Sort function for EnumToNameGroupEntry (sort by name index)
|
|
U_CFUNC int32_t
|
|
compareEnumToNameGroupEntry(const void * /*context*/, const void* e1, const void* e2) {
|
|
return ((EnumToNameGroupEntry*)e1)->enumValue - ((EnumToNameGroupEntry*)e2)->enumValue;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
/**
|
|
* An element in the map from enumerated property enums to value maps.
|
|
*/
|
|
class EnumToValueEntry {
|
|
public:
|
|
int32_t enumValue;
|
|
EnumToNameGroupEntry* enumToName;
|
|
int32_t enumToName_count;
|
|
NameToEnumEntry* nameToEnum;
|
|
int32_t nameToEnum_count;
|
|
|
|
// are enumValues contiguous for count entries starting with this one?
|
|
// ***!!!*** we assume we are in an array and look at neighbors ***!!!***
|
|
UBool isContiguous(int32_t count) const {
|
|
const EnumToValueEntry* p = this;
|
|
for (int32_t i=1; i<count; ++i) {
|
|
if (p[i].enumValue != (this->enumValue + i)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
};
|
|
|
|
// Sort function for EnumToValueEntry (sort by enum)
|
|
U_CFUNC int32_t
|
|
compareEnumToValueEntry(const void * /*context*/, const void* e1, const void* e2) {
|
|
return ((EnumToValueEntry*)e1)->enumValue - ((EnumToValueEntry*)e2)->enumValue;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// BEGIN Builder
|
|
|
|
#define IS_VALID_OFFSET(x) (((x)>=0)&&((x)<=MAX_OFFSET))
|
|
|
|
class Builder {
|
|
// header:
|
|
PropertyAliases header;
|
|
|
|
// 0:
|
|
NonContiguousEnumToOffset* enumToName;
|
|
int32_t enumToName_size;
|
|
Offset enumToName_offset;
|
|
|
|
// 1: (deleted)
|
|
|
|
// 2:
|
|
NameToEnum* nameToEnum;
|
|
int32_t nameToEnum_size;
|
|
Offset nameToEnum_offset;
|
|
|
|
// 3:
|
|
NonContiguousEnumToOffset* enumToValue;
|
|
int32_t enumToValue_size;
|
|
Offset enumToValue_offset;
|
|
|
|
// 4:
|
|
ValueMap* valueMap;
|
|
int32_t valueMap_size;
|
|
int32_t valueMap_count;
|
|
Offset valueMap_offset;
|
|
|
|
// for any i, one of valueEnumToName[i] or valueNCEnumToName[i] is
|
|
// NULL and one is not. valueEnumToName_size[i] is the size of
|
|
// the non-NULL one. i=0..valueMapCount-1
|
|
// 5a:
|
|
EnumToOffset** valueEnumToName;
|
|
// 5b:
|
|
NonContiguousEnumToOffset** valueNCEnumToName;
|
|
int32_t* valueEnumToName_size;
|
|
Offset* valueEnumToName_offset;
|
|
// 6:
|
|
// arrays of valueMap_count pointers, sizes, & offsets
|
|
NameToEnum** valueNameToEnum;
|
|
int32_t* valueNameToEnum_size;
|
|
Offset* valueNameToEnum_offset;
|
|
|
|
// 98:
|
|
Offset* nameGroupPool;
|
|
int32_t nameGroupPool_count;
|
|
int32_t nameGroupPool_size;
|
|
Offset nameGroupPool_offset;
|
|
|
|
// 99:
|
|
char* stringPool;
|
|
int32_t stringPool_count;
|
|
int32_t stringPool_size;
|
|
Offset stringPool_offset;
|
|
Offset* stringPool_offsetArray; // relative to stringPool
|
|
|
|
int32_t total_size; // size of everything
|
|
|
|
int32_t debug;
|
|
|
|
public:
|
|
|
|
Builder(int32_t debugLevel);
|
|
~Builder();
|
|
|
|
void buildTopLevelProperties(const NameToEnumEntry* propName,
|
|
int32_t propNameCount,
|
|
const EnumToNameGroupEntry* propEnum,
|
|
int32_t propEnumCount);
|
|
|
|
void buildValues(const EnumToValueEntry* e2v,
|
|
int32_t count);
|
|
|
|
void buildStringPool(const AliasName* propertyNames,
|
|
int32_t propertyNameCount,
|
|
const int32_t* nameGroupIndices,
|
|
int32_t nameGroupIndicesCount);
|
|
|
|
void fixup();
|
|
|
|
int8_t* createData(int32_t& length) const;
|
|
|
|
private:
|
|
|
|
static EnumToOffset* buildEnumToOffset(const EnumToNameGroupEntry* e2ng,
|
|
int32_t count,
|
|
int32_t& size);
|
|
static NonContiguousEnumToOffset*
|
|
buildNCEnumToNameGroup(const EnumToNameGroupEntry* e2ng,
|
|
int32_t count,
|
|
int32_t& size);
|
|
|
|
static NonContiguousEnumToOffset*
|
|
buildNCEnumToValue(const EnumToValueEntry* e2v,
|
|
int32_t count,
|
|
int32_t& size);
|
|
|
|
static NameToEnum* buildNameToEnum(const NameToEnumEntry* nameToEnum,
|
|
int32_t count,
|
|
int32_t& size);
|
|
|
|
Offset stringIndexToOffset(int32_t index, UBool allowNeg=FALSE) const;
|
|
void fixupNameToEnum(NameToEnum* n);
|
|
void fixupEnumToNameGroup(EnumToOffset* e2ng);
|
|
void fixupNCEnumToNameGroup(NonContiguousEnumToOffset* e2ng);
|
|
|
|
void computeOffsets();
|
|
void fixupStringPoolOffsets();
|
|
void fixupNameGroupPoolOffsets();
|
|
void fixupMiscellaneousOffsets();
|
|
|
|
static int32_t align(int32_t a);
|
|
static void erase(void* p, int32_t size);
|
|
};
|
|
|
|
Builder::Builder(int32_t debugLevel) {
|
|
debug = debugLevel;
|
|
enumToName = 0;
|
|
nameToEnum = 0;
|
|
enumToValue = 0;
|
|
valueMap_count = 0;
|
|
valueMap = 0;
|
|
valueEnumToName = 0;
|
|
valueNCEnumToName = 0;
|
|
valueEnumToName_size = 0;
|
|
valueEnumToName_offset = 0;
|
|
valueNameToEnum = 0;
|
|
valueNameToEnum_size = 0;
|
|
valueNameToEnum_offset = 0;
|
|
nameGroupPool = 0;
|
|
stringPool = 0;
|
|
stringPool_offsetArray = 0;
|
|
}
|
|
|
|
Builder::~Builder() {
|
|
uprv_free(enumToName);
|
|
uprv_free(nameToEnum);
|
|
uprv_free(enumToValue);
|
|
uprv_free(valueMap);
|
|
for (int32_t i=0; i<valueMap_count; ++i) {
|
|
uprv_free(valueEnumToName[i]);
|
|
uprv_free(valueNCEnumToName[i]);
|
|
uprv_free(valueNameToEnum[i]);
|
|
}
|
|
uprv_free(valueEnumToName);
|
|
uprv_free(valueNCEnumToName);
|
|
uprv_free(valueEnumToName_size);
|
|
uprv_free(valueEnumToName_offset);
|
|
uprv_free(valueNameToEnum);
|
|
uprv_free(valueNameToEnum_size);
|
|
uprv_free(valueNameToEnum_offset);
|
|
uprv_free(nameGroupPool);
|
|
uprv_free(stringPool);
|
|
uprv_free(stringPool_offsetArray);
|
|
}
|
|
|
|
int32_t Builder::align(int32_t a) {
|
|
U_ASSERT(a >= 0);
|
|
int32_t k = a % sizeof(int32_t);
|
|
if (k == 0) {
|
|
return a;
|
|
}
|
|
a += sizeof(int32_t) - k;
|
|
return a;
|
|
}
|
|
|
|
void Builder::erase(void* p, int32_t size) {
|
|
U_ASSERT(size >= 0);
|
|
int8_t* q = (int8_t*) p;
|
|
while (size--) {
|
|
*q++ = 0;
|
|
}
|
|
}
|
|
|
|
EnumToOffset* Builder::buildEnumToOffset(const EnumToNameGroupEntry* e2ng,
|
|
int32_t count,
|
|
int32_t& size) {
|
|
U_ASSERT(e2ng->isContiguous(count));
|
|
size = align(EnumToOffset::getSize(count));
|
|
EnumToOffset* result = (EnumToOffset*) uprv_malloc(size);
|
|
erase(result, size);
|
|
result->enumStart = e2ng->enumValue;
|
|
result->enumLimit = e2ng->enumValue + count;
|
|
Offset* p = result->getOffsetArray();
|
|
for (int32_t i=0; i<count; ++i) {
|
|
// set these to NGI index values
|
|
// fix them up to NGI offset values
|
|
U_ASSERT(IS_VALID_OFFSET(e2ng[i].nameGroupIndex));
|
|
p[i] = (Offset) e2ng[i].nameGroupIndex; // FIXUP later
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NonContiguousEnumToOffset*
|
|
Builder::buildNCEnumToNameGroup(const EnumToNameGroupEntry* e2ng,
|
|
int32_t count,
|
|
int32_t& size) {
|
|
U_ASSERT(!e2ng->isContiguous(count));
|
|
size = align(NonContiguousEnumToOffset::getSize(count));
|
|
NonContiguousEnumToOffset* nc = (NonContiguousEnumToOffset*) uprv_malloc(size);
|
|
erase(nc, size);
|
|
nc->count = count;
|
|
EnumValue* e = nc->getEnumArray();
|
|
Offset* p = nc->getOffsetArray();
|
|
for (int32_t i=0; i<count; ++i) {
|
|
// set these to NGI index values
|
|
// fix them up to NGI offset values
|
|
e[i] = e2ng[i].enumValue;
|
|
U_ASSERT(IS_VALID_OFFSET(e2ng[i].nameGroupIndex));
|
|
p[i] = (Offset) e2ng[i].nameGroupIndex; // FIXUP later
|
|
}
|
|
return nc;
|
|
}
|
|
|
|
NonContiguousEnumToOffset*
|
|
Builder::buildNCEnumToValue(const EnumToValueEntry* e2v,
|
|
int32_t count,
|
|
int32_t& size) {
|
|
U_ASSERT(!e2v->isContiguous(count));
|
|
size = align(NonContiguousEnumToOffset::getSize(count));
|
|
NonContiguousEnumToOffset* result = (NonContiguousEnumToOffset*) uprv_malloc(size);
|
|
erase(result, size);
|
|
result->count = count;
|
|
EnumValue* e = result->getEnumArray();
|
|
for (int32_t i=0; i<count; ++i) {
|
|
e[i] = e2v[i].enumValue;
|
|
// offset must be set later
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Given an index into the string pool, return an offset. computeOffsets()
|
|
* must have been called already. If allowNegative is true, allow negatives
|
|
* and preserve their sign.
|
|
*/
|
|
Offset Builder::stringIndexToOffset(int32_t index, UBool allowNegative) const {
|
|
// Index 0 is ""; we turn this into an Offset of zero
|
|
if (index == 0) return 0;
|
|
if (index < 0) {
|
|
if (allowNegative) {
|
|
return -Builder::stringIndexToOffset(-index);
|
|
} else {
|
|
die("Negative string pool index");
|
|
}
|
|
} else {
|
|
if (index >= stringPool_count) {
|
|
die("String pool index too large");
|
|
}
|
|
Offset result = stringPool_offset + stringPool_offsetArray[index];
|
|
U_ASSERT(result >= 0 && result < total_size);
|
|
return result;
|
|
}
|
|
return 0; // never executed; make compiler happy
|
|
}
|
|
|
|
NameToEnum* Builder::buildNameToEnum(const NameToEnumEntry* nameToEnum,
|
|
int32_t count,
|
|
int32_t& size) {
|
|
size = align(NameToEnum::getSize(count));
|
|
NameToEnum* n2e = (NameToEnum*) uprv_malloc(size);
|
|
erase(n2e, size);
|
|
n2e->count = count;
|
|
Offset* p = n2e->getNameArray();
|
|
EnumValue* e = n2e->getEnumArray();
|
|
for (int32_t i=0; i<count; ++i) {
|
|
// set these to SP index values
|
|
// fix them up to SP offset values
|
|
U_ASSERT(IS_VALID_OFFSET(nameToEnum[i].nameIndex));
|
|
p[i] = (Offset) nameToEnum[i].nameIndex; // FIXUP later
|
|
e[i] = nameToEnum[i].enumValue;
|
|
}
|
|
return n2e;
|
|
}
|
|
|
|
|
|
void Builder::buildTopLevelProperties(const NameToEnumEntry* propName,
|
|
int32_t propNameCount,
|
|
const EnumToNameGroupEntry* propEnum,
|
|
int32_t propEnumCount) {
|
|
enumToName = buildNCEnumToNameGroup(propEnum,
|
|
propEnumCount,
|
|
enumToName_size);
|
|
nameToEnum = buildNameToEnum(propName,
|
|
propNameCount,
|
|
nameToEnum_size);
|
|
}
|
|
|
|
void Builder::buildValues(const EnumToValueEntry* e2v,
|
|
int32_t count) {
|
|
int32_t i;
|
|
|
|
U_ASSERT(!e2v->isContiguous(count));
|
|
|
|
valueMap_count = count;
|
|
|
|
enumToValue = buildNCEnumToValue(e2v, count,
|
|
enumToValue_size);
|
|
|
|
valueMap_size = align(count * sizeof(ValueMap));
|
|
valueMap = (ValueMap*) uprv_malloc(valueMap_size);
|
|
erase(valueMap, valueMap_size);
|
|
|
|
valueEnumToName = MALLOC(EnumToOffset*, count);
|
|
valueNCEnumToName = MALLOC(NonContiguousEnumToOffset*, count);
|
|
valueEnumToName_size = MALLOC(int32_t, count);
|
|
valueEnumToName_offset = MALLOC(Offset, count);
|
|
valueNameToEnum = MALLOC(NameToEnum*, count);
|
|
valueNameToEnum_size = MALLOC(int32_t, count);
|
|
valueNameToEnum_offset = MALLOC(Offset, count);
|
|
|
|
for (i=0; i<count; ++i) {
|
|
UBool isContiguous =
|
|
e2v[i].enumToName->isContiguous(e2v[i].enumToName_count);
|
|
valueEnumToName[i] = 0;
|
|
valueNCEnumToName[i] = 0;
|
|
if (isContiguous) {
|
|
valueEnumToName[i] = buildEnumToOffset(e2v[i].enumToName,
|
|
e2v[i].enumToName_count,
|
|
valueEnumToName_size[i]);
|
|
} else {
|
|
valueNCEnumToName[i] = buildNCEnumToNameGroup(e2v[i].enumToName,
|
|
e2v[i].enumToName_count,
|
|
valueEnumToName_size[i]);
|
|
}
|
|
valueNameToEnum[i] =
|
|
buildNameToEnum(e2v[i].nameToEnum,
|
|
e2v[i].nameToEnum_count,
|
|
valueNameToEnum_size[i]);
|
|
}
|
|
}
|
|
|
|
void Builder::buildStringPool(const AliasName* propertyNames,
|
|
int32_t propertyNameCount,
|
|
const int32_t* nameGroupIndices,
|
|
int32_t nameGroupIndicesCount) {
|
|
int32_t i;
|
|
|
|
nameGroupPool_count = nameGroupIndicesCount;
|
|
nameGroupPool_size = sizeof(Offset) * nameGroupPool_count;
|
|
nameGroupPool = MALLOC(Offset, nameGroupPool_count);
|
|
|
|
for (i=0; i<nameGroupPool_count; ++i) {
|
|
// Some indices are negative.
|
|
int32_t a = nameGroupIndices[i];
|
|
if (a < 0) a = -a;
|
|
U_ASSERT(IS_VALID_OFFSET(a));
|
|
nameGroupPool[i] = (Offset) nameGroupIndices[i];
|
|
}
|
|
|
|
stringPool_count = propertyNameCount;
|
|
stringPool_size = 0;
|
|
// first string must be "" -- we skip it
|
|
U_ASSERT(*propertyNames[0].str == 0);
|
|
for (i=1 /*sic*/; i<propertyNameCount; ++i) {
|
|
stringPool_size += (int32_t)(uprv_strlen(propertyNames[i].str) + 1);
|
|
}
|
|
stringPool = MALLOC(char, stringPool_size);
|
|
stringPool_offsetArray = MALLOC(Offset, stringPool_count);
|
|
Offset soFar = 0;
|
|
char* p = stringPool;
|
|
stringPool_offsetArray[0] = -1; // we don't use this entry
|
|
for (i=1 /*sic*/; i<propertyNameCount; ++i) {
|
|
const char* str = propertyNames[i].str;
|
|
int32_t len = (int32_t)uprv_strlen(str);
|
|
uprv_strcpy(p, str);
|
|
p += len;
|
|
*p++ = 0;
|
|
stringPool_offsetArray[i] = soFar;
|
|
soFar += (Offset)(len+1);
|
|
}
|
|
U_ASSERT(soFar == stringPool_size);
|
|
U_ASSERT(p == (stringPool + stringPool_size));
|
|
}
|
|
|
|
// Confirm that PropertyAliases is a POD (plain old data; see C++
|
|
// std). The following union will _fail to compile_ if
|
|
// PropertyAliases is _not_ a POD. (Note: We used to use the offsetof
|
|
// macro to check this, but that's not quite right, so that test is
|
|
// commented out -- see below.)
|
|
typedef union {
|
|
int32_t i;
|
|
PropertyAliases p;
|
|
} PropertyAliasesPODTest;
|
|
|
|
void Builder::computeOffsets() {
|
|
int32_t i;
|
|
Offset off = sizeof(header);
|
|
|
|
if (debug>0) {
|
|
printf("header \t offset=%4d size=%5d\n", 0, off);
|
|
}
|
|
|
|
// PropertyAliases must have no v-table and must be
|
|
// padded (if necessary) to the next 32-bit boundary.
|
|
//U_ASSERT(offsetof(PropertyAliases, enumToName_offset) == 0); // see above
|
|
U_ASSERT(sizeof(header) % sizeof(int32_t) == 0);
|
|
|
|
#define COMPUTE_OFFSET(foo) COMPUTE_OFFSET2(foo,int32_t)
|
|
|
|
#define COMPUTE_OFFSET2(foo,type) \
|
|
if (debug>0)\
|
|
printf(#foo "\t offset=%4d size=%5d\n", off, (int)foo##_size);\
|
|
foo##_offset = off;\
|
|
U_ASSERT(IS_VALID_OFFSET(off + foo##_size));\
|
|
U_ASSERT(foo##_offset % sizeof(type) == 0);\
|
|
off = (Offset) (off + foo##_size);
|
|
|
|
COMPUTE_OFFSET(enumToName); // 0:
|
|
COMPUTE_OFFSET(nameToEnum); // 2:
|
|
COMPUTE_OFFSET(enumToValue); // 3:
|
|
COMPUTE_OFFSET(valueMap); // 4:
|
|
|
|
for (i=0; i<valueMap_count; ++i) {
|
|
if (debug>0) {
|
|
printf(" enumToName[%d]\t offset=%4d size=%5d\n",
|
|
(int)i, off, (int)valueEnumToName_size[i]);
|
|
}
|
|
|
|
valueEnumToName_offset[i] = off; // 5:
|
|
U_ASSERT(IS_VALID_OFFSET(off + valueEnumToName_size[i]));
|
|
off = (Offset) (off + valueEnumToName_size[i]);
|
|
|
|
if (debug>0) {
|
|
printf(" nameToEnum[%d]\t offset=%4d size=%5d\n",
|
|
(int)i, off, (int)valueNameToEnum_size[i]);
|
|
}
|
|
|
|
valueNameToEnum_offset[i] = off; // 6:
|
|
U_ASSERT(IS_VALID_OFFSET(off + valueNameToEnum_size[i]));
|
|
off = (Offset) (off + valueNameToEnum_size[i]);
|
|
}
|
|
|
|
// These last two chunks have weaker alignment needs
|
|
COMPUTE_OFFSET2(nameGroupPool,Offset); // 98:
|
|
COMPUTE_OFFSET2(stringPool,char); // 99:
|
|
|
|
total_size = off;
|
|
if (debug>0) printf("total size=%5d\n\n", (int)total_size);
|
|
U_ASSERT(total_size <= (MAX_OFFSET+1));
|
|
}
|
|
|
|
void Builder::fixupNameToEnum(NameToEnum* n) {
|
|
// Fix the string pool offsets in n
|
|
Offset* p = n->getNameArray();
|
|
for (int32_t i=0; i<n->count; ++i) {
|
|
p[i] = stringIndexToOffset(p[i]);
|
|
}
|
|
}
|
|
|
|
void Builder::fixupStringPoolOffsets() {
|
|
int32_t i;
|
|
|
|
// 2:
|
|
fixupNameToEnum(nameToEnum);
|
|
|
|
// 6:
|
|
for (i=0; i<valueMap_count; ++i) {
|
|
fixupNameToEnum(valueNameToEnum[i]);
|
|
}
|
|
|
|
// 98:
|
|
for (i=0; i<nameGroupPool_count; ++i) {
|
|
nameGroupPool[i] = stringIndexToOffset(nameGroupPool[i], TRUE);
|
|
}
|
|
}
|
|
|
|
void Builder::fixupEnumToNameGroup(EnumToOffset* e2ng) {
|
|
EnumValue i;
|
|
int32_t j;
|
|
Offset* p = e2ng->getOffsetArray();
|
|
for (i=e2ng->enumStart, j=0; i<e2ng->enumLimit; ++i, ++j) {
|
|
p[j] = nameGroupPool_offset + sizeof(Offset) * p[j];
|
|
}
|
|
}
|
|
|
|
void Builder::fixupNCEnumToNameGroup(NonContiguousEnumToOffset* e2ng) {
|
|
int32_t i;
|
|
/*EnumValue* e = e2ng->getEnumArray();*/
|
|
Offset* p = e2ng->getOffsetArray();
|
|
for (i=0; i<e2ng->count; ++i) {
|
|
p[i] = nameGroupPool_offset + sizeof(Offset) * p[i];
|
|
}
|
|
}
|
|
|
|
void Builder::fixupNameGroupPoolOffsets() {
|
|
int32_t i;
|
|
|
|
// 0:
|
|
fixupNCEnumToNameGroup(enumToName);
|
|
|
|
// 1: (deleted)
|
|
|
|
// 5:
|
|
for (i=0; i<valueMap_count; ++i) {
|
|
// 5a:
|
|
if (valueEnumToName[i] != 0) {
|
|
fixupEnumToNameGroup(valueEnumToName[i]);
|
|
}
|
|
// 5b:
|
|
if (valueNCEnumToName[i] != 0) {
|
|
fixupNCEnumToNameGroup(valueNCEnumToName[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Builder::fixupMiscellaneousOffsets() {
|
|
int32_t i;
|
|
|
|
// header:
|
|
erase(&header, sizeof(header));
|
|
header.enumToName_offset = enumToName_offset;
|
|
header.nameToEnum_offset = nameToEnum_offset;
|
|
header.enumToValue_offset = enumToValue_offset;
|
|
// header meta-info used by Java:
|
|
U_ASSERT(total_size > 0 && total_size < 0x7FFF);
|
|
header.total_size = (int16_t) total_size;
|
|
header.valueMap_offset = valueMap_offset;
|
|
header.valueMap_count = (int16_t) valueMap_count;
|
|
header.nameGroupPool_offset = nameGroupPool_offset;
|
|
header.nameGroupPool_count = (int16_t) nameGroupPool_count;
|
|
header.stringPool_offset = stringPool_offset;
|
|
header.stringPool_count = (int16_t) stringPool_count - 1; // don't include "" entry
|
|
|
|
U_ASSERT(valueMap_count <= 0x7FFF);
|
|
U_ASSERT(nameGroupPool_count <= 0x7FFF);
|
|
U_ASSERT(stringPool_count <= 0x7FFF);
|
|
|
|
// 3:
|
|
Offset* p = enumToValue->getOffsetArray();
|
|
/*EnumValue* e = enumToValue->getEnumArray();*/
|
|
U_ASSERT(valueMap_count == enumToValue->count);
|
|
for (i=0; i<valueMap_count; ++i) {
|
|
p[i] = (Offset)(valueMap_offset + sizeof(ValueMap) * i);
|
|
}
|
|
|
|
// 4:
|
|
for (i=0; i<valueMap_count; ++i) {
|
|
ValueMap& v = valueMap[i];
|
|
v.enumToName_offset = v.ncEnumToName_offset = 0;
|
|
if (valueEnumToName[i] != 0) {
|
|
v.enumToName_offset = valueEnumToName_offset[i];
|
|
}
|
|
if (valueNCEnumToName[i] != 0) {
|
|
v.ncEnumToName_offset = valueEnumToName_offset[i];
|
|
}
|
|
v.nameToEnum_offset = valueNameToEnum_offset[i];
|
|
}
|
|
}
|
|
|
|
void Builder::fixup() {
|
|
computeOffsets();
|
|
fixupStringPoolOffsets();
|
|
fixupNameGroupPoolOffsets();
|
|
fixupMiscellaneousOffsets();
|
|
}
|
|
|
|
int8_t* Builder::createData(int32_t& length) const {
|
|
length = total_size;
|
|
int8_t* result = MALLOC(int8_t, length);
|
|
|
|
int8_t* p = result;
|
|
int8_t* limit = result + length;
|
|
|
|
#define APPEND2(x, size) \
|
|
U_ASSERT((p+size)<=limit); \
|
|
uprv_memcpy(p, x, size); \
|
|
p += size
|
|
|
|
#define APPEND(x) APPEND2(x, x##_size)
|
|
|
|
APPEND2(&header, sizeof(header));
|
|
APPEND(enumToName);
|
|
APPEND(nameToEnum);
|
|
APPEND(enumToValue);
|
|
APPEND(valueMap);
|
|
|
|
for (int32_t i=0; i<valueMap_count; ++i) {
|
|
U_ASSERT((valueEnumToName[i] != 0 && valueNCEnumToName[i] == 0) ||
|
|
(valueEnumToName[i] == 0 && valueNCEnumToName[i] != 0));
|
|
if (valueEnumToName[i] != 0) {
|
|
APPEND2(valueEnumToName[i], valueEnumToName_size[i]);
|
|
}
|
|
if (valueNCEnumToName[i] != 0) {
|
|
APPEND2(valueNCEnumToName[i], valueEnumToName_size[i]);
|
|
}
|
|
APPEND2(valueNameToEnum[i], valueNameToEnum_size[i]);
|
|
}
|
|
|
|
APPEND(nameGroupPool);
|
|
APPEND(stringPool);
|
|
|
|
if (p != limit) {
|
|
fprintf(stderr, "p != limit; p = %p, limit = %p", p, limit);
|
|
exit(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// END Builder
|
|
//----------------------------------------------------------------------
|
|
|
|
/* UDataInfo cf. udata.h */
|
|
static UDataInfo dataInfo = {
|
|
sizeof(UDataInfo),
|
|
0,
|
|
|
|
U_IS_BIG_ENDIAN,
|
|
U_CHARSET_FAMILY,
|
|
sizeof(UChar),
|
|
0,
|
|
|
|
{PNAME_SIG_0, PNAME_SIG_1, PNAME_SIG_2, PNAME_SIG_3},
|
|
{PNAME_FORMAT_VERSION, 0, 0, 0}, /* formatVersion */
|
|
{VERSION_0, VERSION_1, VERSION_2, VERSION_3} /* Unicode version */
|
|
};
|
|
|
|
class genpname {
|
|
|
|
// command-line options
|
|
UBool useCopyright;
|
|
UBool verbose;
|
|
int32_t debug;
|
|
|
|
public:
|
|
int MMain(int argc, char *argv[]);
|
|
|
|
private:
|
|
NameToEnumEntry* createNameIndex(const AliasList& list,
|
|
int32_t& nameIndexCount);
|
|
|
|
EnumToNameGroupEntry* createEnumIndex(const AliasList& list);
|
|
|
|
int32_t writeDataFile(const char *destdir, const Builder&);
|
|
};
|
|
|
|
int main(int argc, char *argv[]) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
u_init(&status);
|
|
if (U_FAILURE(status) && status != U_FILE_ACCESS_ERROR) {
|
|
// Note: u_init() will try to open ICU property data.
|
|
// failures here are expected when building ICU from scratch.
|
|
// ignore them.
|
|
fprintf(stderr, "genpname: can not initialize ICU. Status = %s\n",
|
|
u_errorName(status));
|
|
exit(1);
|
|
}
|
|
|
|
genpname app;
|
|
U_MAIN_INIT_ARGS(argc, argv);
|
|
int retVal = app.MMain(argc, argv);
|
|
u_cleanup();
|
|
return retVal;
|
|
}
|
|
|
|
static UOption options[]={
|
|
UOPTION_HELP_H,
|
|
UOPTION_HELP_QUESTION_MARK,
|
|
UOPTION_COPYRIGHT,
|
|
UOPTION_DESTDIR,
|
|
UOPTION_VERBOSE,
|
|
UOPTION_DEF("debug", 'D', UOPT_REQUIRES_ARG),
|
|
};
|
|
|
|
NameToEnumEntry* genpname::createNameIndex(const AliasList& list,
|
|
int32_t& nameIndexCount) {
|
|
|
|
// Build name => enum map
|
|
|
|
// This is an n->1 map. There are typically multiple names
|
|
// mapping to one enum. The name index is sorted in order of the name,
|
|
// as defined by the uprv_compareAliasNames() function.
|
|
|
|
int32_t i, j;
|
|
int32_t count = list.count();
|
|
|
|
// compute upper limit on number of names in the index
|
|
int32_t nameIndexCapacity = count * MAX_NAMES_PER_GROUP;
|
|
NameToEnumEntry* nameIndex = MALLOC(NameToEnumEntry, nameIndexCapacity);
|
|
|
|
nameIndexCount = 0;
|
|
int32_t names[MAX_NAMES_PER_GROUP];
|
|
for (i=0; i<count; ++i) {
|
|
const Alias& p = list[i];
|
|
int32_t n = p.getUniqueNames(names);
|
|
for (j=0; j<n; ++j) {
|
|
U_ASSERT(nameIndexCount < nameIndexCapacity);
|
|
nameIndex[nameIndexCount++] =
|
|
NameToEnumEntry(names[j], p.enumValue);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* use a stable sort to ensure consistent results between
|
|
* genpname.cpp and the propname.cpp swapping code
|
|
*/
|
|
UErrorCode errorCode = U_ZERO_ERROR;
|
|
uprv_sortArray(nameIndex, nameIndexCount, sizeof(nameIndex[0]),
|
|
compareNameToEnumEntry, NULL, TRUE, &errorCode);
|
|
if (debug>1) {
|
|
printf("Alias names: %d\n", (int)nameIndexCount);
|
|
for (i=0; i<nameIndexCount; ++i) {
|
|
printf("%s => %d\n",
|
|
STRING_TABLE[nameIndex[i].nameIndex].str,
|
|
(int)nameIndex[i].enumValue);
|
|
}
|
|
printf("\n");
|
|
}
|
|
// make sure there are no duplicates. for a sorted list we need
|
|
// only compare adjacent items. Alias.getUniqueNames() has
|
|
// already eliminated duplicate names for a single property, which
|
|
// does occur, so we're checking for duplicate names between two
|
|
// properties, which should never occur.
|
|
UBool ok = TRUE;
|
|
for (i=1; i<nameIndexCount; ++i) {
|
|
if (STRING_TABLE[nameIndex[i-1].nameIndex] ==
|
|
STRING_TABLE[nameIndex[i].nameIndex]) {
|
|
printf("Error: Duplicate names in property list: \"%s\", \"%s\"\n",
|
|
STRING_TABLE[nameIndex[i-1].nameIndex].str,
|
|
STRING_TABLE[nameIndex[i].nameIndex].str);
|
|
ok = FALSE;
|
|
}
|
|
}
|
|
if (!ok) {
|
|
die("Two or more duplicate names in property list");
|
|
}
|
|
|
|
return nameIndex;
|
|
}
|
|
|
|
EnumToNameGroupEntry* genpname::createEnumIndex(const AliasList& list) {
|
|
|
|
// Build the enum => name map
|
|
|
|
// This is a 1->n map. Each enum maps to 1 or more names. To
|
|
// accomplish this the index entry points to an element of the
|
|
// NAME_GROUP array. This is the short name (which may be empty).
|
|
// From there, subsequent elements of NAME_GROUP are alternate
|
|
// names for this enum, up to and including the first one that is
|
|
// negative (negate for actual index).
|
|
|
|
int32_t i, j, k;
|
|
int32_t count = list.count();
|
|
|
|
EnumToNameGroupEntry* enumIndex = MALLOC(EnumToNameGroupEntry, count);
|
|
for (i=0; i<count; ++i) {
|
|
const Alias& p = list[i];
|
|
enumIndex[i] = EnumToNameGroupEntry(p.enumValue, p.nameGroupIndex);
|
|
}
|
|
|
|
UErrorCode errorCode = U_ZERO_ERROR;
|
|
uprv_sortArray(enumIndex, count, sizeof(enumIndex[0]),
|
|
compareEnumToNameGroupEntry, NULL, FALSE, &errorCode);
|
|
if (debug>1) {
|
|
printf("Property enums: %d\n", (int)count);
|
|
for (i=0; i<count; ++i) {
|
|
printf("%d => %d: ",
|
|
(int)enumIndex[i].enumValue,
|
|
(int)enumIndex[i].nameGroupIndex);
|
|
UBool done = FALSE;
|
|
for (j=enumIndex[i].nameGroupIndex; !done; ++j) {
|
|
k = NAME_GROUP[j];
|
|
if (k < 0) {
|
|
k = -k;
|
|
done = TRUE;
|
|
}
|
|
printf("\"%s\"", STRING_TABLE[k].str);
|
|
if (!done) printf(", ");
|
|
}
|
|
printf("\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
return enumIndex;
|
|
}
|
|
|
|
int genpname::MMain(int argc, char* argv[])
|
|
{
|
|
int32_t i, j;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
u_init(&status);
|
|
if (U_FAILURE(status) && status != U_FILE_ACCESS_ERROR) {
|
|
fprintf(stderr, "Error: u_init returned %s\n", u_errorName(status));
|
|
status = U_ZERO_ERROR;
|
|
}
|
|
|
|
|
|
/* preset then read command line options */
|
|
options[3].value=u_getDataDirectory();
|
|
argc=u_parseArgs(argc, argv, sizeof(options)/sizeof(options[0]), options);
|
|
|
|
/* error handling, printing usage message */
|
|
if (argc<0) {
|
|
fprintf(stderr,
|
|
"error in command line argument \"%s\"\n",
|
|
argv[-argc]);
|
|
}
|
|
|
|
debug = options[5].doesOccur ? (*options[5].value - '0') : 0;
|
|
|
|
if (argc!=1 || options[0].doesOccur || options[1].doesOccur ||
|
|
debug < 0 || debug > 9) {
|
|
fprintf(stderr,
|
|
"usage: %s [-options]\n"
|
|
"\tcreate " PNAME_DATA_NAME "." PNAME_DATA_TYPE "\n"
|
|
"options:\n"
|
|
"\t-h or -? or --help this usage text\n"
|
|
"\t-v or --verbose turn on verbose output\n"
|
|
"\t-c or --copyright include a copyright notice\n"
|
|
"\t-d or --destdir destination directory, followed by the path\n"
|
|
"\t-D or --debug 0..9 emit debugging messages (if > 0)\n",
|
|
argv[0]);
|
|
return argc<0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR;
|
|
}
|
|
|
|
/* get the options values */
|
|
useCopyright=options[2].doesOccur;
|
|
verbose = options[4].doesOccur;
|
|
|
|
// ------------------------------------------------------------
|
|
// Do not sort the string table, instead keep it in data.h order.
|
|
// This simplifies data swapping and testing thereof because the string
|
|
// table itself need not be sorted during swapping.
|
|
// The NameToEnum sorter sorts each such map's string offsets instead.
|
|
|
|
if (debug>1) {
|
|
printf("String pool: %d\n", (int)STRING_COUNT);
|
|
for (i=0; i<STRING_COUNT; ++i) {
|
|
if (i != 0) {
|
|
printf(", ");
|
|
}
|
|
printf("%s (%d)", STRING_TABLE[i].str, (int)STRING_TABLE[i].index);
|
|
}
|
|
printf("\n\n");
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// Create top-level property indices
|
|
|
|
PropertyArrayList props(PROPERTY, PROPERTY_COUNT);
|
|
int32_t propNameCount;
|
|
NameToEnumEntry* propName = createNameIndex(props, propNameCount);
|
|
EnumToNameGroupEntry* propEnum = createEnumIndex(props);
|
|
|
|
// ------------------------------------------------------------
|
|
// Create indices for the value list for each enumerated property
|
|
|
|
// This will have more entries than we need...
|
|
EnumToValueEntry* enumToValue = MALLOC(EnumToValueEntry, PROPERTY_COUNT);
|
|
int32_t enumToValue_count = 0;
|
|
for (i=0, j=0; i<PROPERTY_COUNT; ++i) {
|
|
if (PROPERTY[i].valueCount == 0) continue;
|
|
AliasArrayList values(PROPERTY[i].valueList,
|
|
PROPERTY[i].valueCount);
|
|
enumToValue[j].enumValue = PROPERTY[i].enumValue;
|
|
enumToValue[j].enumToName = createEnumIndex(values);
|
|
enumToValue[j].enumToName_count = PROPERTY[i].valueCount;
|
|
enumToValue[j].nameToEnum = createNameIndex(values,
|
|
enumToValue[j].nameToEnum_count);
|
|
++j;
|
|
}
|
|
enumToValue_count = j;
|
|
|
|
uprv_sortArray(enumToValue, enumToValue_count, sizeof(enumToValue[0]),
|
|
compareEnumToValueEntry, NULL, FALSE, &status);
|
|
|
|
// ------------------------------------------------------------
|
|
// Build PropertyAliases layout in memory
|
|
|
|
Builder builder(debug);
|
|
|
|
builder.buildTopLevelProperties(propName,
|
|
propNameCount,
|
|
propEnum,
|
|
PROPERTY_COUNT);
|
|
|
|
builder.buildValues(enumToValue,
|
|
enumToValue_count);
|
|
|
|
builder.buildStringPool(STRING_TABLE,
|
|
STRING_COUNT,
|
|
NAME_GROUP,
|
|
NAME_GROUP_COUNT);
|
|
|
|
builder.fixup();
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// Write the output file
|
|
////////////////////////////////////////////////////////////
|
|
int32_t wlen = writeDataFile(options[3].value, builder);
|
|
if (verbose) {
|
|
fprintf(stdout, "Output file: %s.%s, %ld bytes\n",
|
|
U_ICUDATA_NAME "_" PNAME_DATA_NAME, PNAME_DATA_TYPE, (long)wlen);
|
|
}
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
int32_t genpname::writeDataFile(const char *destdir, const Builder& builder) {
|
|
int32_t length;
|
|
int8_t* data = builder.createData(length);
|
|
|
|
UNewDataMemory *pdata;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
pdata = udata_create(destdir, PNAME_DATA_TYPE, PNAME_DATA_NAME, &dataInfo,
|
|
useCopyright ? U_COPYRIGHT_STRING : 0, &status);
|
|
if (U_FAILURE(status)) {
|
|
die("Unable to create data memory");
|
|
}
|
|
|
|
udata_writeBlock(pdata, data, length);
|
|
|
|
int32_t dataLength = (int32_t) udata_finish(pdata, &status);
|
|
if (U_FAILURE(status)) {
|
|
die("Error writing output file");
|
|
}
|
|
if (dataLength != length) {
|
|
die("Written file doesn't match expected size");
|
|
}
|
|
|
|
return dataLength;
|
|
}
|
|
|
|
//eof
|