/* * Copyright (c) 2014, 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.file; 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.ElementKind; 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.Relationship; import com.google.dart.engine.index.UniverseElement; import com.google.dart.engine.internal.context.AnalysisContextImpl; import com.google.dart.engine.internal.context.InstrumentedAnalysisContextImpl; import com.google.dart.engine.source.Source; import com.google.dart.engine.source.SourceContainer; import org.apache.commons.lang3.ArrayUtils; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * An {@link IndexStore} which keeps index information in separate nodes for each unit. * * @coverage dart.engine.index */ public class SplitIndexStoreImpl implements IndexStore { /** * The {@link NodeManager} to get/put {@link IndexNode}s. */ private final NodeManager nodeManager; /** * The {@link ContextCodec} to encode/decode {@link AnalysisContext}s. */ private final ContextCodec contextCodec; /** * The {@link ElementCodec} to encode/decode {@link Element}s. */ private final ElementCodec elementCodec; /** * The {@link StringCodec} to encode/decode {@link String}s. */ private final StringCodec stringCodec; /** * A table mapping elements to the node names that may have relations with these elements. */ private final IntToIntSetMap elementToNodeNames = new IntToIntSetMap(10000, 0.75f); /** * Information about "universe" elements. We need to keep them together to avoid loading of all * index nodes. * <p> * Order of keys: contextId, nodeId, Relationship. */ private final Map<Integer, Map<Integer, Map<Relationship, List<LocationData>>>> contextNodeRelations = 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(); /** * The set of known {@link Source}s. */ private final Set<Source> sources = Sets.newHashSet(); private int currentContextId; private String currentNodeName; private int currentNodeNameId; private IndexNode currentNode; public SplitIndexStoreImpl(NodeManager nodeManager) { this.nodeManager = nodeManager; this.contextCodec = nodeManager.getContextCodec(); this.elementCodec = nodeManager.getElementCodec(); this.stringCodec = nodeManager.getStringCodec(); } @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 library/unit relations recordUnitInLibrary(context, library, unit); recordLibraryWithUnit(context, library, unit); sources.add(library); sources.add(unit); // prepare node String libraryName = library.getFullName(); String unitName = unit.getFullName(); int libraryNameIndex = stringCodec.encode(libraryName); int unitNameIndex = stringCodec.encode(unitName); currentNodeName = libraryNameIndex + "_" + unitNameIndex + ".index"; currentNodeNameId = stringCodec.encode(currentNodeName); currentNode = nodeManager.newNode(context); currentContextId = contextCodec.encode(context); // remove Universe information for the current node for (Map<Integer, ?> nodeRelations : contextNodeRelations.values()) { nodeRelations.remove(currentNodeNameId); } // done 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); // remember library/unit relations recordUnitInLibrary(context, null, source); // prepare node String sourceName = source.getFullName(); int sourceNameIndex = stringCodec.encode(sourceName); currentNodeName = sourceNameIndex + ".index"; currentNodeNameId = stringCodec.encode(currentNodeName); currentNode = nodeManager.newNode(context); return true; } @Override public void clear() { nodeManager.clear(); elementToNodeNames.clear(); } @Override public void doneIndex() { if (currentNode != null) { nodeManager.putNode(currentNodeName, currentNode); currentNodeName = null; currentNodeNameId = -1; currentNode = null; currentContextId = -1; } } @Override public Location[] getRelationships(Element element, Relationship relationship) { // special support for UniverseElement if (element == UniverseElement.INSTANCE) { return getRelationshipsUniverse(relationship); } // prepare node names int elementId = elementCodec.encodeHash(element); int[] nodeNameIds = elementToNodeNames.get(elementId); // check each node List<Location> locations = Lists.newArrayList(); for (int i = 0; i < nodeNameIds.length; i++) { int nodeNameId = nodeNameIds[i]; String nodeName = stringCodec.decode(nodeNameId); IndexNode node = nodeManager.getNode(nodeName); if (node != null) { Collections.addAll(locations, node.getRelationships(element, relationship)); } else { nodeNameIds = ArrayUtils.removeElement(nodeNameIds, nodeNameId); i--; } } // done return locations.toArray(new Location[locations.size()]); } @Override public String getStatistics() { return "[" + nodeManager.getLocationCount() + " locations, " + sources.size() + " sources, " + elementToNodeNames.size() + " elements]"; } @Override public void recordRelationship(Element element, Relationship relationship, Location location) { if (element == null || element.getKind() == ElementKind.ERROR) { return; } if (location == null) { return; } // special support for UniverseElement if (element == UniverseElement.INSTANCE) { recordRelationshipUniverse(relationship, location); return; } // other elements recordNodeNameForElement(element); currentNode.recordRelationship(element, relationship, location); } @Override public void removeContext(AnalysisContext context) { context = unwrapContext(context); if (context == null) { return; } // remove sources removeSources(context, null); // remove context information contextToLibraryToUnits.remove(context); contextToUnitToLibraries.remove(context); contextNodeRelations.remove(contextCodec.encode(context)); // remove context from codec contextCodec.removeContext(context); } @Override public void removeSource(AnalysisContext context, Source source) { context = unwrapContext(context); if (context == null) { return; } // remove nodes for unit/library pairs Map<Source, Set<Source>> unitToLibraries = contextToUnitToLibraries.get(context); if (unitToLibraries != null) { Set<Source> libraries = unitToLibraries.remove(source); if (libraries != null) { for (Source library : libraries) { removeLocations(context, library, source); } } } // remove nodes for library/unit pairs Map<Source, Set<Source>> libraryToUnits = contextToLibraryToUnits.get(context); if (libraryToUnits != null) { Set<Source> units = libraryToUnits.remove(source); if (units != null) { for (Source unit : units) { removeLocations(context, source, unit); } } } } @Override public void removeSources(AnalysisContext context, SourceContainer container) { context = unwrapContext(context); if (context == null) { return; } // remove nodes for unit/library pairs Map<Source, Set<Source>> unitToLibraries = contextToUnitToLibraries.get(context); if (unitToLibraries != null) { List<Source> units = Lists.newArrayList(unitToLibraries.keySet()); for (Source source : units) { if (container == null || container.contains(source)) { removeSource(context, source); } } } // remove nodes for library/unit pairs Map<Source, Set<Source>> libraryToUnits = contextToLibraryToUnits.get(context); if (libraryToUnits != null) { List<Source> libraries = Lists.newArrayList(libraryToUnits.keySet()); for (Source source : libraries) { if (container == null || container.contains(source)) { removeSource(context, source); } } } } private Location[] getRelationshipsUniverse(Relationship relationship) { List<Location> locations = Lists.newArrayList(); for (Entry<Integer, Map<Integer, Map<Relationship, List<LocationData>>>> contextEntry : contextNodeRelations.entrySet()) { int contextId = contextEntry.getKey(); AnalysisContext context = contextCodec.decode(contextId); if (context != null) { for (Map<Relationship, List<LocationData>> nodeRelations : contextEntry.getValue().values()) { List<LocationData> nodeLocations = nodeRelations.get(relationship); if (nodeLocations != null) { for (LocationData locationData : nodeLocations) { Location location = locationData.getLocation(context, elementCodec); if (location != null) { locations.add(location); } } } } } } return locations.toArray(new Location[locations.size()]); } private void recordLibraryWithUnit(AnalysisContext context, Source library, Source unit) { Map<Source, Set<Source>> libraryToUnits = contextToLibraryToUnits.get(context); if (libraryToUnits == null) { libraryToUnits = Maps.newHashMap(); contextToLibraryToUnits.put(context, libraryToUnits); } Set<Source> units = libraryToUnits.get(library); if (units == null) { units = Sets.newHashSet(); libraryToUnits.put(library, units); } units.add(unit); } private void recordNodeNameForElement(Element element) { int elementId = elementCodec.encodeHash(element); elementToNodeNames.add(elementId, currentNodeNameId); } private void recordRelationshipUniverse(Relationship relationship, Location location) { // in current context Map<Integer, Map<Relationship, List<LocationData>>> nodeRelations = contextNodeRelations.get(currentContextId); if (nodeRelations == null) { nodeRelations = Maps.newHashMap(); contextNodeRelations.put(currentContextId, nodeRelations); } // in current node Map<Relationship, List<LocationData>> relations = nodeRelations.get(currentNodeNameId); if (relations == null) { relations = Maps.newHashMap(); nodeRelations.put(currentNodeNameId, relations); } // for the given relationship List<LocationData> locations = relations.get(relationship); if (locations == null) { locations = Lists.newArrayList(); relations.put(relationship, locations); } // record LocationData locations.add(new LocationData(elementCodec, location)); } 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) { // remove node String libraryName = library != null ? library.getFullName() : null; String unitName = unit.getFullName(); int libraryNameIndex = stringCodec.encode(libraryName); int unitNameIndex = stringCodec.encode(unitName); String nodeName = libraryNameIndex + "_" + unitNameIndex + ".index"; nodeManager.removeNode(nodeName); // remove source sources.remove(library); sources.remove(unit); } /** * 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. */ private AnalysisContext unwrapContext(AnalysisContext context) { if (context instanceof InstrumentedAnalysisContextImpl) { context = ((InstrumentedAnalysisContextImpl) context).getBasis(); } return context; } }