blob: 5a186a6e43e14e82efedfff4293d68f976de396e [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.resolver;
import com.google.common.annotations.VisibleForTesting;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartObsoleteMetadata;
import com.google.dart.compiler.ast.Modifiers;
import com.google.dart.compiler.common.SourceInfo;
import com.google.dart.compiler.type.Type;
import java.util.ArrayList;
import java.util.List;
/**
* A more efficient version of {@link com.google.common.collect.Multimap} specifically for
* {@link NodeElement}
*/
@VisibleForTesting
public class ElementMap {
/**
* A synthetic place holder for an element where the name given to the element map does not match
* the value returned by {@link NodeElement#getName()} or where there are multiple elements associated
* with the same name.
*/
static class ElementHolder implements NodeElement {
private static final String INTERNAL_ONLY_ERROR =
"ElementHolder should not be accessed outside this class";
final String name;
final NodeElement element;
ElementHolder nextHolder;
ElementHolder(String name, NodeElement element) {
this.name = name;
this.element = element;
}
@Override
public SourceInfo getNameLocation() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public EnclosingElement getEnclosingElement() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public ElementKind getKind() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public DartObsoleteMetadata getMetadata() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public Modifiers getModifiers() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public String getName() {
return name;
}
@Override
public DartNode getNode() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public String getOriginalName() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public Type getType() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public boolean isDynamic() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
@Override
public SourceInfo getSourceInfo() {
throw new AssertionError(INTERNAL_ONLY_ERROR);
}
}
// Array indexed by hashed name ... length is always power of 2
private NodeElement[] elements;
private List<NodeElement> ordered = new ArrayList<NodeElement>();
@VisibleForTesting
public ElementMap() {
clear();
}
/**
* Associate the specified element with the specified name. If the element is already associated
* with that name, do not associate it again.
*/
@VisibleForTesting
public void add(String name, NodeElement element) {
// Most of the time name equals getName() thus holder == element
NodeElement newHolder;
if (name.equals(element.getName())) {
newHolder = element;
} else {
newHolder = new ElementHolder(name, element);
}
// 75% fill rate which anecdotal evidence claims is a good threshold for growing
if ((elements.length >> 2) * 3 <= size()) {
grow();
}
int index = internalAdd(newHolder);
if (index == -1) {
ordered.add(element);
return;
}
// Handle existing element with the same name
NodeElement existingHolder = elements[index];
if (existingHolder == element) {
return;
}
if (!(existingHolder instanceof ElementHolder)) {
existingHolder = new ElementHolder(name, existingHolder);
elements[index] = existingHolder;
}
// Check the list for a duplicate element entry, and append if none found
ElementHolder holder = (ElementHolder) existingHolder;
while (true) {
if (holder.element == element) {
return;
}
if (holder.nextHolder == null) {
holder.nextHolder = new ElementHolder(name, element);
ordered.add(element);
return;
}
holder = holder.nextHolder;
}
}
void clear() {
elements = new NodeElement[16];
ordered.clear();
}
/**
* Answer the element last associated with the specified name.
*
* @return the element or <code>null</code> if none
*/
@VisibleForTesting
public NodeElement get(String name) {
NodeElement element = internalGet(name);
if (element instanceof ElementHolder) {
return ((ElementHolder) element).element;
} else {
return element;
}
}
/**
* Answer the element associated with the specified name and kind
*
* @return the element of that kind or <code>null</code> if none
*/
@VisibleForTesting
public NodeElement get(String name, ElementKind kind) {
NodeElement element = internalGet(name);
if (element instanceof ElementHolder) {
ElementHolder holder = (ElementHolder) element;
while (true) {
element = holder.element;
if (ElementKind.of(element).equals(kind)) {
return element;
}
holder = holder.nextHolder;
if (holder == null) {
break;
}
}
} else {
if (ElementKind.of(element).equals(kind)) {
return element;
}
}
return null;
}
@VisibleForTesting
public boolean isEmpty() {
return ordered.isEmpty();
}
@VisibleForTesting
public int size() {
return ordered.size();
}
@VisibleForTesting
public List<NodeElement> values() {
return ordered;
}
private void grow() {
NodeElement[] old = elements;
elements = new NodeElement[elements.length << 2];
for (NodeElement element : old) {
if (element != null) {
if (internalAdd(element) != -1) {
// Every element in the array should have a unique name, so there should not be any collision
throw new RuntimeException("Failed to grow: " + element.getName());
}
}
}
}
/**
* If an element with the given name does not exist in the array, then add the element and return
* -1 otherwise nothing is added and the index of the existing element returned.
*/
private int internalAdd(NodeElement element) {
String name = element.getName();
int mask = elements.length - 1;
int probe = name.hashCode() & mask;
for (int i = probe; i < probe + mask + 1; i++) {
int index = i & mask;
NodeElement current = elements[index];
if (current == null) {
elements[index] = element;
return -1;
}
if (current.getName().equals(name)) {
return index;
}
}
throw new AssertionError("overfilled array");
}
private NodeElement internalGet(String name) {
NodeElement element;
int mask = elements.length - 1;
int probe = name.hashCode() & mask;
for (int i = probe; i < probe + mask + 1; i++) {
element = elements[i & mask];
if (element == null || element.getName().equals(name)) {
return element;
}
}
throw new AssertionError("overfilled array");
}
}