/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.index;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.HtmlElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.index.IndexStore;
import com.google.dart.engine.index.Location;
import com.google.dart.engine.index.MemoryIndexStore;
import com.google.dart.engine.index.Relationship;
import com.google.dart.engine.internal.context.AnalysisContextImpl;
import com.google.dart.engine.internal.context.InstrumentedAnalysisContextImpl;
import com.google.dart.engine.internal.element.member.Member;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.source.SourceContainer;
import com.google.dart.engine.utilities.translation.DartExpressionBody;
import com.google.dart.engine.utilities.translation.DartOmit;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* {@link IndexStore} which keeps full index in memory.
*
* @coverage dart.engine.index
*/
public class MemoryIndexStoreImpl implements MemoryIndexStore {
static class ElementRelationKey {
final Element element;
final Relationship relationship;
public ElementRelationKey(Element element, Relationship relationship) {
this.element = element;
this.relationship = relationship;
}
@Override
public boolean equals(Object obj) {
ElementRelationKey other = (ElementRelationKey) obj;
Element otherElement = other.element;
return other.relationship == relationship
&& otherElement.getNameOffset() == element.getNameOffset()
&& otherElement.getKind() == element.getKind()
&& Objects.equal(otherElement.getDisplayName(), element.getDisplayName())
&& Objects.equal(otherElement.getSource(), element.getSource());
}
@Override
public int hashCode() {
return Objects.hashCode(
element.getSource(),
element.getNameOffset(),
element.getKind(),
element.getDisplayName(),
relationship);
}
@Override
public String toString() {
return element + " " + relationship;
}
}
static class Source2 {
final Source librarySource;
final Source unitSource;
public Source2(Source librarySource, Source unitSource) {
this.librarySource = librarySource;
this.unitSource = unitSource;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Source2)) {
return false;
}
Source2 other = (Source2) obj;
return Objects.equal(other.librarySource, librarySource)
&& Objects.equal(other.unitSource, unitSource);
}
@Override
public int hashCode() {
return Objects.hashCode(librarySource, unitSource);
}
@Override
public String toString() {
return librarySource + " " + unitSource;
}
}
/**
* When logging is on, {@link AnalysisEngine} actually creates
* {@link InstrumentedAnalysisContextImpl}, which wraps {@link AnalysisContextImpl} used to create
* actual {@link Element}s. So, in index we have to unwrap {@link InstrumentedAnalysisContextImpl}
* when perform any operation.
*/
public static AnalysisContext unwrapContext(AnalysisContext context) {
if (context instanceof InstrumentedAnalysisContextImpl) {
context = ((InstrumentedAnalysisContextImpl) context).getBasis();
}
return context;
}
/**
* @return the {@link Source} of the enclosing {@link LibraryElement}, may be {@code null}.
*/
private static Source getLibrarySourceOrNull(Element element) {
LibraryElement library = element.getLibrary();
if (library == null) {
return null;
}
if (library.isAngularHtml()) {
return null;
}
return library.getSource();
}
/**
* This map is used to canonicalize equal keys.
*/
private final Map<ElementRelationKey, ElementRelationKey> canonicalKeys = Maps.newHashMap();
/**
* The mapping of {@link ElementRelationKey} to the {@link Location}s, one-to-many.
*/
final Map<ElementRelationKey, Set<Location>> keyToLocations = Maps.newHashMap();
/**
* The mapping of {@link Source} to the {@link ElementRelationKey}s. It is used in
* {@link #removeSource(AnalysisContext, Source)} to identify keys to remove from
* {@link #keyToLocations}.
*/
final Map<AnalysisContext, Map<Source2, Set<ElementRelationKey>>> contextToSourceToKeys = Maps.newHashMap();
/**
* The mapping of {@link Source} to the {@link Location}s existing in it. It is used in
* {@link #clearSource0(AnalysisContext, Source)} to identify locations to remove from
* {@link #keyToLocations}.
*/
final Map<AnalysisContext, Map<Source2, List<Location>>> contextToSourceToLocations = Maps.newHashMap();
/**
* The mapping of library {@link Source} to the {@link Source}s of part units.
*/
final Map<AnalysisContext, Map<Source, Set<Source>>> contextToLibraryToUnits = Maps.newHashMap();
/**
* The mapping of unit {@link Source} to the {@link Source}s of libraries it is used in.
*/
final Map<AnalysisContext, Map<Source, Set<Source>>> contextToUnitToLibraries = Maps.newHashMap();
private int sourceCount;
private int keyCount;
private int locationCount;
@Override
public boolean aboutToIndexDart(AnalysisContext context, CompilationUnitElement unitElement) {
context = unwrapContext(context);
// may be already disposed in other thread
if (context.isDisposed()) {
return false;
}
// validate unit
if (unitElement == null) {
return false;
}
LibraryElement libraryElement = unitElement.getLibrary();
if (libraryElement == null) {
return false;
}
CompilationUnitElement definingUnitElement = libraryElement.getDefiningCompilationUnit();
if (definingUnitElement == null) {
return false;
}
// prepare sources
Source library = definingUnitElement.getSource();
Source unit = unitElement.getSource();
// special handling for the defining library unit
if (unit.equals(library)) {
// prepare new parts
Set<Source> newParts = Sets.newHashSet();
for (CompilationUnitElement part : libraryElement.getParts()) {
newParts.add(part.getSource());
}
// prepare old parts
Map<Source, Set<Source>> libraryToUnits = contextToLibraryToUnits.get(context);
if (libraryToUnits == null) {
libraryToUnits = Maps.newHashMap();
contextToLibraryToUnits.put(context, libraryToUnits);
}
Set<Source> oldParts = libraryToUnits.get(library);
// check if some parts are not in the library now
if (oldParts != null) {
Set<Source> noParts = Sets.difference(oldParts, newParts);
for (Source noPart : noParts) {
removeLocations(context, library, noPart);
}
}
// remember new parts
libraryToUnits.put(library, newParts);
}
// remember libraries in which unit is used
recordUnitInLibrary(context, library, unit);
// remove locations
removeLocations(context, library, unit);
// remove keys
{
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(context);
if (sourceToKeys != null) {
Source2 source2 = new Source2(library, unit);
boolean hadSource = sourceToKeys.remove(source2) != null;
if (hadSource) {
sourceCount--;
}
}
}
// OK, we can index
return true;
}
@Override
public boolean aboutToIndexHtml(AnalysisContext context, HtmlElement htmlElement) {
context = unwrapContext(context);
// may be already disposed in other thread
if (context.isDisposed()) {
return false;
}
// remove locations
Source source = htmlElement.getSource();
removeLocations(context, null, source);
// remove keys
{
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(context);
if (sourceToKeys != null) {
Source2 source2 = new Source2(null, source);
boolean hadSource = sourceToKeys.remove(source2) != null;
if (hadSource) {
sourceCount--;
}
}
}
// remember libraries in which unit is used
recordUnitInLibrary(context, null, source);
// OK, we can index
return true;
}
@Override
public void clear() {
canonicalKeys.clear();
keyToLocations.clear();
contextToSourceToKeys.clear();
contextToSourceToLocations.clear();
contextToLibraryToUnits.clear();
contextToUnitToLibraries.clear();
}
@Override
public void doneIndex() {
}
@Override
public Location[] getRelationships(Element element, Relationship relationship) {
ElementRelationKey key = new ElementRelationKey(element, relationship);
Set<Location> locations = keyToLocations.get(key);
if (locations != null) {
return locations.toArray(new Location[locations.size()]);
}
return Location.EMPTY_ARRAY;
}
@Override
public String getStatistics() {
return locationCount + " relationships in " + keyCount + " keys in " + sourceCount + " sources";
}
@VisibleForTesting
public int internalGetKeyCount() {
return keyToLocations.size();
}
@VisibleForTesting
public int internalGetLocationCount() {
int count = 0;
for (Set<Location> locations : keyToLocations.values()) {
count += locations.size();
}
return count;
}
@VisibleForTesting
public int internalGetLocationCountForContext(AnalysisContext context) {
context = unwrapContext(context);
int count = 0;
for (Set<Location> locations : keyToLocations.values()) {
for (Location location : locations) {
if (location.getElement().getContext() == context) {
count++;
}
}
}
return count;
}
@VisibleForTesting
public int internalGetSourceKeyCount(AnalysisContext context) {
int count = 0;
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(context);
if (sourceToKeys != null) {
for (Set<ElementRelationKey> keys : sourceToKeys.values()) {
count += keys.size();
}
}
return count;
}
@Override
@DartOmit
public void readIndex(AnalysisContext context, InputStream input) throws IOException {
context = unwrapContext(context);
new MemoryIndexReader(this, context, input).read();
}
@Override
public void recordRelationship(Element element, Relationship relationship, Location location) {
if (element == null || location == null) {
return;
}
location = location.newClone();
// at the index level we don't care about Member(s)
if (element instanceof Member) {
element = ((Member) element).getBaseElement();
}
// System.out.println(element + " " + relationship + " " + location);
// prepare information
AnalysisContext elementContext = element.getContext();
AnalysisContext locationContext = location.getElement().getContext();
Source elementSource = element.getSource();
Source locationSource = location.getElement().getSource();
Source elementLibrarySource = getLibrarySourceOrNull(element);
Source locationLibrarySource = getLibrarySourceOrNull(location.getElement());
// sanity check
if (locationContext == null) {
return;
}
if (locationSource == null) {
return;
}
if (elementContext == null && !(element instanceof NameElementImpl)
&& !(element instanceof UniverseElementImpl)) {
return;
}
if (elementSource == null && !(element instanceof NameElementImpl)
&& !(element instanceof UniverseElementImpl)) {
return;
}
// may be already disposed in other thread
if (elementContext != null && elementContext.isDisposed()) {
return;
}
if (locationContext.isDisposed()) {
return;
}
// record: key -> location(s)
ElementRelationKey key = getCanonicalKey(element, relationship);
{
Set<Location> locations = keyToLocations.remove(key);
if (locations == null) {
locations = createLocationIdentitySet();
} else {
keyCount--;
}
keyToLocations.put(key, locations);
keyCount++;
locations.add(location);
locationCount++;
}
// record: location -> key
location.internalKey = key;
// prepare source pairs
Source2 elementSource2 = new Source2(elementLibrarySource, elementSource);
Source2 locationSource2 = new Source2(locationLibrarySource, locationSource);
// record: element source -> keys
{
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(elementContext);
if (sourceToKeys == null) {
sourceToKeys = Maps.newHashMap();
contextToSourceToKeys.put(elementContext, sourceToKeys);
}
Set<ElementRelationKey> keys = sourceToKeys.get(elementSource2);
if (keys == null) {
keys = Sets.newHashSet();
sourceToKeys.put(elementSource2, keys);
sourceCount++;
}
keys.remove(key);
keys.add(key);
}
// record: location source -> locations
{
Map<Source2, List<Location>> sourceToLocations = contextToSourceToLocations.get(locationContext);
if (sourceToLocations == null) {
sourceToLocations = Maps.newHashMap();
contextToSourceToLocations.put(locationContext, sourceToLocations);
}
List<Location> locations = sourceToLocations.get(locationSource2);
if (locations == null) {
locations = Lists.newArrayList();
sourceToLocations.put(locationSource2, locations);
}
locations.add(location);
}
}
@Override
public void removeContext(AnalysisContext context) {
context = unwrapContext(context);
if (context == null) {
return;
}
// remove sources
removeSources(context, null);
// remove context
contextToSourceToKeys.remove(context);
contextToSourceToLocations.remove(context);
contextToLibraryToUnits.remove(context);
contextToUnitToLibraries.remove(context);
}
@Override
public void removeSource(AnalysisContext context, Source unit) {
context = unwrapContext(context);
if (context == null) {
return;
}
// remove locations defined in source
Map<Source, Set<Source>> unitToLibraries = contextToUnitToLibraries.get(context);
if (unitToLibraries != null) {
Set<Source> libraries = unitToLibraries.remove(unit);
if (libraries != null) {
for (Source library : libraries) {
Source2 source2 = new Source2(library, unit);
// remove locations defined in source
removeLocations(context, library, unit);
// remove keys for elements defined in source
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(context);
if (sourceToKeys != null) {
Set<ElementRelationKey> keys = sourceToKeys.remove(source2);
if (keys != null) {
for (ElementRelationKey key : keys) {
canonicalKeys.remove(key);
Set<Location> locations = keyToLocations.remove(key);
if (locations != null) {
keyCount--;
locationCount -= locations.size();
}
}
sourceCount--;
}
}
}
}
}
}
@Override
public void removeSources(AnalysisContext context, SourceContainer container) {
context = unwrapContext(context);
if (context == null) {
return;
}
// remove sources #1
Map<Source2, Set<ElementRelationKey>> sourceToKeys = contextToSourceToKeys.get(context);
if (sourceToKeys != null) {
List<Source2> sources = Lists.newArrayList(sourceToKeys.keySet());
for (Source2 source2 : sources) {
Source source = source2.unitSource;
if (container == null || container.contains(source)) {
removeSource(context, source);
}
}
}
// remove sources #2
Map<Source2, List<Location>> sourceToLocations = contextToSourceToLocations.get(context);
if (sourceToLocations != null) {
List<Source2> sources = Lists.newArrayList(sourceToLocations.keySet());
for (Source2 source2 : sources) {
Source source = source2.unitSource;
if (container == null || container.contains(source)) {
removeSource(context, source);
}
}
}
}
@Override
@DartOmit
public void writeIndex(AnalysisContext context, OutputStream output) throws IOException {
context = unwrapContext(context);
new MemoryIndexWriter(this, context, output).write();
}
/**
* Creates new {@link Set} that uses object identity instead of equals.
*/
@DartExpressionBody("new Set<Location>.identity()")
private Set<Location> createLocationIdentitySet() {
return Sets.newSetFromMap(new IdentityHashMap<Location, Boolean>(4));
}
/**
* @return the canonical {@link ElementRelationKey} for given {@link Element} and
* {@link Relationship}, i.e. unique instance for this combination.
*/
private ElementRelationKey getCanonicalKey(Element element, Relationship relationship) {
ElementRelationKey key = new ElementRelationKey(element, relationship);
ElementRelationKey canonicalKey = canonicalKeys.get(key);
if (canonicalKey == null) {
canonicalKey = key;
canonicalKeys.put(key, canonicalKey);
}
return canonicalKey;
}
private void recordUnitInLibrary(AnalysisContext context, Source library, Source unit) {
Map<Source, Set<Source>> unitToLibraries = contextToUnitToLibraries.get(context);
if (unitToLibraries == null) {
unitToLibraries = Maps.newHashMap();
contextToUnitToLibraries.put(context, unitToLibraries);
}
Set<Source> libraries = unitToLibraries.get(unit);
if (libraries == null) {
libraries = Sets.newHashSet();
unitToLibraries.put(unit, libraries);
}
libraries.add(library);
}
/**
* Removes locations recorded in the given library/unit pair.
*/
private void removeLocations(AnalysisContext context, Source library, Source unit) {
Source2 source2 = new Source2(library, unit);
Map<Source2, List<Location>> sourceToLocations = contextToSourceToLocations.get(context);
if (sourceToLocations != null) {
List<Location> sourceLocations = sourceToLocations.remove(source2);
if (sourceLocations != null) {
for (Location location : sourceLocations) {
ElementRelationKey key = (ElementRelationKey) location.internalKey;
Set<Location> relLocations = keyToLocations.get(key);
if (relLocations != null) {
relLocations.remove(location);
locationCount--;
// no locations with this key
if (relLocations.isEmpty()) {
canonicalKeys.remove(key);
keyToLocations.remove(key);
keyCount--;
}
}
}
}
}
}
}