/* * eXist Open Source Native XML Database * Copyright (C) 2000-2014 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.dom.persistent; import org.exist.Namespaces; import org.exist.dom.QName; import org.exist.numbering.NodeId; import org.exist.storage.DBBroker; import org.exist.storage.RangeIndexSpec; import org.exist.storage.Signatures; import org.exist.util.ByteArrayPool; import org.exist.util.ByteConversion; import org.exist.util.UTF8; import org.exist.util.XMLString; import org.exist.util.pool.NodePool; import org.exist.util.serializer.AttrList; import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.TypeInfo; import org.w3c.dom.UserDataHandler; import javax.xml.XMLConstants; import static java.nio.charset.StandardCharsets.UTF_8; public class AttrImpl extends NamedNode implements Attr { public static final int LENGTH_NS_ID = 2; //sizeof short public static final int LENGTH_PREFIX_LENGTH = 2; //sizeof short public static final int CDATA = 0; public static final int ID = 1; public static final int IDREF = 2; public static final int IDREFS = 3; private static final int DEFAULT_ATTRIBUTE_TYPE = CDATA; private int attributeType = DEFAULT_ATTRIBUTE_TYPE; private int indexType = RangeIndexSpec.NO_INDEX; private XMLString value = null; public AttrImpl() { super(Node.ATTRIBUTE_NODE); } public AttrImpl(final QName name, final SymbolTable symbols) throws DOMException { super(Node.ATTRIBUTE_NODE, name); if(symbols != null && symbols.getSymbol(nodeName.getLocalPart()) < 0) { throw new DOMException(DOMException.INVALID_ACCESS_ERR, "Too many element/attribute names registered in the database. No of distinct names is limited to 16bit. Aborting store."); } } public AttrImpl(final QName name, final String str, final SymbolTable symbols) throws DOMException { this(name, symbols); this.value = new XMLString(str.toCharArray()); } public AttrImpl(final AttrImpl other) { super(other); this.attributeType = other.attributeType; this.value = other.value; } @Override public void clear() { super.clear(); this.attributeType = DEFAULT_ATTRIBUTE_TYPE; this.value = null; } /** * Serializes a (persistent DOM) Attr to a byte array * * data = signature nodeIdUnitsLength nodeId localNameId namespace? value * * signature = [byte] 0x80 | localNameType | attrType | hasNamespace? * * localNameType = noContent OR intContent OR shortContent OR byteContent * noContent = 0x0 * intContent = 0x1 * shortContent = 0x2 * byteContent = 0x3 * * attrType = cdata OR id OR idref OR idrefs * cdata = 0x0; * id = 0x4 * idref = 0x8 * idrefs = 0xC * * hasNamespace = 0x10 * * nodeIdUnitsLength = [short] (2 bytes) The number of units of the attr's NodeId * nodeId = {@see org.exist.numbering.DLNBase#serialize(byte[], int)} * * localNameId = [int] (4 bytes) | [short] (2 bytes) | [byte] 1 byte. The Id of the attr's local name from SymbolTable (symbols.dbx) * * namespace = namespaceUriId namespacePrefixLength attrNamespacePrefix? * namespaceUriId = [short] (2 bytes) The Id of the namespace URI from SymbolTable (symbols.dbx) * namespacePrefixLength = [short] (2 bytes) * attrNamespacePrefix = eUtf8 * * value = eUtf8 * * eUtf8 = {@see org.exist.util.UTF8#encode(java.lang.String, byte[], int)} */ @Override public byte[] serialize() { if(nodeName.getLocalPart() == null) { throw new RuntimeException("Local name is null"); } final short id = ownerDocument.getBrokerPool().getSymbols().getSymbol(this); final byte idSizeType = Signatures.getSizeType(id); int prefixLen = 0; if(nodeName.hasNamespace() && nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) { prefixLen = UTF8.encoded(nodeName.getPrefix()); } final int nodeIdLen = nodeId.size(); final byte[] data = ByteArrayPool.getByteArray( LENGTH_SIGNATURE_LENGTH + NodeId.LENGTH_NODE_ID_UNITS + nodeIdLen + Signatures.getLength(idSizeType) + (nodeName.hasNamespace() ? LENGTH_NS_ID + LENGTH_PREFIX_LENGTH + prefixLen : 0) + value.UTF8Size()); int pos = 0; data[pos] = (byte) (Signatures.Attr << 0x5); data[pos] |= idSizeType; data[pos] |= (byte) (attributeType << 0x2); if(nodeName.hasNamespace()) { data[pos] |= 0x10; } pos += StoredNode.LENGTH_SIGNATURE_LENGTH; ByteConversion.shortToByte((short) nodeId.units(), data, pos); pos += NodeId.LENGTH_NODE_ID_UNITS; nodeId.serialize(data, pos); pos += nodeIdLen; Signatures.write(idSizeType, id, data, pos); pos += Signatures.getLength(idSizeType); if(nodeName.hasNamespace()) { final short nsId = ownerDocument.getBrokerPool().getSymbols().getNSSymbol(nodeName.getNamespaceURI()); ByteConversion.shortToByte(nsId, data, pos); pos += LENGTH_NS_ID; ByteConversion.shortToByte((short) prefixLen, data, pos); pos += LENGTH_PREFIX_LENGTH; if(nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) { UTF8.encode(nodeName.getPrefix(), data, pos); } pos += prefixLen; } value.UTF8Encode(data, pos); return data; } public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc, final boolean pooled) { int pos = start; final byte idSizeType = (byte) (data[pos] & 0x3); final boolean hasNamespace = (data[pos] & 0x10) == 0x10; final int attrType = (data[pos] & 0x4) >> 0x2; pos += StoredNode.LENGTH_SIGNATURE_LENGTH; final int dlnLen = ByteConversion.byteToShort(data, pos); pos += NodeId.LENGTH_NODE_ID_UNITS; final NodeId dln = doc.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos); pos += dln.size(); final short id = (short) Signatures.read(idSizeType, data, pos); pos += Signatures.getLength(idSizeType); final String name = doc.getBrokerPool().getSymbols().getName(id); if(name == null) { throw new RuntimeException("no symbol for id " + id); } short nsId = 0; String prefix = null; if(hasNamespace) { nsId = ByteConversion.byteToShort(data, pos); pos += LENGTH_NS_ID; int prefixLen = ByteConversion.byteToShort(data, pos); pos += LENGTH_PREFIX_LENGTH; if(prefixLen > 0) { prefix = UTF8.decode(data, pos, prefixLen).toString(); } pos += prefixLen; } final String namespace = nsId == 0 ? "" : doc.getBrokerPool().getSymbols().getNamespace(nsId); XMLString value = UTF8.decode(data, pos, len - (pos - start)); //OK : we have the necessary material to build the attribute final AttrImpl attr; if(pooled) { attr = (AttrImpl) NodePool.getInstance().borrowNode(Node.ATTRIBUTE_NODE); } else { attr = new AttrImpl(); } attr.setNodeName(doc.getBrokerPool().getSymbols().getQName(Node.ATTRIBUTE_NODE, namespace, name, prefix)); attr.value = value; attr.setNodeId(dln); attr.setType(attrType); return attr; } public static void addToList(final DBBroker broker, final byte[] data, final int start, final int len, final AttrList list) { int pos = start; final byte idSizeType = (byte) (data[pos] & 0x3); final boolean hasNamespace = (data[pos] & 0x10) == 0x10; final int attrType = (data[pos] & 0x4) >> 0x2; pos += StoredNode.LENGTH_SIGNATURE_LENGTH; final int dlnLen = ByteConversion.byteToShort(data, pos); pos += NodeId.LENGTH_NODE_ID_UNITS; final NodeId dln = broker.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos); pos += dln.size(); final short id = (short) Signatures.read(idSizeType, data, pos); pos += Signatures.getLength(idSizeType); final String name = broker.getBrokerPool().getSymbols().getName(id); if(name == null) { throw new RuntimeException("no symbol for id " + id); } short nsId = 0; String prefix = null; if(hasNamespace) { nsId = ByteConversion.byteToShort(data, pos); pos += LENGTH_NS_ID; int prefixLen = ByteConversion.byteToShort(data, pos); pos += LENGTH_PREFIX_LENGTH; if(prefixLen > 0) { prefix = UTF8.decode(data, pos, prefixLen).toString(); } pos += prefixLen; } final String namespace = nsId == 0 ? XMLConstants.NULL_NS_URI : broker.getBrokerPool().getSymbols().getNamespace(nsId); final String value = new String(data, pos, len - (pos - start), UTF_8); list.addAttribute(broker.getBrokerPool().getSymbols().getQName(Node.ATTRIBUTE_NODE, namespace, name, prefix), value, attrType, dln); } @Override public String getName() { return getNodeName(); } public int getType() { return attributeType; } public void setType(final int type) { //TODO : range check -pb this.attributeType = type; } public static String getAttributeType(final int type) { switch(type) { case AttrImpl.ID: return "ID"; case AttrImpl.IDREF: return "IDREF"; case AttrImpl.IDREFS: return "IDREFS"; case AttrImpl.CDATA: return "CDATA"; default: return null; } } public void setIndexType(final int idxType) { this.indexType = idxType; } public int getIndexType() { return indexType; } @Override public String getValue() { return value.toString(); } @Override public String getNodeValue() { return getValue(); } @Override public void setValue(final String value) throws DOMException { this.value = new XMLString(value.toCharArray()); } @Override public Element getOwnerElement() { return (Element)getOwnerDocument().getNode(nodeId.getParentId()); } @Override public boolean getSpecified() { return true; } @Override public String toString() { return String.valueOf(nodeName) + "=\"" + value + "\""; } @Override public String toString(final boolean top) { if(top) { return"<exist:attribute " + "xmlns:exist=\"" + Namespaces.EXIST_NS + "\" " + "exist:id=\"" + getNodeId() + "\" exist:source=\"" + getOwnerDocument().getFileURI() + "\" " + getNodeName() + "=\"" + getValue() + "\"/>"; } else { return toString(); } } @Override public int getChildCount() { return 0; } @Override public Node getFirstChild() { return null; } @Override public TypeInfo getSchemaTypeInfo() { return null; } @Override public boolean isId() { return this.getType() == ID; } @Override public String getBaseURI() { final Element e = getOwnerElement(); if(e != null) { return e.getBaseURI(); } return null; } @Override public short compareDocumentPosition(final Node other) throws DOMException { return 0; } @Override public String getTextContent() throws DOMException { return null; } @Override public void setTextContent(final String textContent) throws DOMException { } @Override public String lookupPrefix(final String namespaceURI) { return null; } @Override public boolean isDefaultNamespace(final String namespaceURI) { return false; } @Override public String lookupNamespaceURI(final String prefix) { return null; } @Override public boolean isEqualNode(final Node arg) { return false; } @Override public Object getFeature(final String feature, final String version) { return null; } @Override public Object setUserData(final String key, final Object data, final UserDataHandler handler) { return null; } @Override public Object getUserData(final String key) { return null; } }