/*******************************************************************************
* Copyright (c) 2005, 2011 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:
* Doug Schaefer (QNX) - Initial API and implementation
* Markus Schorn (Wind River Systems)
* IBM Corporation
* Andrew Ferguson (Symbian)
* Jens Elmenthaler - http://bugs.eclipse.org/173458 (camel case completion)
*******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.dom;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import org.eclipse.cdt.core.dom.IPDOMVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.IParameter;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.IValue;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDirective;
import org.eclipse.cdt.core.index.IIndexLinkage;
import org.eclipse.cdt.core.parser.util.CharArrayMap;
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.ITypeMarshalBuffer;
import org.eclipse.cdt.internal.core.index.IIndexBindingConstants;
import org.eclipse.cdt.internal.core.index.IIndexScope;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.cdt.internal.core.pdom.WritablePDOM;
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.IBTreeComparator;
import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor;
import org.eclipse.cdt.internal.core.pdom.db.IString;
import org.eclipse.cdt.internal.core.pdom.db.TypeMarshalBuffer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
/**
* This class represents a collection of symbols that can be linked together at
* link time. These are generally global symbols specific to a given language.
*/
public abstract class PDOMLinkage extends PDOMNamedNode implements IIndexLinkage, IIndexBindingConstants {
// record offsets
private static final int ID_OFFSET = PDOMNamedNode.RECORD_SIZE + 0;
private static final int NEXT_OFFSET = PDOMNamedNode.RECORD_SIZE + 4;
private static final int INDEX_OFFSET = PDOMNamedNode.RECORD_SIZE + 8;
private static final int NESTED_BINDINGS_INDEX = PDOMNamedNode.RECORD_SIZE + 12;
private static final int MACRO_BTREE = PDOMNamedNode.RECORD_SIZE + 16;
@SuppressWarnings("hiding")
protected static final int RECORD_SIZE = PDOMNamedNode.RECORD_SIZE + 20;
protected static final long[] FILE_LOCAL_REC_DUMMY = new long[]{0};
// node types
protected static final int LINKAGE= 0; // special one for myself
private BTree fMacroIndex= null; // No need for volatile, all fields of BTree are final.
private final PDOM fPDOM;
private final Database fDatabase;
public PDOMLinkage(PDOM pdom, long record) {
super(null, record);
fPDOM= pdom;
fDatabase= pdom.getDB();
}
protected PDOMLinkage(PDOM pdom, String languageId, char[] name) throws CoreException {
super(pdom.getDB(), name);
final Database db= pdom.getDB();
fPDOM= pdom;
fDatabase= db;
db.putRecPtr(record + ID_OFFSET, db.newString(languageId).getRecord());
pdom.insertLinkage(this);
}
@Override
public final PDOM getPDOM() {
return fPDOM;
}
@Override
public final PDOMLinkage getLinkage() {
return this;
}
@Override
public final Database getDB() {
return fDatabase;
}
@Override
protected int getRecordSize() {
return RECORD_SIZE;
}
@Override
public int getNodeType() {
return LINKAGE;
}
public static IString getLinkageID(PDOM pdom, long record) throws CoreException {
Database db = pdom.getDB();
long namerec = db.getRecPtr(record + ID_OFFSET);
return db.getString(namerec);
}
public static long getNextLinkageRecord(PDOM pdom, long record) throws CoreException {
return pdom.getDB().getRecPtr(record + NEXT_OFFSET);
}
public void setNext(long nextrec) throws CoreException {
getDB().putRecPtr(record + NEXT_OFFSET, nextrec);
}
public BTree getIndex() throws CoreException {
return new BTree(getDB(), record + INDEX_OFFSET, getIndexComparator());
}
/**
* Returns the BTree for the nested bindings.
* @throws CoreException
*/
public BTree getNestedBindingsIndex() throws CoreException {
return new BTree(fDatabase, record + NESTED_BINDINGS_INDEX, getNestedBindingsComparator());
}
@Override
public void accept(final IPDOMVisitor visitor) throws CoreException {
if (visitor instanceof IBTreeVisitor) {
getIndex().accept((IBTreeVisitor) visitor);
} else {
getIndex().accept(new IBTreeVisitor() {
public int compare(long record) throws CoreException {
return 0;
}
public boolean visit(long record) throws CoreException {
PDOMNode node= getNode(record);
if (node != null) {
if (visitor.visit(node))
node.accept(visitor);
visitor.leave(node);
}
return true;
}
});
}
}
@Override
public void addChild(PDOMNode child) throws CoreException {
getIndex().insert(child.getRecord());
}
public final PDOMBinding getBinding(long record) throws CoreException {
final PDOMNode node= getNode(record);
if (node instanceof PDOMBinding)
return (PDOMBinding) node;
return null;
}
public final PDOMNode getNode(long record) throws CoreException {
if (record == 0) {
return null;
}
final int nodeType= PDOMNode.getNodeType(fDatabase, record);
switch (nodeType) {
case LINKAGE:
return null;
}
return getNode(record, nodeType);
}
abstract public PDOMNode getNode(long record, int nodeType) throws CoreException;
public abstract IBTreeComparator getIndexComparator();
public IBTreeComparator getNestedBindingsComparator() {
return new FindBinding.NestedBindingsBTreeComparator(this);
}
protected boolean cannotAdapt(final IBinding inputBinding) throws CoreException {
if (inputBinding == null || inputBinding instanceof IProblemBinding || inputBinding instanceof IParameter) {
return true;
}
if (inputBinding instanceof PDOMBinding) {
PDOMBinding pdomBinding = (PDOMBinding) inputBinding;
if (pdomBinding.getPDOM() != getPDOM() && pdomBinding.isFileLocal()) {
return true;
}
}
return false;
}
protected final PDOMBinding attemptFastAdaptBinding(final IBinding binding) throws CoreException {
PDOMBinding pdomBinding= (PDOMBinding) binding.getAdapter(PDOMBinding.class);
// There is no guarantee, that the binding is from the same PDOM object.
if (pdomBinding != null && pdomBinding.getPDOM() == getPDOM()) {
return pdomBinding;
}
return (PDOMBinding) fPDOM.getCachedResult(binding);
}
public abstract PDOMBinding adaptBinding(IBinding binding) throws CoreException;
public abstract PDOMBinding addBinding(IASTName name) throws CoreException;
final protected long getLocalToFileRec(PDOMNode parent, IBinding binding, PDOMBinding glob) throws CoreException {
long rec= 0;
if (parent instanceof PDOMBinding) {
rec= ((PDOMBinding) parent).getLocalToFileRec();
}
if (rec == 0) {
PDOMFile file= getLocalToFile(binding, glob);
if (file != null) {
rec= file.getRecord();
}
}
return rec;
}
protected PDOMFile getLocalToFile(IBinding binding, PDOMBinding glob) throws CoreException {
if (fPDOM instanceof WritablePDOM) {
final WritablePDOM wpdom= (WritablePDOM) fPDOM;
if (binding instanceof IField) {
return null;
}
boolean checkIfInSourceOnly= false;
boolean requireDefinition= false;
if (binding instanceof IVariable) {
if (!(binding instanceof IField)) {
checkIfInSourceOnly= ((IVariable) binding).isStatic();
}
} else if (binding instanceof IFunction) {
IFunction f= (IFunction) binding;
checkIfInSourceOnly= ASTInternal.isStatic(f, false);
} else if (binding instanceof ITypedef || binding instanceof ICompositeType || binding instanceof IEnumeration) {
checkIfInSourceOnly= true;
requireDefinition= true;
}
if (checkIfInSourceOnly) {
IASTNode node= ASTInternal.getDeclaredInSourceFileOnly(binding, requireDefinition, glob);
if (node != null) {
return wpdom.getFileForASTNode(getLinkageID(), node);
}
}
}
return null;
}
public abstract int getBindingType(IBinding binding);
/**
* Call-back informing the linkage that a name has been added. This is
* used to do additional processing, like establishing inheritance relationships.
* @param file the file that has triggered the creation of the name
* @param name the name that caused the insertion
* @param pdomName the name that was inserted into the linkage
* @throws CoreException
* @since 4.0
*/
public void onCreateName(PDOMFile file, IASTName name, PDOMName pdomName) throws CoreException {
IASTNode parentNode= name.getParent();
if (parentNode instanceof IASTDeclSpecifier) {
IASTDeclSpecifier ds= (IASTDeclSpecifier) parentNode;
if (ds.getStorageClass() == IASTDeclSpecifier.sc_typedef) {
if (pdomName.getEnclosingDefinitionRecord() != 0) {
pdomName.setIsBaseSpecifier();
}
}
}
}
/**
* Callback informing the linkage that a name is about to be deleted. This is
* used to do additional processing, like removing inheritance relationships.
* @param name the name that is about to be deleted
* @throws CoreException
* @since 4.0
*/
public void onDeleteName(PDOMName name) throws CoreException {
}
/**
* Callback informing the linkage that a binding has been added. Used to index nested bindings.
* @param pdomBinding
* @throws CoreException
* @since 4.0.1
*/
protected final void insertIntoNestedBindingsIndex(PDOMBinding pdomBinding) throws CoreException {
if (pdomBinding.getParentNodeRec() != record) {
getNestedBindingsIndex().insert(pdomBinding.getRecord());
}
}
/**
* Call-back informing the linkage that a binding is about to be removed. Used to index nested bindings.
* @param pdomBinding
* @throws CoreException
* @since 4.0.1
*/
public void beforeRemoveBinding(PDOMBinding pdomBinding) throws CoreException {
if (pdomBinding.getParentNodeRec() != record) {
getNestedBindingsIndex().delete(pdomBinding.getRecord());
}
}
public ICPPUsingDirective[] getUsingDirectives(PDOMFile file) throws CoreException {
return ICPPUsingDirective.EMPTY_ARRAY;
}
public BTree getMacroIndex() {
if (fMacroIndex == null) {
fMacroIndex= new BTree(getDB(), record + MACRO_BTREE, new FindBinding.MacroBTreeComparator(fDatabase));
}
return fMacroIndex;
}
public PDOMMacroContainer findMacroContainer(final char[] name) throws CoreException {
return findMacroContainer(name, fPDOM.createKeyForCache(record, name));
}
private PDOMMacroContainer findMacroContainer(final char[] name, final String key) throws CoreException {
Object result= fPDOM.getCachedResult(key);
if (result instanceof PDOMMacroContainer) {
return ((PDOMMacroContainer) result);
}
assert result==null;
MacroContainerFinder visitor = new MacroContainerFinder(this, name);
getMacroIndex().accept(visitor);
PDOMMacroContainer container= visitor.getMacroContainer();
if (container != null) {
fPDOM.putCachedResult(key, container);
}
return container;
}
public PDOMMacroContainer getMacroContainer(char[] name) throws CoreException {
String key= fPDOM.createKeyForCache(record, name);
PDOMMacroContainer result= findMacroContainer(name, key);
if (result == null) {
result= new PDOMMacroContainer(this, name);
getMacroIndex().insert(result.getRecord());
fPDOM.putCachedResult(key, result);
}
return result;
}
public void removeMacroContainer (PDOMMacroContainer container) throws CoreException {
String key= fPDOM.createKeyForCache(record, container.getNameCharArray());
fPDOM.putCachedResult(key, null);
getMacroIndex().delete(container.getRecord());
}
/**
* For debugging purposes, only.
*/
@Override
public String toString() {
return getLinkageName();
}
/**
* Usually bindings are added on behalf of a name, only. For unknown values or using declarations
* we need to add further bindings.
* @throws CoreException
*/
public PDOMBinding addPotentiallyUnknownBinding(IBinding binding) throws CoreException {
return null;
}
/**
* Returns the list of global bindings for the given name.
* @throws CoreException
*/
public PDOMBinding[] getBindingsViaCache(char[] name, IProgressMonitor monitor) throws CoreException {
CharArrayMap<PDOMBinding[]> map = getBindingMap();
synchronized(map) {
PDOMBinding[] result= map.get(name);
if (result != null)
return result;
}
BindingCollector visitor = new BindingCollector(this, name, null, false, false, true);
visitor.setMonitor(monitor);
getIndex().accept(visitor);
PDOMBinding[] result= visitor.getBindings();
synchronized(map) {
map.put(name, result);
}
return result;
}
private CharArrayMap<PDOMBinding[]> getBindingMap() {
final Long key= getRecord();
final PDOM pdom = getPDOM();
@SuppressWarnings("unchecked")
Reference<CharArrayMap<PDOMBinding[]>> cached= (Reference<CharArrayMap<PDOMBinding[]>>) pdom.getCachedResult(key);
CharArrayMap<PDOMBinding[]> map= cached == null ? null : cached.get();
if (map == null) {
map= new CharArrayMap<PDOMBinding[]>();
pdom.putCachedResult(key, new SoftReference<CharArrayMap<?>>(map));
}
return map;
}
public abstract PDOMBinding addTypeBinding(IBinding type) throws CoreException;
public abstract IType unmarshalType(ITypeMarshalBuffer buffer) throws CoreException;
public void storeType(long offset, IType type) throws CoreException {
final Database db= getDB();
deleteType(db, offset);
storeType(db, offset, type);
}
private void storeType(Database db, long offset, IType type) throws CoreException {
if (type != null) {
TypeMarshalBuffer bc= new TypeMarshalBuffer(this);
bc.marshalType(type);
int len= bc.getPosition();
if (len > 0) {
if (len <= Database.TYPE_SIZE) {
db.putBytes(offset, bc.getBuffer(), len);
} else if (len <= Database.MAX_MALLOC_SIZE-2){
long ptr= db.malloc(len+2);
db.putShort(ptr, (short) len);
db.putBytes(ptr+2, bc.getBuffer(), len);
db.putByte(offset, TypeMarshalBuffer.INDIRECT_TYPE);
db.putRecPtr(offset+2, ptr);
}
}
}
}
private void deleteType(Database db, long offset) throws CoreException {
byte firstByte= db.getByte(offset);
if (firstByte == TypeMarshalBuffer.INDIRECT_TYPE) {
long ptr= db.getRecPtr(offset+2);
clearType(db, offset);
db.free(ptr);
} else {
clearType(db, offset);
}
}
private void clearType(Database db, long offset) throws CoreException {
db.clearBytes(offset, Database.TYPE_SIZE);
}
public IType loadType(long offset) throws CoreException {
final Database db= getDB();
final byte firstByte= db.getByte(offset);
byte[] data= null;
switch(firstByte) {
case TypeMarshalBuffer.INDIRECT_TYPE:
long ptr= db.getRecPtr(offset+2);
int len= db.getShort(ptr) & 0xffff;
data= new byte[len];
db.getBytes(ptr+2, data);
break;
case TypeMarshalBuffer.UNSTORABLE_TYPE:
return TypeMarshalBuffer.UNSTORABLE_TYPE_PROBLEM;
case TypeMarshalBuffer.NULL_TYPE:
return null;
default:
data= new byte[Database.TYPE_SIZE];
db.getBytes(offset, data);
break;
}
return new TypeMarshalBuffer(this, data).unmarshalType();
}
public void storeValue(long offset, IValue type) throws CoreException {
final Database db= getDB();
deleteValue(db, offset);
storeValue(db, offset, type);
}
private void storeValue(Database db, long offset, IValue value) throws CoreException {
if (value != null) {
TypeMarshalBuffer bc= new TypeMarshalBuffer(this);
bc.marshalValue(value);
int len= bc.getPosition();
if (len > 0) {
if (len <= Database.TYPE_SIZE) {
db.putBytes(offset, bc.getBuffer(), len);
} else if (len <= Database.MAX_MALLOC_SIZE-2){
long ptr= db.malloc(len+2);
db.putShort(ptr, (short) len);
db.putBytes(ptr+2, bc.getBuffer(), len);
db.putByte(offset, TypeMarshalBuffer.INDIRECT_TYPE);
db.putRecPtr(offset+2, ptr);
}
}
}
}
private void deleteValue(Database db, long offset) throws CoreException {
deleteType(db, offset);
}
public IValue loadValue(long offset) throws CoreException {
final Database db= getDB();
final byte firstByte= db.getByte(offset);
byte[] data= null;
switch(firstByte) {
case TypeMarshalBuffer.INDIRECT_TYPE:
long ptr= db.getRecPtr(offset+2);
int len= db.getShort(ptr) & 0xffff;
data= new byte[len];
db.getBytes(ptr+2, data);
break;
case TypeMarshalBuffer.NULL_TYPE:
return null;
default:
data= new byte[Database.TYPE_SIZE];
db.getBytes(offset, data);
break;
}
return new TypeMarshalBuffer(this, data).unmarshalValue();
}
public IIndexScope[] getInlineNamespaces() {
return IIndexScope.EMPTY_INDEX_SCOPE_ARRAY;
}
}