/*******************************************************************************
* Copyright (c) 2015 Bruno Medeiros and other Contributors.
* 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:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package melnorme.lang.tooling.symbols;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import melnorme.lang.tooling.engine.OverloadedNamedElement;
import melnorme.utilbox.collections.Collection2;
import melnorme.utilbox.collections.HashMap2;
import dtool.ast.definitions.EArcheType;
import dtool.engine.analysis.ModuleProxy;
/**
* A symbol table (map of named elements).
*
* Automatically handles merging of package namespaces.
*
*/
public class SymbolTable {
protected final HashMap2<String, INamedElement> map = new HashMap2<>();
public HashMap<String,INamedElement> getMap() {
return map;
}
public Set<Entry<String, INamedElement>> getEntries() {
return map.entrySet();
}
public Collection2<INamedElement> getElements() {
return map.getValuesView();
}
public void addSymbols(SymbolTable symbolTable) {
addSymbols(symbolTable.map.values());
}
public void addSymbols(Iterable<INamedElement> values) {
for (INamedElement namedElement : values) {
addSymbol(namedElement);
}
}
public void addSymbol(INamedElement newElement) {
String name = newElement.getNameInRegularNamespace();
INamedElement existingNamedElement = map.get(name);
if(existingNamedElement instanceof PackageNamespace && newElement instanceof PackageNamespace) {
PackageNamespace existingNamespace = (PackageNamespace) existingNamedElement;
PackageNamespace newNamespace = (PackageNamespace) newElement;
if(existingNamespace.isSemanticReady()) {
existingNamespace = existingNamespace.doCloneTree();
map.put(name, existingNamespace);
}
existingNamespace.getNamespaceForModification().addSymbols(newNamespace.getNamespaceElements());
} else {
addEntryToMap(name, newElement);
}
}
protected void addEntryToMap(String name, INamedElement newElement) {
INamedElement existingEntry = map.get(name);
if(existingEntry == null) {
doAddEntryToMap(name, newElement);
} else {
// An entry already exists
if(existingEntry.getArcheType() == EArcheType.Module && newElement.getArcheType() == EArcheType.Module) {
assertTrue(existingEntry.getFullyQualifiedName().equals(newElement.getFullyQualifiedName()));
return; // Don't add duplicated element.
}
if(existingEntry == newElement) {
// I don't think this case actually happens, but still, just in case:
return; // They are the same, so ignore
}
OverloadedNamedElement overloadElement;
if(existingEntry instanceof OverloadedNamedElement) {
overloadElement = (OverloadedNamedElement) existingEntry;
if(!overloadElement.isSemanticReady()) {
overloadElement.addElement(newElement);
return;
}
}
// Give priority to ModuleProxy element (note: this isn't entirely like DMD behavior
if(newElement instanceof ModuleProxy && existingEntry instanceof PackageNamespace) {
doAddEntryToMap(name, newElement);
return;
}
if(newElement instanceof PackageNamespace && existingEntry instanceof ModuleProxy) {
return;
}
overloadElement = new OverloadedNamedElement(existingEntry);
doAddEntryToMap(name, overloadElement);
overloadElement.addElement(newElement);
}
}
public void doAddEntryToMap(String name, INamedElement newElement) {
map.put(name, newElement);
}
public void addVisibleSymbols(SymbolTable otherSymbolTable) {
for (Entry<String, INamedElement> nameEntry : otherSymbolTable.getEntries()) {
String matchedName = nameEntry.getKey();
INamedElement matchedElement = nameEntry.getValue();
INamedElement existingSymbol = map.get(matchedName);
if(existingSymbol == null ||
(existingSymbol instanceof PackageNamespace && matchedElement instanceof PackageNamespace)) {
addSymbol(matchedElement);
}
}
otherSymbolTable.map.clear(); // otherSymbolTable can no longer be used
}
public void setCompleted() {
for (INamedElement namedElement : map.getValuesView()) {
if(namedElement instanceof OverloadedNamedElement) {
OverloadedNamedElement overloadedNamedElement = (OverloadedNamedElement) namedElement;
if(!overloadedNamedElement.isSemanticReady()) {
overloadedNamedElement.setElementReady();
}
}
}
}
}