| /* |
| * 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; |
| } |
| } |