Each expanded name is allocated a unique integer namecode. The namecode enables all three parts of the expanded name to be determined, that is, the prefix, the URI, and the local name.
The equivalence betweem names depends only on the URI and the local name. The namecode is designed so that if two namecodes represent names with the same URI and local name, the two namecodes are the same in the bottom 20 bits. It is therefore possible to compare two names for equivalence by performing an integer comparison of these 20 bits. The bottom 20 bits of a namecode are referred to as a fingerprint.
The NamePool eliminates duplicate names if they have the same prefix, uri, and local part. It retains duplicates if they have different prefixes
Internally the NamePool is organized as a fixed number of hash chains. The selection of a hash chain is based on hashing the local name, because it is unusual to have many names that share the same local name but use different URIs. There are 1024 hash chains and the identifier of the hash chain forms the bottom ten bits of the namecode. The next ten bits represent the sequential position of an entry within the hash chain. The upper bits represent the selection of prefix, from among the list of prefixes that have been used with a given URI. A prefix part of zero means no prefix; if the two prefixes used with a particular namespace are "xs" and "xsd", say, then these will be prefix codes 1 and 2.
Fingerprints in the range 0 to 1023 are reserved for system use, and are allocated as constants mainly to names in the XSLT and XML Schema namespaces: constants representing these names are found in {@link StandardNames}.
Operations that update the NamePool, or that have the potential to update it, are synchronized. Read-only operations are done without synchronization. Although technically unsafe, this has not led to any problems in practice. Performance problems due to excessive contention on the NamePool have occasionally been observed: if this happens, the best strategy is to consider splitting the workload to use multiple Configurations each with a separate NamePool.
The NamePool holds two kinds of entry: name entries, representing expanded names (local name + prefix + URI), identified by a name code, and namespace entries (prefix + URI) identified by a namespace code.
The data structure of the name table is as follows.
There is a fixed size hash table; names are allocated to slots in this table by hashing on the local name. Each entry in the table is the head of a chain of NameEntry objects representing names that have the same hash code.
Each NameEntry represents a distinct name (same URI and local name). It contains the local name as a string, plus a short integer representing the URI (as an offset into the array uris[] - this is known as the URIcode).
The fingerprint of a name consists of the hash slot number (in the bottom 10 bits) concatenated with the depth of the entry down the chain of hash synonyms (in the next 10 bits). Fingerprints with depth 0 (i.e., in the range 0-1023) are reserved for predefined names (names of XSLT elements and attributes, and of built-in types). These names are not stored in the name pool, but are accessible as if they were.
A nameCode contains the fingerprint in the bottom 20 bits. It also contains a 10-bit prefix index. This distinguishes the prefix used, among all the prefixes that have been used with this namespace URI. If the prefix index is zero, the prefix is null. Otherwise, it indexes an array of prefix Strings associated with the namespace URI. Note that the data structures and algorithms are optimized for the case where URIs usually use the same prefix.
The nameCode -1 is reserved to mean "not known" or inapplicable. The fingerprint -1 has the same meaning. Note that masking the nameCode -1 to extract its bottom 20 bits is incorrect, and will lead to errors.
@author Michael H. Kay
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|