/*******************************************************************************
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.tag.ITag;
import org.eclipse.cdt.core.dom.ast.tag.IWritableTag;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.cdt.internal.core.pdom.db.BTree;
import org.eclipse.cdt.internal.core.pdom.db.Database;
import org.eclipse.cdt.internal.core.pdom.db.PDOMStringSet;
import org.eclipse.core.runtime.CoreException;
/**
* Not thread-safe.
*/
public class PDOMTagIndex {
private static enum Fields {
TaggerIds, Tags, _last;
public final long offset = ordinal() * Database.PTR_SIZE;
public static int sizeof = _last.ordinal() * Database.PTR_SIZE;
}
private final Database db;
private final long ptr;
private long rootRecord;
private PDOMStringSet taggerIds;
private BTree tags;
public PDOMTagIndex(Database db, long ptr) throws CoreException {
this.db = db;
this.ptr = ptr;
this.rootRecord = 0;
}
private long getFieldAddress(Fields field) throws CoreException {
if (rootRecord == 0)
rootRecord = db.getRecPtr(ptr);
if (rootRecord == 0) {
rootRecord = db.malloc(Fields.sizeof);
db.putRecPtr(ptr, rootRecord);
}
return rootRecord + field.offset;
}
private PDOMStringSet getTaggerIds() throws CoreException {
if (taggerIds == null)
taggerIds = new PDOMStringSet(db, getFieldAddress(Fields.TaggerIds));
return taggerIds;
}
private BTree getTagsBTree() throws CoreException {
if (tags == null)
tags = new BTree(db, getFieldAddress(Fields.Tags), new PDOMTag.BTreeComparator(db));
return tags;
}
/**
* Return the record storing the specified tagger id. Create a new record if needed.
*/
private long getIdRecord(String taggerId, boolean createIfNeeded) {
assert taggerId != null;
assert !taggerId.isEmpty();
if (db == null || taggerId == null || taggerId.isEmpty()
|| (taggerIds == null && !createIfNeeded)) {
return 0L;
}
try {
long record = getTaggerIds().find(taggerId);
if (record == 0 && createIfNeeded)
record = getTaggerIds().add(taggerId);
return record;
} catch (CoreException e) {
CCorePlugin.log(e);
}
return 0L;
}
private IWritableTag createTag(long record, String id, int len) {
if (db == null)
return null;
long idRecord = getIdRecord(id, true);
if (idRecord == 0L)
return null;
try {
PDOMTag tag = new PDOMTag(db, len);
tag.setNode(record);
tag.setTaggerId(idRecord);
// return the tag if it was properly inserted
long inserted = getTagsBTree().insert(tag.getRecord());
if (inserted == tag.getRecord())
return tag;
// TODO check that the existing record has the same length
// otherwise destroy this provisional one and return the tag that was actually inserted
// TODO figure out what this case means
tag.delete();
return inserted == 0 ? null : new PDOMTag(db, inserted);
} catch (CoreException e) {
CCorePlugin.log(e);
}
return null;
}
private ITag getTag(long record, String id) {
if (db == null)
return null;
long idRecord = getIdRecord(id, false);
if (idRecord == 0L)
return null;
PDOMTag.BTreeVisitor v = new PDOMTag.BTreeVisitor(db, record, idRecord);
try {
getTagsBTree().accept(v);
} catch (CoreException e) {
CCorePlugin.log(e);
}
return v.hasResult ? new PDOMTag(db, v.tagRecord) : null;
}
private Iterable<ITag> getTags(long binding_record) {
BTree btree = null;
try {
btree = getTagsBTree();
} catch (CoreException e) {
CCorePlugin.log(e);
return Collections.emptyList();
}
final Long bindingRecord = Long.valueOf(binding_record);
return new BTreeIterable<ITag>(btree, new BTreeIterable.Descriptor<ITag>() {
@Override
public ITag create(long record) {
return new PDOMTag(db, record);
}
@Override
public int compare(long test_record) throws CoreException {
long test_node = new PDOMTag(db, test_record).getNode();
// -1 if record < key, 0 if record == key, 1 if record > key
return Long.valueOf(test_node).compareTo(bindingRecord);
}
});
}
private boolean setTags(long binding_record, Iterable<ITag> tags) {
// There could be several tags for the given record in the database, one for each taggerId. We need
// to delete all of those tags and replace them with given list. The incoming tags are first put
// into a map, indexed by their taggerId. Then we examine the btree of tags to find all tags for this
// record. In each case we decide whether to delete or update the tag. Tags of the same size can be
// updated in place, otherwise the tag needs to be deleted and recreated.
final Map<String, ITag> newTags = new HashMap<String, ITag>();
for (ITag tag : tags) {
ITag dupTag = newTags.put(tag.getTaggerId(), tag);
if (dupTag != null)
CCorePlugin.log("Duplicate incoming tag for record " + binding_record //$NON-NLS-1$
+ " from taggerId " + tag.getTaggerId()); //$NON-NLS-1$
}
BTree btree = null;
try {
btree = getTagsBTree();
} catch (CoreException e) {
CCorePlugin.log(e);
return false;
}
PDOMTagSynchronizer sync = new PDOMTagSynchronizer(db, Long.valueOf(binding_record), newTags);
// visit the full tree, then return true on success and false on failure
try {
btree.accept(sync);
} catch (CoreException e) {
CCorePlugin.log(e);
return false;
}
// Complete the synchronization (delete/insert the records that could not be modified in-place). This
// will only have something to do when a tag has changed length, which should be a rare.
sync.synchronize(btree);
// insert any new tags that are left in the incoming list
for (ITag newTag : newTags.values()) {
IWritableTag pdomTag = createTag(binding_record, newTag.getTaggerId(), newTag.getDataLen());
pdomTag.putBytes(0, newTag.getBytes(0, -1), -1);
}
return true;
}
private static PDOMTagIndex getTagIndex(PDOM pdom) {
if (pdom == null)
return null;
try {
PDOMTagIndex index = pdom.getTagIndex();
return index.db == null ? null : index;
} catch (CoreException e) {
CCorePlugin.log(e);
}
return null;
}
// common implementations
public static IWritableTag createTag(PDOM pdom, long record, String id, int len) {
PDOMTagIndex index = getTagIndex(pdom);
if (index == null)
return null;
return index.createTag(record, id, len);
}
public static ITag getTag(PDOM pdom, long record, String id) {
PDOMTagIndex index = getTagIndex(pdom);
if (index == null)
return null;
return index.getTag(record, id);
}
public static Iterable<ITag> getTags(PDOM pdom, long record) {
PDOMTagIndex index = getTagIndex(pdom);
if (index == null)
return Collections.emptyList();
return index.getTags(record);
}
public static boolean setTags(PDOM pdom, long record, Iterable<ITag> tags) {
if (record == 0)
return true;
PDOMTagIndex index = getTagIndex(pdom);
if (index == null)
return false;
return index.setTags(record, tags);
}
}