/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 jetbrains.mps.textgen.trace;
import jetbrains.mps.smodel.SNodePointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public class DebugInfo {
private final Map<SNodeReference, DebugInfoRoot> myRoots = new HashMap<SNodeReference, DebugInfoRoot>();
public DebugInfo() {
}
private DebugInfoRoot getOrCreateDebugInfoRoot(SNode rootNode) {
SNodeReference ref = getRef(rootNode);
DebugInfoRoot infoRoot = myRoots.get(ref);
if (infoRoot == null) {
infoRoot = new DebugInfoRoot(ref);
myRoots.put(ref, infoRoot);
}
return infoRoot;
}
private SNodeReference getRef(SNode node) {
return (node == null ? null : node.getReference());
}
public void addPosition(TraceablePositionInfo position, SNode containingRoot) {
getOrCreateDebugInfoRoot(containingRoot).addPosition(position);
}
public void addScopePosition(ScopePositionInfo position, SNode containingRoot) {
getOrCreateDebugInfoRoot(containingRoot).addScopePosition(position);
}
public void addUnitPosition(UnitPositionInfo unitPosition, SNode containingRoot) {
getOrCreateDebugInfoRoot(containingRoot).addUnitPosition(unitPosition);
}
public DebugInfoRoot getRootInfo(@Nullable SNode root) {
return myRoots.get(getRef(root));
}
public DebugInfoRoot getRootInfo(@Nullable SNodeReference root) {
return myRoots.get(root);
}
public void putRootInfo(DebugInfoRoot root) {
myRoots.put(root.getNodeRef(), root);
}
@Nullable
public TraceablePositionInfo getPositionForNode(final SNode node) {
// used in mbeddr
DebugInfoRoot root = myRoots.get(getRef(node.getContainingRoot()));
if (root == null) {
return null;
}
for (TraceablePositionInfo pi : root.getPositions()) {
if (pi.matches(node.getNodeId())) {
return pi;
}
}
return null;
}
public Set<TraceablePositionInfo> getPositions(SNode rootNode) {
// for mbeddr
assert (rootNode.getParent() == null);
DebugInfoRoot root = myRoots.get(getRef(rootNode));
if (root == null) {
return Collections.emptySet();
}
return root.getPositions();
}
@NotNull
public List<UnitPositionInfo> getUnitsForNode(SNode node) {
final SNodeId id = node.getNodeId();
DebugInfoRoot debugInfoRoot = myRoots.get(getRef(node.getContainingRoot()));
if (debugInfoRoot != null) {
ArrayList<UnitPositionInfo> unitPositions = new ArrayList<>(debugInfoRoot.getUnitPositions());
// FIXME StartLineComparator is always wrapped with reverseOrder, no need to have it that way
Collections.sort(unitPositions, Collections.reverseOrder(new PositionInfo.StartLineComparator()));
unitPositions.removeIf(pi -> !pi.matches(id));
return unitPositions;
}
return Collections.emptyList();
}
/**
* @see #getTracedNodesForPosition(String, int, Consumer)
*/
@NotNull
public List<SNodeReference> getTracedNodesForPosition(@NotNull String fileName, int line) {
return getTracedNodesForPosition(fileName, line, null);
}
/**
* Look up all {@link TraceablePositionInfo} that cover specified location, sort them by distance of starting line from the
* line specified (closes coming first), prepare {@code SNodeReference} and yield the list.
* Takes an optional code that may further filter out {@link TraceablePositionInfo} according to specific needs. Consumer can respect certain
* approaches to code generation, language differences and relation of line and position.
*
* @param fileName name of generated file with text
* @param line index of line in question
* @param extraProcessing optional code that takes sorted list of {@linkplain TraceablePositionInfo positions} that span specified line, the one with
* closest start coming first. The list could be modified/augmented or even emptied, positions left in the list are translated
* to respective node references and constitute the outcome of the method. Consumer list argument is never {@code null}.
*/
@NotNull
public List<SNodeReference> getTracedNodesForPosition(@NotNull String fileName, int line, @Nullable Consumer<List<TraceablePositionInfo>> extraProcessing) {
// XXX implementation note: this method, much like the next one, getVariableNodesForPosition, is identical to getUnitNodesForPosition
// and all are worth refactoring (though not the way it used to be in TraceInfoUtil)
// Perhaps, can throw in some Java 8?
PersistenceFacade persFacade = PersistenceFacade.getInstance();
ArrayList<SNodeReference> traceNode = new ArrayList<SNodeReference>();
for (DebugInfoRoot dr : getRootsForFile(fileName)) {
ArrayList<TraceablePositionInfo> positionInfos = new ArrayList<TraceablePositionInfo>(dr.getPositions());
Collections.sort(positionInfos, Collections.reverseOrder(new PositionInfo.StartLineComparator()));
ArrayList<TraceablePositionInfo> infosForLine = new ArrayList<TraceablePositionInfo>(positionInfos.size());
for (TraceablePositionInfo tpi : positionInfos) {
if (tpi.contains(fileName, line)) {
infosForLine.add(tpi);
}
}
if (extraProcessing != null) {
extraProcessing.accept(infosForLine);
}
for (TraceablePositionInfo tpi : infosForLine) {
// FIXME new SNodePointer here seems to be the only reason we depend from [smodel]
traceNode.add(new SNodePointer(dr.getNodeRef().getModelReference(), persFacade.createNodeId(tpi.getNodeId())));
}
}
return traceNode;
}
/**
* Look up for variable nodes within scope that covers specified line of the file.
* List is sorted in reverse order, with variables from the bottom (most nested) coming first.
* <p/>
* The reason I don't want to expose ScopePositionInfo and let clients filter positions themselves is
* that they'd need access to DebugInfoRoot as well (to find out container node/model), and the API would get too complicated.
*/
@NotNull
public List<SNodeReference> getVariableNodesForPosition(@NotNull String fileName, int line, @NotNull String varName) {
PersistenceFacade persFacade = PersistenceFacade.getInstance();
ArrayList<SNodeReference> rv = new ArrayList<SNodeReference>();
for (DebugInfoRoot dr : getRootsForFile(fileName)) {
ArrayList<ScopePositionInfo> positionInfos = new ArrayList<>(dr.getScopePositions());
Collections.sort(positionInfos, Collections.reverseOrder(new PositionInfo.StartLineComparator()));
for (ScopePositionInfo spi : positionInfos) {
if (!spi.contains(fileName, line)) {
continue;
}
if (spi.getVarNames().contains(varName)) {
rv.add(new SNodePointer(dr.getNodeRef().getModelReference(), persFacade.createNodeId(spi.getVarId(varName))));
}
}
}
return rv;
}
/**
* @return list of nodes known at the specified line, sorted in reversed order (with the node most close to the line coming first)
*/
@NotNull
public List<SNodeReference> getUnitNodesForPosition(String fileName, int line) {
PersistenceFacade persFacade = PersistenceFacade.getInstance();
ArrayList<SNodeReference> unitNodes = new ArrayList<SNodeReference>();
for (DebugInfoRoot dr : getRootsForFile(fileName)) {
ArrayList<UnitPositionInfo> positionInfos = new ArrayList<UnitPositionInfo>(dr.getUnitPositions());
Collections.sort(positionInfos, Collections.reverseOrder(new PositionInfo.StartLineComparator()));
for (UnitPositionInfo upi : positionInfos) {
if (upi.contains(fileName, line)) {
unitNodes.add(new SNodePointer(dr.getNodeRef().getModelReference(), persFacade.createNodeId(upi.getNodeId())));
}
}
}
return unitNodes;
}
public Iterable<DebugInfoRoot> getRoots() {
return myRoots.values();
}
private List<DebugInfoRoot> getRootsForFile(String filename) {
ArrayList<DebugInfoRoot> rv = new ArrayList<DebugInfoRoot>(myRoots.size());
for (DebugInfoRoot dr : myRoots.values()) {
if (dr.getFileNames().contains(filename)) {
rv.add(dr);
}
}
return rv;
}
}