/*******************************************************************************
* Copyright (c) 2013, 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andrew Eidsness - Initial implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.tag;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.tag.IWritableTag;
import org.eclipse.cdt.internal.core.pdom.db.Database;
import org.eclipse.cdt.internal.core.pdom.db.IBTreeComparator;
import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor;
import org.eclipse.core.runtime.CoreException;
/**
* A container for storing tags in the PDOM. The storage format is as follows:
*
* <pre>
* PDOMTag
* {
* u4 node_record; # pointer to the node that is being tagged
* u4 tagger_id; # pointer to the string that identifies the tagger that
* # created this tag
* u4 data_len; # number of bytes stored in this tag's payload
* u1[]; # a buffer for storing the tag's payload
* };
* </pre>
*
* Each tag points to the contributing tagger id, the record that is being tagged. Finally, it stores a buffer
* for the tag's payload. The buffer is read and written by the contributor of the tag.
*/
public class PDOMTag implements IWritableTag {
private final Database db;
private final long record;
private String taggerId;
private int dataLen = -1;
private static enum Fields {
Node, TaggerId, DataLen, Data;
public final long offset = ordinal() * Database.PTR_SIZE;
public static int sizeof(int datalen) {
return (int) Data.offset + datalen;
}
public long getRecPtr(Database db, long instance, long data_offset) throws CoreException {
return db.getRecPtr(instance + offset + data_offset);
}
public void putRecPtr(Database db, long instance, long data_offset, long value)
throws CoreException {
db.putRecPtr(instance + offset + data_offset, value);
}
public void put(Database db, long instance, long data_offset, byte value)
throws CoreException {
db.putByte(instance + offset + data_offset, value);
}
public void put(Database db, long instance, byte[] data, long data_offset, int len)
throws CoreException {
db.putBytes(instance + offset + data_offset, data, len);
}
public byte getByte(Database db, long instance, long data_offset) throws CoreException {
return db.getByte(instance + offset + data_offset);
}
public byte[] getBytes(Database db, long instance, long data_offset, int len)
throws CoreException {
byte[] data = new byte[len];
db.getBytes(instance + offset + data_offset, data);
return data;
}
public void put(Database db, long instance, long data_offset, int value)
throws CoreException {
db.putInt(instance + offset + data_offset, value);
}
public int getInt(Database db, long instance, long data_offset) throws CoreException {
return db.getInt(instance + offset + data_offset);
}
}
public PDOMTag(Database db, long record) {
this.db = db;
this.record = record;
}
public PDOMTag(Database db, int dataLen) throws CoreException {
this.db = db;
this.record = db.malloc(Fields.sizeof(dataLen));
this.dataLen = dataLen;
Fields.DataLen.put(db, record, 0, dataLen);
}
public long getNode() throws CoreException {
return Fields.Node.getRecPtr(db, record, 0);
}
@Override
public String getTaggerId() {
if (taggerId == null) {
try {
long taggerIdRecord = Fields.TaggerId.getRecPtr(db, record, 0);
taggerId = taggerIdRecord == 0L ? "" : db.getString(taggerIdRecord).getString(); //$NON-NLS-1$
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
return taggerId;
}
@Override
public int getDataLen() {
if (dataLen < 0) {
try {
dataLen = Fields.DataLen.getInt(db, record, 0);
} catch (CoreException e) {
CCorePlugin.log(e);
return 0;
}
}
return dataLen;
}
public long getRecord() {
return record;
}
/**
* Create and return a new PDOMTag that has the same node/taggerId as the receiver but with the specified
* data. Return null on failure.
*/
public PDOMTag cloneWith(byte[] data) throws CoreException {
PDOMTag partialTag = null;
try {
long existing_node = Fields.Node.getRecPtr(db, record, 0);
long existing_id = Fields.TaggerId.getRecPtr(db, record, 0);
partialTag = new PDOMTag(db, data.length);
Fields.Node.putRecPtr(db, partialTag.record, 0, existing_node);
Fields.TaggerId.putRecPtr(db, partialTag.record, 0, existing_id);
if (partialTag.putBytes(0, data, data.length)) {
PDOMTag tag = partialTag;
partialTag = null;
return tag;
}
} finally {
if (partialTag != null)
partialTag.delete();
}
return null;
}
public void delete() {
if (db != null && record != 0) {
try {
db.free(record);
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
}
public static class BTreeComparator implements IBTreeComparator {
private final Database db;
public BTreeComparator(Database db) {
this.db = db;
}
@Override
public int compare(long record1, long record2) throws CoreException {
if (record1 == record2)
return 0;
long node1 = Fields.Node.getRecPtr(db, record1, 0);
long node2 = Fields.Node.getRecPtr(db, record2, 0);
if (node1 < node2)
return -1;
if (node1 > node2)
return 1;
long tagger1 = Fields.TaggerId.getRecPtr(db, record1, 0);
long tagger2 = Fields.TaggerId.getRecPtr(db, record2, 0);
if (tagger1 < tagger2)
return -1;
if (tagger1 > tagger2)
return 1;
return 0;
}
}
public static class BTreeVisitor implements IBTreeVisitor {
private final Database db;
private final long node2;
private final long tagger2;
public boolean hasResult = false;
public long tagRecord = 0;
public BTreeVisitor(Database db, long node2, long tagger2) {
this.db = db;
this.node2 = node2;
this.tagger2 = tagger2;
}
@Override
public int compare(long record1) throws CoreException {
long node1 = Fields.Node.getRecPtr(db, record1, 0);
if (node1 < node2)
return -1;
if (node1 > node2)
return 1;
long tagger1 = Fields.TaggerId.getRecPtr(db, record1, 0);
if (tagger1 < tagger2)
return -1;
if (tagger1 > tagger2)
return 1;
return 0;
}
@Override
public boolean visit(long record) throws CoreException {
tagRecord = record;
hasResult = true;
return false;
}
}
public void setNode(long node) throws CoreException {
Fields.Node.putRecPtr(db, record, 0, node);
}
public void setTaggerId(long idRecord) throws CoreException {
Fields.TaggerId.putRecPtr(db, record, 0, idRecord);
}
private boolean isInBounds(int offset, int len) {
int data_len = getDataLen();
return offset >= 0 && offset < data_len && (offset + len) <= data_len;
}
@Override
public boolean putByte(int offset, byte data) {
if (!isInBounds(offset, 1))
return false;
try {
Fields.Data.put(db, record, offset, data);
return true;
} catch (CoreException e) {
CCorePlugin.log(e);
return false;
}
}
@Override
public boolean putBytes(int offset, byte[] data, int len) {
boolean fullWrite = len < 0;
if (fullWrite)
len = data.length;
if (!isInBounds(offset, len))
return false;
try {
Fields.Data.put(db, record, data, offset, len);
// if the new buffer replaces all of the existing one, then modify the receiver's stored length
int currLen = getDataLen();
if (fullWrite && offset == 0 && currLen > len) {
Fields.DataLen.put(db, record, 0, len);
dataLen = len;
}
return true;
} catch (CoreException e) {
CCorePlugin.log(e);
return false;
}
}
@Override
public int getByte(int offset) {
if (!isInBounds(offset, 1))
return FAIL;
try {
return Fields.Data.getByte(db, record, offset);
} catch (CoreException e) {
CCorePlugin.log(e);
return FAIL;
}
}
@Override
public byte[] getBytes(int offset, int len) {
len = len >= 0 ? len : getDataLen() - offset;
if (!isInBounds(offset, len))
return null;
try {
return Fields.Data.getBytes(db, record, offset, len);
} catch (CoreException e) {
CCorePlugin.log(e);
return null;
}
}
}