/* * 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 jetbrains.mps.smodel.adapter.structure.concept.SAbstractConceptAdapter; import jetbrains.mps.util.containers.MultiMap; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.model.SNodeReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * New serialization format for trace.info files * * @author Artem Tikhomirov */ final class SerializeSupport { //Achtung! For now this must be in sync with FileType.TRACE_CACHE's data!! private static final String ELEMENT_DEBUG_INFO = "debug-info"; private static final String ATTR_VER = "version"; private static final String CURRENT_VERSION = "2"; private static final String ELEMENT_ROOT = "root"; private static final String ELEMENT_FILE = "file"; private static final String ELEMENT_NODE_INFO = "node"; private static final String ELEMENT_SCOPE_INFO = "scope"; private static final String ELEMENT_UNIT_INFO = "unit"; private static final String ELEMENT_VAR_INFO = "var"; private static final String CONCEPT = "concept"; private static final String ATTR_NODE_REF = "nodeRef"; private static final String ATTR_NAME = "name"; private static final String ATTR_TRACEABLE_PROP = "trace"; private static final String ATTR_NODE_ID = "id"; private static final String ATTR_AT = "at"; private static final String ATTR_FQN = "fqn"; @NotNull public static Element serialize(@NotNull DebugInfo debugInfo) { Element top = new Element(ELEMENT_DEBUG_INFO); top.setAttribute(ATTR_VER, CURRENT_VERSION); DebugInfoRoot[] roots = sortedRoots(debugInfo); TreeSet<SAbstractConcept> allConcepts = new TreeSet<SAbstractConcept>(new Comparator<SAbstractConcept>() { @Override public int compare(SAbstractConcept o1, SAbstractConcept o2) { return o1.getQualifiedName().compareTo(o2.getQualifiedName()); } }); collectAllConcepts(roots, allConcepts); int i = 0; HashMap<SAbstractConcept, Integer> conceptsOrder = new HashMap<SAbstractConcept, Integer>(); for (SAbstractConcept concept : allConcepts) { conceptsOrder.put(concept, i++); top.addContent(new Element(CONCEPT).setAttribute(ATTR_FQN, ((SAbstractConceptAdapter) concept).serialize())); } for (DebugInfoRoot dr : roots) { Element r = new Element(ELEMENT_ROOT); top.addContent(r); SNodeReference nr = dr.getNodeRef(); if (nr != null) { r.setAttribute(ATTR_NODE_REF, nr.toString()); } r.addContent(serialize(dr, conceptsOrder)); } return top; } @NotNull public static DebugInfo restore(@NotNull Element top) { if (!ELEMENT_DEBUG_INFO.equals(top.getName())) { return new DebugInfo(); } if (!CURRENT_VERSION.equals(top.getAttributeValue(ATTR_VER))) { //drop older versions return new DebugInfo(); } DebugInfo rv = new DebugInfo(); int i = 0; HashMap<Integer, SAbstractConcept> conceptsOrder = new HashMap<Integer, SAbstractConcept>(); for (Element c : top.getChildren(CONCEPT)) { conceptsOrder.put(i++, SAbstractConceptAdapter.deserialize(c.getAttributeValue(ATTR_FQN))); } for (Element r : top.getChildren(ELEMENT_ROOT)) { String nr = r.getAttributeValue(ATTR_NODE_REF); DebugInfoRoot dr = new DebugInfoRoot(nr == null ? null : SNodePointer.deserialize(nr)); rv.putRootInfo(dr); for (Element f : r.getChildren(ELEMENT_FILE)) { String filename = f.getAttributeValue(ATTR_NAME); for (Element e : f.getChildren(ELEMENT_NODE_INFO)) { TraceablePositionInfo pi = new TraceablePositionInfo(); pi.setFileName(filename); restore(e, pi); String conceptAttr = e.getAttributeValue(CONCEPT); SAbstractConcept conc = null; if (conceptAttr != null) { conc = conceptsOrder.get(Integer.parseInt(conceptAttr)); } pi.setConcept(conc); pi.setPropertyString(e.getAttributeValue(ATTR_TRACEABLE_PROP)); dr.addPosition(pi); } for (Element e : f.getChildren(ELEMENT_SCOPE_INFO)) { ScopePositionInfo pi = new ScopePositionInfo(); pi.setFileName(filename); restore(e, pi); for (Element varInfo : e.getChildren(ELEMENT_VAR_INFO)) { VarInfo vi = new VarInfo(); vi.setVarName(varInfo.getAttributeValue(ATTR_NAME)); vi.setNodeId(varInfo.getAttributeValue(ATTR_NODE_ID)); pi.addVarInfo(vi); } dr.addScopePosition(pi); } for (Element e : f.getChildren(ELEMENT_UNIT_INFO)) { UnitPositionInfo pi = new UnitPositionInfo(); pi.setFileName(filename); restore(e, pi); pi.setUnitName(e.getAttributeValue(ATTR_NAME)); dr.addUnitPosition(pi); } } } return rv; } private static List<Element> serialize(DebugInfoRoot debugRoot, HashMap<SAbstractConcept, Integer> conceptsOrder) { MultiMap<String, TraceablePositionInfo> p1 = new MultiMap<String, TraceablePositionInfo>(); MultiMap<String, ScopePositionInfo> p2 = new MultiMap<String, ScopePositionInfo>(); MultiMap<String, UnitPositionInfo> p3 = new MultiMap<String, UnitPositionInfo>(); for (TraceablePositionInfo pi : debugRoot.getPositions()) { p1.putValue(pi.getFileName(), pi); } for (ScopePositionInfo pi : debugRoot.getScopePositions()) { p2.putValue(pi.getFileName(), pi); } for (UnitPositionInfo pi : debugRoot.getUnitPositions()) { p3.putValue(pi.getFileName(), pi); } HashSet<String> allFiles = new HashSet<String>(); allFiles.addAll(p1.keySet()); allFiles.addAll(p2.keySet()); allFiles.addAll(p3.keySet()); final String[] allFilesSorted = allFiles.toArray(new String[allFiles.size()]); Arrays.sort(allFilesSorted); ArrayList<Element> rv = new ArrayList<Element>(allFilesSorted.length); for (String filename : allFilesSorted) { Element fileElement = new Element(ELEMENT_FILE); fileElement.setAttribute(ATTR_NAME, filename); rv.add(fileElement); for (TraceablePositionInfo pi : p1.get(filename)) { Element e = new Element(ELEMENT_NODE_INFO); fileElement.addContent(e); serialize(pi, e); if (pi.getConcept() != null) { assert conceptsOrder.containsKey(pi.getConcept()); e.setAttribute(CONCEPT, conceptsOrder.get(pi.getConcept()).toString()); } if (pi.getPropertyString() != null) { e.setAttribute(ATTR_TRACEABLE_PROP, pi.getPropertyString()); } } for (ScopePositionInfo pi : p2.get(filename)) { Element e = new Element(ELEMENT_SCOPE_INFO); fileElement.addContent(e); serialize(pi, e); for (String varName : pi.getVarNames()) { Element var = new Element(ELEMENT_VAR_INFO); var.setAttribute(ATTR_NAME, varName); var.setAttribute(ATTR_NODE_ID, pi.getVarId(varName)); e.addContent(var); } } for (UnitPositionInfo pi : p3.get(filename)) { Element e = new Element(ELEMENT_UNIT_INFO); fileElement.addContent(e); serialize(pi, e); if (pi.getUnitName() != null) { e.setAttribute(ATTR_NAME, pi.getUnitName()); } } } return rv; } private static void serialize(PositionInfo pi, Element e) { if (pi.getNodeId() != null) { e.setAttribute(ATTR_NODE_ID, pi.getNodeId()); } e.setAttribute(ATTR_AT, String.format("%d,%d,%d,%d", pi.getStartLine(), pi.getStartPosition(), pi.getEndLine(), pi.getEndPosition())); } private static void restore(Element e, PositionInfo pi) { final String id = e.getAttributeValue(ATTR_NODE_ID); if (id != null) { pi.setNodeId(id); } final String[] at = e.getAttributeValue(ATTR_AT).split(","); pi.setStartLine(Integer.parseInt(at[0])); pi.setStartPosition(Integer.parseInt(at[1])); pi.setEndLine(Integer.parseInt(at[2])); pi.setEndPosition(Integer.parseInt(at[3])); } // ensure roots get serialized in the same order private static DebugInfoRoot[] sortedRoots(DebugInfo debugInfo) { DebugInfoRoot[] rv = toArray(debugInfo.getRoots()); Arrays.sort(rv, new Comparator<DebugInfoRoot>() { @Override public int compare(DebugInfoRoot o1, DebugInfoRoot o2) { if (o1.getNodeRef() == null || o2.getNodeRef() == null) { if (o1.getNodeRef() == null) { // in fact, both never null, as DebugInfo uses nodeRef as key. Just to keep it complete. return o2.getNodeRef() == null ? 0 : -1; } return 1; } return o1.getNodeRef().toString().compareTo(o2.getNodeRef().toString()); } }); return rv; } private static DebugInfoRoot[] toArray(Iterable<DebugInfoRoot> roots) { ArrayList<DebugInfoRoot> rv = new ArrayList<DebugInfoRoot>(); for (DebugInfoRoot root : roots) { rv.add(root); } return rv.toArray(new DebugInfoRoot[rv.size()]); } private static void collectAllConcepts(DebugInfoRoot[] roots, Set<SAbstractConcept> concepts) { for (DebugInfoRoot r : roots) { for (TraceablePositionInfo pi : r.getPositions()) { final SAbstractConcept concept = pi.getConcept(); if (concept != null) { concepts.add(concept); } } } } }